Source code for rational_linkages.dualQuaternionAction

"""
DualQuaternionAction — functions for acting on geometric objects with dual quaternions.

The public entry point is :func:`act`. The private helpers below it implement
the two concrete action formulas and the type-dispatch logic.

Keeping this as a plain module (rather than a class) reflects the fact that
there is no state to manage: every function is a pure transformation.

Usage via ``DualQuaternion.act``
--------------------------------
.. code-block:: python

    from rational_linkages import DualQuaternion
    from rational_linkages import NormalizedLine

    dq = DualQuaternion([1, 0, 0, 1, 0, 3, 2, -1])
    line = NormalizedLine.from_direction_and_point([0, 0, 1], [0, -2, 0])
    line_transformed = dq.act(line)

Direct usage
------------
.. code-block:: python

    from rational_linkages.dualQuaternionAction import act
    from rational_linkages import NormalizedLine, DualQuaternion

    dq = DualQuaternion([1, 0, 0, 1, 0, 3, 2, -1])
    line = NormalizedLine.from_direction_and_point([0, 0, 1], [0, -2, 0])

    result = act(dq, line)

.. clear-namespace::
"""

from typing import Union

from .DualQuaternion import DualQuaternion
from .NormalizedLine import NormalizedLine
from .PointHomogeneous import PointHomogeneous


[docs] def act( acting_object: Union["DualQuaternion", list["DualQuaternion"]], affected_object: Union["NormalizedLine", "PointHomogeneous"], ) -> Union["NormalizedLine", "PointHomogeneous"]: """ Act on a geometric object with a dual quaternion (or a list of factors). Dispatches to ``_act_on_line`` or ``_act_on_point`` based on the type of ``affected_object``. When ``acting_object`` is a list, the factors are multiplied left-to-right before the action is applied. Parameters ---------- acting_object : A :class:`.DualQuaternion`, or a list of :class:`.DualQuaternion` factors whose product is used. affected_object : A :class:`.NormalizedLine` or :class:`.PointHomogeneous` to transform. Returns ------- NormalizedLine or PointHomogeneous The transformed geometric object, of the same type as ``affected_object``. Raises ------ TypeError If ``affected_object`` is neither a ``NormalizedLine`` nor a ``PointHomogeneous``. Examples -------- .. code-block:: python from rational_linkages import DualQuaternion from rational_linkages.dualQuaternionAction import act from rational_linkages.NormalizedLine import NormalizedLine dq = DualQuaternion([1, 0, 0, 1, 0, 3, 2, -1]) line = NormalizedLine.from_direction_and_point([0, 0, 1], [0, -2, 0]) line_transformed = act(dq, line) .. clear-namespace:: """ acting_dq = _prepare_acting_object(acting_object) affected_type = _classify_affected_object(affected_object) if affected_type == "is_line": return _act_on_line(acting_dq, affected_object) else: return _act_on_point(acting_dq, affected_object)
# --------------------------------------------------------------------------- # Private helpers # --------------------------------------------------------------------------- def _classify_affected_object( affected_object: Union["NormalizedLine", "PointHomogeneous"], ) -> str: """ Return ``'is_line'`` or ``'is_point'`` based on the type of *affected_object*. Parameters ---------- affected_object : The object to classify. Returns ------- str ``'is_line'`` or ``'is_point'``. Raises ------ TypeError If *affected_object* is neither a ``NormalizedLine`` nor a ``PointHomogeneous``. """ if isinstance(affected_object, NormalizedLine): return "is_line" elif isinstance(affected_object, PointHomogeneous): return "is_point" else: raise TypeError( "Other types than NormalizedLine or " "PointHomogeneous not yet implemented" ) def _prepare_acting_object( acting_object: Union["DualQuaternion", list["DualQuaternion"]], ) -> "DualQuaternion": """ Reduce *acting_object* to a single :class:`.DualQuaternion`. If *acting_object* is already a ``DualQuaternion`` it is returned as-is. If it is a list of ``DualQuaternion`` factors they are multiplied left-to-right starting from the identity. Parameters ---------- acting_object : A single ``DualQuaternion`` or a list of ``DualQuaternion`` factors. Returns ------- DualQuaternion The effective acting dual quaternion. """ if isinstance(acting_object, DualQuaternion): return acting_object result = DualQuaternion() for factor in acting_object: result = result * factor return result def _act_on_line( acting_dq: "DualQuaternion", line: "NormalizedLine", ) -> "NormalizedLine": """ Apply the half-turn action of *acting_dq* to *line*. Uses the formula ``acting_dq * line_as_dq * acting_dq.conjugate()``. The conjugation is already embedded in :meth:`.NormalizedLine.line2dq_array`, so the standard (non-epsilon) conjugate is used here. Parameters ---------- acting_dq : The acting dual quaternion. line : The line to transform. Returns ------- NormalizedLine The transformed line. """ line_as_dq = DualQuaternion(line.line2dq_array()) result = acting_dq * line_as_dq * acting_dq.conjugate() return NormalizedLine.from_dual_quaternion(result) def _act_on_point( acting_dq: "DualQuaternion", point: "PointHomogeneous", ) -> "PointHomogeneous": """ Apply the half-turn action of *acting_dq* to *point*. Uses the formula ``acting_dq.eps_conjugate() * point_as_dq * acting_dq.conjugate()``. Parameters ---------- acting_dq : The acting dual quaternion. point : The point to transform. Returns ------- PointHomogeneous The transformed point. """ point_as_dq = DualQuaternion(point.point2dq_array()) result = acting_dq.eps_conjugate() * point_as_dq * acting_dq.conjugate() return PointHomogeneous.from_dual_quaternion(result)