Example #1
0
    def _with_rotation_to_minimize_angle(
        self: _T,
        start: np.ndarray,
        target: np.ndarray,
        axis: np.ndarray,
        origin: np.ndarray,
    ) -> _T:
        # If the vector being rotated is not finite, exit. This is
        # probably due to a planar molecule.
        if not all(np.isfinite(x) for x in start):
            return self
        if np.allclose(target, [0, 0, 0], atol=1e-15):
            raise ValueError(
                'target has a magnitude of 0. It is therefore not '
                'possible to calculate an angle.')

        self._with_displacement(-origin)

        # 1. Remove any component of the start and target vectors long
        # the axis. This puts them both on the same plane.
        # 2. Calculate the angle between them.
        # 3. Apply the rotation.
        tstart = start - np.dot(start, axis) * axis

        # If `tstart` is 0, it is parallel to the rotation axis, stop.
        if np.allclose(tstart, [0, 0, 0], 1e-8):
            self._with_displacement(origin)
            return self

        tend = target - np.dot(target, axis) * axis
        # If `tend` is 0, it is parallel to the rotation axis, stop.
        if np.allclose(tend, [0, 0, 0], 1e-8):
            self._with_displacement(origin)
            return self

        angle = _utilities.vector_angle(tstart, tend)

        projection = tstart @ np.cross(axis, tend)
        if projection > 0:
            angle = 2 * np.pi - angle

        rotation_matrix = _utilities.rotation_matrix_arbitrary_axis(
            angle=angle,
            axis=axis,
        )
        self._position_matrix = rotation_matrix @ self._position_matrix
        self._with_displacement(origin)
        return self
Example #2
0
    def _get_angle(self, item):
        """
        Get the angle of `vector` relative to `reference`.

        Parameters
        ----------
        item : :class:`object`
            The item being sorted.

        Returns
        -------
        :class:`float`
            The angle between `item` and the reference vector.

        """

        vector = self._get_vector(item)
        theta = vector_angle(self._reference, vector)
        projection = vector @ self._axis
        if theta > 0 and projection < 0:
            return 2 * np.pi - theta
        return theta