pyextal.superspace.approximant ============================== .. py:module:: pyextal.superspace.approximant .. autoapi-nested-parse:: The commensurate approximant: a modulated structure as an ordinary 3D crystal. When the modulation wave vectors are rational the structure has a genuine 3D supercell, and inside that supercell it is a plain periodic crystal with the modulation frozen into the atomic positions. That is worth having for its own sake, but here it does a specific job: **it is the one form of a modulated structure the existing Bloch-wave engine can already compute.** No Fortran change, no rebuild, nothing touched -- the supercell is written out as an ordinary ``.dat`` file and `bloch_parse` reads it like any other crystal. So the dynamical half of the TaS2 example costs nothing in risk. The kinematic (3+d) calculation and the dynamical supercell calculation are independent implementations of the same physics, and each checks the other: the supercell is exact where the incommensurate description is an idealisation, and the superspace description is exact where the supercell is an approximation. The supercell is not small. 1T-TaS2's in-plane CDW gives a 13-fold cell, 39 atoms; including the ``1/3 c*`` stacking term makes it 39-fold and 117 atoms. Both are far inside the engine's limits (``nk = 5000``), but the reciprocal lattice is 13 or 39 times denser, so keep ``gmax`` modest when selecting beams. Functions --------- .. autoapisummary:: pyextal.superspace.approximant.supercell_cell pyextal.superspace.approximant.lattice_cosets pyextal.superspace.approximant.supercell_atoms pyextal.superspace.approximant.write_dat pyextal.superspace.approximant.assign_superspace_index pyextal.superspace.approximant.dynamical_pattern pyextal.superspace.approximant.supercell_saed Module Contents --------------- .. py:function:: supercell_cell(lat, M) Lattice parameters of the supercell. :param lat: Supplies the basic metric. :type lat: SuperspaceLattice :param M: (3, 3) integer supercell matrix; rows are the supercell direct lattice vectors in terms of the basic ones. :type M: array-like :returns: ``(a, b, c, alpha, beta, gamma)``, Angstrom and degrees. :rtype: tuple .. py:function:: lattice_cosets(M) Basic-cell translations that are distinct modulo the supercell. There are ``|det M|`` of them, and they are the origins of the basic cells packed into one supercell. :param M: (3, 3) integer supercell matrix. :type M: array-like :returns: (|det M|, 3) integer translations. :rtype: np.ndarray :raises ValueError: If `M` is singular, or the search does not find them all. .. py:function:: supercell_atoms(lat, atoms, M=None, wrap=True) Freeze the modulation into an ordinary list of 3D atomic positions. Each basic-cell atom is replicated onto every lattice coset of the supercell. Its internal coordinate follows from where it sits, ``xbar_j = q_j . (t + r)``, so each copy gets a different displacement -- which is exactly what makes the supercell a superstructure rather than ``|det M|`` identical copies. :param lat: Must be commensurate. :type lat: SuperspaceLattice :param atoms: The basic structure, listed in P1. :type atoms: list[ModulatedAtom] :param M: Supercell matrix; taken from :meth:`SuperspaceLattice.supercell_matrix` when None. :type M: array-like, optional :param wrap: Wrap fractional coordinates into [0, 1). :type wrap: bool :returns: ``(M, cell, sites)`` with `sites` a list of ``(label, element, position, B)``; `position` is (3,) in supercell fractional coordinates. :rtype: tuple :raises ValueError: If the structure is incommensurate, so no supercell exists. .. py:function:: write_dat(path, lat, atoms, zone=(0, 0, 1), xaxis=(1, 0, 0), hv=200.0, conv=0.0, samp=20, M=None, gmax=0.9, sgmax=3.0, sgmin=0.3, omeg=30.0, bmin=0.1, aper=None, abs_pot=0.0, name='approximant', norm=None, kt=(0, 0, 0), header=None, hkl=None) Write the approximant as a ``.dat`` file the Bloch-wave engine can parse. The file uses ``spg 1 1`` -- P1 with every atom listed -- because the supercell's symmetry is lower than the basic structure's, and letting the engine expand a higher group would put atoms in the wrong places. The ``.dat`` format is parsed sequentially and is order-sensitive, so the keywords below are emitted in the order the parser expects; see ``docs/source/configuration.md``. ``sele`` chooses the beams geometrically, which is convenient but means the beam count moves with the voltage and the cell -- and the supercell reciprocal lattice is ``|det M|`` times denser than the basic one, so a `gmax` that is unremarkable for the parent cell can select thousands of beams here. Start small and check what you got. :param path: Destination file. :type path: path-like :param lat: The commensurate lattice. :type lat: SuperspaceLattice :param atoms: Basic-structure atoms, listed in P1. :type atoms: list[ModulatedAtom] :param zone: Zone axis in *supercell* direct indices. With the basis :meth:`SuperspaceLattice.supercell_matrix` returns, the parent's [001] is still (0, 0, 1). :type zone: tuple :param xaxis: In-plane reference reflection, supercell indices. :type xaxis: tuple :param hv: Accelerating voltage in kV. :type hv: float :param conv: Convergence semi-angle in units of ``|g_xaxis|/|K|``. Zero is a parallel beam, which is what a SAED pattern is. :type conv: float :param samp: Sampling across the disc. :type samp: int :param M: Supercell matrix; derived when None. :type M: array-like, optional :param gmax: Beam-selection radius, inverse Angstrom. :type gmax: float :param sgmax: Largest excitation error for beam selection. :type sgmax: float :param sgmin: Excitation error below which a beam is fully diagonalised. :type sgmin: float :param omeg: Bethe-potential parameter; larger is more accurate. :type omeg: float :param bmin: Bethe potential cutoff. :type bmin: float :param aper: Output aperture radius; `gmax` when None. :type aper: float, optional :param abs_pot: Mean absorption potential in volts. :type abs_pot: float :param name: Crystal name written into the file. :type name: str :param norm: Surface normal; `zone` when None. :type norm: tuple, optional :param kt: Tangential incident wavevector. :type kt: tuple :param header: Extra comment lines at the top. :type header: str, optional :param hkl: Explicit beam list in *supercell* indices, written as an ``hkl`` block instead of a ``sele`` line. Use it to make a supercell calculation solve the same scattering problem as a superspace one: pass the images of the superspace beams under `M`, and the two share a beam set so their intensities can be compared reflection by reflection. :type hkl: array-like, optional :returns: ``(path, M, cell, sites)`` -- the file written plus its contents. :rtype: tuple .. py:function:: assign_superspace_index(lat, hkl_super, M=None, max_order=6, tol=1e-06) Label supercell reflections with their (3+d) superspace indices. This is what makes the dynamical calculation comparable with the kinematic one. The engine works in the supercell and knows nothing about modulation, so it reports a reflection as ``(1, 0, 0)``; in the parent's terms that same reflection is the first-order satellite ``(0 0 0 | 1 0)``. Going back is a matter of undoing ``h' = M h`` and then splitting the result into an integral main index plus a satellite part. The split is not unique for a commensurate structure -- that is the same degeneracy :meth:`SuperspaceLattice.supercell_matrix` exists because of -- so the assignment of lowest satellite order is chosen, which is the physically meaningful one: a reflection is called a first-order satellite rather than a fifth-order one that happens to land in the same place. :param lat: The commensurate parent lattice. :type lat: SuperspaceLattice :param hkl_super: (n, 3) integer supercell reflection indices. :type hkl_super: array-like :param M: Supercell matrix; derived when None. :type M: array-like, optional :param max_order: Largest satellite order to consider. :type max_order: int :param tol: Integrality tolerance on the main part. :type tol: float :returns: ``(H, ok)`` with `H` of shape (n, 3+d) and `ok` a boolean mask. Where `ok` is False no assignment within `max_order` was found and that row of `H` is zero. :rtype: tuple[np.ndarray, np.ndarray] .. py:function:: dynamical_pattern(lat, atoms, thickness=200.0, zone=(0, 0, 1), xaxis=(1, 0, 0), hv=200.0, gmax=0.8, path=None, max_order=6, keep_engine=False, backend='numpy', **dat_kw) A dynamical zone-axis pattern of the approximant, via the Bloch engine. Writes the supercell as a ``.dat``, runs one incident orientation through the **unmodified** engine, and labels the output beams with their superspace indices so the result lines up with :func:`~pyextal.superspace.saed.saed_pattern`. The engine keeps one crystal in Fortran module-global state, so this reparses and resets it. It calls ``terminate()`` at the end unless `keep_engine` is set -- and note that ``terminate()`` ends the engine for the life of the process, so anything else needing it must run first. Leaving it out instead orphans the MPI workers at 100% CPU. :param lat: Must be commensurate. :type lat: SuperspaceLattice :param atoms: The basic structure, listed in P1. :type atoms: list[ModulatedAtom] :param thickness: Specimen thickness in Angstrom. :type thickness: float :param zone: Zone axis in supercell indices. :type zone: tuple :param xaxis: In-plane reference reflection, supercell indices. :type xaxis: tuple :param hv: Accelerating voltage in kV. :type hv: float :param gmax: Beam-selection radius, inverse Angstrom. The supercell reciprocal lattice is dense; raising this is expensive. :type gmax: float :param path: Where to write the ``.dat``; a temporary file when None. :type path: path-like, optional :param max_order: Largest satellite order when labelling reflections. :type max_order: int :param keep_engine: Skip ``terminate()``, leaving the engine live. :type keep_engine: bool :param backend: Eigensolver, ``'numpy'`` (default; any build) or ``'fortran'`` (MPI build only). :type backend: str :param \*\*dat_kw: Passed through to :func:`write_dat`. :returns: With keys `H` (n, 3+d superspace indices), `hkl_super` (n, 3), `intensity` (n,), `order` (n,), `labelled` (n,) bool, `M`, `cell`, `nbeams` and `path`. :rtype: dict .. py:function:: supercell_saed(lat, atoms, H, thickness=200.0, zone=(0, 0, 1), xaxis=(1, 0, 0), hv=200.0, M=None, path=None, keep_engine=False, backend='numpy', **dat_kw) Dynamical pattern of the **exact supercell**, on the same beams as `H`. The commensurate structure is an ordinary 3D crystal, so this needs nothing from the superspace machinery: the modulation is frozen into ``|det M|`` times as many atomic coordinates and the unmodified engine solves it. What it does need is the *same beam set* as the superspace calculation, or the two dynamical results would not be comparable -- different beams means a different scattering problem. Each superspace reflection maps to the supercell index ``M @ S(H)``, which is integral exactly because the structure is commensurate, and that list is written out explicitly. Agreement between this and :func:`~pyextal.superspace.engine.dynamical_saed` on the same structure is a strong check on the superspace engine: one route freezes the modulation into 507 atoms and never mentions a satellite, the other keeps 3 atoms and integrates over internal space. :param lat: Must be commensurate. :type lat: SuperspaceLattice :param atoms: The basic structure in P1. :type atoms: list[ModulatedAtom] :param H: (n, 3+d) superspace beam list. :type H: array-like :param thickness: Specimen thickness, Angstrom. :type thickness: float :param zone: Zone axis in supercell indices. :type zone: tuple :param xaxis: In-plane reference reflection, supercell indices. :type xaxis: tuple :param hv: Accelerating voltage, kV. :type hv: float :param M: Supercell matrix; derived when None. :type M: array-like, optional :param path: Where to write the ``.dat``. :type path: path-like, optional :param keep_engine: Skip ``terminate()``. :type keep_engine: bool :param backend: Eigensolver, ``'numpy'`` (default; any build) or ``'fortran'`` (MPI build only). :type backend: str :param \*\*dat_kw: Passed to :func:`write_dat`. :returns: ``H`` (the beams, unchanged and in order), ``hkl_super``, ``intensity`` normalised to the strongest diffracted beam, ``pattern`` (a :class:`~pyextal.superspace.saed.DiffractionPattern`, so the result renders through :func:`~pyextal.superspace.saed.plot_image` like any other), ``M``, ``cell``, ``natoms`` and ``nbeams``. :rtype: dict :raises ValueError: If the structure is incommensurate, or a beam does not map to an integral supercell index.