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
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