pyextal.superspace.structurefactor ================================== .. py:module:: pyextal.superspace.structurefactor .. autoapi-nested-parse:: The modulated structure factor. This is item 2: the change to how ``sfact`` works. For an unmodulated crystal a structure factor is a sum over atoms. For a modulated one each atom is smeared over the internal coordinate, and the sum becomes a sum of integrals: F(H) = sum_mu f_mu(s) T_mu(s) Int_[0,1]^d dxbar P_mu(xbar) exp{ 2 pi i [ h . r_mu + m . xbar + S(H) . u_mu(xbar) ] } with ``h`` the *integer* main index, ``m`` the satellite index, ``S(H)`` the effective, non-integral reciprocal index, and ``u_mu`` the displacement field. Setting every ``u = 0`` and ``m = 0`` collapses it to the ordinary expression, which is one of the tests. Why quadrature rather than Bessel functions ------------------------------------------- For a single harmonic displacive modulation the integral has the closed form ``J_-m(2 pi S.A)``, and textbooks present it that way. That form does not survive several harmonics, or an occupational modulation multiplying a displacive one, and the published 1T-TaS2 refinement used 21 harmonics on Ta. The integrand is smooth and periodic, so the plain rectangle rule converges *exponentially* in the number of points -- there is no accuracy left on the table, and it handles every case with one code path. :func:`bessel_reference` provides the closed form so the quadrature can be checked against it. The one thing that will silently go wrong is an under-resolved grid: the integrand's bandwidth grows with satellite order and with modulation amplitude, and too few points aliases high harmonics onto low ones. :func:`auto_n_internal` sizes the grid from both, and :func:`structure_factor` uses it by default. Not implemented: modulated ADPs. `B` is a constant per atom. A modulated ADP would slot into the integrand next to `P`, but nothing here needs it yet and an untested implementation is worse than an honest gap. Functions --------- .. autoapisummary:: pyextal.superspace.structurefactor.internal_grid pyextal.superspace.structurefactor.auto_n_internal pyextal.superspace.structurefactor.structure_factor pyextal.superspace.structurefactor.bessel_reference Module Contents --------------- .. py:function:: internal_grid(d, n) Uniform sampling of the internal unit cell ``[0, 1)^d``. :param d: Number of modulation dimensions. :type d: int :param n: Points per dimension. :type n: int :returns: (n**d, d) internal coordinates. :rtype: np.ndarray .. py:function:: auto_n_internal(atoms, hkl, max_order, safety=4, lo=16, hi=256) Choose a grid fine enough that the quadrature does not alias. The integrand carries ``exp(2 pi i m.xbar)``, contributing bandwidth ``max|m|``, and ``exp(2 pi i S.u(xbar))``, whose Jacobi-Anger expansion has significant terms out to roughly ``|z| + 4`` in each harmonic present, where ``z = 2 pi max|S.u|``. The rectangle rule is exact below ``n/2``, so the grid is sized at `safety` times the estimated bandwidth. :param atoms: Supplies the modulation amplitudes. :type atoms: list[ModulatedAtom] :param hkl: (n, 3) effective reciprocal indices. :type hkl: np.ndarray :param max_order: Largest satellite order in the reflection set. :type max_order: int :param safety: Multiplier on the estimated bandwidth. :type safety: int :param lo: Floor on the returned value. :type lo: int :param hi: Ceiling, to keep ``n**d`` affordable. :type hi: int :returns: Points per internal dimension, rounded up to even. :rtype: int .. py:function:: structure_factor(lat, atoms, H, kind='electron', n_internal=None, max_points=4000000, s_floor=1e-05) Structure factors of a modulated structure. :param lat: Supplies the metric and the q-vectors. :type lat: SuperspaceLattice :param atoms: The structure, listed in P1 -- every atom of the basic cell, not just the asymmetric unit. :type atoms: list[ModulatedAtom] :param H: (n, 3+d) integer superspace indices. :type H: array-like :param kind: ``'electron'`` for Mott-Bethe scattering factors, matching the Bloch-wave engine, or ``'xray'`` for the Cromer-Mann values. :type kind: str :param n_internal: Points per internal dimension. Sized by :func:`auto_n_internal` when None. :type n_internal: int, optional :param max_points: Reflections are processed in chunks so that ``chunk * n_internal**d`` stays under this, bounding memory. :type max_points: int :param s_floor: Lower clamp on ``s = |g|/2``, as the engine uses. :type s_floor: float :returns: (n,) complex structure factors, in the units of the chosen scattering factor. :rtype: np.ndarray :raises ValueError: If `kind` is not recognised, or `H` has the wrong width. .. py:function:: bessel_reference(lat, H, amplitude, element='TA', position=(0.0, 0.0, 0.0), kind='electron', s_floor=1e-05) Closed-form structure factor for one atom with a single harmonic. For ``u(xbar) = A sin(2 pi xbar)`` in (3+1)D, Jacobi-Anger gives Int_0^1 exp{2 pi i [m xbar + S.A sin(2 pi xbar)]} dxbar = J_-m(2 pi S.A) exactly. This is the analytic check on the quadrature in :func:`structure_factor`; see ``test_superspace.py``. :param lat: Must be (3+1)-dimensional. :type lat: SuperspaceLattice :param H: (n, 4) superspace indices. :type H: array-like :param amplitude: (3,) sine amplitude in fractional coordinates. :type amplitude: array-like :param element: Element symbol for the scattering factor. :type element: str :param position: (3,) fractional position of the single atom. :type position: array-like :param kind: ``'electron'`` or ``'xray'``. :type kind: str :param s_floor: Lower clamp on ``s``. :type s_floor: float :returns: (n,) complex structure factors. :rtype: np.ndarray :raises ValueError: If `lat` is not (3+1)-dimensional.