Python API#
Every CrystOD analysis is also available as a Python function, so that a part of CrystOD can be used inside another program without going through the command line. The API mirrors the command structure: one module per command, with the same vocabulary (irreps, q points, order parameters) as the printed output.
import crystod
crystod.salc # crystal-orbital SALC analysis (crystod)
crystod.group # irreps, isotropy subgroups (crystod-group)
crystod.phonon # phonon irreps, modes, subgroups (crystod-phonon)
crystod.bz # Brillouin zones, k paths (crystod-bz)
crystod.mag # symmetry-adapted spin bases (crystod-mag)
crystod.md # MD-trajectory analyses (crystod-md)
crystod.mol # molecular SALCs, MO diagrams (crystod-mol)
crystod.xrd # powder XRD patterns (crystod-xrd)
crystod.search # Materials Project search (crystod-search)
Testsuite section 35 checks everything documented on this page.
Note
Importing CrystOD is cheap: import crystod and the nine domain modules pull
in nothing heavier than NumPy. phonopy, spgrep, spglib, PySCF and matplotlib
are imported only when a function that needs them is actually called, so a
program that uses one corner of CrystOD does not pay for the rest.
Isotropy subgroups of an irrep#
crystod.group.isotropy_subgroups is the API form of
crystod-group --parent: given a space group and one
of its irreps, it enumerates the order-parameter directions and the subgroup
each of them condenses into.
from crystod.group import isotropy_subgroups
for sub in isotropy_subgroups("Pm-3m", "R4+"):
print(sub.label, "->", sub.number, sub.symbol, "size", sub.size, "index", sub.index)
R4+(0,0,a) -> 140 I4/mcm size 2 index 6
R4+(a,a,a) -> 167 R-3c size 2 index 8
R4+(0,a,a) -> 74 Imma size 2 index 12
R4+(0,a,b) -> 12 C2/m size 2 index 24
R4+(a,a,b) -> 15 C2/c size 2 index 24
R4+(a,b,c) -> 2 P-1 size 2 index 48
The space group is given as a symbol or a number (221 works as well), and a
list of labels enumerates the subgroups of coupled order parameters, exactly as
--irrep X3- X2+ does on the command line.
Bad input raises ValueError — an unknown space group, an invalid order
parameter, or an irrep that is not tabulated for that space group. The last
case is worth catching: the labels of symmetry lines and planes (DT5, LD3,
SM1, …) are legitimate mode labels, but only the maximal k points have
isotropy subgroups in the tables.
Note
Every function of every API domain reports bad input as ValueError. The
implementation modules double as command-line entry points and raise
SystemExit instead, which a caller’s except Exception would not catch —
the API namespaces translate that at their boundary. The classes they
expose (IsotropyAnalyzer, SpaceGroupIrrepAlgebra, MODiagram, …) are
the implementation classes themselves, so that isinstance and dataclass
equality keep working; their constructors still raise SystemExit on bad
input. Prefer the functions, and construct the classes from values you have
already validated.
try:
subs = isotropy_subgroups(221, mode.labels[0])
except ValueError as exc:
print("no subgroups for this level:", exc)
order_parameter takes plain numbers and parameter names (["0", "0", "a"]).
Hexagonal and trigonal irreps also have strata whose enumerated direction
carries a coefficient — K3(0.282a;a) of P6_3/mmc, for instance. Those
entries can be read from the enumeration but not resolved from components,
so passing them back raises ValueError instead of quietly returning the
subgroup of a different, more generic direction.
Each entry is an IsotropySubgroup with the fields irrep, direction,
label, number, symbol, size, index, n_free, and — for a single
direction — the conventional basis and origin of the subgroup cell in the
parent convention:
sub = isotropy_subgroups(221, "R4+", order_parameter=["0", "0", "a"])[0]
sub.symbol # 'I4/mcm'
sub.basis # array([[-1., 0., 1.], [1., 0., 1.], [0., 2., 0.]])
sub.origin # array([0., 0., 0.])
Phonon modes and their irreps#
crystod.phonon.label_phonon_modes labels the modes of a live phonopy object
with ISO-IR irreps — the machinery behind
crystod-phonon --irreps, without the YAML file:
import phonopy
from crystod.phonon import label_phonon_modes
ph = phonopy.load(supercell_matrix=[4, 4, 4], primitive_matrix="auto",
unitcell_filename="221_PPOSCAR_SrTiO3",
force_sets_filename="FORCE_SETS")
for mode in label_phonon_modes(ph, [0.5, 0.5, 0.5]):
print(mode)
modes 1,2,3: -1.0867 THz R5-
modes 4,5,6: 3.9891 THz R4-
modes 7,8,9: 11.6887 THz R5+
modes 10,11,12: 12.6621 THz R4-
modes 13,14: 14.8133 THz R3-
modes 15: 23.2302 THz R2-
Each PhononMode carries band_indices (1-based, as in every CrystOD output),
frequency in THz, the irrep labels, the qpoint, the name of its star
(qpoint_label), degeneracy, and is_imaginary. A q point given as a
non-representative arm of a star is mapped onto the tabulated arm
automatically, so [-0.5, 0.5, 0.5] is labeled R just like [0.5, 0.5, 0.5].
Note
Build the phonopy object with primitive_matrix="auto". With an identity
primitive matrix, a zone-boundary instability of a supercell calculation is
folded onto the supercell Gamma point, where it cannot be labeled with an
irrep of the parent space group.
From imaginary phonons to subgroups#
Combining the two — labeling an imaginary mode and enumerating the isotropy
subgroups of its irrep — is the symmetry-lowering step of a structure search.
crystod.phonon.imaginary_mode_subgroups does it in one call:
from crystod.phonon import imaginary_mode_subgroups
for result in imaginary_mode_subgroups(ph, [0.5, 0.5, 0.5]):
mode = result.mode
print(f"{mode.frequency:.4f} THz {'+'.join(mode.labels)} "
f"(degeneracy {mode.degeneracy})")
for sub in result.subgroups:
print(" ", sub.label, "->", sub.symbol)
-1.0867 THz R5- (degeneracy 3)
R5-(0,0,a) -> I4/mcm
R5-(a,a,a) -> R-3c
R5-(0,a,a) -> Imma
R5-(0,a,b) -> C2/m
R5-(a,a,b) -> C2/c
R5-(a,b,c) -> P-1
Every order-parameter direction of the degenerate level is covered, including
the ones a single frozen-in modulation would miss: freezing in one eigenvector
of the R-point triplet of SrTiO3 gives I4/mcm, but the same triplet also
reaches R-3c and Imma, which are the tilt systems a structure search has to
try before it can claim a ground state.
scan_imaginary_modes runs the same analysis over every q point the supercell
resolves, so no q point has to be guessed:
from crystod.phonon import scan_imaginary_modes, commensurate_qpoints
len(commensurate_qpoints(ph)) # 64 for a 4x4x4 supercell
results = scan_imaginary_modes(ph) # most unstable level first
Arms of the same star are analyzed once, q points that cannot be labeled are
skipped with a warning instead of aborting the scan, and threshold (default
-0.1 THz) sets what counts as imaginary. A level whose irrep has no tabulated
subgroups (a symmetry line or plane) comes back with an empty subgroups and
the reason in errors, so a scan never dies part-way through:
for result in scan_imaginary_modes(ph):
if result.errors:
print(result.mode, "->", result.errors)
See also
The same analysis is available from the command line as
crystod-phonon --subgroup, and the theory behind the
enumeration is described in
Isotropy subgroups.
The other domains#
The remaining modules expose the computational core of their command. A few representative entry points:
from crystod.salc import CrystalOrbital, CrystalOrbitalDiagram
from crystod.group import SpaceGroupIrrepAlgebra, decompose, shell_terms
from crystod.bz import get_brillouin_zone_3d, get_seekpath_kpath
from crystod.mag import get_spin_representation
from crystod.md import read_xdatcar, build_symmetry_projector
from crystod.mol import load_molecule, project_salcs, MODiagram
from crystod.xrd import load_structure, compute_xrd_pattern, smear_pattern
from crystod.search import search_materials, fetch_materials, write_poscar
dir() on any domain module lists everything it exports:
import crystod.phonon
dir(crystod.phonon)
The implementation modules (crystod.phonon_irreps, crystod.isotropy_subgroup,
crystod.operations, …) remain importable under their own names, so code
written against CrystOD before the API modules existed keeps working unchanged;
the domain modules are a curated, stable view over them.
See also
The group theory the API evaluates is explained in
Theoretical background, whose
crystod.operations.wigner_D_real example is the smallest CrystOD API call
there is.