pyextal.superspace.symmetry =========================== .. py:module:: pyextal.superspace.symmetry .. autoapi-nested-parse:: Superspace symmetry operations. A (3+d) superspace operation is a 3D operation ``(R | tau)`` together with its action ``(epsilon | tau_i)`` on the internal coordinates. The internal part is not free: it is *determined* by `R` through how `R` rearranges the modulation wave vectors, R^T q_j = sum_k epsilon_jk q_k + G_j, G_j a reciprocal lattice vector, and :func:`epsilon_for` solves that for ``epsilon`` and ``G``. Getting this wrong is a quiet failure -- the structure factors come out plausible and the symmetry-equivalent reflections simply do not match. Scope ----- :func:`transform_reflection` is general: the ``G_j`` terms fold cleanly into the main indices. :func:`expand_atoms` is not -- it is implemented only for operations with every ``G_j = 0``, because a nonzero ``G_j`` makes the phase relation between symmetry-equivalent modulation functions depend on the atom's own position, which needs the full superspace-group machinery to get right. It raises rather than guessing. For a structure where that bites, list the atoms in P1; that is what :mod:`~pyextal.superspace.tas2` does. Classes ------- .. autoapisummary:: pyextal.superspace.symmetry.SuperspaceOperation Functions --------- .. autoapisummary:: pyextal.superspace.symmetry.epsilon_for pyextal.superspace.symmetry.transform_reflection pyextal.superspace.symmetry.expand_atoms Module Contents --------------- .. py:class:: SuperspaceOperation One symmetry operation of a (3+d) superspace group. :ivar R: (3, 3) integer rotation on direct fractional coordinates. :vartype R: np.ndarray :ivar tau: (3,) translation part, fractional. :vartype tau: np.ndarray :ivar epsilon: (d, d) integer action on internal coordinates. :vartype epsilon: np.ndarray :ivar tau_i: (d,) internal translation. :vartype tau_i: np.ndarray :ivar G: (d, 3) integer reciprocal lattice vectors from the q-transformation; all zero for the well-behaved settings. :vartype G: np.ndarray :ivar name: Optional label. :vartype name: str .. py:attribute:: R :type: numpy.ndarray .. py:attribute:: tau :type: numpy.ndarray :value: None .. py:attribute:: epsilon :type: numpy.ndarray :value: None .. py:attribute:: tau_i :type: numpy.ndarray :value: None .. py:attribute:: G :type: numpy.ndarray :value: None .. py:attribute:: name :type: str :value: '' .. py:method:: __post_init__() .. py:property:: d Number of modulation dimensions. :type: int .. py:method:: resolve(lat, max_epsilon=3, tol=1e-06) Fill in `epsilon` and `G` from the lattice, if not already given. :param lat: Supplies the q-vectors. :type lat: SuperspaceLattice :param max_epsilon: Half-width of the integer search for epsilon. :type max_epsilon: int :param tol: Tolerance on the residual being a lattice vector. :type tol: float :returns: A new operation with the internal part filled in. :rtype: SuperspaceOperation :raises ValueError: If `R` does not map the q-set onto itself. .. py:function:: epsilon_for(R, q, max_epsilon=3, tol=1e-06) Solve ``R^T q_j = sum_k epsilon_jk q_k + G_j`` for integers. A 3D operation is only a symmetry of a modulated structure if it permutes the Fourier module, which is what this checks. The search is a small exhaustive one over integer `epsilon` rows -- ``d <= 3`` and the entries are tiny, so there are at most a few hundred candidates. The decomposition is **not unique** when the q-vectors are dependent modulo the main lattice, which is exactly the 1T-TaS2 case: with three CDW arms satisfying ``q1 + q2 + q3 = c*``, any solution can be shifted by a multiple of that relation. The smallest one is returned -- minimum ``|epsilon|`` first, then minimum ``|G|`` -- which picks out the plain permutation with ``G = 0`` rather than some equivalent monster. Taking whichever candidate an unordered search happened to reach first would make the answer depend on iteration order. :param R: (3, 3) integer rotation on direct fractional coordinates. :type R: array-like :param q: (d, 3) modulation wave vectors. :type q: array-like :param max_epsilon: Half-width of the integer search per component. :type max_epsilon: int :param tol: How close the residual has to be to an integer vector. :type tol: float :returns: ``(epsilon, G)``, shapes (d, d) and (d, 3). :rtype: tuple[np.ndarray, np.ndarray] :raises ValueError: If some ``R^T q_j`` cannot be written this way, meaning `R` is not a symmetry of the modulated structure. .. py:function:: transform_reflection(op, H) Apply a superspace operation to reflection indices. From ``R^T S(H) = (R^T h + G^T m) + sum_k (epsilon^T m)_k q_k``, so the main part picks up the ``G`` contribution and the satellite part transforms by ``epsilon^T``. Valid for any `G`. :param op: Operation with its internal part resolved. :type op: SuperspaceOperation :param H: (n, 3+d) integer superspace indices. :type H: array-like :returns: (n, 3+d) transformed indices. :rtype: np.ndarray :raises ValueError: If `op` has no internal part, or the widths disagree. .. py:function:: expand_atoms(atoms, ops, lat, tol=1e-06) Generate the symmetry-equivalent atoms and their modulation functions. Under ``(R | tau)`` an atom moves to ``R r + tau`` and its displacement field becomes ``u'(xbar) = R u(epsilon^-1 (xbar - tau_i))``. Written out in harmonics, the index maps as ``n -> epsilon^-T n`` and the amplitudes mix by the phase ``phi = 2 pi n' . tau_i``:: A' = R (A cos phi + B sin phi) B' = R (-A sin phi + B cos phi) :param atoms: The asymmetric unit. :type atoms: list[ModulatedAtom] :param ops: Operations, resolved or not. :type ops: list[SuperspaceOperation] :param lat: Supplies the q-vectors for resolving. :type lat: SuperspaceLattice :param tol: Tolerance for recognising a duplicate position. :type tol: float :returns: The expanded set, duplicates removed. :rtype: list[ModulatedAtom] :raises NotImplementedError: If any operation has a nonzero `G`. See the module docstring -- list the structure in P1 instead.