Repository: scipy/scipy
File: scipy/spatial/transform/_rigid_transform.py (Python)

Code before:
            return NotImplemented
        if not broadcastable(self._matrix.shape, other._matrix.shape):
            raise ValueError(
                f"Cannot broadcast {self._matrix.shape[:-2]} transforms in "
                f"first to {other._matrix.shape[:-2]} transforms in second object."
            )
        cython_compatible = self._matrix.ndim < 4 and other._matrix.ndim < 4
        backend = select_backend(self._xp, cython_compatible=cython_compatible)
        matrix = backend.compose_transforms(self._matrix, other._matrix)
        # Only necessary for cython. Array API broadcasting handles this by default
        if self._single and other._single:
            matrix = matrix[0, ...]
        return RigidTransform(matrix, normalize=True, copy=False)

    @xp_capabilities(
        skip_backends=[("dask.array", "missing linalg.cross/det functions")]
    )
    def __pow__(self, n: float) -> RigidTransform:
        """Compose this transform with itself `n` times.

        A rigid transform `p` when raised to non-integer powers can be thought
        of as finding a fraction of the transformation. For example, a power of
        0.5 finds a "halfway" transform from the identity to `p`.

        This is implemented by applying screw linear interpolation (ScLERP)
        between `p` and the identity transform, where the angle of the rotation
        component is scaled by `n`, and the translation is proportionally
        adjusted along the screw axis.

        ``q = p ** n`` can also be expressed as
        ``q = RigidTransform.from_exp_coords(p.as_exp_coords() * n)``.

        If `n` is negative, then the transform is inverted before the power
        is applied. In other words, ``p ** -abs(n) == p.inv() ** abs(n)``.

        Parameters
        ----------
        n : float
            The number of times to compose the transform with itself.

        Returns
        -------
        `RigidTransform` instance
            If the input Rotation `p` contains `N` multiple rotations, then
            the output will contain `N` rotations where the `i` th rotation
            is equal to ``p[i] ** n``.

        Notes
        -----
        There are three notable cases: if ``n == 1`` then a copy of the original
        transform is returned, if ``n == 0`` then the identity transform is

Review comment (suggestion):
we might need to add to https://github.com/scipy/scipy/blob/main/doc/source/_templates/autosummary/class.rst?plain=1 and https://github.com/scipy/scipy/blob/29c5d610796109e99b96893bc56926ee82353192/doc/source/conf.py#L298-L300 if we want `__rmul__` to show up in the docs

Code after:
        (5, 4, 3)
        """
        if isinstance(other, Rotation):
            other = RigidTransform.from_rotation(other)
        elif not isinstance(other, RigidTransform):
            # If other is not a RigidTransform, we return NotImplemented to allow other
            # types to implement __rmul__
            return NotImplemented
        if not broadcastable(self._matrix.shape, other._matrix.shape):
            raise ValueError(
                f"Cannot broadcast {self._matrix.shape[:-2]} transforms in "
                f"first to {other._matrix.shape[:-2]} transforms in second object."
            )
        cython_compatible = self._matrix.ndim < 4 and other._matrix.ndim < 4
        backend = select_backend(self._xp, cython_compatible=cython_compatible)
        matrix = backend.compose_transforms(self._matrix, other._matrix)
        # Only necessary for cython. Array API broadcasting handles this by default
        if self._single and other._single:
            matrix = matrix[0, ...]
        return RigidTransform(matrix, normalize=True, copy=False)

    @xp_capabilities(
        skip_backends=[("dask.array", "missing linalg.cross/det functions")]
    )
    def __rmul__(self, other: Rotation) -> RigidTransform | NotImplementedType:
        """Compose a rotation with this transform (rotation applied second).

        See `__mul__` for more details.

        Parameters
        ----------
        other : `Rotation` instance
            The rotation to compose with this transform. The shapes must be
            broadcastable.

        Returns
        -------
        `RigidTransform` instance
            The composed transform.
        """
        if isinstance(other, Rotation):
            other = RigidTransform.from_rotation(other)
            return other * self
        # When other is a RigidTransform __mul__ is called, so we don't handle it here
        return NotImplemented

    @xp_capabilities(
        skip_backends=[("dask.array", "missing linalg.cross/det functions")]
    )
    def __pow__(self, n: float) -> RigidTransform:
        """Compose this transform with itself `n` times.