pyextal.superspace.indexing

Enumerating and labelling (3+d) reflections.

Item 1 of the superspace work in concrete form: reflections are arrays of shape (n, 3+d) everywhere, never 3-tuples, and the helpers here build, filter and print them.

Satellite order is not sum|m|

The obvious measure of “how far out” a satellite is, sum |m_j|, is not invariant under the crystal’s symmetry once d > 1, and using it as a cutoff quietly destroys the symmetry of everything downstream. In 1T-TaS2 the three-fold sends (1,0) -> (0,1) -> (-1,1); the first two have sum|m| = 1 and the third has 2, so a cutoff at 1 keeps two thirds of a symmetry orbit and the computed diffraction pattern comes out visibly not three-fold. Spijkerman and co-workers make the same point in section II of Phys. Rev. B 56, 13757: “‘order of a satellite’ cannot be represented by a single number anymore”.

What is invariant is the length of the modulation part, |sum_j m_j q_j|, since any symmetry operation permutes the q-vectors and so preserves it. That is what enumerate_reflections() cuts on, in units of the shortest q-vector, and what ReflectionSet.order ranks. For d = 1 the two measures coincide, so nothing changes there.

Classes

ReflectionSet

Superspace reflections with their derived 3D quantities.

Functions

enumerate_reflections(lat, gmax[, max_index, ...])

All superspace reflections inside a resolution sphere.

satellite_metric(lat)

Metric on satellite indices: Q_jk = q_j . q_k in inverse Angstrom squared.

satellite_length(lat, H)

|sum_j m_j q_j| for each reflection, in inverse Angstrom.

satellite_indices(lat, max_order[, tol])

All (m_1..m_d) whose modulation vector is within max_order q-lengths.

format_index(H)

Render one superspace index as (h k l | m1 m2).

merge_coincident(lat, H[, tol])

Group superspace indices that are the same physical reflection.

coincidence_coset(lat[, dh_max, max_order, tol])

The satellite offsets that leave a reflection where it is.

distinct_beams(lat, H[, who, warn])

Reduce a beam list to one entry per physical reflection.

Module Contents

class pyextal.superspace.indexing.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:

ReflectionSet

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:

ReflectionSet

labels()

list[str]: One formatted index per reflection.

pyextal.superspace.indexing.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, unlike sum |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. See pyextal.superspace.tas2.reflection_condition().

Returns:

Sorted by |S(H)|, ascending.

Return type:

ReflectionSet

Raises:

ValueError – If nothing falls inside gmax.

pyextal.superspace.indexing.satellite_metric(lat)

Metric on satellite indices: Q_jk = q_j . q_k in 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

pyextal.superspace.indexing.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.indexing.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. For d = 1 it 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.indexing.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.indexing.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) to S(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 degeneracy SuperspaceLattice.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.indexing.coincidence_coset(lat, dh_max=1.05, max_order=24, tol=1e-06)

The satellite offsets that leave a reflection where it is.

c belongs to the coset when c . q is an integer reciprocal-lattice vector, so that (h, m) and (h - c.q, m + c) have the same effective index S(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 order sum |c_j|, and that is not a detail. The coset is closed under the crystal’s point group, but sum |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 . q must 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.indexing.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 hkl block, so bloch_parse fails to resolve some out entries against it and appends them past the array allocated from the declared nbeams. 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