pyextal.superspace.approximant

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

supercell_cell(lat, M)

Lattice parameters of the supercell.

lattice_cosets(M)

Basic-cell translations that are distinct modulo the supercell.

supercell_atoms(lat, atoms[, M, wrap])

Freeze the modulation into an ordinary list of 3D atomic positions.

write_dat(path, lat, atoms[, zone, xaxis, hv, conv, ...])

Write the approximant as a .dat file the Bloch-wave engine can parse.

assign_superspace_index(lat, hkl_super[, M, ...])

Label supercell reflections with their (3+d) superspace indices.

dynamical_pattern(lat, atoms[, thickness, zone, ...])

A dynamical zone-axis pattern of the approximant, via the Bloch engine.

supercell_saed(lat, atoms, H[, thickness, zone, ...])

Dynamical pattern of the exact supercell, on the same beams as H.

Module Contents

pyextal.superspace.approximant.supercell_cell(lat, M)

Lattice parameters of the supercell.

Parameters:
  • lat (SuperspaceLattice) – Supplies the basic metric.

  • M (array-like) – (3, 3) integer supercell matrix; rows are the supercell direct lattice vectors in terms of the basic ones.

Returns:

(a, b, c, alpha, beta, gamma), Angstrom and degrees.

Return type:

tuple

pyextal.superspace.approximant.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.

Parameters:

M (array-like) – (3, 3) integer supercell matrix.

Returns:

(|det M|, 3) integer translations.

Return type:

np.ndarray

Raises:

ValueError – If M is singular, or the search does not find them all.

pyextal.superspace.approximant.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.

Parameters:
  • lat (SuperspaceLattice) – Must be commensurate.

  • atoms (list[ModulatedAtom]) – The basic structure, listed in P1.

  • M (array-like, optional) – Supercell matrix; taken from SuperspaceLattice.supercell_matrix() when None.

  • wrap (bool) – Wrap fractional coordinates into [0, 1).

Returns:

(M, cell, sites) with sites a list of (label, element, position, B); position is (3,) in supercell fractional coordinates.

Return type:

tuple

Raises:

ValueError – If the structure is incommensurate, so no supercell exists.

pyextal.superspace.approximant.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.

Parameters:
  • path (path-like) – Destination file.

  • lat (SuperspaceLattice) – The commensurate lattice.

  • atoms (list[ModulatedAtom]) – Basic-structure atoms, listed in P1.

  • zone (tuple) – Zone axis in supercell direct indices. With the basis SuperspaceLattice.supercell_matrix() returns, the parent’s [001] is still (0, 0, 1).

  • xaxis (tuple) – In-plane reference reflection, supercell indices.

  • hv (float) – Accelerating voltage in kV.

  • conv (float) – Convergence semi-angle in units of |g_xaxis|/|K|. Zero is a parallel beam, which is what a SAED pattern is.

  • samp (int) – Sampling across the disc.

  • M (array-like, optional) – Supercell matrix; derived when None.

  • gmax (float) – Beam-selection radius, inverse Angstrom.

  • sgmax (float) – Largest excitation error for beam selection.

  • sgmin (float) – Excitation error below which a beam is fully diagonalised.

  • omeg (float) – Bethe-potential parameter; larger is more accurate.

  • bmin (float) – Bethe potential cutoff.

  • aper (float, optional) – Output aperture radius; gmax when None.

  • abs_pot (float) – Mean absorption potential in volts.

  • name (str) – Crystal name written into the file.

  • norm (tuple, optional) – Surface normal; zone when None.

  • kt (tuple) – Tangential incident wavevector.

  • header (str, optional) – Extra comment lines at the top.

  • hkl (array-like, optional) – 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.

Returns:

(path, M, cell, sites) – the file written plus its contents.

Return type:

tuple

pyextal.superspace.approximant.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 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.

Parameters:
  • lat (SuperspaceLattice) – The commensurate parent lattice.

  • hkl_super (array-like) – (n, 3) integer supercell reflection indices.

  • M (array-like, optional) – Supercell matrix; derived when None.

  • max_order (int) – Largest satellite order to consider.

  • tol (float) – Integrality tolerance on the main part.

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.

Return type:

tuple[np.ndarray, np.ndarray]

pyextal.superspace.approximant.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 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.

Parameters:
  • lat (SuperspaceLattice) – Must be commensurate.

  • atoms (list[ModulatedAtom]) – The basic structure, listed in P1.

  • thickness (float) – Specimen thickness in Angstrom.

  • zone (tuple) – Zone axis in supercell indices.

  • xaxis (tuple) – In-plane reference reflection, supercell indices.

  • hv (float) – Accelerating voltage in kV.

  • gmax (float) – Beam-selection radius, inverse Angstrom. The supercell reciprocal lattice is dense; raising this is expensive.

  • path (path-like, optional) – Where to write the .dat; a temporary file when None.

  • max_order (int) – Largest satellite order when labelling reflections.

  • keep_engine (bool) – Skip terminate(), leaving the engine live.

  • backend (str) – Eigensolver, 'numpy' (default; any build) or 'fortran' (MPI build only).

  • **dat_kw – Passed through to 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.

Return type:

dict

pyextal.superspace.approximant.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 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.

Parameters:
  • lat (SuperspaceLattice) – Must be commensurate.

  • atoms (list[ModulatedAtom]) – The basic structure in P1.

  • H (array-like) – (n, 3+d) superspace beam list.

  • thickness (float) – Specimen thickness, Angstrom.

  • zone (tuple) – Zone axis in supercell indices.

  • xaxis (tuple) – In-plane reference reflection, supercell indices.

  • hv (float) – Accelerating voltage, kV.

  • M (array-like, optional) – Supercell matrix; derived when None.

  • path (path-like, optional) – Where to write the .dat.

  • keep_engine (bool) – Skip terminate().

  • backend (str) – Eigensolver, 'numpy' (default; any build) or 'fortran' (MPI build only).

  • **dat_kw – Passed to write_dat().

Returns:

H (the beams, unchanged and in order), hkl_super, intensity normalised to the strongest diffracted beam, pattern (a DiffractionPattern, so the result renders through plot_image() like any other), M, cell, natoms and nbeams.

Return type:

dict

Raises:

ValueError – If the structure is incommensurate, or a beam does not map to an integral supercell index.