pyextal.superspace¶
Incommensurate crystallography in (3+d)-dimensional superspace.
A modulated crystal has no three-dimensional lattice. Its diffraction pattern is
still perfectly sharp, but the reflections do not sit on a 3D lattice, so three
integers cannot label them. The superspace description of de Wolff, Janner,
Janssen and van Smaalen restores a lattice by adding dimensions: every
reflection is indexed by (h, k, l, m1, ..., md) and sits at
S(H) = h a* + k b* + l c* + m1 q1 + … + md qd
This subpackage implements that indexing, the modulated structure factor, and a kinematic zone-axis pattern, with 1T-TaS2 as the worked example.
Quick start¶
from pyextal.superspace import tas2, saed_pattern, plot_pattern
lat, atoms = tas2.structure(d=2) # (3+2)D, incommensurate
pattern = saed_pattern(lat, atoms, zone=(0, 0, 1))
plot_pattern(pattern, annotate=12)
What is and is not here¶
d = 1, 2, 3are all supported. The q-vectors have to be independent modulo the main lattice, andSuperspaceLatticewarns when they are not – 1T-TaS2’s three CDW arms satisfyq1 + q2 + q3 = c*, so they span a rank-2 module and the correct description is (3+2)D, not (3+3)D.Displacive and occupational modulation, with arbitrarily many harmonics. Modulated ADPs are not implemented.
Structure factors are computed by quadrature over internal space, checked against the Bessel-function closed form to machine precision.
Symmetry: superspace operations, the
epsilonsolver, and reflection transformation. Expanding modulation functions through an operation is implemented only for the well-behaved case; seesymmetry.The dynamical side goes through the commensurate approximant (
approximant), which writes an ordinary.datfile for the unmodified Bloch-wave engine. Nothing in this subpackage changes the Fortran,pyextal.acom, orjaxEngine.
The 1T-TaS2 modulation amplitudes in tas2 are a
labelled stand-in, not the published refinement. See that module.
Submodules¶
Classes¶
Superspace reflections with their derived 3D quantities. |
|
A 3D lattice plus |
|
Positional modulation: a displacement field over internal space. |
|
One term of a Fourier series in the internal coordinate. |
|
One atom of the basic structure, with its modulation functions. |
|
Occupancy as a function of internal coordinate. |
|
A computed zone-axis pattern. |
|
One symmetry operation of a (3+d) superspace group. |
Functions¶
|
Label supercell reflections with their (3+d) superspace indices. |
|
A dynamical zone-axis pattern of the approximant, via the Bloch engine. |
|
Freeze the modulation into an ordinary list of 3D atomic positions. |
|
Lattice parameters of the supercell. |
|
Dynamical pattern of the exact supercell, on the same beams as H. |
|
Write the approximant as a |
|
All superspace reflections inside a resolution sphere. |
|
Render one superspace index as |
|
The satellite offsets that leave a reflection where it is. |
|
Reduce a beam list to one entry per physical reflection. |
|
Group superspace indices that are the same physical reflection. |
|
All |
|
|
|
Metric on satellite indices: |
|
Direct-space metric tensor from the six lattice parameters. |
|
Marker areas for a diffraction pattern, saturating the direct beam. |
|
Draw a rendered pattern as an image. |
|
Draw a pattern, distinguishing main reflections from satellite orders. |
|
In-plane coordinates of superspace reflections at a zone axis. |
|
The pseudo-Voigt point spread the JAX refinement fits, on a pixel grid. |
|
Render a pattern as a 2D intensity image, the way a plate records it. |
|
Compute a kinematic zone-axis pattern. |
|
The intensity |
|
Electron scattering factor by the Mott-Bethe relation. |
|
X-ray atomic form factor. |
|
Choose a grid fine enough that the quadrature does not alias. |
|
Closed-form structure factor for one atom with a single harmonic. |
|
Uniform sampling of the internal unit cell |
|
Structure factors of a modulated structure. |
|
Solve |
|
Generate the symmetry-equivalent atoms and their modulation functions. |
|
Apply a superspace operation to reflection indices. |
Package Contents¶
- pyextal.superspace.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 undoingh' = M hand 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.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 withsaed_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 thatterminate()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.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.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.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 indexM @ 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,intensitynormalised to the strongest diffracted beam,pattern(aDiffractionPattern, so the result renders throughplot_image()like any other),M,cell,natomsandnbeams.- Return type:
dict
- Raises:
ValueError – If the structure is incommensurate, or a beam does not map to an integral supercell index.
- pyextal.superspace.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
.datfile 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
.datformat is parsed sequentially and is order-sensitive, so the keywords below are emitted in the order the parser expects; seedocs/source/configuration.md.selechooses 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
hklblock instead of aseleline. 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
- class pyextal.superspace.ReflectionSet¶
Superspace reflections with their derived 3D quantities.
- Variables:
H (np.ndarray) – (n, 3+d) integer superspace indices.
hkl (np.ndarray) – (n, 3) effective, generally non-integral, 3D reciprocal indices.
gmag (np.ndarray) – (n,)
|S(H)|in inverse Angstrom.order (np.ndarray) – (n,) satellite order – the rank of this reflection’s modulation length among the distinct lengths present, so 0 for a main reflection, 1 for the shortest satellites, and so on. Symmetry-equivalent satellites always share an order; see the module docstring for why
sum |m|will not do.satlen (np.ndarray) – (n,)
|sum_j m_j q_j|in inverse Angstrom, the quantity order ranks.d (int) – Number of modulation dimensions.
- H: numpy.ndarray¶
- hkl: numpy.ndarray¶
- gmag: numpy.ndarray¶
- order: numpy.ndarray¶
- satlen: numpy.ndarray¶
- d: int¶
- property index_sum¶
(n,)
sum |m_j|.The conventional label, kept because it is what the literature usually quotes. It is not symmetry-invariant – do not filter on it.
- Type:
np.ndarray
- __len__()¶
- __repr__()¶
- select(mask)¶
A new set holding only the reflections mask keeps.
- Parameters:
mask (np.ndarray) – Boolean or integer index array.
- Returns:
The subset.
- Return type:
- main()¶
ReflectionSet: Just the main reflections.
- satellites(order=None)¶
Satellites, optionally of one exact order.
- Parameters:
order (int, optional) – Keep only this order; all satellites if None.
- Returns:
The subset.
- Return type:
- labels()¶
list[str]: One formatted index per reflection.
- pyextal.superspace.enumerate_reflections(lat, gmax, max_index=6, max_order=4, include_origin=True, condition=None)¶
All superspace reflections inside a resolution sphere.
- Parameters:
lat (SuperspaceLattice) – Supplies the metric and the q-vectors.
gmax (float) – Largest
|S(H)|to keep, inverse Angstrom.max_index (int) – Half-width of the
(h, k, l)search box.max_order (int) – Satellite cutoff, as a multiple of the shortest modulation wave vector:
|sum_j m_j q_j| <= max_order * min|q_j|. This is symmetry-invariant, unlikesum |m|– see the module docstring. The published 1T-TaS2 data reached order 6.include_origin (bool) – Keep
(0, 0, 0 | 0 ...). Defaults to True, since a diffraction pattern has a direct beam.condition (callable, optional) – Reflection condition – takes the
(n, 3+d)index array and returns a boolean mask of the allowed reflections. Systematically absent reflections come out with a zero structure factor anyway, since the centred copies cancel, so this saves work and keeps a listing readable rather than correcting anything. Seepyextal.superspace.tas2.reflection_condition().
- Returns:
Sorted by
|S(H)|, ascending.- Return type:
- Raises:
ValueError – If nothing falls inside gmax.
- pyextal.superspace.format_index(H)¶
Render one superspace index as
(h k l | m1 m2).The bar keeps the main indices visually separate from the satellite ones, which matters when reading a list where most entries differ only after it.
- Parameters:
H (array-like) – (3+d,) integer index.
- Returns:
Formatted label.
- Return type:
str
- pyextal.superspace.coincidence_coset(lat, dh_max=1.05, max_order=24, tol=1e-06)¶
The satellite offsets that leave a reflection where it is.
cbelongs to the coset whenc . qis an integer reciprocal-lattice vector, so that(h, m)and(h - c.q, m + c)have the same effective indexS(H)and are therefore the same physical reflection. The set is a lattice – infinite – so it has to be truncated.The truncation is on the length of the shift
|c . q|, not on the ordersum |c_j|, and that is not a detail. The coset is closed under the crystal’s point group, butsum |c_j|is not invariant under it: cutting there admits some members of a symmetry orbit and not others, which breaks the pattern’s symmetry rather than restoring it. Measured on 1T-TaS2’s commensurate phase against the exact supercell, an order cut made the agreement worse than no coset at all (174% at order 4, against 41% uncorrected) while shell cuts improve monotonically.|c . q|is a reciprocal-space length and so is invariant, and truncating there keeps whole shells.merge_coincident()handles the same degeneracy at the level of a finished reflection list; this is the form the dynamical calculation needs, because there the coincident amplitudes have to be summed inside every U(g,h) rather than afterwards.For an incommensurate lattice only the identity qualifies, and the returned arrays are the single zero row – the no-op the engine starts from.
- Parameters:
lat (SuperspaceLattice) – The lattice.
dh_max (float) – Largest
|c . q|to include, in inverse Angstrom. The default takes the first shell of shifts.max_order (int) – How far to search in
c; raise it only if a shell needs offsets larger than this, which the returned members make obvious.tol (float) – How close
c . qmust be to integral.
- Returns:
(c, dh), each (n, d) and (n, 3) of ints, ordered by increasing|c . q|with the identity at row 0.- Return type:
tuple[np.ndarray, np.ndarray]
- pyextal.superspace.distinct_beams(lat, H, who='this calculation', warn=True)¶
Reduce a beam list to one entry per physical reflection.
A dynamical calculation must be given distinct reflections. On a commensurate lattice an enumerated list is not: several superspace indices name one reciprocal-lattice point, and handing them over as separate beams breaks the engine twice over. They become identical rows in the structure matrix, which is then singular – the eigensolver does not report that, it just returns an arbitrary basis in the degenerate subspace. And the .dat written from such a list repeats indices in its
hklblock, sobloch_parsefails to resolve someoutentries against it and appends them past the array allocated from the declarednbeams. That one is memory corruption, and it presents from a long way off: an MKL complaint about a CGEBAL parameter, a SIGSEGV inside CGEEV, or the child simply being killed.Reducing costs nothing physically. U(g,h) already sums the coincident amplitudes internally – see
coincidence_coset()– so the distinct reflections carry the whole structure.- Parameters:
lat (SuperspaceLattice) – The lattice.
H (array-like) – (n, 3+d) superspace indices, as enumerated.
who (str) – Name used in the warning.
warn (bool) – Warn when the list actually shrinks.
- Returns:
(m, 3+d) representatives, m <= n, in the input order.
- Return type:
np.ndarray
- pyextal.superspace.merge_coincident(lat, H, tol=1e-08)¶
Group superspace indices that are the same physical reflection.
For an incommensurate structure this never does anything: the map from
(h k l m1 .. md)toS(H)is injective, which is what makes the indexing unique. For a commensurate one it is not – distinct indices land on the same reciprocal-lattice point, because the Fourier module has collapsed onto a 3D lattice. In 1T-TaS2’s commensurate phase, 36 of 85 positions in a modest beam list carry two indices.That matters because the intensity at such a point is
|F_1 + F_2 + ...|^2, a coherent sum of amplitudes. Treating the indices as independent reflections splits the intensity between them arbitrarily and destroys the pattern’s symmetry – symmetry-equivalent spots end up with different shares. It is the same degeneracySuperspaceLattice.is_commensurate()warns about, seen from the diffraction side.- Parameters:
lat (SuperspaceLattice) – The lattice.
H (array-like) – (n, 3+d) superspace indices.
tol (float) – Distance in reciprocal lattice units below which two effective indices count as the same reflection.
- Returns:
(representatives, group, ngroups)– representatives is (ngroups, 3+d), taking the lowest-order index of each group; group is (n,) mapping every input row to its group; and ngroups is how many distinct reflections there are.- Return type:
tuple[np.ndarray, np.ndarray, int]
- pyextal.superspace.satellite_indices(lat, max_order, tol=1e-06)¶
All
(m_1..m_d)whose modulation vector is within max_order q-lengths.The cutoff is
|sum_j m_j q_j| <= max_order * min_j |q_j|– a length, measured in units of the shortest modulation wave vector. Because it is built from the invariant metric it keeps whole symmetry orbits, never part of one. Ford = 1it reduces to|m| <= max_order.- Parameters:
lat (SuperspaceLattice) – The lattice.
max_order (int) – Cutoff in units of the shortest q-vector.
tol (float) – Relative slack, so a satellite exactly on the boundary is kept rather than lost to rounding.
- Returns:
(n, d) integer satellite indices, including all-zero.
- Return type:
np.ndarray
- pyextal.superspace.satellite_length(lat, H)¶
|sum_j m_j q_j|for each reflection, in inverse Angstrom.How far the satellite sits from its main reflection – zero for a main reflection, and equal for every member of a symmetry orbit.
- Parameters:
lat (SuperspaceLattice) – The lattice.
H (array-like) – (n, 3+d) superspace indices.
- Returns:
(n,) lengths.
- Return type:
np.ndarray
- pyextal.superspace.satellite_metric(lat)¶
Metric on satellite indices:
Q_jk = q_j . q_kin inverse Angstrom squared.A symmetry operation permutes the q-vectors, so it preserves this form. Any cutoff or grouping built from it is therefore symmetry-invariant, which
sum |m|is not.- Parameters:
lat (SuperspaceLattice) – Supplies the q-vectors and reciprocal metric.
- Returns:
(d, d) positive-definite matrix.
- Return type:
np.ndarray
- class pyextal.superspace.SuperspaceLattice(cell, q, check_rank=True, tol=1e-06)¶
A 3D lattice plus
dmodulation wave vectors.- Variables:
cell (tuple) – The six lattice parameters of the basic (average) structure.
q (np.ndarray) – (d, 3) modulation wave vectors, in reciprocal lattice units of the basic structure.
d (int) – Number of modulation dimensions, 1 to 3.
gmx (np.ndarray) – (3, 3) direct metric tensor.
gmxr (np.ndarray) – (3, 3) reciprocal metric tensor.
- MAX_D = 3¶
- cell¶
- q¶
- gmx¶
- gmxr¶
- tol¶
- _warn_on_rank()¶
Warn about a rank-deficient q set, distinguishing the two causes.
Commensurate q-vectors are rank-deficient too, but that is a legitimate thing to build – it is the approximant – so it gets its own, quieter message pointing at the supercell rather than telling the user they made a mistake.
- property d¶
Number of modulation dimensions.
- Type:
int
- property dim¶
Total superspace dimension,
3 + d.- Type:
int
- __repr__()¶
- effective_hkl(H)¶
The 3D reciprocal-space index a superspace reflection actually sits at.
This is the whole point of the superspace description: a satellite is not at an integral position, so it cannot be named by three integers, but it is at a perfectly well defined place.
- Parameters:
H (array-like) – (n, 3+d) integer superspace indices, or a single (3+d,) index.
- Returns:
(n, 3) non-integral reciprocal indices
(h, k, l) + sum_j m_j q_j, in units of the basic reciprocal cell.- Return type:
np.ndarray
- Raises:
ValueError – If the last axis is not 3+d long.
- g_magnitude(H)¶
Length of the scattering vector, inverse Angstrom.
- Parameters:
H (array-like) – (n, 3+d) superspace indices.
- Returns:
(n,) values of
|S(H)|.- Return type:
np.ndarray
- d_spacing(H)¶
Interplanar spacing
1/|S(H)|in Angstrom.- Parameters:
H (array-like) – (n, 3+d) superspace indices.
- Returns:
(n,) spacings;
inffor the direct beam.- Return type:
np.ndarray
- static satellite_order(H)¶
Satellite order
sum |m_j|; zero for a main reflection.- Parameters:
H (array-like) – (n, 3+d) superspace indices.
- Returns:
(n,) integer orders.
- Return type:
np.ndarray
- is_main(H)¶
Whether each reflection is a main reflection.
- Parameters:
H (array-like) – (n, 3+d) superspace indices.
- Returns:
(n,) booleans.
- Return type:
np.ndarray
- integer_relations(max_n=6, tol=None)¶
Integer combinations of the q-vectors that land on the main lattice.
Each one found means two nominally distinct satellites fall at the same place, so the description carries a dimension the data does not. Searches
nin a box of half-width max_n, taking the first of each +/- pair and skipping multiples of a relation already found.- Parameters:
max_n (int) – Half-width of the integer search box.
tol (float, optional) – Distance from a reciprocal lattice point, in reciprocal lattice units. The constructor’s tolerance when None.
- Returns:
(n, g)pairs, n the (d,) integer coefficients and g the (3,) integer reciprocal lattice vector hit. Empty when the q-vectors are properly independent.- Return type:
list[tuple]
- rationalise(max_denominator=256, tol=None)¶
Exact integer form of the q-vectors, when they are all rational.
Returns
q_j = p_j / Nwith a single common denominator. This is what makes commensurability decidable rather than guessed at: a brute-force search for integer relations would have to reachn = 13to notice thatq = (3/13, 1/13, 1/3)is rational, and ton = 39in three dimensions that is half a million candidates.- Parameters:
max_denominator (int) – Largest denominator to accept.
tol (float, optional) – How close the rational has to be; the constructor’s tolerance when None.
- Returns:
(P, N)with P the (d, 3) integer numerators and N the common denominator, or None if any component is irrational to within tol.- Return type:
tuple or None
- rank(tol=None)¶
Rank of the Fourier module beyond the three main dimensions.
- Parameters:
tol (float, optional) – Relation tolerance; the constructor’s when None.
- Returns:
The true number of independent modulation dimensions –
dwhen the q-vectors are independent, 0 when they are all rational (a commensurate structure, which is a 3D supercell in disguise), and something between when only some directions are redundant.- Return type:
int
- is_commensurate(tol=None)¶
Whether every q-vector is rational, so an exact 3D supercell exists.
- Parameters:
tol (float, optional) – Rationality tolerance; the constructor’s when None.
- Returns:
True when the modulation adds no incommensurate dimension.
- Return type:
bool
- supercell_matrix(max_denominator=64, tol=None)¶
The smallest 3D supercell in which every satellite is a main reflection.
For rational q-vectors the modulated structure is an ordinary periodic crystal with a larger cell, and that cell is the commensurate approximant – the one thing about a modulated structure the existing Bloch-wave engine can already handle, since it is plain 3D.
The rows of the returned matrix are the supercell’s direct lattice vectors in terms of the basic ones, so a reflection’s supercell index is
M @ hkland the volume ratio is|det M|. The rows are the three shortest independent integer vectorsvwith everyv . q_jintegral, which in a rank-3 lattice is guaranteed to be a basis.- Parameters:
max_denominator (int) – Largest denominator to consider when rationalising the q-vectors, and the half-width of the search.
tol (float, optional) – Rationality tolerance; the constructor’s when None.
- Returns:
(3, 3) integer matrix, or None if the structure is genuinely incommensurate and no finite supercell exists.
- Return type:
np.ndarray or None
- _relation_text(n, g)¶
Human-readable form of one integer relation, for the warning.
- pyextal.superspace.cell_to_metric(cell)¶
Direct-space metric tensor from the six lattice parameters.
- Parameters:
cell (array-like) –
(a, b, c, alpha, beta, gamma)with lengths in Angstrom and angles in degrees.- Returns:
(3, 3) direct metric tensor
G, so that a vector with fractional coordinatesxhas lengthsqrt(x @ G @ x).- Return type:
np.ndarray
- class pyextal.superspace.DisplaciveModulation(harmonics=(), d=1)¶
Bases:
_FourierSeriesPositional modulation: a displacement field over internal space.
- Variables:
harmonics (tuple[Harmonic]) – Terms with (3,) amplitudes, in fractional coordinates of the basic cell.
d (int) – Number of modulation dimensions.
- evaluate(xbar)¶
Displacement at each internal coordinate.
- Parameters:
xbar (np.ndarray) – (npts, d) internal coordinates.
- Returns:
(npts, 3) displacement in fractional coordinates; all zeros when there are no harmonics.
- Return type:
np.ndarray
- max_amplitude()¶
float: Largest single-harmonic amplitude, for grid sizing.
- class pyextal.superspace.Harmonic¶
One term of a Fourier series in the internal coordinate.
- Variables:
n (tuple) – (d,) integer harmonic index.
sin (np.ndarray) – Amplitude of the sine term – (3,) for a displacive modulation, () for an occupational one.
cos (np.ndarray) – Amplitude of the cosine term, same shape as sin.
- n: tuple¶
- sin: numpy.ndarray¶
- cos: numpy.ndarray¶
- __post_init__()¶
- property order¶
sum |n_j|, the harmonic order.- Type:
int
- class pyextal.superspace.ModulatedAtom¶
One atom of the basic structure, with its modulation functions.
- Variables:
label (str) – Free-form name, e.g.
'Ta1'.element (str) – Symbol used to look up the scattering factor.
position (np.ndarray) – (3,) fractional coordinates in the basic cell.
occupancy (float) – Site occupancy of the basic structure; ignored when occupational is set, which carries its own average.
B (float) – Isotropic displacement parameter in Angstrom^2. Modulated ADPs are not implemented – see the module note in
structurefactor.displacive (DisplaciveModulation, optional) – Positional modulation.
occupational (OccupationalModulation, optional) – Occupancy modulation.
- label: str¶
- element: str¶
- position: numpy.ndarray¶
- occupancy: float = 1.0¶
- B: float = 0.0¶
- displacive: DisplaciveModulation = None¶
- occupational: OccupationalModulation = None¶
- __post_init__()¶
- displacement(xbar)¶
Displacement field, zeros if the atom is not positionally modulated.
- Parameters:
xbar (np.ndarray) – (npts, d) internal coordinates.
- Returns:
(npts, 3) fractional displacements.
- Return type:
np.ndarray
- occupation(xbar)¶
Occupancy field, constant if the atom is not occupationally modulated.
- Parameters:
xbar (np.ndarray) – (npts, d) internal coordinates.
- Returns:
(npts,) occupancies.
- Return type:
np.ndarray
- class pyextal.superspace.OccupationalModulation(average=1.0, harmonics=(), d=1)¶
Bases:
_FourierSeriesOccupancy as a function of internal coordinate.
- Variables:
average (float) – The
n = 0term, i.e. the mean occupancy.harmonics (tuple[Harmonic]) – Terms with scalar amplitudes.
d (int) – Number of modulation dimensions.
- average¶
- evaluate(xbar)¶
Occupancy at each internal coordinate.
- Parameters:
xbar (np.ndarray) – (npts, d) internal coordinates.
- Returns:
(npts,) occupancies.
- Return type:
np.ndarray
- class pyextal.superspace.DiffractionPattern(H, xy, intensity, order, sg, F, zone, xaxis, satlen=None)¶
A computed zone-axis pattern.
- Variables:
H (np.ndarray) – (n, 3+d) superspace indices.
xy (np.ndarray) – (n, 2) in-plane coordinates, inverse Angstrom.
intensity (np.ndarray) – (n,) relative intensities, normalised so the strongest diffracted beam is 1. The direct beam is far above that and is meant to be clipped when plotted.
order (np.ndarray) – (n,) satellite order, ranked by modulation length so symmetry-equivalent satellites share it. See
indexing.satlen (np.ndarray) – (n,)
|sum_j m_j q_j|in inverse Angstrom.sg (np.ndarray) – (n,) excitation errors, inverse Angstrom.
F (np.ndarray) – (n,) complex structure factors.
zone (tuple) – The zone axis.
xaxis (tuple) – The in-plane reference reflection.
- satlen¶
- property index_sum¶
(n,)
sum |m_j|; not symmetry-invariant, see order.- Type:
np.ndarray
- __len__()¶
- __repr__()¶
- visible(rel=0.0001)¶
Reflections above a relative intensity threshold.
The threshold is measured against the strongest diffracted beam, not the direct one – intensities are already normalised that way, and the direct beam is orders of magnitude above it, so scaling to that would cut everything.
- Parameters:
rel (float) – Fraction of the strongest diffracted intensity.
- Returns:
Boolean mask.
- Return type:
np.ndarray
- brightest(n=20, exclude_origin=True)¶
Indices of the strongest reflections, for a quick look at a list.
- Parameters:
n (int) – How many to return.
exclude_origin (bool) – Skip the direct beam, which dominates.
- Returns:
Indices into the pattern arrays.
- Return type:
np.ndarray
- pyextal.superspace.marker_sizes(intensity, size=260, gamma=0.35, floor=1.5)¶
Marker areas for a diffraction pattern, saturating the direct beam.
Two things make a linear area-to-intensity map useless here. The direct beam is normalised against the strongest diffracted beam and the Mott-Bethe factor diverges as
s -> 0, so its “intensity” is of order 1e7 – scaled linearly it becomes a single marker larger than the axes, and the whole plot comes out a solid block of colour. And satellites are ~1e-3 of a main reflection, so anything that renders the mains sensibly renders the satellites as nothing.Intensity is therefore clipped at 1 (the direct beam saturates, exactly as it does on a recorded plate) and raised to gamma, the power stretch normally used to display a diffraction pattern.
- Parameters:
intensity (array-like) – Relative intensities, mains near 1.
size (float) – Marker area for a saturated reflection.
gamma (float) – Power stretch. 1.0 is linear; smaller lifts weak reflections into view.
floor (float) – Smallest marker area, so a very weak reflection is still a visible dot rather than nothing.
- Returns:
Marker areas in points squared.
- Return type:
np.ndarray
- pyextal.superspace.plot_image(pattern, ax=None, shape=(512, 512), extent=None, sigma=2.0, gamma=1.0, radius=None, stretch=0.4, cmap='inferno', include_direct=True, saturate=True, title=None, rel_min=0.0, colorbar=False, vmax=None)¶
Draw a rendered pattern as an image.
Two things have to be handled or the picture is a black square with one white dot. The direct beam is some seven orders of magnitude above the strongest diffracted beam, so the display is scaled to the diffracted maximum and the centre is allowed to saturate – which is what a recorded plate does anyway. And satellites are ~1e-3 of a main reflection, so a power stretch lifts them into view.
- Parameters:
pattern (DiffractionPattern) – What to draw.
ax (matplotlib.axes.Axes, optional) – Target axes.
shape (tuple) – Image size in pixels.
extent (float, optional) – Half-width of the field, inverse Angstrom.
sigma (float) – Gaussian width of the point spread, pixels.
gamma (float) – Lorentzian width, pixels.
radius (int, optional) – Kernel half-width.
stretch (float) – Display power. 1.0 is linear; smaller lifts the weak reflections. 0.4 shows satellites without washing out the mains.
cmap (str) – Colormap.
include_direct (bool) – Render the direct beam at all.
saturate (bool) – Scale the display to the strongest diffracted beam and let the direct beam clip. Turning this off scales to the true maximum, which is the direct beam, and shows little else.
title (str, optional) – Axes title.
rel_min (float) – Intensity cutoff before rendering.
colorbar (bool) – Add a colorbar keyed to the saturation level.
vmax (float, optional) –
Intensity that maps to full scale, in the units of the returned image; everything above it clips. Chosen automatically when None – the strongest diffracted beam, which is what saturate describes.
Pass it to brighten (smaller) or dim (larger) a plot, and above all to put several patterns on one scale: the automatic choice is made per pattern, so side-by-side panels are otherwise each normalised to their own maximum and cannot be compared.
saturation_level()computes the automatic value without drawing anything.
- Returns:
(ax, image)with image the unscaled intensity array.- Return type:
tuple
- Raises:
ValueError – If vmax is not positive.
- pyextal.superspace.plot_pattern(pattern, ax=None, size=260, rel_min=0.0001, label_orders=True, colors=None, annotate=0, title=None, gamma=0.35)¶
Draw a pattern, distinguishing main reflections from satellite orders.
Marker area is a power-stretched, saturating function of intensity; see
marker_sizes()for why a linear map does not work.- Parameters:
pattern (DiffractionPattern) – What to draw.
ax (matplotlib.axes.Axes, optional) – Target axes; a new figure when None.
size (float) – Marker area for a saturated reflection.
rel_min (float) – Relative intensity below which a reflection is dropped.
label_orders (bool) – Show a legend keyed by satellite order.
colors (dict, optional) – Satellite order -> colour.
annotate (int) – Label this many of the strongest reflections with their superspace index. Zero draws none.
title (str, optional) – Axes title.
gamma (float) – Power stretch on intensity; smaller lifts the satellites.
- Returns:
The axes drawn on.
- Return type:
matplotlib.axes.Axes
- pyextal.superspace.project_pattern(lat, H, zone=(0, 0, 1), xaxis=None)¶
In-plane coordinates of superspace reflections at a zone axis.
Shared by every route that produces a pattern – kinematic, the superspace engine, the exact supercell – so their images can be laid side by side and compared pixel for pixel. Two routes computing the same reflection at slightly different coordinates would be a subtle and very annoying bug.
- Parameters:
lat (SuperspaceLattice) – The lattice.
H (array-like) – (n, 3+d) superspace indices.
zone (tuple) – Zone axis, direct-space indices.
xaxis (tuple, optional) – In-plane reference reflection. The shortest in-plane main reflection when None, which keeps the axis in the basic structure’s terms so it survives a change of q.
- Returns:
(xy, xaxis)with xy (n, 2) in inverse Angstrom.- Return type:
tuple[np.ndarray, tuple]
- Raises:
ValueError – If the zone admits no in-plane main reflection.
- pyextal.superspace.pseudo_voigt_kernel(radius, sigma, gamma)¶
The pseudo-Voigt point spread the JAX refinement fits, on a pixel grid.
Identical in form to
jaxEngine.postProcess.create_pseudo_voigt_kernel, including the Thompson-Cox-Hastings width combination and the eta approximation, so a sigma and gamma refined against experimental data can be handed straight torender_image()and mean the same thing. Widths are in pixels.The kernel is tapered to zero on a disc rather than left square. Its Lorentzian part decays only as 1/r^2, so at any affordable radius it is still a per cent or so of the peak at the edge – and truncating that squarely puts a visible square halo around every strong reflection in the convolved image. A raised-cosine taper over the outer quarter removes it and costs nothing, since the discarded intensity is renormalised back.
- Parameters:
radius (int) – Kernel half-width; the kernel is (2r+1) square, with the corners tapered away.
sigma (float) – Gaussian width, pixels.
gamma (float) – Lorentzian width, pixels.
- Returns:
(2r+1, 2r+1) kernel summing to 1.
- Return type:
np.ndarray
- pyextal.superspace.render_image(pattern, shape=(512, 512), extent=None, sigma=2.0, gamma=1.0, radius=None, rel_min=0.0, include_direct=True)¶
Render a pattern as a 2D intensity image, the way a plate records it.
A scatter plot draws markers whose area someone chose; this puts intensity on a pixel grid, so the result is an image in the same sense as the engine’s own diffraction output – something you can threshold, integrate, line-scan or compare against a recorded pattern.
Each reflection’s intensity is spread bilinearly over the four pixels around its position, so a peak that lands between pixels keeps its centroid and its total counts rather than snapping to a grid point. The whole image is then convolved with the pseudo-Voigt of
pseudo_voigt_kernel().The direct beam is orders of magnitude above everything else, so plot the result on a log or power scale –
plot_image()does.- Parameters:
pattern (DiffractionPattern) – What to render.
shape (tuple) –
(rows, cols)in pixels.extent (float, optional) – Half-width of the field in inverse Angstrom. Taken from the pattern, with a margin, when None.
sigma (float) – Gaussian width of the point spread, in pixels.
gamma (float) – Lorentzian width, in pixels.
radius (int, optional) – Kernel half-width.
ceil(6*max(sigma, gamma))when None – generous because the Lorentzian component has long tails;pseudo_voigt_kernel()tapers whatever is left.rel_min (float) – Drop reflections below this fraction of the strongest diffracted beam before rendering.
include_direct (bool) – Render the direct beam. It saturates everything; turning it off is often the more readable picture.
- Returns:
The
(rows, cols)image, and the(left, right, bottom, top)extent in inverse Angstrom, ready forimshow(..., extent=...).- Return type:
tuple[np.ndarray, tuple]
- pyextal.superspace.saed_pattern(lat, atoms, zone=(0, 0, 1), gmax=1.6, max_index=8, max_order=4, thickness=200.0, hv=200.0, xaxis=None, kind='electron', sg_max=None, n_internal=None, condition=None, merge=None)¶
Compute a kinematic zone-axis pattern.
- Parameters:
lat (SuperspaceLattice) – The superspace lattice.
atoms (list[ModulatedAtom]) – Structure, listed in P1.
zone (tuple) – Zone axis, direct-space indices.
gmax (float) – Resolution limit, inverse Angstrom.
max_index (int) – Half-width of the
(h, k, l)search box.max_order (int) – Largest satellite order.
thickness (float) – Specimen thickness in Angstrom, setting the relrod length
1/t. Larger means a thinner slice of reciprocal space contributes, so out-of-zone satellites fade.hv (float) – Accelerating voltage in kV, for the Ewald sphere radius.
xaxis (tuple, optional) – In-plane reference reflection; the shortest in-plane main reflection when None.
kind (str) –
'electron'or'xray'scattering factors.sg_max (float, optional) – Discard reflections with
|s_g|above this.5 / thicknesswhen None, past which the relrod contributes essentially nothing.n_internal (int, optional) – Internal quadrature grid; auto when None.
condition (callable, optional) – Reflection condition, passed to
enumerate_reflections().merge (bool, optional) – Add the structure factors of superspace indices that are the same physical reflection before squaring. Only commensurate lattices have any, and for them it is not optional – the intensity there is
|F_1 + F_2|^2, and treating the indices separately splits it arbitrarily and destroys the pattern’s symmetry. Decided fromSuperspaceLattice.is_commensurate()when None, which is the right answer either way.
- Returns:
The pattern.
- Return type:
- Raises:
ValueError – If the zone admits no in-plane reflection within gmax.
- pyextal.superspace.saturation_level(pattern, shape=(512, 512), extent=None, sigma=2.0, gamma=1.0, radius=None, rel_min=0.0)¶
The intensity
plot_image()clips at when it chooses for itself.The peak of the strongest diffracted beam once rendered and convolved. Useful for putting two patterns on one scale: plot_image picks this separately for each, so two panels drawn side by side are each normalised to their own maximum and their brightnesses cannot be compared. Compute it once and pass it to both as vmax:
level = saturation_level(kinematic, shape=(650, 650), sigma=3.0) plot_image(kinematic, vmax=level, shape=(650, 650), sigma=3.0) plot_image(dynamical, vmax=level, shape=(650, 650), sigma=3.0)
The render arguments must match the ones plot_image is given, since the convolution spreads a fixed number of counts and the peak height therefore depends on the grid and the kernel.
- Parameters:
pattern (DiffractionPattern) – The pattern.
shape (tuple) – Image size in pixels.
extent (float, optional) – Half-width of the field, inverse Angstrom.
sigma (float) – Gaussian width of the point spread, pixels.
gamma (float) – Lorentzian width, pixels.
radius (int, optional) – Kernel half-width.
rel_min (float) – Intensity cutoff before rendering.
- Returns:
Peak image intensity excluding the direct beam.
- Return type:
float
- pyextal.superspace.electron_form_factor(element, s, s_floor=1e-05)¶
Electron scattering factor by the Mott-Bethe relation.
f_e = MOTT * (Z_eff - f_x) / s^2, which diverges ass -> 0; the engine clampssto 1e-5 there ([sfsub.f90:27]) and so does this, so the direct beam gets a large finite value rather than an infinity.Z_effis the Cromer-Mann parameterisation’s own zero-angle value,sum(a) + c, not the integer nuclear charge – which is what the Fortran engine uses (ztcomes out as 72.95366 for Ta, against Z = 73). The difference is the fit’s residual ats = 0, and dividing it bys^2turns it into a spurious tail that grows without bound ass -> 0: 0.8% off_efor Ta already ats = 0.125. Since satellites sit close to their main reflections, that is exactly where they live. Using the integer Z here made the Python disagree with the engine by ~1e-3 across the board.- Parameters:
element (str) – Element symbol.
s (array-like) –
|g| / 2in inverse Angstrom.s_floor (float) – Lower clamp on s.
- Returns:
f_e(s)in Angstrom, same shape as s.- Return type:
np.ndarray
- pyextal.superspace.xray_form_factor(element, s)¶
X-ray atomic form factor.
- Parameters:
element (str) – Element symbol.
s (array-like) –
|g| / 2in inverse Angstrom, i.e. sin(theta)/lambda.
- Returns:
f_x(s)in electrons, same shape as s.- Return type:
np.ndarray
- pyextal.superspace.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 bandwidthmax|m|, andexp(2 pi i S.u(xbar)), whose Jacobi-Anger expansion has significant terms out to roughly|z| + 4in each harmonic present, wherez = 2 pi max|S.u|. The rectangle rule is exact belown/2, so the grid is sized at safety times the estimated bandwidth.- Parameters:
atoms (list[ModulatedAtom]) – Supplies the modulation amplitudes.
hkl (np.ndarray) – (n, 3) effective reciprocal indices.
max_order (int) – Largest satellite order in the reflection set.
safety (int) – Multiplier on the estimated bandwidth.
lo (int) – Floor on the returned value.
hi (int) – Ceiling, to keep
n**daffordable.
- Returns:
Points per internal dimension, rounded up to even.
- Return type:
int
- pyextal.superspace.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 givesInt_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
structure_factor(); seetest_superspace.py.- Parameters:
lat (SuperspaceLattice) – Must be (3+1)-dimensional.
H (array-like) – (n, 4) superspace indices.
amplitude (array-like) – (3,) sine amplitude in fractional coordinates.
element (str) – Element symbol for the scattering factor.
position (array-like) – (3,) fractional position of the single atom.
kind (str) –
'electron'or'xray'.s_floor (float) – Lower clamp on
s.
- Returns:
(n,) complex structure factors.
- Return type:
np.ndarray
- Raises:
ValueError – If lat is not (3+1)-dimensional.
- pyextal.superspace.internal_grid(d, n)¶
Uniform sampling of the internal unit cell
[0, 1)^d.- Parameters:
d (int) – Number of modulation dimensions.
n (int) – Points per dimension.
- Returns:
(n**d, d) internal coordinates.
- Return type:
np.ndarray
- pyextal.superspace.structure_factor(lat, atoms, H, kind='electron', n_internal=None, max_points=4000000, s_floor=1e-05)¶
Structure factors of a modulated structure.
- Parameters:
lat (SuperspaceLattice) – Supplies the metric and the q-vectors.
atoms (list[ModulatedAtom]) – The structure, listed in P1 – every atom of the basic cell, not just the asymmetric unit.
H (array-like) – (n, 3+d) integer superspace indices.
kind (str) –
'electron'for Mott-Bethe scattering factors, matching the Bloch-wave engine, or'xray'for the Cromer-Mann values.n_internal (int, optional) – Points per internal dimension. Sized by
auto_n_internal()when None.max_points (int) – Reflections are processed in chunks so that
chunk * n_internal**dstays under this, bounding memory.s_floor (float) – Lower clamp on
s = |g|/2, as the engine uses.
- Returns:
(n,) complex structure factors, in the units of the chosen scattering factor.
- Return type:
np.ndarray
- Raises:
ValueError – If kind is not recognised, or H has the wrong width.
- class pyextal.superspace.SuperspaceOperation¶
One symmetry operation of a (3+d) superspace group.
- Variables:
R (np.ndarray) – (3, 3) integer rotation on direct fractional coordinates.
tau (np.ndarray) – (3,) translation part, fractional.
epsilon (np.ndarray) – (d, d) integer action on internal coordinates.
tau_i (np.ndarray) – (d,) internal translation.
G (np.ndarray) – (d, 3) integer reciprocal lattice vectors from the q-transformation; all zero for the well-behaved settings.
name (str) – Optional label.
- R: numpy.ndarray¶
- tau: numpy.ndarray = None¶
- epsilon: numpy.ndarray = None¶
- tau_i: numpy.ndarray = None¶
- G: numpy.ndarray = None¶
- name: str = ''¶
- __post_init__()¶
- property d¶
Number of modulation dimensions.
- Type:
int
- resolve(lat, max_epsilon=3, tol=1e-06)¶
Fill in epsilon and G from the lattice, if not already given.
- Parameters:
lat (SuperspaceLattice) – Supplies the q-vectors.
max_epsilon (int) – Half-width of the integer search for epsilon.
tol (float) – Tolerance on the residual being a lattice vector.
- Returns:
A new operation with the internal part filled in.
- Return type:
- Raises:
ValueError – If R does not map the q-set onto itself.
- pyextal.superspace.epsilon_for(R, q, max_epsilon=3, tol=1e-06)¶
Solve
R^T q_j = sum_k epsilon_jk q_k + G_jfor 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 <= 3and 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 withG = 0rather than some equivalent monster. Taking whichever candidate an unordered search happened to reach first would make the answer depend on iteration order.- Parameters:
R (array-like) – (3, 3) integer rotation on direct fractional coordinates.
q (array-like) – (d, 3) modulation wave vectors.
max_epsilon (int) – Half-width of the integer search per component.
tol (float) – How close the residual has to be to an integer vector.
- Returns:
(epsilon, G), shapes (d, d) and (d, 3).- Return type:
tuple[np.ndarray, np.ndarray]
- Raises:
ValueError – If some
R^T q_jcannot be written this way, meaning R is not a symmetry of the modulated structure.
- pyextal.superspace.expand_atoms(atoms, ops, lat, tol=1e-06)¶
Generate the symmetry-equivalent atoms and their modulation functions.
Under
(R | tau)an atom moves toR r + tauand its displacement field becomesu'(xbar) = R u(epsilon^-1 (xbar - tau_i)). Written out in harmonics, the index maps asn -> epsilon^-T nand the amplitudes mix by the phasephi = 2 pi n' . tau_i:A' = R (A cos phi + B sin phi) B' = R (-A sin phi + B cos phi)
- Parameters:
atoms (list[ModulatedAtom]) – The asymmetric unit.
ops (list[SuperspaceOperation]) – Operations, resolved or not.
lat (SuperspaceLattice) – Supplies the q-vectors for resolving.
tol (float) – Tolerance for recognising a duplicate position.
- Returns:
The expanded set, duplicates removed.
- Return type:
list[ModulatedAtom]
- Raises:
NotImplementedError – If any operation has a nonzero G. See the module docstring – list the structure in P1 instead.
- pyextal.superspace.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 theGcontribution and the satellite part transforms byepsilon^T. Valid for any G.- Parameters:
op (SuperspaceOperation) – Operation with its internal part resolved.
H (array-like) – (n, 3+d) integer superspace indices.
- Returns:
(n, 3+d) transformed indices.
- Return type:
np.ndarray
- Raises:
ValueError – If op has no internal part, or the widths disagree.