Changelog#

All notable changes to CrystOD, newest first.

v0.4.2#

  • New quantitative engine crystod --diagram --vasp: crystal-orbital diagrams from finished VASP runs. The third engine next to extended Hückel and PySCF, and the first one that computes nothing itself: crystod --diagram -c POSCAR --co-left SrTi --co-right O3 --vasp [ROOT] reads the crystal run in ROOT/BAND and the two sublattice runs in ROOT/BAND_sublattice* (DIR/band when it carries the PROCAR; gzipped files are accepted) and draws the same three columns from them. Which sublattice directory is the left and which the right column is decided from the real elements of its POSCAR, not from its name; --vasp-crystal/-left/-right DIR override the search. A sublattice run keeps one sublattice and replaces the other by Va<q><sign> point-charge species (Va2-, Va4+) of a modified VASP — a smeared Coulomb charge plus a short repulsive wall standing for the Pauli repulsion of the removed ion’s core; VASP is proprietary and is not distributed with CrystOD, the patch is available to VASP licensees on request, and --vasp itself needs only the output files. The companion crystod --diagram ... --vasp-setup [ROOT] writes those runs’ inputs — POSCAR with the crystal’s cell and atom order and the removed atoms renamed from the resolved oxidation states, POTCAR split out of the crystal run’s (or taken from --potcar-dir / PMG_VASP_PSP_DIR, with --potcar-map EL=NAME choosing a variant), INCAR with LORBIT = 12 and no NELECT, and one explicit KPOINTS holding the symmetry-reduced weighted mesh followed by the zero-weight special points, so one run per directory gives both the SCF and the special-point eigenvalues — and prints the three run commands without starting VASP. Levels, occupations and the complex LORBIT = 12 projections come from PROCAR; each band’s projection vector is treated as a vector in an AO-like (atom, l, m) basis and classified with the same representation matrices and character projectors CrystOD builds for a genuine AO basis, so every level carries a space-group irrep whose multiset reproduces the site-symmetry induced representation the report prints (32 of 34 manifolds of SrTiO3 exactly; a best irrep weight below 0.9 is warned about, and on SrTiO3 every level reaches 1.000). The three runs each pin their own G = 0 potential, so the crystal column is taken as the reference and every fragment level is raised onto it by a site-resolved shift: one constant per (column, element), fitted to the symmetry-forbidden probes (fragment levels whose irrep has no partner in the other sublattice at that k and which must therefore coincide with their crystal counterpart, weighted four times) and to the pure XPS-style counterparts. The shells of one atom move together, different sites do not: on SrTiO3 the Ti site, charged by covalent back-donation in the crystal, has all its levels raised 12.3 eV more than the ionic Sr site, which no single shift per column can describe. The energy zero of the page and the tables is --vasp-zero vbm|efermi|raw (default E − E_VBM); --vasp-align rigid falls back to one shift per column (its numbers are printed as a diagnostic in either mode), --vasp-anchor EL nl pins a column on a chosen shell and --no-align keeps the raw scales. An occupied-band trace sum rule that uses no projection cross-checks the result, and the bonding/antibonding colouring — which a plane-wave calculation must read from the aligned energies because it has no overlap matrix — is active for every crystal level whose parents sit on sites whose own anchors agree within 1 eV, and suppressed (neutral grey stroke) elsewhere rather than guessed. Output: CrystOD_{cell}_vasp.html through the usual writer, a CrystOD_{cell}_vasp_levels.txt with every number (per-level shift and the full anchor table included), a machine-readable CrystOD_{cell}_vasp_levels.json with the same content, and the terminal report of the other engines. Non-spin-polarized primitive cells only, and every share is a PAW-sphere projection, blind to the radial channel. New modules crystod/vasp_io.py and crystod/crystal_orbital_vasp.py; documentation: crystod. Tests in section 3, run offline against example/03_hybridization/vasp_SrTiO3 and example/03_hybridization/vasp_CaF2, trimmed copies of six real VASP runs restricted to the four special k points each, which reproduce every number of the untrimmed runs.

    The PROCAR projections are classified in the Bloch gauge CrystOD builds its permutation representation in, with no further conjugation. The engine first conjugated the representation with Lambda_a = exp(2 pi i k . tau_a), by analogy with the PySCF engine, whose AO coefficients really are in the atomic gauge. That conjugation is an exact no-op for every site on half-integer coordinates, so neither SrTiO3 nor ScF3 could test it. Fluorite CaF2 can — its F sites sit on (1/4,1/4,1/4) and (3/4,3/4,3/4) of the primitive basis — and it shows the conjugation is wrong: with it 21 of the 85 labelled levels fall below irrep purity 0.90 (minimum 0.000 at W, 12 failures at L), the fragment manifolds stop reproducing the SALC decomposition, the Ca shell spread is 6.58 eV and the F spread 2.31 eV so both sites fail the scale test, an F 2s anchor is paired with a crystal level 13.6 eV away, and the page carries 0 bonding and 0 antibonding levels. Without it every labelled level of CaF2 has purity 1.000 at GM, L, X and W, the manifolds match the group-theory decomposition exactly, and the page comes out 30 nonbonding / 6 antibonding / 5 bonding. The three half-integer-site systems are numerically unchanged, as the no-op argument predicts. CaF2 is now a test fixture of section 3, and it is also the ionic control of the site-resolved alignment: Ca +4.54 and F +4.43 eV move together to 0.1 eV, against 12.3 eV between Ti and Sr in SrTiO3.

    The point-charge wall is calibrated, and it is the default. --vasp-setup writes VACSIGMA 0.5, VACRWALL 0.45 and VACWALL = 2.5 q e^2 sqrt(2/pi)/sigma, one height per Va species so the charge scaling stays automatic; the new --vasp-sigma and --vasp-wall-factor change the first and the last, and --vasp-rwall defaults to 0.45 instead of 0.5. The same values are the defaults of the modified VASP, so a run made by hand and a run set up by CrystOD agree. A 72-point scan over (sigma, VACRWALL, height) on SrTiO3 and ScF3 organizes the whole family by one coordinate, the radius r10 = b sqrt(2 ln(A/10 eV)) at which the wall still repels by 10 eV: below 0.7 A the anion sublattice collapses into the bare -q/r well (occupied bands below the projection floor, negative gaps, metallic points), above 1.4 A the wall expels the anion valence shell (an O 2p manifold three times too wide, alignment shifts to +47 eV). The recommended setting sits at r10 = 0.99 A for q = +2 and 1.13 A for q = +4, the middle of the 0.85-1.25 A plateau, so ONE setting serves +2, +3 and +4; it is the joint minimum of the anchor-residual rms on both systems (SrTiO3 0.407 eV, ScF3 0.304 eV) and simultaneously the point where the anion p-manifold width matches the crystal best (-0.13 and +0.06 eV, against +0.49 and +0.40 eV before), and it transfers to CaF2 without retuning (rms 0.289 eV). What the wall chooses is the anion column’s position: across the scan the alignment shift ranges over 45 eV while the residual rms on the plateau moves by 0.1 eV. The shipped example/SrTiO3_Pm-3m and example/ScF3_Pm-3m sublattice runs were recomputed at the new default (the SrTiO3 anion shift moves from +5.144 to +6.147 eV).

    Two defects the fluorite study exposed are fixed with it. The anchor pool is cut per element, with that element’s own provisional shift, instead of once per column with a rigid pre-shift: with two sites 9 eV apart that one pre-shift is wrong for one of them and silently drops a whole shell’s anchors, so a site could report shell spread 0.20 eV -> shells agree when its own level table shows 1.56 eV. A shell that is drawn but contributed no anchor is now named in the report. And the parent energy a bond character is read against averages over all parents, with the deep semicore shells excluded (the criterion --valence-only uses for the other two engines) instead of everything below a 5 % connector weight: the old cut made both members of one three-level interaction antibonding with no bonding partner at all. The report also names, per k point, the fragment levels the drawn window cuts off the top — an empty cation d manifold can land a few hundredths of an eV above the default VBM + 10 eV and vanish at one k point out of four with no message.

    The forms of --vasp, the optional -c, and the setting the labels belong to. --vasp now takes no path (the current directory is the ROOT), one ROOT as before, or the three run directories in any order — --vasp ./BAND ./BAND_sublattice1 ./BAND_sublattice2, whatever they are named and wherever they live, the crystal run being the one whose POSCAR carries no Va species; two paths, or more than three, are refused in one line that shows the accepted forms (naming the three used to be an argparse “unrecognized arguments” error). -c is now optional with --vasp: without it the structure is the crystal run’s own POSCAR. With it, the analysis setting is the primitive cell of that file — so the irreps agree level for level with the extended-Hückel and PySCF diagrams and with crystod -c FILE --element ... --orbital ... of the same file — and every run is mapped onto it: the same lattice (the Cartesian matrices within a relative 1e-3), one global origin shift per run, a permutation of the atoms and lattice-vector wraps, with Va atoms mapping onto the removed ones. A -c file with Sr at the origin against runs written with Ti at the origin — the same crystal, the origin moved by (1/2,1/2,1/2) — used to stop with ERROR: ./BAND/POSCAR has Ti where the primitive cell has Sr (atom 1); it is now the same analysis, and every number of it (raw and aligned energies, site shifts, projected weights, purities, shares, connector weights) is identical to the analysis in the runs’ own setting to the last printed digit. What does differ is the irrep labels, which depend on the choice of origin (SrTiO3 at R: 1+ ↔ 2-, 3+ ↔ 3-, 4+ ↔ 5-; at X: 1+ ↔ 3-, 2+ ↔ 4-, 2- ↔ 4+, 5+ ↔ 5-; at M: 1+ ↔ 4+, 2+ ↔ 3+, 2- ↔ 3-), so the mapping is stated in the terminal report, in the level table, in the JSON ("setting") and as a chip on the page whenever it is not the identity. The transport needs no per-atom Bloch phase: the PROCAR projection vector is in VASP’s periodic gauge (the gauge the permutation representation is built in, section 2 of the engine report), where a coefficient belongs to an atom and not to one image of it, so the wraps cancel against the lattice sum and the origin shift contributes the one factor exp(−2πi k·s) to every atom alike. Proved on fluorite, whose quarter-coordinate F sites are the only fixture that can see the difference: transported with the atomic-gauge wrap factor exp(−2πi k·n_a) instead, 6 of 85 labelled levels fall below irrep purity 0.90 (minimum 0.249, three levels of one L manifold split 0.249/0.251/0.469), while the bare reordering gives the reference run’s 80 levels with purity 1.000 and identical energies. Names: without -c, or with a -c file that lives inside one of the run directories, the page is no longer named after the run directory (CrystOD_BAND_vasp.html) but after the compound and the space group — CrystOD_SrTiO3_Pm-3m_vasp.html, titled SrTiO3, Pm-3m; an ordinary -c file keeps the CrystOD_{cell}_vasp.html convention of the other engines. If the -c file and the crystal run are the same crystal in genuinely different bases the analysis falls back to the crystal run’s own setting and says so; if they are not the same crystal, the run stops with one line naming what differs and suggesting -c BAND/POSCAR. --vasp-setup follows the crystal run, not the -c file: -c is optional there too (without it the structure is ROOT/BAND/POSCAR), and the POSCARs it writes are now ROOT/BAND/POSCAR with one sublattice renamed to Va<q><sign> — its lattice, its origin and its ion order, line for line — with the -c file only naming the fragments and the formal charges; it used to write them in the -c setting, so a shifted -c silently produced fragment runs on another origin and on a lattice constant 3.2e-5 Å away from the crystal run they are compared with, which the new mapping then quietly repaired. A run directory that already holds a finished calculation (OUTCAR, PROCAR, vasprun.xml) is no longer overwritten without --force: rewriting it replaced the INCAR that was actually run. example/03_hybridization/vasp_SrTiO3 gained the Sr-origin POSCAR-finish of those runs and a README; tests in section 3.

  • New command crystod-xrd: powder X-ray diffraction patterns. crystod-xrd -c POSCAR prints the Bragg peaks of the structure (h k l, multiplicity, d, 2θ, relative intensity), writes them as a comma-separated table and draws the broadened pattern as a PDF with tick marks at the peak positions. The intensities come from pymatgen’s XRDCalculator. --xraytype selects the radiation: a Kα doublet (CuKa, the default, and MoKa, AgKa, CoKa, FeKa, CrKa) superposes the Kα1 and Kα2 patterns in the 2:1 ratio, a single line (CuKa1, CuKb, MoKa2, …) gives the monochromatic pattern, with the wavelengths of the RIETAN-FP manual. --peak-profile draws Lorentzian (default) or Gaussian peaks, both normalized to unit area so the two figures share one intensity scale; --width, --two-theta and --min-intensity adjust the figure and the window. Every reflection is kept by default, including the ones below pymatgen’s own 0.1 % cut-off. Families of planes that share a d spacing (cubic (3 0 0) and (2 2 1)) are listed together. This is the former script/xrd_pattern_poscar.py; on ScF3 the command reproduces all 36 peaks of that script to within 5×10⁻⁵ in 2θ and intensity. The Python form is the new API domain crystod.xrd (load_structure, compute_xrd_pattern, smear_pattern, write_peak_table, plot_xrd_pattern), and crystod-xrd --example ScF3 / --example SrTiO3 run on the bundled cells. Documentation: crystod-xrd. Tests in the new section 36 (21 checks, including Bragg’s law for every peak and the unit area of both profiles); worked example in example/36_xrd_pattern.

  • New command crystod-search: search the Materials Project and download POSCAR files. crystod-search SrTiO3 lists the matches the way the website’s table does (formula, space group, material ID, band gap, energy above hull, sites), sorted by the energy above the hull, with * after the ID of every experimentally observed material (mp-5229 *). The query is read like the website’s search box: a formula (SrTiO3, *TiO3), an anonymous formula (ABO3), a chemical system (Sr-Ti-O for the compounds of exactly these elements, Sr-* with a wildcard element), a list of elements (Sr,Ti,O, every material containing at least these) or material IDs (mp-5229,mp-5532). Server-side filters narrow the list: --experimental, --stable, --ehull, --band-gap, --sites, --spg, --exclude and --subsystems (a chemical system with all its subsystems), with --sort and --max (default 1000; a longer list, or --sort spg, which the server cannot sort on, is fetched in ID order and sorted locally, so no material is repeated or skipped at a page boundary). crystod-search --get mp-5532 writes POSCAR_Sr2TiO4_I4mmm_mp-5532 into the current directory, and a bare --get after a query downloads every listed material. The POSCAR holds the spglib-standardized conventional cell (14 atoms for Sr2TiO4), found at the tolerances the Materials Project assigns its space groups with (0.1 Å, 5°), so the file carries the listed space group exactly (the stored cell of Sr3Ti2O7, mp-3349, reads as C2/m at the default tolerance of crystod, the download as I4/mmm); --cell primitive writes the standardized primitive cell as PPOSCAR_Sr2TiO4_I4mmm_mp-5532 instead (7 atoms; the SALC analysis gives the same result for either file), so the two cells of one material never overwrite each other; --directory writes {formula}_{space group}_{ID}/POSCAR (PPOSCAR), -o names a single file, an identical existing file is kept and a different one is replaced only with --force. The API returns material IDs in its 8-letter alphabetical form (mp-aaaaahtd); they are shown as the website shows them (mp-5229, numbers up to mp-3347529, letters above), and both spellings are accepted. The client talks to the materials/summary endpoint of the Materials Project REST API with requests, now a dependency; the free API key is read from MP_API_KEY or from PMG_MAPI_KEY of the pymatgen settings file, and a missing, legacy or rejected key stops with a one-line ERROR: that says where to get one. The Python form is the new API domain crystod.search (search_materials, fetch_materials, write_poscar, normalize_material_id, …), and crystod-search --example SrTiO3 / Sr-Ti-O / mp-5229 run three ready-made searches. Documentation: crystod-search. Tests in the new section 37 (95 checks, run offline against a local stand-in for the API that serves data recorded from the Materials Project; CRYSTOD_LIVE_MP=1 adds one live search); worked example in example/37_mp_search.

  • Fixed: crystod-phonon --modulation crashed at many q points and froze wrong structures at the others. Reported on a Cmcm structure (--qpoint 0.5 0.5 0 --mode 1 --amplitude 1.5, expected Pnma) and on I4/mmm La2-xSrxNiO4 (the two X arms --qpoint1 0 0 0.5 --mode1 1 --qpoint2 -0.5 0.5 0 --mode2 1, expected P4_2/mnm): both stopped with “RuntimeError: Coupled irrep spaces are not equivalent.” Three errors in the mode construction were behind it. spgrep’s irrep-projected rows were read as bras instead of kets; phonopy’s dynamical matrix, which already carries the phase convention the displacement representation acts on, was re-phased once more; and the symmetry analysis ran on an spglib re-standardized copy of the primitive cell (shifted by c/2 for the Cmcm structure, with three atoms wrapped by a lattice vector for the nickelate) while the dynamical matrix stayed on phonopy’s. Whether a q point crashed therefore depended on how it was written, not on which arm it was: on the shipped Sr3Ti2O7 example (0.5, 0.5, 0) ran and (-0.5, 0.5, 0), the same point, crashed; “Coupled irrep spaces with different dimensions” (ScF3 at (-0.5, 0.5, 0.5)) and “… is not scalar” were the other forms. Where it did not crash, the result was silently wrong for structures with atoms off the inversion centres: the frozen-in pattern was not the eigen-displacement of the chosen mode but a mixture of the levels of its irrep (overlap 0.31 with the correct pattern for Sr3Ti2O7 X3- mode 1), Sr3Ti2O7 X3- mode 1 at (0.5, 0.5, 0) printed Cmcm only because the space group is reported at 0.1 Å (Ama2 at 1e-5), and the two-arm direction X3-(a;a) came out Pnnm (Pnn2 at 1e-3) instead of P4_2/mnm. Structures with every atom at 0 or 1/2, such as the cubic perovskite examples, kept their space groups. One solver, crystod.symmetry_adapted_modes, now serves --modulation and --vector: it works on phonopy’s primitive cell as it is (nothing is re-standardized, so the output keeps the origin and orientation of the input) with the dynamical matrix exactly as phonopy returns it, reads the projected rows as kets, orthonormalizes repeated occurrences of one irrep (spgrep leaves them non-orthogonal; ScF3 in R32 and RuCl3 in P3_112 failed even at Γ), and checks every run against the phonopy spectrum, the eigenvalue equation of every vector and orthonormality. The two reported commands give Pnma and P4_2/mnm; every arm and every q + G spelling constructs, and q + G freezes the same structure as q.

  • Fixed: --modulation froze the mass-weighted eigenvector instead of the displacement, with an arbitrary phase. The frozen-in field is now the eigen-displacement e_j exp(2 pi i q.x_j) / sqrt(m_j) — for a non-degenerate mode at a time-reversal-invariant q, the pattern phonopy’s own MODULATION writes, up to its sign (the test suite compares the two); without the 1/sqrt(m), heavy atoms moved too much against light ones in every mode that moves several species (the Sc-F mode X3- 9 of ScF3: overlap 0.990 with the true pattern). --amplitude is now the norm of the displacement of one primitive cell, exactly so at a time-reversal-invariant q (2q a reciprocal lattice vector), where every mode vector is now real; the global phase used to be whatever the eigensolver returned, so only part of the requested amplitude was frozen in (70 % for Sr3Ti2O7 X3- mode 1 at (0.5, 0.5, 0)). At those q the partners of a degenerate level are real, orthonormal and chosen, and numbered, by the isotropy subgroups they freeze into (the most symmetric first), a rule that does not depend on the origin, orientation or lattice basis of the cell, equivalent partners listed along the crystal axes where those tell them apart (--mode 1 2 3 of the R4+ triplet of ScF3 rotate about a, b and c; the Si Γ triplets are x, y, z, and the Si --vector example files were regenerated) and by their displacement patterns where they do not (the two Pnma partners at X of Si, which used to swap with the origin of the input), and the partners of one level signed relative to each other, so single partners and equal-amplitude sums give order-parameter directions, the same domain for every origin (a complex irrep and its time-reversal partner form one real level, and the Degeneracy column shows twice the irrep dimension; its partners are fixed by the displacement patterns); at any other q the global phase is fixed by a convention only (independent of the orientation of the input) and is not physically controlled. The manual’s new “What is frozen in” section states the convention.

  • Fixed with it: --vector, --subgroup --modulate and the error paths. --vector shares the solver and no longer falls back to plain phonopy eigenvectors (with a warning) where the construction failed, e.g. at the reported Cmcm q point and at the P point of I4/mmm Sr3Ti2O7. --subgroup --modulate writes the X3-(a;a) P4_2/mnm and X3-(a;b) Pnnm structures of Sr3Ti2O7 that it reported as “no candidate reproduced”, and the Y2- (Pnma) and Y4- structures of the reported Cmcm case, which it skipped with “no structures generated”; it now also tries negative coefficients, after all positive ones, so directions such as N1+(a;-a;a;a) (I4_1/amd), (a;-a;b;b) (Imma) and (a;b;-a;b) (C2/c) of the La2-xSrxNiO4 case are generated too, and every printed reproduce command regenerates its file exactly (the amplitudes are generated with the ten significant digits the command shows; 0.3 x 0.5478 used to be printed as 0.16434 but frozen in as 0.16433999999999999). --subgroup no longer lists the acoustic modes at Γ (rigid translations) when --threshold lies above zero; their directions could only end as “no candidate reproduced” notes. A q that no supercell of at most 12 cells per axis holds is refused before any mode is computed (0.15 used to be frozen into a 7-cell supercell on which the modulation is not periodic); a phonopy object whose primitive cell is not primitive is refused with a message pointing to primitive_matrix="auto"; a construction that fails ends in one ERROR: line instead of a traceback. Frequencies use the phonopy object’s unit conversion factor instead of the VASP constant, and the “Inputed cell was converted into primitive cell” line is gone from --modulation. Python API: SymmetryAdaptedModulation.mode_vectors now holds the freezing vectors above (with 1/sqrt(m), normalized over the primitive cell), the new attribute eigenvectors the phonopy-convention eigenvectors; get_commensurate_supercell_sizes raises ValueError for an incommensurate q; crystod.symmetry_adapted_modes.NonPrimitiveCellError (a ValueError) is new.

  • Structures written by --modulation, --subgroup --modulate or --vector of v0.4.1 and earlier should be regenerated — for structures with atoms off the inversion centres they are wrong whatever space group was printed, and elsewhere the domain may differ. The shipped Sr3Ti2O7 structures in example/25_modulation were regenerated (same space groups; the old X3- pattern overlapped the correct one by 0.31), and the directory gained the (a;a)/(a;b) structures of --subgroup --modulate and the two-arm P4_2/mnm example. The ScF3 structures there are kept as written: every command gives the same space group, and the README says which ones now come out as another domain. Its Im-3 command, whose third arm repeated the first (--qpoint3 0 0.5 0.5, which gives Immm), now reads --qpoint3 0.5 0.5 0. Tests in sections 24, 25 (including a comparison with phonopy’s own modulation) and 27.

  • Fixed: crystod-phonon --vibration wrote structures of the wrong symmetry. The symmetry-only command froze the raw spgrep projected row as Re(row_j exp(2 pi i q.R)): the Bloch factor exp(2 pi i q.x_j) of each atom’s own position was missing, and the phase and, at a time-reversal-invariant q, the partner basis of the row were whatever the projection returned — the error fixed in --modulation above. Where the missing factors happened to be equal on all atoms of the pattern (ScF3 at R written 0.5 0.5 0.5) the structure came out right; elsewhere it could not be trusted. Sr3Ti2O7 X3- (--qpoint 0.5 0.5 0) froze into Ama2 in 4 of its 6 mode spaces, and at the same point written -0.5 0.5 0 one space gave Cmce, the subgroup of another irrep (X3- is Cmcm, crystod-group --parent I4/mmm --irrep X3-); in all, 54 of the 144 components of the X irreps at four spellings of the two X arms were not an isotropy subgroup of their irrep, 162 of 252 for La2-xSrxNiO4 at X and N (most N components Cm or Cc instead of C2/m or C2/c), 156 of 216 for the Y irreps of the reported Cmcm SrLi2Nb2O7 (8 of the 10 Y2- spaces Pna2_1 instead of Pnma), and 9 of 108 for ScF3 at Γ, R, M and X (R1+ gave I4/mmm at -0.5 0.5 0.5). A written component is now a genuine partner of its mode space, built by the solver of --modulation with no dynamical matrix (crystod.symmetry_adapted_modes.solve_symmetry_adapted_spaces; every mode space one level; the spaces of a repeated irrep an orthonormal basis of its patterns fixed by the structure — the atom orbit each lives on, then how the atoms move along the crystal axes, then the products of neighbouring displacements — instead of the copies spgrep’s projection returns, which depend on how q is written): at a time-reversal-invariant q real in the lattice gauge, along the directions, in the order and with the signs --modulation uses, with the Bloch factor taken from the cell the supercell is built on; at any other q a Bloch wave with the phase convention of --modulation. It is a unit-norm symmetry-adapted displacement pattern, not a normal mode (no masses, no force constants), and --amplitude is now the displacement norm of one primitive cell at such a q. Every component of those four test sets (720 in all) now freezes into the single-arm isotropy subgroup of its irrep at a tolerance of 1e-5 (X3- Cmcm, X4- Cmme, X2+ and X3+ Cmce, X1- Ccce, X1+ Cmmm, X2- Cmcm, X4+ Cccm; Y2- and Y2+ Pnma, Y4- Pmmn, Y4+ Pbcn, Y1+ Pmma, Y1- Pnna, Y3+ Pnnm, Y3- Pbcm; ScF3 R4+ I4/mcm for each partner), and so do the 15645 labelled components at the time-reversal-invariant special points of 58 test structures in 54 space groups, apart from the rigid translations at Γ; at those points q, q + G and -q write the same structure for the same irrep label, occurrence and component (the mode-space numbers follow the irrep order of the spelling used, as before; a complex irrep and its conjugate, which share one real space, hand out its partners in the order of their labels), an origin-shifted input gives the same space groups, and where an irrep occurs once the written pattern is the --modulation mode of that irrep (identical vectors on the same cell for ScF3 at Γ, R, M and X and for Sr3Ti2O7 and La2-xSrxNiO4 at Γ and M). The mode-space listing, labels and numbering are unchanged; the written structures, the --export-npz displacements and the printed displacement lines change. Python API: the new SymmetryOnlyVibrations.get_symmetry_adapted_spaces returns the partners in the order of describe_mode_spaces, and get_supercell_displacements now applies exp(2 pi i q.(R + x_j)), so a projected row given to it gets its per-atom factor as well. The shipped example/26_vibration files were regenerated (the same I4/mcm; component 1 of R4+ is now the rotation about a, the structure --modulation --mode 1 writes) and its README, which still showed the 0-based numbering, reproduced. Tests in section 26.

  • Fixed with it: partners chosen by round-off where symmetry leaves the basis open. At a time-reversal-invariant q, the real space of a complex irrep and its conjugate, and a pseudo-real level, get their partners from quadratic forms of the displacements (--modulation, --vector and --vibration alike). Where every atom of such a space moves on a circle, those forms were degenerate, and the eigensolver turned the basis by round-off, so the written pattern could change with how q was written (--vibration on P-4 MnZnGa4Se8 at Γ, spelled 0 -1 -1: a GM3 pattern rotated by 45°). The forms now include how each atom moves along the crystal axes, and a part that no form splits keeps its basis. The --modulation, --vector and --subgroup --modulate runs of the reported Cmcm and La2-xSrxNiO4 cases, Sr3Ti2O7, ScF3 and Si write the same files as before.

v0.4.1#

  • crystod-group --parent is now the primary spelling of the isotropy-subgroup command; --supergroup stays as an alias. --supergroup SG --irrep IR sat next to --supergroup-cif FILE --subgroup-cif FILE (the symmetry-mode analysis of section 16), and the two were easy to confuse when coming back to the tool after a while: one lists the subgroups an irrep of the parent group SG can produce, the other decomposes a distorted structure. The command is therefore crystod-group --parent Pm-3m --irrep R4+ [--order-parameter 0 0 a], named after the value it takes; --supergroup Pm-3m is accepted unchanged and gives byte-identical output, so existing scripts keep working. The help texts, error messages (--parent requires --irrep), the documentation (section 13, quick start, tutorials, MCP page), the example README and the test suite use the new spelling.

  • crystod-group --supergroup-cif: the sublattice setting follows the orientation of the input files (reported on PbTiO3 Pm-3m → P4mm: the polarization along c of the P4mm CIF came out along −b of the cubic parent in the displacement table and in the GM4- VESTA file — crystallographically equivalent, but not the structure the user supplied). A symmetric parent leaves the sublattice basis degenerate: every basis S·W (W a point operation of the parent) pairs the atoms equally well and merely presents the same distortion in a rotated setting, 24 of them for a cubic parent, and the enumeration order used to pick one. Among the settings tied on the total distortion, the one whose child axes are rotated least against the parent axes is now chosen, both lattices taken as the input files orient them (recovered from spglib’s non-idealized standardization, which keeps the Cartesian frame of the file: a CIF sits with c along z and a in the xz plane, a POSCAR as written), so displacements and mode VESTA files follow the axes of the subgroup structure — a POSCAR whose polar axis lies along Cartesian y comes out along b. When no setting aligns the axes (standard settings that permute them, √2×√2 cells, a rhombohedral child in its hexagonal setting) the residual rotation is printed with the cell relation. Irreps, isotropy subgroups, dimensions and amplitudes of all 18 validated pairs are unchanged to the last digit; the direction labels of some pairs switch to a domain-equivalent form in the new setting (SrTiO3 R5- (a,0,0) instead of (0,0,a)), and the example VESTA files were regenerated. Tests in section 16 (PbTiO3 polarized along c and along Cartesian y).

v0.4.0#

  • PySCF is now an optional dependency (breaking change). pip install CrystOD no longer pulls in PySCF (about 500 MB with its dependencies); the quantitative engines — crystod --diagram/--band/--dos/--visualize --pyscf and crystod-mol --diagram --pyscf — need pip install "CrystOD[quantum]" once. Everything else (SALC irreps, extended-Hückel crystal-orbital and MO diagrams, group theory, phonon irreps, modulations, spin bases, Brillouin zones, molecular SALCs) runs without it. A --pyscf run in an environment without PySCF stops with one line that names the install command instead of a traceback, and the API classes crystod.salc.PySCFCrystalOrbitalDiagram / crystod.mol.PyscfDiagram raise ImportError with the same hint (crystod/_optional.py). The pyscf<2.14 cap moves into the extra. Verified by installing the built wheel into a fresh environment without PySCF: every command imports, the bundled examples run, and both --pyscf paths fail with the one-line message. New extras: quantum, doc, dev (= quantum + doc + ruff, nbconvert, ipykernel, build, twine) and all.

  • --example NAME: the quick start runs right after pip install. crystod, crystod-phonon, crystod-mol and crystod-bz gained --example. A few small inputs ship inside the package (crystod/examples/: the ScF3 and SrTiO3 cells, the 4×4×4 FORCE_SETS of SrTiO3, CH4 and NH3 — 108 kB in total), and --example NAME copies the files of that example into the working directory, prints the ordinary command line it stands for (Running: crystod -c 221_PPOSCAR_ScF3 --element Sc --orbital d) and runs it, so the files are there to edit and re-run. --example alone lists the names (ScF3_d, SrTiO3_d, ScF3_diagram; SrTiO3, SrTiO3_subgroup; CH4, NH3; ScF3). An existing identical file is left alone, a differing file of the same name is never overwritten, and further arguments pass through (crystod-phonon --example SrTiO3 --tolerance 1e-3). The registry (crystod.examples) is shared by the tutorials and the MCP server. Tests in sections 6, 20, 27 and 34.

  • --kpoint accepts a high-symmetry label in every mode of crystod. crystod -c POSCAR --atomic-orbital Sc-d F-p --kpoint R — the form the documentation had shown all along — was rejected in the SALC and hybridization modes (”–kpoint requires three coordinates”): labels were resolved only by --star-of-k, --visualize and --diagram. The label is now looked up in the ISO-IR special-point table the survey itself uses — the names crystod-bz --show-kpoint prints (GM, X, M, R; GM, Y, A, V, L, M for C2/m), resolved in the spglib primitive basis of the analysis — so --kpoint R, --kpoint Y and --kpoint GM (also GAMMA, G and Γ) work in every mode and print exactly what the coordinate form prints; an unknown label answers with the labels the space group has. Tests in section 6.

  • Fixed: seekpath labels were resolved in the wrong basis on base-centred monoclinic cells. crystod --star-of-k, crystod --visualize and the --qpoint labels of crystod-phonon --vibration/--vector/--modulation take their coordinates from seekpath, whose primitive cell differs from spglib’s by an integer change of basis for C-centred monoclinic lattices (VO2 C2/m: seekpath’s C was analyzed at [0.355, 0.355, 0], a different point, after a warning that the “coordinates might need a basis transformation”). The coordinates are now transformed into the spglib basis whenever the two cells are related by such a change of basis (C → [-0.355, 0.355, 0], the ISO-IR line LD), and the warning remains only for the case that cannot be transformed. Also fixed: the G alias of Gamma hid the genuine G point of body-centred tetragonal cells (--qpoint G on I4/mmm Sr3Ti2O7 now gives seekpath’s G = [1/2, 1/2, −0.018]). Tests in section 6.

  • crystod-phonon stops with one line when FORCE_SETS is missing. --irreps, --fatband, --lt, --vector and --subgroup read FORCE_SETS (FORCE_CONSTANTS with --readfc) from the working directory, as phonopy does; a run from another directory used to die in a phonopy FileNotFoundError traceback and now says where the file is expected (only --modulation also looks next to the cell file).

  • README and documentation for a first-time visitor. The README opens with the questions CrystOD answers, four output images (crystal-orbital diagram of ScF3, phonon dispersion of SrTiO3 with ISO-IR labels, 3D Brillouin zone, MO diagram of CH4), badges, the two-line installation and a quick start that needs no input file. The documentation site gained the same images, a “Try it” column in the question table, two Getting-Started pages (Your first analysis, CrystOD for phonopy users), a Tutorials page and a Contributing page.

  • API reference (Sphinx autodoc). Every symbol exported by crystod.salc, crystod.group, crystod.phonon, crystod.bz, crystod.mag, crystod.md and crystod.mol now carries a Google-style docstring (arguments, returns, raised errors, a runnable example) and is rendered at https://mochizuki-tus.github.io/CrystOD/api/ (doc/api/); the lazy namespaces work with autodoc unchanged. The docs workflow installs the package for the build; the -W build stays clean.

  • Three Jupyter tutorials in tutorials/ — phonon irrep labeling for phonopy users, isotropy-subgroup search from imaginary modes, MO diagrams of CH4 and NH3 — executed on the bundled inputs with their outputs saved, the Python API and the command line side by side.

  • crystod-mcp: a Model Context Protocol server. A separate package in crystod-mcp/ (pip install ./crystod-mcp; MCP Python SDK 2.x) exposes ten CrystOD analyses as tools — isotropy subgroups, irrep products, reducible-representation decomposition, ligand-field splitting, character tables, phonon irreps, imaginary-mode subgroups, crystal-orbital irreps, special k points, molecular symmetry — to Claude Desktop, Claude Code and other MCP clients. Every result carries the ISO-IR and CrystOD citations, each tool runs under a timeout (60 s, CRYSTOD_MCP_TIMEOUT), and 22 tests cover the tools and the protocol round trip. Not on PyPI yet.

  • Continuous integration. GitHub Actions now run the test suite on every push and pull request (Linux with Python 3.10/3.12/3.13, macOS with 3.12), ruff (rule set in ruff.toml), the docs build with warnings as errors (as before), and publish a tagged release v* to PyPI through Trusted Publishing after a manual approval (publish.yml; the one-time PyPI setup is described in the file). CONTRIBUTING.md describes the development setup, the section-number convention (feature N ↔ testsuite.py N ↔ example/N_* ↔ documentation section N) and the release recipe.

  • Housekeeping: *.bak and .DS_Store are ignored, the duplicated .gitignore block is gone, crystod --version is checked against the package version in the test suite, and the PyPI description says what the package does.

v0.3.8#

  • Package metadata now describes what the package can actually do. requires-python said >=3.9, which was untrue and already published: phonopy’s newest release carrying a CPython 3.9 wheel is 2.9.1 (2021), so pip back-solves to a phonopy sdist that fails to build, and pip install crystod cannot succeed on 3.9 at all — measured, with both pip and uv. The floor is now >=3.10, verified end to end by building throwaway environments on 3.10 through 3.14 and running the analyses in them (the source itself is 3.9-clean; the dependency stack is what sets the floor). Operating System :: OS Independent was likewise false: pyscf has never published a Windows wheel for any release and its own documentation directs Windows users to the Windows Subsystem for Linux, so the classifier is replaced by Linux and macOS, joined by the per-minor Python classifiers. Every one of the twelve dependencies now carries a lower bound traced to the specific API that sets it — phonopy>=2.9 for phonopy.phonon.character_table, sympy>=1.10 for hermite_normal_form, spgrep>=0.3.1 for the spinor irreps behind --spinor, ase>=3.21 because 3.20 silently writes a VASP 4 POSCAR with no element line, pymatgen>=2025.1.9 because older releases pull a monty whose zopen signature breaks Structure.from_file, and so on — and CITATION.cff gained the date-released, repository-code, url, abstract and keywords fields it needed for a citation with a year in it (validated with cffconvert).

  • pyscf is capped below 2.14.0, which cannot run a zero-electron cell. 2.14.0 rewrote pbc.scf.khf.get_occ as if 0 < nocc < nmo / elif nocc == nmo / else: raise, and a fragment that a GTH pseudopotential fully ionizes — Al³⁺ of AlN keeps no valence electrons — has nocc == 0, which matches neither branch and dies with Failed to assign mo_occ. Nocc (0) > Nmo (52), an inequality that is not even true. Isolated by crossing the two variables: the same structure passes on 2.13.1 under both Python 3.10 and 3.11 and fails on 2.14.0 under both, so it is the library version and not the interpreter. Only fragments that end up empty are affected — the crystal column of the same AlN run converges on 2.14.0. The cap is a stopgap; the durable fix is to skip the self-consistent cycle for a cell with no electrons.

  • A purely ferroelastic pair no longer aborts the symmetry-mode analysis (found while reproducing 27 saved AMPLIMODES runs; La3Ni2O7 I4/mmm → Fmmm died with “cannot separate the surviving from the broken parent operations”). How many parent operations survive in the child was derived from the child’s own space group, but that count is only a lower bound: mode amplitudes are measured in the strain-free parent-derived reference lattice, so an operation that the child’s metric breaks with no atomic counterpart still leaves the displacement field invariant and belongs in the analysis group. In La3Ni2O7 all 16 parent operations hold exactly while spglib reads the child as Fmmm (8), and cutting the mismatch spectrum in the middle of 16 exact zeros was impossible — correctly refused, but the wrong question. Whenever at least that many operations sit at the numerical-noise level they are now all kept, and the report says so (“8 parent operations that the child’s own metric breaks leave its atomic pattern exactly invariant … a spontaneous strain”). Reproduces the AMPLIMODES reference exactly: a single GM1+ mode, isotropy subgroup I4/mmm itself, dimension 4, 0.4666 Å, maximum displacement 0.2669 Å.

  • --poscar2cif warns when the CIF comes out more symmetric than the POSCAR. The CIF holds the spglib-standardized structure, so a displacement smaller than the tolerance is averaged away — silently turning a distorted structure into its own parent, after which a symmetry-mode analysis reports no distortion at all with nothing in the output to explain why. GaN F-43m → I-42d is the case that exposed it: its entire distortion is 0.0002 Å against the 0.01 Å default, and the child CIF came out byte-identical to the parent’s. The conversion now names both space groups and points at --tolerance; at --tolerance 0.0001 the pair reproduces AMPLIMODES exactly (W1 at (1/2,1/4,3/4) → I-42d, 0.0004 Å). Tests in sections 15 and 16.

  • crystod-group --supergroup-cif works for rhombohedral parents (reported on Ag3SI R32 → R3, which died with “could not fit the ISO-IR origin shift”). The centring translations used to match the ISO-IR origin against spglib’s conventional operations were generated from the rows of the primitive matrix, whose mod-1 lattice happens to coincide with the columns’ for every centring type except R — rows give (1/3, 1/3, 1/3) where the obverse setting needs (2/3, 1/3, 1/3), so no origin could ever fit an R-centred parent. Validated against ISODISTORT for Ag3SI: GM1 0.0741 Å matches exactly, and the polar GM2 amplitude agrees to five decimals (Ag in-plane 0.52161, I 0.16902, S 0.44628 Å) once the origin conventions are aligned — ISODISTORT pins the first orbit’s polar displacement to zero, while CrystOD keeps the AMPLIMODES minimum-distortion origin (the acoustic translation is removed, so the reported GM2 is 0.6512 Å instead of ISODISTORT’s origin-dependent 0.7070 Å). Tests in section 16, including the measured origin-convention equivalence.

  • --diagram --pyscf caches its SCFs by default, as CHK_{formula}.chk. A three-SCF periodic run is the expensive part of the page, and until now it was thrown away unless the user had thought to pass --chk beforehand. The densities are now saved under the compound’s reduced formula in conventional chemical order (rutile TiO2 → CHK_TiO2.chk, the same naming the symmetry-mode tables use), and the next run on that structure reads them and skips the SCFs (measured on TiO2 at Γ: 8 s instead of a full three-SCF run). The automatic file is a cache, so unlike an explicit --chk it never blocks a run and never costs one: options that do not match the stored ones (a different --xc, --kmesh, an --onsite file where fragment columns are wanted, a pre-electron-count-fix checkpoint), a checkpoint left by a same-formula polymorph (rocksalt and wurtzite AlN are both CHK_AlN.chk, with 2 and 4 atoms per cell — that comparison used to raise a raw numpy broadcast error), and a file that is not readable at all (an interrupted write, a foreign file that happens to match the name) each print one line and recompute. The checkpoint is written to a temporary file and renamed, so an interrupted write cannot leave a truncated one behind, and if it cannot be written at all (read-only or full working directory) the run says so and keeps going — losing a finished three-SCF calculation to a cache the user never asked for would be absurd. --no-chk turns the cache off entirely. --chk FILE keeps the strict contract — that file is the user’s, so a mismatch still aborts with the crystod --chk-info pointer instead of silently overwriting it. --electrons no longer invalidates a checkpoint (it fills the diagram’s arrows, it is not an SCF input), while the SCF’s own conv_tol/max_cycle, which do shape the density, are now part of the comparison. --chk FILE keeps the strict contract — that file is the user’s, so a mismatch still aborts with the crystod --chk-info pointer instead of silently overwriting it. Tests in section 3.

  • The extended-Hückel diagram now CAUTIONs that its level ORDER can be qualitatively wrong. Prompted by the rutile TiO2 case above: the model is non-self-consistent with fixed atomic parameters, so the symmetry labels, the state compositions and the bonding/antibonding pattern are reliable, but the energies carry no charge-transfer response and a whole band can land in the wrong place (TiO2’s artificial Ti 4p band inside the O 2p band, which costs the compound its insulating filling). The terminal report prints the caution once per run, the page footer carries it, and the --visualize levels viewer — which solves the identical Hamiltonian — prints it too. What the caution claims is deliberately narrow: the symmetry is rigorous (irrep labels, which states may mix, which stay nonbonding), the order can be wrong, and a wrong order takes the compositions with it (in the same TiO2 the bonding Γ₃⁺ comes out Ti-dominated and its antibonding partner O-dominated — the reverse of the heteropolar rule and of PySCF, verified against both engines). The prescribed --pyscf cross-check is qualified rather than absolute: it cannot run for the lanthanides, which no GTH basis covers, and there the caution says to read the diagram as a symmetry analysis instead. Test in section 3.

  • Fixed: the --diagram hover sketch could draw a 90%-s level as d lobes. Rutile TiO2’s Ti 4s Γ₁⁺ fragment level (Löwdin: Ti 4s 89.9%, Ti 3s 5.6%, Ti 3d 0.8%) rendered with drawn d:s amplitudes of 2.7:1 — the picture contradicted its own tooltip. The extended-Hückel sketch weighted raw eigenvector coefficients by the STO amplitude at a fixed 2-bohr radius, where the compact Ti 3d weighs ~4× the diffuse Ti 4s and the semicore 3s orthogonality tail cancels most of the s channel. The sketch now uses the recipe the PySCF sketches and the --visualize levels viewer already use: lobe sizes calibrated per (atom, l) channel to the Löwdin populations, with the probe radius chosen per channel (1.5/2.0/2.5/3.0 bohr, the one with the largest accumulated amplitude — away from semicore nodes). The TiO2 4s sphere now dominates its own sketch, and the drawn d amplitude² matches its 0.8% population. Level energies and occupations are untouched (verified identical across the full TiO2 page, 373 levels); the filtered --atomic-orbital sketch keeps the legacy fixed-radius amplitudes, since its channel populations include shells deliberately not drawn. Tests in section 3.

  • The extended-Hückel diagram discloses its two artifact modes for diffuse cation shells instead of silently mis-filling. Diagnosed on rutile TiO2, where the Γ-point HOMO (Γ₅⁻) showed 2 of 4 electrons although TiO2 is a band insulator. The electron count is correct everywhere (76 = 2×22 + 4×8; every column sums exactly); what breaks is the level ordering, twice: (1) the ζ = 0.675 Ti 4p STO engulfs the whole O²⁻ coordination cage, so the point-charge penetration term — a small correction for compact shells — shifts its on-site energy by −45.9 eV (to −51.3 eV, below Ti 3p); (2) its Bloch sums are all near-dependent (shell-block overlap eigenvalues ≤ 0.055); the doublet that survives the linear-dependency cut comes back as a first-order ~ estimate at ≈ h̄(1+(K−1)(ε−1)) ≈ −13 eV, right at the O 2p band top, and breaks the insulating filling — in the reporter’s phonopy-relaxed cell it lands inside the band, swallows 4 electrons and leaves the true O 2p top (Γ₅⁻) half-filled; in the slightly larger experimental cell shipped as the test structure it lands just above the band and is itself the half-filled HOMO, taking 2. (The deep Ti 4s Γ₁⁺ fragment level at −31.4 eV is the same physics from the other side: its in-phase Bloch sum has ε = 8.8, and the Wolfsberg–Helmholz energy h̄(K+(1−K)/ε) → K·h̄ overbinds large-overlap combinations — the classic EHT counterintuitive-overbinding artifact — to −36.7 eV pure; intra-sublattice mixing with the Ti 3s/3d Γ₁⁺ sums brings the fragment level back up to −31.4 eV. It miscounts nothing: in the crystal column the corresponding Ti 4s/O 2s bonding state at −36.6 eV simply occupies the O 2s Γ₁⁺ slot’s 2 electrons.) Now disclosed at every surface: the on-site report prints the decomposition (Ti 4p -51.32 [VSIP -5.44, field -45.88]) and WARNs when the point-charge shift exceeds 8 eV; the atomic-column tooltip carries the same warning; and an aufbau that runs out mid-level flags the partially filled level in the terminal and its tooltip (both engines — a genuinely metallic k point prints the same honest note). A column whose electron count is odd (a neutral open-shell sublattice: one level is necessarily half-filled at every k point) and a fractional --electrons request get their own matter-of-fact tooltip notes instead of the warning — parity and rounding are properties, not artifacts. Tests in section 3 (TiO2 at Γ).

  • Fixed: five two-shell --multiplet configurations aborted before printing the ground state (260828 bug report). (t2g)^2(eg)^2, (t2g)^3(eg)^{1,2,3} and (t2g)^4(eg)^2 of m-3m/d died with ERROR: multiplet-energy coefficient failed to snap to an exact fraction (8.66...) — exit 1, no Ground-state Term Symbol. The coupled-parent CI table recovered the off-diagonal coefficients as gram[pivot,q]/sqrt(gram[pivot,pivot]), and in these configurations they are genuinely irrational (2sqrt(2)B, 5sqrt(3)B, 4sqrt(3)B — the same square roots the printed Tanabe–Sugano matrices carry), so the rational-snap assumption could never hold. The Gram products c_p c_q themselves are basis-invariant and rational, so the off-diagonal is now snapped at the Gram level and factored exactly as sqrt(radicand) * (rational linear form) with a squarefree radicand: <1|H|2> = +-(5sqrt(3)B) prints where the run used to die, purely rational forms print byte-identically to before (+-(10B)), and a float reconstruction cross-check guards the factorization. Should a future block still have no such closed form, its table row says so in one line instead of killing the run — the term energies and the ground state never depended on it (they use the trace identity) — and a rank-1 consistency guard (the Gram diagonal must reproduce as radicand * c_q^2) protects the printed closed forms against any silent violation of the single-intertwiner assumption. All five configurations now complete with the physically correct ground states ((t2g)^3(eg)^2 → ^6A1g, the d⁵ high-spin sextet). Tests in section 14., so the choice is the user’s rather than a guess. --basis lists all 21 GTH basis sets PySCF ships grouped by element coverage — the decisive facts being that only gth-szv-molopt-sr and gth-dzvp-molopt-sr reach past Ar (71 elements: H–Rn minus the lanthanides) and that no GTH basis PySCF ships covers La–Lu at all, so --pyscf cannot run on a rare-earth compound (the extended-Hückel engine, which has its own La/Ce/Sm/Gd/Yb/Lu parameters, still can), while gth-tzvp/gth-qzv2p/gth-aug-*/gth-cc-* are main-group or light-element sets; --pseudo lists all 11 GTH pseudopotentials the same way (gth-pbe, gth-pade, gth-lda, gth-hfrev span H–Rn); --xc lists the LDA/GGA/meta-GGA/hybrid functionals verified (by running each of them) in this code path (lda, svwn, pbe, pbesol, revpbe, blyp, bp86, pw91, b97-d, scan, r2scan, tpss, revtpss, b3lyp, b3lyp5, pbe0, hse06, m06, m06-2x, wb97x, and hf), noting that hybrids want --ke-cutoff 150 or more; --orbital names the shells it accepts (s–i); --kpoint points at crystod-bz --show-kpoint for a space group’s labels. The lists are hard-coded so that --help stays import-free (no PySCF import for a help screen), and a testsuite check compares them against pyscf.pbc.gto.basis/pseudo.ALIAS so they cannot drift. Also fixed in the same pass: a basis or pseudopotential with no entry for one of the elements now fails with ERROR: the basis 'gth-qzv2p' has no entry for Sc. plus the list of sets that do cover the structure, instead of PySCF’s BasisNotFoundError from deep inside cell.build(); --xc is validated the same way, so an unknown name (pz, vwn — common shorthands libxc does not have) and a VV10 functional (wb97m-v, b97m-v, wb97x-v, which PySCF’s periodic code cannot evaluate — it has no nonlocal-correlation path, and the old failure was an AttributeError inside get_veff after all three cells were built) each end with one sentence naming the remedy; and the empty-shell caveat added earlier in this release recommended --basis gth-qzv2p unconditionally, which is impossible for anything past Ar — the recommendation now appears only when PySCF actually ships that set for every element, and says so plainly otherwise.

  • Removed the stale “pre-v0.3.0 flat modes” notice from crystod --help, the quickstart and the CLI docstrings: the flat spellings predate the first PyPI release (0.3.6), so no released version ever had them and the version numbers only dated the help text. A flag belonging to a sectioned command still gets the same helpful answer, now without the archaeology: ERROR: '--bz' is not a crystod option. The equivalent command is: crystod-bz.

  • Fixed: charged fragment cells converged with the wrong electron count on any k-mesh larger than 1×1×1 — every fragment column of the default (formal-charge) --diagram --pyscf mode was affected. PySCF subtracts cell.charge once from the Born–von Kármán supercell total (tot_electrons(nkpts) = ΣZ_val·nkpts − charge), not once per cell, so a fragment with formal charge q kept q·(nkpts−1)/nkpts spurious electrons per cell. Measured on the shipped ScF3 checkpoint (2×2×2 mesh): the Sc⁺³ sublattice carried 10.5 electrons per cell instead of 8, the F₃⁻³ sublattice 21.5 instead of 24, while the crystal (charge 0) was exact at 32. The fragment cells now pin their per-cell count explicitly (cell.nelectron), which makes tot_electrons(nkpts) exact on every mesh. Checkpoints written before this fix are refused when the bug could have bitten them — charged fragments on a k-mesh — with a message saying why; crystal-only (--onsite) and neutral-fragment checkpoints remain valid. Affected .chk files and the fragment columns of previously generated default-mode pages should be regenerated. Found by the adversarial review of the neutral-sublattice feature below, whose all-zero charges are immune (they were, until this fix, the only exactly counted fragment configuration).

  • --diagram --pyscf accepts neutral-atom sublattices (--oxidation Al=0 N=0) — the extended-Hückel engine’s convention, previously rejected with “the formal charges must make every fragment closed-shell”. Two things had to give: (1) a neutral sublattice usually has an odd electron count per cell (neutral Al: 3), which a spin-restricted driver with integer occupations cannot hold — pyscf’s aufbau would silently drop the unpaired electron (nocc = nelectron // 2). Such a cell is now solved with Fermi smearing automatically (fractional occupations; sigma 0.2 eV unless --sigma says otherwise, announced in the run report; which columns were smeared is recorded in the checkpoint) — the same smearing the convergence ladder already used as a fallback, so nothing downstream is new. (2) The isolated-atom columns must hit the right ground spin state: the old parity fallback (spin 0, else spin 1) would have computed neutral N as an excited spin-1 doublet. The ion/atom solver now scans the parity-consistent spins (up to 8 unpaired — Gd/Cm reach 4f⁷5d¹/5f⁷6d¹) and keeps the lowest converged energy, so neutral N comes out as the Hund ⁴S spin-3 atom, neutral Al as the ²P spin-1 atom, and closed-shell ions still win at spin 0 (sub-second atomic calculations; this also upgrades genuinely open-shell ions such as high-spin d⁵, which the old path forced to spin 1). A trial whose DIIS stalls is retried with second-order SCF (plain DIIS fails on neutral Ni’s near-degenerate 3d⁹4s¹ ground state, which would otherwise silently lose to a converged excited quintet 4.8 eV up), a failed trial is skipped with a note instead of killing the run, and if an unconverged spin state still undercuts the chosen one, a warning says the shown column may be an excited spin state. With all oxidation states 0 the point-charge lattice vanishes, so the fragments are bare neutral sublattices + ghost basis, exactly the EHT model. Verified on rocksalt AlN at X: Al column 3 electrons, N column 5, both smeared, Al^+0 (UKS PBE, 3 electrons, spin 1 (alpha levels)) / N^+0 (UKS PBE, 5 electrons, spin 3 (alpha levels)), and the Al 3p Bloch combination below Al 3s — the same intra-sublattice-overlap physics the EHT pages show. Test in section 3.

  • Empty ion shells in the PySCF atomic columns are now flagged as what they are — finite-basis virtuals, not Rydberg levels. In crystod --diagram --pyscf, the isolated-ion columns showed Al⁺³ 4p below 4s (and N⁻³ 3p below 3s), inverting the physical order. The cause is the default gth-dzvp-molopt-sr basis: optimized for condensed-phase valence states, it carries no diffuse functions, so any empty level beyond the lowest empty state of its l is merely the orthogonal remainder of the contraction — a compact, node-rich function pushed up by the kinetic energy of its enforced node (the repulsive GTH s-channel projector plausibly adds to this, though the experiment did not separate the two contributions). Verified against the NIST free-ion spectrum of Al⁺³ (Na-like Al III): with the production basis 4s is +7.1 eV off (4p only +1.5 eV, hence the inversion), while re-running the identical bare-ion problem in gth-qzv2p lands 4s/4p within 0.1 eV of experiment — the pseudopotential is innocent. For anions the defect is deeper: one more electron on a free anion is unbound, so every empty anion level is a discretized continuum state with no physical counterpart at any basis size (in the crystal the Madelung potential provides the binding). Both cases are now disclosed everywhere the levels appear: the tooltip of each affected level, the page footer, and the terminal report — each including the concrete remedy --basis gth-qzv2p when the run uses a molopt-family basis (with two honest caveats: diffuse-richer bases make the periodic SCF heavier and possibly ill-conditioned, and the 0.1 eV figure is the bare-ion validation — Kohn–Sham virtuals of ions that keep electrons retain the functional’s own eV-scale attachment error, which no basis removes). The lowest empty state of each l of a cation is left unflagged — occupied semicore shells below it do not consume the slot, so Na⁺ 3s (the first empty s above the 2s²2p⁶ core, basis-converged to 0.2 eV) is trusted while Na⁺ 4s is flagged — because those are genuine electron-affinity levels (Al⁺³ 3s/3p/3d match NIST to 0.2–1.2 eV even in the production basis); a formally neutral atom, whose Coulomb tail binds no attachment states, has all its empty shells flagged. Test in section 3 (NaCl: Na⁺ virtual notes, Cl⁻ continuum notes, and the recommendation string in the page).

  • The atomic-shell columns work in the PySCF engine too — as genuine isolated-ion calculations. crystod --diagram --pyscf now runs one additional PySCF calculation per element: the isolated ion at its formal charge (the same pymatgen-guessed/--oxidation charges the fragment sublattices already use), with the same basis, pseudopotential and functional as the periodic calculations — so the page tells the user’s three-stage story explicitly: charged atom → charged sublattice (+ point-charge lattice) → crystal. Closed-shell ions (Na⁺, Cl⁻, Sc³⁺, F⁻, N³⁻ …) are solved with RKS; a cation whose formal charge empties the pseudo-valence space entirely (Al³⁺ with GTH-q3) gets the bare-ion one-electron spectrum instead (its 3s eigenvalue, −27.9 eV, reproduces Al’s third ionization potential of 28.4 eV); odd-electron ions fall back to UKS with a tooltip flag. Because the molecular (vacuum) and periodic (G = 0) energy references share no common zero — and vacuum anion levels are unbound (F⁻ 2p at +3.5 eV) — each element’s ion column is rigidly shifted so its deepest shell sits at the degeneracy-weighted, all-k mean of the fragment levels that shell dominates (the band’s center of gravity, which is the tight-binding on-site energy in an orthogonal basis); the anchor, the shift and the raw vacuum levels are all printed and repeated in the tooltips. Splitting connector lines reuse the levels’ displayed Löwdin/Mulliken shell populations. --onsite pages are unchanged (the single-Hamiltonian mode has no atomic stage). Tests in section 3 (default-mode NaCl at X: ion report, anchoring, and the Cl 3p fragment→ion link).

  • crystod --diagram gained outermost atomic-shell columns — the crystal analogue of the MolOD ligand-ao column. With more than one atom of an element per primitive cell (wurtzite AlN: two Al), the fragment sublattice column alone can look puzzling — 26 electrons on the Al side, and Al-3p Bloch combinations below Al-3s at several k points, both genuine consequences of the intra-sublattice Al–Al overlap. The diagram now shows where that splitting comes from, exactly like the H₄ column of crystod-mol: the new outermost columns hold one level per (element, shell) at the k-independent on-site energy (VSIP + spherical part of the point-charge ligand field, e.g. 2Al 3s, 2Al 3p, no occupation arrows), and every sublattice level is connected to its parent shell(s) by correlation lines weighted with the same Löwdin shell populations as its tooltip — so “Al 3p GM4 at −16.35 eV sits below Al 3s GM4 at −6.50 eV” reads directly as the bonding/antibonding fan opened by the two equivalent atoms out of the atomic 3s (−16.80) and 3p (−11.00) levels. The terminal report prints the same on-site levels per fragment (“on-site atomic levels (VSIP + ligand field, eV): …”), and the page footer explains the columns. Layout grows from three to five columns (2Al AOs | Al | crystal orbitals | N | 2N AOs); engines that do not build the AO columns (--pyscf) keep the three-column page unchanged. Tests in section 3 (ScF3: Sc AOs/3F AOs headers and 3F 2p label; rocksalt AlN at X: the Al 3s fragment level linked to its parent Al 3s on-site level).

  • Fixed: crystod --diagram deleted physical levels in dense sublattices — rocksalt AlN had no Al 3s at the X point. The extended-Hückel solver excludes Bloch combinations whose overlap eigenvalue falls below 0.2, because their variational energies diverge as (1−K)·h/ε (the overlap catastrophe of diffuse shells). In a dense sublattice, however, a single shell’s own Bloch sum can fall below that floor — the fcc Al sublattice of rocksalt AlN (twelve neighbours at 2.86 Å) puts the one Al 3s X1+ combination at eigenvalue 0.15 — and deleting it removed a whole physical level: the Al column at X had no 3s state, so the aufbau filling put three electrons into 3p. Such modes are now kept as separate levels whose energies are estimated at first order in the Löwdin orthogonalization (H̃ = H − (S−1)(h̄ᵢ+h̄ⱼ)/2, the same correction the coupling tables use; for a single Bloch sum this gives the bounded E = h(1+(K−1)σ) instead of the divergent h(K+(1−K)/ε)). Estimated energies are marked ~ in the terminal and explained in the level’s tooltip (and carry "est": 1 in the page’s level JSON); only truly linearly dependent combinations (eigenvalue < 10⁻⁶, no physical content) are still removed, reported separately. The variational levels are bit-identical to before, and in the previously validated materials (ScF3, SrTiO3, BaTiO3, ZrO2) every restored level is empty, so no occupied manifold changed. AlN now shows Al 3s X1+ at X and Al 3p GM4- at Γ with the textbook 3s²3p¹ filling at Γ, and the crystal column gains the previously missing antibonding partners — the lowest empty state at X becomes X1+, matching the X-point conduction-band symmetry of rocksalt AlN in DFT. Regression tests in section 3 (the restored Sc 4s R1+ level of ScF3, and rocksalt AlN at X: the ~ estimate plus the 13-electron aufbau count of the Al column).

v0.3.7#

  • The SALC viewer shows the k point in both bases with --conventional: --kpoint is given in the primitive reciprocal basis throughout CrystOD, and the conventional-cell display now lists both — X [0.0, 0.0, 0.5] (primitive) and X [0.5, 0.5, 0.0] (conventional) for I4/mmm La3Ni2O7 — so the shape of the commensurate display supercell (2 × 2 × 1 conventional cells at X) is evident at a glance. The conversion uses the same primitive-to-conventional matrix as the displayed cell (fcc Si X: [0,1/2,1/2] → [1,0,0]); pages without --conventional are unchanged. Tests in section 6 (the –visualize extras block).

  • The symmetry-mode decomposition table is saved as sym_mode_{formula} (e.g. sym_mode_K2SeO4) next to the VESTA files of every --supergroup-cif run, named by the parent composition. The formula follows the conventional chemical order — cations before anions, the cation on the most special Wyckoff site (letter closest to a) first, then increasing valence (SrTiO3, KNbO3, PbZrO3; La3Ni2O7 keeps La first because the site rule beats Ni’s lower valence) — computed from the structure itself (oxidation-state guesses, electronegativity fallback), so POSCAR inputs work the same as CIFs. The same formula is written into every --poscar2cif output as the IUCr _chemical_formula_sum field ("K2 Se O4"). Second non-special-k reference case added: the K2SeO4 lock-in ferroelectric Pnma → Pna2₁ (3× along a, SM = (1/3,0,0)) reproduces AMPLIMODES entry by entry — GM1+ 0.9467, polar GM4- 0.4230, SM2 1.2885, SM3 0.1727 Å, total 1.6629 Å. Tests in sections 15 and 16.

  • Fixed: the one-sentence rejection of a wrong --dim in crystod-phonon --modulation depended on the phonopy version — older phonopy raises ValueError for a supercell that does not match the force data, phonopy 4 an IndexError from the site-symmetry lookup, which escaped as a raw traceback. Both are caught now.

  • crystod-group --supergroup-cif supports order parameters at non-special k points (symmetry lines, planes and general points on the 1/24 grid). The reference case is the PbZrO3 antiferroelectric, Pm-3m → Pbam at 8× the cubic primitive cell, whose distortion condenses the Σ point (1/4,1/4,0) and the S point (1/4,1/2,1/4) on top of R/X/M modes — previously rejected with “some folding k-points are not tabulated special points”. Small irreps at such points are computed with spgrep at the exact k, named through the bundled ISO-IR tables (SM2, S4, …), and the full induced representations are built from the ISO-IR full-star matrices themselves (tabulated arm order, deterministic across spgrep versions; the spgrep-basis induction is the automatic fallback). Every AMPLIMODES reference number is reproduced — SM2 1.2871 Å → Pbam, R4+ 1.5232 Å → Imma, S4 0.4470 Å → Cmce, R5+ 0.0810 Å, X3- 0.0374 Å, M5- 0.0473 Å, total 2.0462 Å — and each mode’s isotropy subgroup is additionally verified by freezing that single mode’s displacement field and re-measuring its space group with spglib. Because the phase gauge that fixes the ISOTROPY web tables’ order-parameter axes is not part of the bundled data, the direction pattern of a line mode may differ from ISOSUBGROUP’s (a printed note says so); subgroup, dimension and amplitude are gauge-independent. Internals: the mode projectors are now assembled per k star in a Fourier-block basis (star arms × parent atoms) instead of dense displacement matrices on the invariant-core supercell — the old route would have needed ~22 GB at PbZrO3’s 4×4×4 invariant core (320 atoms), the new one runs the whole analysis in ~18 s; outputs of all previously validated cases (SrTiO3, ZrO2, CaTiO3, BaTiO3, AlF3, La3Ni2O7, SrLi2Nb2O7 ×2) are unchanged to the last printed digit. Tests in section 16, including the frozen-field re-measurement.

v0.3.6#

  • Python API: every analysis is now reachable as a Python function, grouped into one module per command — crystod.salc, crystod.group, crystod.phonon, crystod.bz, crystod.mag, crystod.md, crystod.mol — so that a part of CrystOD can be used inside another program without going through the command line (full guide: the Python API page of the documentation). The domain modules are a curated view over the implementation modules, which keep their own names: from crystod.phonon_irreps import get_irrep_labels, from crystod.operations import wigner_D_real and every other pre-0.3.6 import path works unchanged, and the command line is untouched apart from the new --subgroup mode below. Attribute access is lazy (PEP 562), so import crystod plus all seven domains costs ~0.09 s and pulls in nothing heavier than NumPy — phonopy, spgrep, spglib, PySCF and matplotlib load only when a function that needs them is actually called. New in the API layer: crystod.group.isotropy_subgroups(space_group, irrep[, order_parameter]) returns the crystod-group --supergroup table as IsotropySubgroup records (irrep, direction, space-group number/symbol, cell size, index, free-parameter count, and the conventional basis/origin in the parent convention); crystod.phonon.label_phonon_modes(phonon, qpoint) labels the modes of a live phonopy object with ISO-IR irreps (star arms mapped onto the tabulated arm automatically), returning PhononMode records with 1-based band indices; crystod.phonon.imaginary_mode_subgroups(phonon, qpoint) and scan_imaginary_modes(phonon) combine the two into the symmetry-lowering step of a structure search. Bad input raises ValueError rather than exiting: the implementation modules report errors the way a command line wants them (raise SystemExit), which would tear down a program that merely called the function — the API layer translates that at its boundary. This matters for the labels of symmetry lines and planes (DT5, LD3, …), which are legitimate mode labels with no isotropy subgroups in the tables; imaginary_mode_subgroups/scan_imaginary_modes record such a level in errors with an empty subgroups and carry on. Tested in the new testsuite section 35 (31 checks: import hygiene, backward compatibility, API-vs-CLI agreement on every order-parameter direction — including the arm grouping of multi-arm stars such as M3+(0;0;a) — the error contract, and the SrTiO3 R-point soft mode).

  • Fixed: the documentation site always displayed version 0.3.3. doc/conf.py read the version through importlib.metadata("CrystOD") and fell back to a hard-coded string, but the docs workflow installs only Sphinx and never CrystOD itself, so every published build took the fallback. The version is now read straight out of pyproject.toml when the package is not installed.

  • crystod-phonon --modulation runs from the files you already have. A unit cell plus FORCE_SETS (or FORCE_CONSTANTS with --readfc) is enough — crystod-phonon --modulation -c 221_PPOSCAR_ScF3 --qpoint 0.5 0.5 0.5 --mode 1 2 3 --amplitude 0.3 produces the byte-identical structure that the --yaml phonopy_params.yaml form does, so no phonopy yaml has to be generated first. The supercell of the force calculation is read from phonopy_disp.yaml when present and otherwise inferred from the atom count of the force file (respecting the axis equivalence of the lattice: a cell with |a| = |b| never gets unequal multipliers); which route was taken is printed, and --dim overrides it. A supercell that does not match the force data now aborts with one sentence instead of a phonopy traceback. --yaml still works and still wins when both are given. Fixed in the same pass: --tolerance/--symprec reached the symmetry-adapted mode construction but never the space group reported for the generated structure, which was hard-coded at 0.1 Å — a tolerance comparable to the modulation amplitude itself, so the printed label depended on the absolute amplitude scale (--mode 1 2 3 --amplitude 0.3 0.15 0.075, the direction (a,b,c), printed C2/c instead of P-1). One --tolerance now sets both.

  • crystod-phonon --subgroup --modulate: the distorted structure of every enumerated order-parameter direction, generated in the same run — one POSCAR per direction plus the --modulation command that reproduces it, so the candidate structures of a structure search come straight out of the instability analysis (cubic SrTiO3 R5-: six structures, I4/mcm, R-3c, Imma, C2/m, C2/c, P-1). Which combination of the degenerate modes realizes which direction is not fixed by any convention CrystOD could assume, so it is measured rather than assumed: candidate combinations are generated, the space group, primitive-cell multiplication and index of each generated structure are determined with spglib, and the triple is matched against the enumerated table — a direction no candidate reproduces is reported as not generated instead of guessed at. When two directions of one irrep share that triple — R5+ of Pm-3m puts both (0,a,b) and (a,a,b) at C2/m, size 2, index 24 — the conventional cell metric separates them, so a domain of one is never written under the other’s name, and the pattern of zero and equal components decides which label a structure takes; every enumerated row ends up either as a file or as an explicit note. Multi-arm stars go through the multi-q form of --modulation automatically (M3+ of a perovskite: one arm for (0;0;a), two for (0;a;a), three for (a;a;a)). --amplitude scales the set (default 0.3 Å). Tests in section 27, including an independent re-measurement of every written structure and an execution of every printed reproduce command.

  • crystod-group --parent is accepted as an alias of --supergroup: the flag names the parent group but returns its subgroups, which reads backwards, so the option is now also available under the name of the value it takes. Both spellings produce byte-identical output.

  • crystod-phonon --subgroup: the command-line form of the same analysis — every imaginary phonon level is labeled with its irrep and each order-parameter direction resolved to the space group it condenses into (cubic SrTiO3, R5- triplet: I4/mcm, R-3c, Imma, C2/m, C2/c, P-1), scanning every q point commensurate with the supercell when no --qpoint is given. --threshold sets what counts as imaginary (default -0.1 THz); --yaml phonopy_params.yaml replaces --dim/-c. Freezing in a single eigenvector explores only one direction of a degenerate level, so this is the enumeration a structure search needs before it can claim a ground state. Tests in section 27.

v0.3.5#

  • All irreducible-representation tables unified on the bundled ISO-IR data; the irreptables and irrep package dependencies are removed. Every table lookup — crystal orbitals, hybridization, SALC/basis functions, phonon irreps/vibrations/modulations, spin bases, direct products, isotropy subgroups, little-group character tables, and special-k listings — now reads the ISO-IR dataset shipped in the package (crystod/CIR_data.txt.gz; ISOTROPY Software Suite: H. T. Stokes, B. J. Campbell and R. Cordes, Acta Cryst. A69, 388-395 (2013)). Labels follow the ISO-IR (ISOTROPY, Miller-Love) convention uniformly at special points and on symmetry lines/planes; at the maximal k points they coincide with the previously used Bilbao-convention labels for all 230 space groups (verified programmatically: operator sets, representative star arms, characters, and label strings all agree). Line terms formerly named from a DIRPRO-fitted map follow ISO-IR now (S of the P6₃/mmc family → Q, T → LD, V of I4/mmm → LD; the DT numbering of Fm-3m/Ia-3d is permuted). The non-self-conjugate P/N/W points of the centred nonsymmorphic groups (P of I-4̄2d/Ia-3̄d, N of I4₁32, W of Fd-3̄m, …) are resolved by one exact rule — complex conjugation combined with the wrapped-lattice translation phase — replacing the previous per-case gauge fixes and fitted-name substitutions (chirality-sensitive results such as I4₁32 N1 → P4₁22 are reproduced). The provenance notes (“absent from the … tables”, “[…; ISO-IR labels]”) are gone from the outputs since there is only one convention now. Verified by the full testsuite (639 checks) and by a feature-by-feature side-by-side comparison against the previous pipeline (61 command runs: no physical differences — decompositions, frequencies, subgroup tables, and exported files agree).

  • License changed to MIT (possible now that the GPL table packages are no longer used).

  • Rare-earth support: archived neutral-atom reference levels (reference/atomic_level_*) added for Y and La–Lu (def2-SVP for Y/La, Stuttgart RSC 1997 / ECP28MWB for Ce–Yb, CRENBL for Lu — the 4f shell stays in the valence space), and extended-Hückel parameters for La, Ce, Sm, Gd, Yb, Lu (YAeHMOP tables) together with new f-orbital STO overlaps (σ/π/δ/φ channels, validated against independent grid integration). crystod --diagram now works for e.g. CeO₂, with the 4f crystal-field splitting labeled (GM4⁻/GM5⁻/GM2⁻ at Γ of Fm-3̄m).

  • crystod --visualize without --kpoint now visualizes the SALCs at every special k point of the space group (one HTML per point, SALC_{POSCAR}_{element}_{orbital}_{kpoint}.html), mirroring the --diagram behaviour.

  • The citation notice is printed at the end of every subcommand’s --help.

  • Documentation front page gained an interactive “molecule → crystal” band-formation demo and embedded MO/crystal-orbital diagrams.

  • Cd parameterized for every extended-Hückel mode (--diagram, the new levels viewer, crystod-mol): the YAeHMOP table entry (5s −11.80 eV / ζ 1.640, 5p −8.20 eV / ζ 1.600; the filled 4d¹⁰ stays inactive there, exactly as for Zn — it enters the full-electron basis as a semicore shell instead) plus the archived neutral-atom reference calculation (reference/atomic_level_Cd, RHF/def2-svp + def2 ECP-28 — def2-svp does cover Cd; the 4d sits at −19.6 eV). CdF₂ at Γ comes out textbook: the ligand-field-split Cd 4d¹⁰ semicore (6e + 4e), the F 2p valence band, and the Cd 5s conduction level as the LUMO. script/collect_atomic_levels.py now takes element arguments (… Cd) to regenerate ONLY those entries — a full re-collection re-runs every archived atom and can trip the 1e-4 hartree cross-check on unrelated elements when the BLAS environment differs from the original archive’s.

  • Extended-Hückel eigen-levels in the SALC viewer (bare crystod --visualize -c POSCAR, crystod/visualize_eht.py): the extended-Hückel counterpart of --visualize --pyscf — without --element/--orbital the viewer now shows the eigen-levels of the --diagram engine instead of the SALC basis: one page per special k point, Mode | Irrep | Comp. | Energy (eV) rows for every level inside the energy window (HOMO−15 .. LUMO+10 eV by default; --window opens the deep shells), the clicked row rendering the eigenvector’s wave function with lobe sizes calibrated to the Löwdin populations (per-channel best probe radius among 1.5/2.0/2.5/3.0 bohr, so semicore orthogonalization nodes don’t flip drawn signs — same recipe as the PySCF viewer). No SCF and no options required: STO basis, atomic levels and point charges are tabulated (ScF₃’s four k-point pages appear in seconds, levels identical to the --diagram crystal column). --sublattice Sc shows the pre-bonding sublattice block of the shared Hamiltonian instead of the crystal — all views on ONE energy reference by construction. --kpoint/--window/--diagonalize/--valence-only/--bond/--conventional/--electrons/--oxidation work as in the PySCF version (--real-coefficient is accepted but inert on the level pages — the degenerate partners are always realified and the drawn field is Re[ψ], which the sidebar now states honestly on the PySCF pages too); PySCF-only flags (--chk, --basis, --kmesh, …) are rejected with a pointer to --visualize --pyscf, and --element without --orbital still directs to the basis viewer. Tests in section 5.

  • Fixed: a missing structure file crashed with an AttributeError traceback ('NoneType' object has no attribute 'totuple') in most modes — phonopy’s read_crystal_structure returns None instead of raising. Every POSCAR-reading entry point (--diagram both engines, --dos, --band, --visualize --pyscf, the SALC analysis, --product hybridization, crystod-phonon --qpoint) now goes through the shared read_poscar_or_exit helper (previously only the SALC viewer/BZ/star-of-k/mag paths used it) and prints ERROR: No POSCAR named 225_PPOSCAR_ZrO2! — the unified message across all modes.

  • --diagram --conventional: conventional-cell orbital sketches (both engines): the Level-details hover sketch draws the wave function on the primitive k-commensurate supercell by default — at Γ of an fcc crystal that is the 3-atom rhombohedral primitive cell, hard to relate to the textbook conventional picture. --conventional (same flag as the SALC viewer) wraps every atom into the conventional cell of the detected centring (times the diagonal multiples that keep the Bloch phase exactly commensurate: ZrO₂ Fm-3m draws the full 12-atom fluorite cube at Γ and X, 2×1×1 cubes at W, 2×2×2 at L), each atom carrying the exact phase of its own primitive-lattice translation — verified against exp(2πi k_conv·r) sign patterns at X for both engines. The sketch caption names the displayed cell (orbital sketch (conventional cell (F centring), 1 x 1 x 1)), the page footer says which cell the sketches use, and the flag is display-only: the diagram, the populations and any --chk are untouched (a checkpoint written without it is reused as-is). Default-mode pages are unchanged apart from the new caption (the rewritten supercell enumeration was verified bit-identical). --visualize --pyscf gained the same flag (it was silently dropped there before — the PySCF SALC-viewer pages now honor --conventional like the classic viewer always did). Adversarial review fixes in passing: the primitive→conventional matrix was transposed for non-symmetric centrings — phonopy’s M acts on column-vector lattices, so the row matrix is inv(M)ᵀ, not inv(M); the two coincide for P/F/I (why every cubic example was right) but R-centred structures drew an oblique det-3 supercell instead of the hexagonal cell, and A/C got axis-swapped cells — a pre-existing bug shared by the SALC viewer’s --conventional, crystod-phonon --vector --conventional and crystod-mag’s display cell, all fixed at the source (GeTe R3m now frames the true 4.23/4.23/10.89 Å γ=120° hexagonal cell); --dos now rejects being combined with --diagram/--visualize (it silently swallowed them and their flags); the --bond/--conventional guards run before every mode branch (--star-of-k --conventional used to be silently ignored). Regression tests: the conventional NaCl frame must be the cubic cell (orthogonal, equal edges) and the Na 2s Bloch signs at X must follow exp(2πi k_conv·r); the NH3 1e/2e sketch-phase check was rewritten to the gauge-invariant bond-directed product Σ_H s_H (p_N·r_NH) — the old max-|s| pick broke on the mirror-hydrogen tie whenever the degenerate-partner canonicalization landed on the other gauge (a BLAS-environment effect, the sketches themselves were correct).

  • Added two-fragment extended-Hückel MO diagrams (crystod-mol --diagram --xyz FILE.xyz --ao-left FORMULA --ao-right FORMULA, no --pyscf): the molecule is split into two arbitrary submolecules by chemical formula (benzene H6/C6, CH3OH H4/CO, O2 O/O) and drawn as the three-column diagram (left fragment MOs | molecule MOs | right fragment MOs) with the same symmetry + overlap machinery as the single-center mode — all three columns solved in the one molecular AO space (the fragment’s (H, S) sub-block is the isolated fragment in extended Hückel), molecular MOs projected onto the fragment MOs through the shared overlap matrix, COOP bonding colors, VESTA fragment-line colors, orbital sketches, and irrep labels assigned from the MO characters after solving with core shells counted in the numbering (benzene: (2a1g)^2 … (1e1g)^4 with the Hückel π ladder 1a2u < 1e1g < 1e2u < 1b1g, label-compatible with --pyscf). A fragment keeping a higher symmetry than the molecule (the exactly degenerate CO π pair of CH3OH under Cs) is decomposed with the irrep projectors; non-invariant fragments and linear molecules fall back to unlabeled levels. Tests in section 33.

  • Fixed (2026-08-01/02): the --pyscf MolOD orbital sketches drew CORE levels from their valence orthogonalization tails — the r0 = 2 bohr radial weighting of the 2026-07-28 phase fix, taken alone, kills the contracted 1s functions (dead at r0), so e.g. the C 1s combinations of benzene’s C6 fragment rendered as 2p-like lobes. Each (atom, l) sketch channel is now rescaled to its Löwdin population (signs/orientation still from the r0 amplitudes; the same two-step recipe as the crystal engine’s sketches), the r0 amplitudes gained the missing per-primitive gto_norm normalization (verified to reproduce eval_gto to 8 digits; without it the per-shell weights were off by 0.28–7.7× and could flip a channel’s relative sign), and non-degenerate levels get a deterministic dominant-lobe-positive phase. Core levels are drawn as the pure s spheres they are (Löwdin p admixture of the benzene C-1s a1g state: 0.22%, honestly drawn at √0.002 ≈ 4% of the s lobes); the NH3 1e/2e phase regression test still passes and the example diagrams were regenerated. — the --chk restart files are binary (compressed npz, for size and load speed), so the calculation conditions they lock in were invisible until a parameter-mismatch error spelled one of them out. --chk-info prints everything the file stores — structure, method (XC/basis/pseudo/ke_cutoff/k-mesh/max_l/smearing), electron count and oxidation states, fragment split, which densities it holds (full three-SCF run vs an --onsite crystal-only checkpoint) on how many k points × AOs, the SCF energies — plus a ready-to-paste reuse with: --co-left … --kmesh … --chk FILE option string that reproduces the stored conditions exactly (with --onsite appended automatically for crystal-only files). The parameter-mismatch error now points to it.

  • crystod --band --pyscf: electronic band structure and fatbands from the recorded density matrix (crystod/band_pyscf.py): the whole-band-structure companion to --dos — read the full dispersion first, then zoom into the special k points with the crystal-orbital diagram. VASP-style two-step: the SCF (or a --chk restart, including the crystal-only checkpoints of --onsite runs via --onsite) provides the density matrix, get_bands diagonalizes it non-self-consistently along the automatic seekpath high-symmetry path (--band-points per leg, default 41); E_F/VBM/CBM come from a zero-temperature filling of the uniform SCF mesh (metal-safe), plots are VBM-referenced (--align absolute to disable) and the terminal names the band-edge k points (ScF3: VBM at R, CBM at Γ, gap 5.199 eV — matching the --dos mesh gap exactly). --fatband adds the same per-AO projections the DOS and the diagram compositions use (--projection Löwdin default): one overview page with every element in its VESTA color, plus one page per element with the s/p/d/f breakdown (total | s | p | d panels, weights as dot sizes). Outputs: BAND_<structure>.pdf (+ _fatband.pdf, _fatband_<El>.pdf), a long-format CSV of every band energy (and every (element, l) weight), and a TXT summary. The chk parameter guard applies as usual — a --max-l 2 checkpoint needs --max-l 2 here too, and the mismatch message names the offending option.

  • Fragment-column levels show their composition too (--diagram, both engines): a repeated fragment label (F 2p GM4-#1/#2, or a fragment-SCF eigenstate whose label only names its dominant shell) used to be opaque — the Level-details bars and hover tooltip now show every fragment/onsite level’s per-shell AO populations in the same --projection measure as the crystal levels, listing the level’s own sublattice shells only: a fragment level is a sublattice state, and per-shell entries of the other element read as contamination (user feedback on SrTiO3 — “O states inside a Ti-only level”). The weight that does sit beyond the own shells — the counterpoise ghost tail in the fragment-SCF mode, and in every mode the Löwdin attribution of the overlap density (the state’s coefficients stay on its own side) — is aggregated into one closing note, e.g. Loewdin: O 2p 78.1%, O 3p 16.7% (+5.2% on the removed sublattice's basis: ghost tail + Loewdin attribution). The full cross-side breakdown of an early draft of this display is what exposed the --onsite block pathology below (a “Ti 3d GM3+” whose bars read Ti 3d 39.5 / Ti 4d 27.6 / O 2s+3s 28%).

  • --onsite: the single-Hamiltonian crystal-orbital diagram (--diagram --pyscf): the default mode draws each fragment column from its own SCF (formal-charge ions + point charges + ghosts), so a crystal level’s vertical offset against its parent mixes the bonding shift with a site-dependent environment error that no rigid alignment can remove — quantified on SrTiO3 at R with the on-site decomposition d = ⟨φ_frag|F_crystal|φ_frag⟩: the chemically inert Sr 4s column sat 1.59 eV too deep (pure reference error, zero bonding), making the weakly bonding Sr 4p crystal level (P=+0.031, true bonding shift −0.06 eV) appear risen by +1.5 eV — a bonding state that looked antibonding. --onsite removes the problem structurally: only the crystal SCF runs, and the fragment columns are the per-(element, shell) on-site multiplets of its converged Fock — F(k) diagonalized within each shell’s own symmetry-adapted Bloch orbitals (the tight-binding on-site energies ⟨φ|F|φ⟩; one level per induced irrep, matching the site-symmetry table exactly; no cross-shell mixing). Whole-sublattice blocks were tried and rejected on user scrutiny: with this diffuse basis the raw AO block lets cation functions fall variationally into the removed side’s potential wells (a “Ti 3d GM3+” at O-2p depth with 28% Löwdin weight on O — a disguised anion state, the ghost/BSSE pathology in new clothes), while Löwdin- or projection-orthogonalized blocks load strongly-overlapped shells with huge orthogonalization penalties (the O 2s on-site swung −6.9 → +0.5 → +12.6 eV across the three block conventions; compact shells agreed throughout) — the per-shell Rayleigh quotient has no variational freedom to abuse, and it is the d = ⟨φ|F_crystal|φ⟩ reference of the decomposition analysis above, so the parent→crystal offset is the orbital interaction by construction. (The extended-Hückel engine’s whole-side block stays safe because its minimal STO basis offers no diffuse escape routes.) One operator for every column means no fragment SCF, no point charges, no reference alignment: on SrTiO3 at R the Sr 4p parent (−5.44) now sits above its weakly bonding crystal level (−5.54), the inert Ti 3s column coincides with its crystal counterpart to 0.2 eV, the O 2s band’s genuine +0.43 eV closed-shell push-up by the Ti 3p semicore below it (upper member of a filled–filled pair; its COOP stays bonding-positive through the valence-channel donation into the empty Ti 4p — energy shift and overlap population are different observables) is displayed honestly instead of being masked by the alignment, and on ScF3 at R the diagram finally reads like the textbook: bonding R3+ below its F 2p parent, the antibonding t2g LUMO above its Sc 3d parent (t2g 3.82 / eg 4.01 bare splitting — the large crystal eg–t2g gap emerges from the hybridization, not the bare ligand field). A full-run --chk is reused (only the crystal density is read; --no-ghost differences are ignored there, being numerically inert); an --onsite-written chk holds only the crystal density and a full-mode run against it aborts with a clear message. crystod --dos --pyscf accepts --onsite too — the DOS never needs the fragment densities, so it runs on a single SCF and on the crystal-only chk files. Column occupations remain the formal ionic counts (a display convention, stated in the page footer). Also fixed in passing: the PySCF diagram pages carried the extended-Hückel footer (“VSIP … Wolfsberg-Helmholz”) and a “(full-electron basis)” chip — each engine now writes its own footer/chips.

  • One displayed composition per crystal level — the PySCF AO populations everywhere (--diagram, extended-Hückel and --pyscf): the Level-details bars used to quote the Löwdin fragment-level projection (“Sc 3d R5+ — 69.7%”) while the hover tooltip quoted the per-(element, shell) AO populations (“Loewdin: Sc 3d 76.0%”) — two genuinely different measures of the same state shown side by side with no way to tell which to trust (the fragment eigenstate “Sc 3d R5+” itself mixes 3d and 4d AO character, so the numbers must disagree — Sc 4d read 19.3% vs 8.9% on the ScF3 t2g LUMO). The panel bars, the tooltip and the terminal now all quote one identical list: the per-(element, shell) AO populations of the PySCF eigenvector in the --projection measure (Löwdin default — non-negative, summing to exactly 100%), i.e. the same partial-charge machinery as crystod --dos --pyscf, with symmetry doing the orbital selection (a state of irrep Γ only picks up the Γ-adapted combination of each shell; forbidden shells project to ~0) — every entry is labeled with the crystal irrep (Sc 3d R5+ 76.0%  F 2p R5+ 9.5%  Sc 4d R5+ 8.9%  F 3p R5+ 5.7%). The extended-Hückel --diagram gets the same treatment (Löwdin over the Bloch-STO overlaps), so both engines read identically. The fragment projection is demoted to an internal quantity — it still positions the correlation lines and feeds the deep-level alignment anchors (bit-identical before/after), but its weights are no longer displayed as percentages. Verified panel==tooltip==terminal over every crystal level of ScF3 (140 PySCF levels × Löwdin and Mulliken, 81 EHT levels) and SrTiO3 (87 EHT levels).

  • Testsuite section 3 caught up with the whole --diagram series (an adversarial review found every regression): the two test commands dropped the removed --atomic-orbital flag, the terminal regexes follow the #N labels and one-decimal percentages, the multi-shell sketch probe parses the current VARIANTS JSON (its physics assertions — bonding Sc 3p lobe in phase with F 2s at X, antibonding opposite — pass unchanged), the “sketches not embedded” expectations flipped to always-embedded, and the --diagram requires---co-left/--co-right error message no longer advertises the removed --atomic-orbital option. 41/41 pass.

  • crystod --dos --pyscf: DOS/PDOS and partial charges from the recorded density matrix (crystod/dos_pyscf.py): the --chk file already stores the converged density matrices — everything the band step needs — so crystod --dos --pyscf -c POSCAR --chk FILE [--dos-kmesh 8 8 8] diagonalizes them non-self-consistently on a dense mesh (VASP-style two-step; ~40 s for ScF3 on 8×8×8 with zero new SCF) and writes, in the calc_pyscf_dos.py style: a Gaussian-broadened total DOS + element×angular PDOS plot (VESTA colors, PDF), a CSV of every curve, and a text summary with the gap (VBM/CBM/E_F from zero-temperature filling of the whole mesh, metal-safe). Partial charges are reported in both conventions side by side — with the diffuse molopt basis they disagree spectacularly (ScF3: Löwdin Sc +0.004 because the diffuse Sc 5s/4d claim the density sitting on the F sites, vs Mulliken Sc +1.57/F −0.52 with the semicore 3s²3p⁶ intact and the covalent d occupation 1.06) — an atomic partition of a continuous density is a convention, and both sums reproduce the electron count to 32.0000 as a built-in sanity check. Unlike the diagram’s composition lists, the PDOS weights live directly in the AO basis (no fragment-state projection), so the fragment double-counting issue cannot occur; --projection lowdin|mulliken picks the PDOS measure.

  • Löwdin-orthogonalized composition lists (--diagram, both engines): the composition panel used to show renormalized plain projections |⟨φ_fragment|S|ψ⟩|² — but the fragment eigenstates of the two columns are mutually non-orthogonal (⟨Sc 4p|S|F 2s⟩ = 0.69 in ScF3), so the projections double-count shared density: the occupied F 2s R4- band state summed to 144% before renormalization and displayed as “Sc 4p 34%”, diffuse virtuals (F 3s at +48 eV) appeared at 9% inside the semicore bands, and the t2g LUMO showed a spurious “F 3d 0.9%”. The compositions are now computed in the Löwdin-orthogonalized fragment-level basis (weights |G^{−1/2}Φ†Sψ|², G = Φ†SΦ — the orthonormal set closest to the original fragment states, so the labels keep their meaning; the weights sum to the span completeness ≤ 100%): the same states become F 2s 76.5 / Sc 4p 15.8 / Sc 3p 7.5, Sc 3p 87.6 with F 3s down to 3.4, and Sc 3d 99.8 with the F 3d admixture gone. The deep-level alignment anchors intentionally keep the raw projections (their purity criteria were validated on them; anchors and shifts are bit-identical before/after). Note the composition percentages are CrystOD’s decomposition of the PySCF eigenvectors, not a PySCF charge analysis — the per-(element, shell) Löwdin/Mulliken row in the tooltip is the population-style companion number.

  • Resonance-based semicore rule in the bonding-character classifier (--diagram, both engines): one filled semicore shell acts in two distinct ways, and the classifier now distinguishes them per level. Resonant filled–filled pairing (Sc 3p × F 2s of ScF3, 6 eV apart) is genuine He₂-like closed-shell physics — its occupied partners now show as the bonding/antibonding pair they are (R4-/X3- #1 blue with P ≈ +0.05, #2 red with P ≈ −0.03..−0.05). Far off-resonant orthogonality tails of the same shell (Sc 3p inside the F 2p band, 23 eV above) are excluded — previously they flipped the sign of donation-bonding states: the occupied M3- of ScF3 at −4.18 eV read “antibonding −0.045” although its Sc 4p × F 2p channel is bonding +0.058, because the 1.5% Sc 3p tail contributed −0.102 (and Sc 3p escaped the old per-fragment-HOMO semicore flag, being the HOMO of Sc³⁺ itself). Semicore shells are now flagged against the crystal VBM using reference-consistent Hamiltonian expectations ⟨φ|F|φ⟩, and excluded from a level’s P only when the level lies more than 10 eV from that shell’s band top. ScF3 outcome: M3- #2 → bonding +0.059, GM4- #2 sharpens from +0.013 to +0.111, R1+ #2 → +0.112, symmetry-nonbonding states stay exactly 0, and the R4-/X3- closed-shell pairs keep their textbook blue/red split. The molecular engines keep the legacy per-fragment-HOMO rule (adequate there; the pathology needs a highly charged cation whose fragment HOMO is the semicore).

  • Löwdin-corrected coupling table (--diagram --pyscf): the fragment levels of the two columns are not mutually orthogonal, so the bare matrix element ⟨φ_L|F(k)|φ_R⟩ carries an unphysical overlap-times-mean-energy part |S|·(e_L+e_R)/2 — a compact semicore level against a diffuse virtual showed |H| of 5–15 eV without any real resonance (Sc 3s × F 2p read 12.5 eV, Sc 4p × F 2s 14.7 eV). The reported strength is now the first-order Löwdin-orthogonalized coupling |H̃| = |⟨φ_L|F|φ_R⟩ − S·(e_L+e_R)/2| with e_X = ⟨φ_X|F|φ_X⟩ (invariant under the G=0 reference), and _coupling.txt gains |S| and |H|raw columns so the overlap contamination is visible (ScF3 R point: Sc 3p × F 2s 4.3 → 1.7 eV, Sc 3s × F 2p 12.5 → 5.7, Sc 4p × F 2s 14.7 → 7.1). Couplings against the very diffuse F 3s/3p/3d fragment virtuals legitimately stay at the eV scale — a state with ⟨r⟩ ≳ 1.1 Å (plus its counterpoise ghost tail) has large Fock elements with everything; the physically suppressing factor is ΔE, i.e. the mix column the table is sorted by. Reminder that ⟨i|H|j⟩ itself does not vanish for large ΔE — the mixing |H|/ΔE and the 2nd-order energy |H|²/ΔE do.

  • Bonding-character colors in every diagram + VESTA element colors for the fragment columns: the COOP classification below now runs in all four diagram commands — crystod --diagram (extended-Hückel and --pyscf) and crystod-mol --diagram (both engines), through one shared classifier (crystal_orbital_diagram.assign_bond_characters); NH₃ verifies the molecular case (σ bonding blue, σ* red, the N 1s core black, valence MOs carrying the “(semicore N 1s excluded)” note). The fragment/sublattice columns are no longer occupied-blue/empty-gray: every fragment level is drawn in the VESTA color of its dominant element (Sc levels purple, F levels light blue-violet, Sr/Ti/O each their own), so a composite sublattice like SrTi reads at a glance; occupation stays visible through the electron arrows.

  • Bonding-character colors in the crystal-orbital diagram (--diagram --pyscf): crystal-orbital lines are now colored blue = bonding / black = nonbonding / red = antibonding, classified by the COOP-style left–right overlap population P = 2 Re[c_L† S_LR c_R] of each eigenstate (P > 0: charge accumulates between the sublattices; P < 0: internuclear node; exactly 0 for symmetry-nonbonding states such as the GM5- of ScF3 — the value is shown in every level’s tooltip, and the footer carries the legend). Two rejected alternatives, for the record: a plain energy comparison with the parent fragment levels fails because point-charge-vs-crystal environment shifts (~0.5 eV) drown the ionic splittings, and an (F − E·S) energy partition is unusable in this non-orthogonal basis (diffuse-shell Mulliken cross terms dominate — the ScF3 σ level came out “antibonding +2.45 eV”). Semicore orthogonality tails are excluded from P (shells whose own bands lie ≥ 10 eV below their fragment column’s HOMO, unless the level is that semicore band): with the Sc 3s tail included the bonding σ R1+ of ScF3 reads P = −0.015, without it P = +0.112 — the same semicore-node story as the sketches, now in the classification. Note the generic COOP asymmetry: |P| of antibonding partners is systematically larger than of their bonding counterparts (0.5–6 vs 0.01–0.2 here).

  • PySCF eigen-levels in the SALC viewer (crystod --visualize --pyscf): the SALC viewer shows the symmetry-adapted basis — the states before any Hamiltonian; it can now show the actual PySCF eigenstates in the same interactive page. crystod --visualize --pyscf -c POSCAR [--sublattice Sc|F3] --bond Sc F 3 --real-coefficient: without --sublattice the full crystal’s levels (the states after bonding), with it one fragment sublattice’s pre-bonding levels (formal-charge ions + ghost basis + point-charge Madelung field — exactly the --diagram --pyscf columns). The SALC-basis table becomes Mode | Irrep | Comp. | Energy (eV) — one row per degenerate partner, labels in the diagram convention (GM4- #2, Sc 3d R3+), energies on the shared deep-level-aligned scale so the Sc page, the F3 page and the crystal page are directly comparable — and clicking a row renders the eigenvector’s wave function (every element, all shells s..f, population-calibrated lobe sizes with a per-level shared normalization) on the k-commensurate display cell with the usual bonds/polyhedra. The special k points are enumerated automatically from the space group (one page per k; --kpoint GM restricts). All --diagram --pyscf options apply (--basis/--xc/--kmesh/--ke-cutoff/--max-l/--projection/--no-align/...), including --chk: the three ScF3 commands above share one SCF through one checkpoint file (~5 s per page set after the first run). --window LO HI overrides the default energy window (HOMO−15 .. LUMO+10 eV of the displayed column), which keeps the page sizes manageable. --diagonalize canonicalizes degenerate partners (RREF): the SCF’s arbitrary unitary mixture — which draws e.g. a tilted d_z² — becomes axis-aligned components (the R5+ t2g triplet of ScF3 turns into pure d_xy/d_yz/d_xz) with unchanged energies; each channel’s drawn sign is probed at the 1.5–3 bohr radius where its amplitude is largest, so a semicore orthogonalization node at one radius cannot flip it (note that a valence σ level can faithfully look “antibonding” near a semicore-carrying atom — the ScF3 R1+ case carries the real orthogonality node against the −50 eV Sc 3s band, verified against real-space pbc_eval_gto). --valence-only drops such semicore shells (⟨r⟩(Sc 3s) = 0.77 Å vs the 2.03 Å bond; auto-detected as occupied fragment bands > 12 eV below the crystal VBM and printed — Sc 3s/3p and F 2s here) from the drawn wave functions, so the σ level shows its bonding Sc 4s component; levels a semicore shell dominates (the semicore bands themselves) keep it.

  • VESTA-style periodic boundary in the orbital sketches (--diagram, extended-Hückel and --pyscf): the hover wave-function sketch used to draw only the bare atom list of the k-commensurate supercell; it now draws the crystal the way VESTA does — atoms on the supercell boundary appear at every translationally equivalent position (a corner atom at all eight corners, an edge atom on all parallel edges), each carrying the wave function, and the supercell outline is drawn as a dashed frame. The boundary images carry identical lobes by construction: within the k-commensurate supercell the Bloch function is exactly periodic (e^{ik·T_super} = 1), which is precisely why that supercell is the drawing unit — at R the frame is the 2×2×2 cell and the sign alternation between primitive cells stays visible inside it. Bonds across the boundary now appear too. Molecular (crystod-mol) sketches are unchanged.

  • Extended-Hückel --diagram fragment columns now number repeated (element, shell, irrep) labels — e.g. the two F 2p GM4- combinations of ScF3 at GM become F 2p GM4-#1/F 2p GM4-#2 in the terminal, the HTML, and the crystal composition lists — the same convention --diagram --pyscf already used.

  • --chk (WAVECAR-style restart), --projection, saved coupling tables, #N crystal labels (--diagram --pyscf): --chk FILE writes the three converged density matrices (all the band step needs) plus the defining parameters after the SCFs, and a rerun with the file present skips all three SCFs entirely — the parameters (basis, functional, k-mesh, cutoff, fragments, structure, …) are verified first and a mismatch aborts with the offending names. --projection lowdin|mulliken selects the population measure used for the sketch lobe sizes and the per-(element, shell) rows (default: Löwdin; mulliken restores Mulliken gross populations — note the two disagree strongly on diffuse empty levels, where atomic attribution is inherently convention-dependent). The full same-irrep resonance-integral tables <φ_left|F(k)|φ_right> (all pairs with |H|, ΔE, and the two-level mixing fraction, per k point, on the aligned scale) are now written to <output-stem>_coupling.txt next to the HTML — the terminal keeps showing the top 8. Crystal-column levels are labeled GM4- #2 (second GM4- multiplet from the bottom, the same #N convention as the fragment columns) instead of GM4-(2), which read like a degeneracy count; the extended-Hückel --diagram uses the same format now.

  • Population-true sketch lobes and a Löwdin row in the Level details (--diagram --pyscf): a crystal GM4- level of ScF3 (--max-l 2) drew a p lobe on the Sc site at 83% of the largest F lobe while its composition list read as pure F — misleading in opposite directions, because the state genuinely carries ~8% Sc p weight (mostly diffuse 4p + semicore 3p: the symmetry-allowed T1u ligand-to-metal donation; every basis function is atom-centred, so nothing sits in the interstitial voids). (i) The sketch used to scale lobes by the wave-function amplitude at r0 = 2 bohr, where the diffuse gth-dzvp Sc 4p is 5.4x an F 2p — a 2–3x exaggeration over any population measure; lobe sizes now follow the per-(atom, l) Löwdin population — |coefficient|² in the symmetrically orthogonalized basis, the orthonormal set closest to the atomic orbitals, i.e. the intuitive “squared LCAO weight” made rigorous for a non-orthogonal basis (non-negative and summing to 100%, where Mulliken’s overlap cross terms go negative or overshoot on diffuse empty levels) — with signs and orientation still taken from the r0 amplitudes so semicore orthogonalization tails keep their inverted phases. (ii) The composition list projects onto the surviving fragment levels only — under --max-l 2 the empty Sc 4p GM4- fragment level is removed by the ghost filter, its 4.5% absolute projection silently vanished and the renormalization inflated the F entries to 92.3%; every crystal level’s detail tooltip and terminal row now add the full per-(element, shell) Löwdin populations and state the fragment-projection coverage whenever it falls below 98%.

  • Exact degeneracies via Fock group-averaging; --max-l (--diagram --pyscf): the PySCF SCF carries no point-group constraint, so the FFT grid breaks degeneracies numerically — negligibly for semilocal functionals (<1 meV for PBE/ScF3), but by 0.4–0.8 eV for grid-evaluated exact exchange (B3LYP at ke_cutoff 80), which tore a GM5- triplet into a doublet plus an unlabeled level. The displayed levels are now re-diagonalized from the group-averaged Fock F̄ = (1/|G|) Σ_g D(g)† F D(g) built with the verified AO representation, so every degeneracy is exact by construction; the raw symmetry-breaking magnitude is printed per k point (--no-symmetrize shows the raw spectrum), and a NOTE recommends ke_cutoff ≥ 150 for hybrid energetics. The new --max-l L drops basis shells above l = L (e.g. --max-l 2 removes the f polarization functions, whose tails otherwise show up as small Sc-4f/Ti-4f weights in the O-2p/F-2p valence compositions — a basis-decomposition effect absent from VASP’s atomic-sphere projections).

  • Ghost-aware fragment sketches and --no-ghost (--diagram --pyscf): a fragment level can legitimately carry weight on the ghost basis of the removed sublattice (symmetry-allowed variational tail toward the point charges, the counterpoise design), but the sketch drew that as an atom-centred orbital on the empty site — and the coefficient-based drawing exaggerated diffuse ghost functions far beyond their density share (an F 2p R1+ level with 10% Mulliken ghost weight drew its peak on the empty Sc site). Fragment sketches now draw only the fragment’s own sublattice components, the ghost weight of every level is printed in the terminal report and shown in the Level-details panel, and the new --no-ghost option applies the hard constraint instead: the removed sublattice’s basis functions are excluded from the fragment variational space entirely (measured BSSE on ScF3: ~2 mHa per fragment), with the small-basis fragment states zero-padded back into the shared AO space for the projections.

  • Frontier-centered default view, always-on wave-function sketches, faster --pyscf (crystal-orbital diagrams): the interactive energy window now opens on ±8 eV around the HOMO/LUMO midpoint (the VBM/CBM region) instead of a fixed −20..10 eV; --diagram no longer takes --atomic-orbital — every level (extended-Hückel and --pyscf) embeds the hover wave-function sketch of all its atomic-orbital components (the old filter could render a level by its minority admixture, e.g. Sr 5s drawn as its Ti 4s tail; same-l shells accumulate with radial weights at r0 = 2 bohr, contracted-GTO weights in the PySCF case); the --pyscf SCF now solves only the irreducible wedge of the k-mesh (symmetry-reduced, expanded with to_khf, point-charge Hamiltonian re-attached) and starts each fragment from the crystal density restricted to its own sublattice AO block, which both speeds it up and fixes the SrTi(6+)/BaTi(6+) convergence failures; a non-converging SCF escalates through a Fermi-smearing / virtual-level-shift ladder automatically. Deep-level (XPS-style) column alignment added in the same series: each of the three calculations pins its own G = 0 average potential, so the raw columns are offset by one rigid k-independent constant each — the deepest chemically inert fragment level (absolute counterpart purity ≥ 80%, near-purest anchor pairs only) keeps its pre-bonding energy (--no-align to disable).

  • Added quantitative crystal-orbital diagrams via PySCF PBC (crystod --diagram --pyscf -c POSCAR --co-left Sc --co-right F3 [--basis ...] [--pseudo ...] [--xc ...] [--kmesh N N N] [--ke-cutoff E] [--sigma S] [--oxidation El=Q ...] [--no-align]), the crystalline counterpart of crystod-mol --diagram --pyscf (crystod/crystal_orbital_pyscf.py): three periodic KRKS/KRHF calculations sharing one AO space — each fragment keeps its sublattice as real formal-charge ions plus the removed sublattice as ghost basis functions (counterpoise-consistent) and its formal-charge point lattice (jellium-referenced FFT potential, validated against point_charge_field.ewald_site_potential to 2e-6 eV for neutral arrays; every cell is neutral, electron counts even and additive). The diagram is built at the special k points only (SCF mesh defaults to round(8 Å/|a_i|)), every level is labeled with its little-group irrep by crystod’s own machinery evaluated directly in the PySCF AO basis — the periodic-vs-atomic Bloch-gauge conjugation Λ D Λ⁻¹ (essential for non-half-integer sites, e.g. fluorite X/L/W) and the l=3 real-orbital reordering are handled, and the representation is verified against PySCF’s overlap (D†SD = S, residual printed) at every k point. Degenerate levels are clustered adaptively (groups merged until the irrep multiplicities are integral, since the FFT grid splits symmetry-degenerate levels by 1e-4..1e-1 eV depending on the system). The three columns are put on one energy scale by deep-level (XPS-style) alignment — each calculation pins its own G=0 average potential, so the raw columns carry one rigid k-independent offset each; the deepest chemically inert fragment level (counterpart purity ≥ 80%) keeps its pre-bonding energy, using only the near-purest anchor pairs (--no-align to disable) — and the report adds the site-symmetry induced representation of every (element, shell) block plus the same-irrep coupling <φ_left|F(k)|φ_right> of the converged crystal Fock operator with the two-level mixing fraction (same irrep does not imply mixing). Non-converging SCFs escalate through a Fermi-smearing ladder automatically. Examples: ScF3, ZrO2 (fluorite), SrTiO3, BaTiO3 (polar P4mm) in example/03_hybridization.

  • Added two standalone PySCF PBC example scripts in example/test_pyscf/: pyscf_band_structure.py (VASP-style two-step band structure — SCF on a regular mesh, then non-SCF diagonalization on the zero-weight k path; reads VASP KPOINTS band paths, overlays VASP EIGENVAL aligned at the VBM, element-projected Mulliken fatbands, DOS, --save-mo crystal-orbital export; reproduces the SrTiO3 VASP reference gap to 0.04 eV) and pyscf_band_localized.py (the same solid solved with molecular-chemistry machinery — Gaussian density fitting, Becke grids, all-electron bases — with a --benchmark mode; measured conclusion: the localized-basis solid is cheapest with the uniform-grid FFT Coulomb, and the molecular GDF route is orders of magnitude slower for periodic systems).

v0.3.4#

ISO-IR fallback labeling: irreducible representations at NON-special k/q points (symmetry lines, planes, and general points) are now labeled everywhere.

  • Added crystod/isoir.py, a parser and labeler for the ISO-IR dataset of the ISOTROPY Software Suite [H. T. Stokes, B. J. Campbell and R. Cordes, Acta Cryst. A 69, 388-395 (2013); iso.byu.edu/irtables.php]. The complex-irrep table (CIR_data.txt.gz, 1.1 MB gzip) ships inside the package, holds the full space-group representation matrices for every k-vector type (points, lines, planes, general point), and is evaluated at arbitrary k with free parameters. A k point absent from the irreptables (BCS) tables — where previous versions fell back to generic irrep_N names, custom, or - — now gets Miller-Love / ISOTROPY irrep labels (T1, DT5, LD3, GP1, …) and its k-vector-type letter as the k-point name, across crystod (SALC, with a provenance note in the output), crystod --atomic-orbital, crystod-mag, and crystod-phonon --vibration/--vector/--modulation (phonopy band sets are decomposed against the ISO-IR irreps, so accidental degeneracy is handled). Tabulated k points keep their Bilbao-convention labels unchanged; --spinor is unaffected (the ISO-IR dataset has no double-group irreps). Conventions handled explicitly: ISO-IR uses the exp(+2 pi i k.t) translation phase (spgrep/irreptables use exp(-2 pi i k.t)), so spgrep characters are matched against the complex conjugate of the ISO-IR characters — for complex-type irreps the two conventions genuinely swap labels (e.g. Pnma R point: Bilbao R1 = ISOTROPY R2) — and the structure is transformed into the ISOTROPY standard setting (origin choice 2 etc.) via the spglib Hall number, verified for two-origin groups such as Fd-3m. Validated against the ISOSUBGROUP-derived label crosswalk (SG 141/142 X, 230 N) and by gauge-free isotropy-subgroup construction (SG 68 T).

  • crystod-phonon --vector/--modulation: output files of a non-special q are named with the ISO-IR k-vector-type letter (phonon_modes_MoS2_U.txt, POSCAR_MoS2_U_mode1_U2_conv.vesta, MPOSCAR_DT_mode1_DT3_Cmcm). Since every q on one symmetry line shares that letter, --keep-q-coords keeps the coordinate-based q label (q_0.5_0_0.2) so line scans do not overwrite each other (the irrep tag stays).

  • crystod-phonon --irreps --all-irreps: writes phonon_irreps_all.yaml, which additionally lists the phonon irreps at the midpoints of the seekpath k-path segments (the symmetry lines DT, Z, SM, LD, S, T, … connecting the special points), labeled via the ISO-IR tables, together with the seekpath path string (k_path:) and the midpoint list (path_midpoints:). The default run stays the fast special-points-only survey and keeps writing phonon_irreps.yaml, so the two files coexist; the output file name is now printed at the end of the run.

  • crystod --atomic-orbital: the element/orbital tokens accept a hyphen as well as an underscore (Ti-d O-p == Ti_d O_p); a token without a separator is now rejected with an explicit message instead of a bare ValueError traceback.

  • crystod-group --supergroup-cif (symmetry-mode analysis): every run now also writes one VESTA file per activated irrep, {supergroup_file}_{irrep}.vesta, visualizing that irrep’s displacement pattern as arrows on the parent-derived reference structure (invariant-core cell, largest arrow scaled to 1.5 Å) — e.g. the five frozen modes of Pnma CaTiO3 (X5+, M2+, M3+, R4+, R5+) as separate files. --conventional switches the files to the parent conventional basis (_conv suffix).

  • crystod-group --table --space-group SG --kpoint ...: displays the character table of the little group of k — the space-group analogue of the point-group --table — with irreptables (BCS) irrep labels at tabulated k points and ISO-IR (Miller-Love) labels on symmetry lines/planes, Seitz-symbol column headers, and the little-group symbol.

  • crystod-group --basis / --generate-basis with --space-group --kpoint: non-special k points are labeled via the ISO-IR tables as well (e.g. --basis x y z --sg Pm-3m --kpoint 0 0 0.1 gives DT [0, 0, 0.1] with DT1(1) + DT5(2) instead of generic irrep_N names), including centred lattices (C/F/I/R). Fixed in the same change: the conventional-to-primitive translation conversion of the --basis space-group path mixed row/column conventions, which silently broke the symmetry-operation group for the rhombohedral (R) centring — R-group --basis runs now label correctly at special points too. The misleading “No irreps … in irreptables!” warning is no longer printed when the ISO-IR fallback succeeds.

  • crystod-group --product --sg: product terms landing on symmetry lines outside both the CDML tables and the DIRPRO-fitted name map — previously shown with positional names like (1/8,1/8,0)(2) — are now named from the ISO-IR tables and marked [non-tabulated; ISO-IR labels] with a citation note (e.g. N1 x P1 = DT1 + DT4 + DT2 + DT3 in I4_132). The DIRPRO-fitted CDML names keep precedence, since the two conventions genuinely differ at some lines (CDML V of I4/mmm = ISOTROPY LD, T/S of the hexagonal groups = ISOTROPY LD/Q, and the DT numbering of Fm-3m/Ia-3d is permuted — recorded in crystod/dirpro_line_names.py). Distinct +k/-k line stars of acentric groups are disambiguated with the CDML “A” suffix (P1 x X1 = LD1 + LD2 + LDA1 + LDA2 in I4), matching the PA-point convention; -k line stars themselves are labeled through the complex conjugates of the tabulated +k irreps. Sweep-validated over 43 centred/polar space groups (all pairwise k-star products): every line term is named, dimension bookkeeping closes throughout.

  • Fixed crystod-group --product --sg 122: every product involving a P or PA irrep of I-42d aborted with “the irreptables characters of P1 … do not correspond to any allowed small representation”. The shipped irreptables P entry of SG 122 is tabulated in the conjugate gauge (its characters are those of the -k partners, matching no allowed small irrep at P itself), so fitted CDML names for P/PA were added to crystod/dirpro_line_names.py through the name-preserving conjugate correspondence — in agreement with the ISO-IR labels at this point and the ISOSUBGROUP P1PA1/P2PA2 conjugate pairing (P1 x PA1 = GM1 + GM4 + GM5, M1 x P1 = PA2, P1 x X1 = LD1 + LD2 + LD3 + LD4).

v0.3.3#

crystod-group grows into a full Bilbao-style representation-theory toolbox: space-group irrep direct products (DIRPRO), isotropy subgroups (ISOSUBGROUP), multi-electron term symbols with exact Racah multiplet energies, POSCAR <-> Bilbao-style CIF conversion, and AMPLIMODES-style symmetry-mode analysis - each validated against its Bilbao counterpart. crystod-mol gains molecular-orbital diagrams from symmetry + overlap alone. The shared section numbers are regrouped strictly by command.

  • Section renumbering: the shared section numbers of testsuite.py, the example/ directories, and the documentation are regrouped strictly by command — 1 library core, 2-6 crystod, 7-17 crystod-group (the v0.3.3 additions now sit inside the group block: 13 --supergroup, 14 --multiplet, 15 --poscar2cif/--cif2poscar, 16 --supergroup-cif, 17 CLI regression), 18-20 crystod-bz, 21-27 crystod-phonon, 28-29 crystod-mag, 30-31 crystod-md, 32-34 crystod-mol. The example directories were renamed accordingly (e.g. example/30_isotropy_subgroup -> example/13_isotropy_subgroup, example/17_phonon_irrep -> example/21_phonon_irrep).

  • Added space-group irrep direct products: crystod-group --product R4- R5+ --sg Pm-3m decomposes the direct product of full space-group irreps (induced over the whole star of each k point) into full space-group irreps with CDML labels — the offline counterpart of the Bilbao Crystallographic Server DIRPRO [M. I. Aroyo et al., Acta Cryst. A62, 115-128 (2006)]. The wavevector selection rules over the star arms are applied exactly (translation sums carried out analytically over the finite factor group), the star-size x small-dimension bookkeeping always closes, and any number of factors is supported. Products landing on symmetry lines outside the tabulated special points (DT, SM, V, T, S, …) are decomposed with on-the-fly spgrep small irreps and named from a reference-fitted CDML table; missing -k stars of polar groups (CDML “A” points such as PA of I-43m) are synthesized as conjugate irreps; the shipped irreptables characters are refined against exact spgrep values (and the broken P-point entry of SG 230 is substituted). Cross-validated line by line against the 9007-product DIRPRO reference set in DIRPRO/ (20 space groups, all Bravais classes): 96.8% exact; every remaining difference was traced — with dimension-count and label-bijection impossibility proofs — to inconsistencies in the reference notes themselves (see example/07_direct_product/README).

  • Added isotropy subgroups (crystod-group --supergroup Pm-3m --irrep GM4- [--order-parameter 0 0 a]): computes which space group survives when a distortion with the given irrep condenses along an order-parameter direction, or enumerates all direction types (strata) with their subgroups, cell sizes, indices, and (single-direction mode) the conventional basis/origin of the subgroup. Full-star treatment of zone-boundary irreps (cell enlargement detected from the order parameter’s translation stabilizer), real (physically) irreducible forms, arbitrary space groups. Several irreps at once (--irrep X3- X2+) enumerate the coupled-order-parameter isotropy subgroups on the direct sum of the irreps (single-irrep tables printed first, then the coupled directions with every irrep nonzero; every irrep keeps its own free-parameter letters) — I4/mmm X3- + X2+ gives the hybrid-improper-ferroelectric X3-(0;a) X2+(0;c) -> Cmc2_1 (= A2_1am, Ca3Ti2O7) for same-arm rotation + tilt, and Pm-3m R4+ + M3+ the Howard-Stokes mixed perovskite tilt systems (a-a-c+ -> Pnma). Complex- and pseudoreal-type irreps (Frobenius-Schur indicator) are analyzed in the physically irreducible doubled real form with ISOTROPY-style pair labels (Ia-3d P2 -> P1P2, +-k pairs I-42d P1 -> P1PA1), with automatic handling of conjugate-gauge and origin-choice tabulations and a fitted-name recovery for broken irreptables entries (N of I4_132); the subgroup types are identified through a generic-orbit structure standardized by spglib (robust for subgroup axes along sublattice-cell diagonals), and the real irrep basis is canonicalized so the direction labels stay clean signed patterns. Offline counterpart of ISOSUBGROUP (ISOTROPY Software Suite, https://iso.byu.edu); validated exhaustively against the 910 ISOSUBGROUP reference tables in SUBGROUP/ (SG 16-230, all parameter-free k points, 3535 irreps, script/validate_isosubgroup.py): 3533/3535 exact in the full (subgroup, size, index) multiset (25 up to the enantiomorphic partner, 44 up to a verified label swap), the only exceptions being W of Ibca (a spgrep limitation) and the untabulated rhombohedral L star of R-3m.

  • Added multi-electron term symbols (crystod-group --multiplet T2g2 --pg m-3m [--orbital d]): the Pauli-allowed many-electron states of an electron configuration over point-group irrep shells, with spin multiplicities sorted Hund-first — (t2g)^2 = ^3T1g + ^1A1g + ^1Eg + ^1T2g. The antisymmetrization is exact (symmetrized powers / Schur functors via the Frobenius formula, Murnaghan-Nakayama symmetric-group characters, class power maps of the point group); several inequivalent shells couple by spatial direct products and spin angular-momentum addition (T2g2 Eg1); shell tokens accept both T2g^2 and the quoting-free T2g2 (an unquoted ^ is a glob character in zsh); --orbital d prints the ligand-field splitting of the parent orbital and validates the occupied shells against it; every result closes with a state-count check (product of C(2 dim, n)). Hole equivalence and closed shells come out automatically ((t2g)^4 = (t2g)^2 terms, (t2g)^6 = ^1A1g). Validated against the standard crystal-field term tables (t2g^n, eg^n, t2g^2 eg^1 in Oh; e^2 in Td/C3v). The ground-state term symbol is always printed (Hund’s rules); with --orbital, the exact Coulomb multiplet energies of every term are computed in Racah parameters (A, B, C for d shells; reduced Slater-Condon F_k otherwise) from exact Gaunt-coefficient two-electron integrals + Slater determinants + spin/point-group projectors on the determinant space — (t2g)^3 gives the Tanabe-Sugano table ^4A2g = 3A - 15B, ^2Eg = ^2T1g = 3A - 6B + 3C, ^2T2g = 3A + 5C — with closed-form configuration-interaction eigenvalues for doubly-occurring terms (e.g. 3A - 3B + 3C +- 3sqrt(2)B in (t2g)^2(eg)^1), a built-in trace-identity check, and the energy-determined ground state (“lowest for any B, C > 0” when provable). The free-ion limit is reproduced exactly ((T1u)^2 –orbital p: ^3P = F0 - 5F2, ^1D = F0 + F2, ^1S = F0 + 10F2). --visualize additionally writes the exact term eigenstates as an interactive HTML page: every term’s full Slater-determinant expansion drawn as orbital box diagrams (up/down arrows over the symmetry-identified real orbitals) with exact coefficients, degenerate partners and CI states switchable.

  • Added POSCAR <-> Bilbao-style CIF (crystod-group --poscar2cif -c POSCAR [--tolerance 0.01] and the inverse --cif2poscar -c FILE.cif [--conventional]): --poscar2cif writes <POSCAR>.cif in the layout of the Bilbao Crystallographic Server files — spglib-standardized ITA conventional cell and origin, aligned CIF keys with the quoted Hermann-Mauguin symbol and 4-decimal cell parameters, the full conventional-cell operator list as compact unquoted x+1/2,-y,z strings (proper block first, centring translations included), and one representative site per Wyckoff orbit with 5-decimal coordinates — unlike the pymatgen CifWriter layout (script/poscar2cif.py keeps producing the pymatgen flavour as <POSCAR>_pmg.cif). --cif2poscar reads any CIF (Bilbao or pymatgen flavour), expands the symmetry operations, and writes the spglib-standardized primitive cell (or, with --conventional, the conventional cell) as a POSCAR in the crystod test-file style to the input path without .cif. Validated against a Bilbao reference CIF (identical operator set; also used directly as --cif2poscar input), the ITA Pnma general positions, and structure-matching round trips (SrTiO3, ScF3, F-centred NaCl).

  • Added symmetry-mode analysis (crystod-group --supergroup-cif HIGH.cif --subgroup-cif LOW.cif): the displacive distortion between a high-symmetry and a low-symmetry structure (CIF or POSCAR) is decomposed into symmetry-adapted modes of the parent space group — for every parent irrep the k-vector, order-parameter direction, isotropy subgroup, number of independent modes, and mode amplitude in Angstrom (AMPLIMODES normalization: within the primitive cell of the distorted structure, strain-free parent-derived reference lattice), plus the automatically determined cell relation (strain-tolerant lattice matching + atom pairing), the displacement table, and normalized polarization vectors. The direction and isotropy-subgroup columns are computed with the full induced-irrep/isotropy machinery of section 13; non-invariant subgroup lattices are handled by enlarging to the largest parent-invariant sublattice (complete k stars) with exact amplitude rescaling; polar subgroups get the minimum-distortion origin (acoustic component removed); the lattice matching uses principal strains (20% tolerance, atom-count-based index), so strongly relaxed pairs work; and every run is closed by a projector-completeness check. Offline counterpart of AMPLIMODES [D. Orobengoa et al., J. Appl. Cryst. 42, 820-833 (2009)]; validated against its output for SrTiO3 Pm-3m -> I4/mcm (R5-, 0.3303 A, entry by entry) and the F-centred ZrO2 Fm-3m -> P4_2/nmc (X2-, 0.5773 A), plus ferroelectric BaTiO3 -> P4mm (GM4-) and large-tilt AlF3 -> R-3c (R4+, ~14% lattice strain), and cross-validated against the section-25 modulation structures (ScF3 R-3c/Im-3/P4mbm/Imma/I4mmm/Pbnm; frozen amplitudes recovered exactly, including the multi-irrep Pbnm case R4+ + M3+ with inactive secondaries X5+/M2+/R5+). f shells are fully supported (--orbital f, reduced F0/F2/F4/F6 output; hydrogenic 4f ratios F4/F2 = 0.138, F6/F2 = 0.0151 as the reference for CI blocks and the ground state) — e.g. (T1u)^3 = ^4A1u + ^2Eu + ^2T1u + ^2T2u with ^4A1u = 3F0 - (105/4)F2 - (189/2)F4 - (3705/4)F6.

  • Added molecular-orbital diagrams (crystod-mol --diagram --xyz FILE.xyz): the MO diagram of a single-center molecule (NH3, CH4, SF6, …) from symmetry and overlap alone — ligand SALCs + central-atom orbitals combined per irrep with the Wolfsberg-Helmholz approximation H_ij = K S_ij (H_ii + H_jj)/2 over exact single-zeta-STO overlap integrals (prolate-spheroidal quadrature; ligand-ligand overlap included), i.e. symmetry-adapted extended Hückel [M. Wolfsberg and L. Helmholz, J. Chem. Phys. 20, 837 (1952); R. Hoffmann, J. Chem. Phys. 39, 1397 (1963)]. Writes an interactive HTML/SVG diagram by default (ligand AOs | ligand SALCs | MOs | central AOs, correlation lines weighted by composition, electron arrows, HOMO/LUMO markers, per-level composition panel, adjustable energy window) and prints the SALCs, the inter-fragment overlap integrals, and the full MO table. The MO numbering counts core shells as in photoelectron spectroscopy: CH4 (2a1)^2 (1t2)^6, NH3 (2a1)^2 (1e)^4 (3a1)^2 (3a1 lone-pair HOMO), SF6 filled to the nonbonding F 2p block. Elements H-Cl. With --pyscf the diagram becomes quantitative: three PySCF SCF calculations in one AO space (fragments with ghost basis on the removed atoms, counterpoise-consistent; spin/spherically averaged by fractional occupations) give the pre-bonding fragment columns and the exact projection of every molecular MO onto them, with crystod-convention irrep labels assigned from the MO characters on the PySCF AO basis (σ/π labels for linear molecules); --ao-left/--ao-right select any fragment partition by formula (O2 O/O, CH3OH H4/CO, benzene H6/C6), and --basis/--theory/--xc/--charge/--spin follow script/calc_pyscf.py [Q. Sun et al., WIREs Comput. Mol. Sci. 8, e1340 (2018)]. Every level of both diagram flavours carries a drag-rotatable orbital sketch in the hover panel (molecule + VESTA-style +/− lobes from the actual eigenvector, degenerate partners switchable), and the energy window of the page is interactive (input boxes / Ctrl+scroll zoom / drag pan; with all-electron core levels below -40 eV the default window is clamped to [-40, 15] eV and a Show-all-energy-levels button restores the full range). Fixed (2026-07-28): the --pyscf sketches compressed each atom’s contracted s/p functions by a bare coefficient sum, which can invert the far-field lobe sign (tight and diffuse contracted functions enter one MO with opposite signs) — the bonding 1e of NH3 was drawn with an inverted N-2p lobe and looked identical to the antibonding 2e; every contracted function is now weighted by its radial amplitude at r0 = 2 bohr before summing, so the drawn phases are the real wave-function phases (verified against real-space psi evaluation; regression test in section 33), and the example + binary pyscf diagrams were regenerated.

  • CLI polish: --space-group/--point-group gained the no-hyphen aliases --spacegroup/--pointgroup everywhere (in addition to --sg/--pg), crystod-bz --show-kpoint also accepts --sg/--spacegroup, and space groups can be given by number (1-230) as well as by symbol in all --sg modes (crystod-bz --show-kpoint --sg 221, crystod-group --basis x y z --spacegroup 221 ...).

  • SALC viewer pages are now ~40-190x smaller (e.g. the R-point PySCF eigen-level page of ScF3: 19.5 MB -> 180 kB; SALC_F_p_R: 17 MB -> 116 kB): the orbital-lobe surface grids — previously ~97-99% of the HTML — are no longer embedded. Each lobe’s angular function Re[sum_m c_m X_lm] is a homogeneous degree-l polynomial on the unit sphere, so the page now ships only the exact polynomial coefficients per lobe (fit residual < 1e-6, asserted) plus one shared lobe-builder JavaScript that reconstructs the identical surface grids client-side at load. Appearance, mode switching, depth fade, opacity and compass behaviour are unchanged; the classic, molecular, and PySCF eigen-level viewers all share the writer and shrink alike.

  • Added crystal-orbital diagrams (crystod --diagram -c POSCAR --co-left SrTi --co-right O3 [--atomic-orbital Ti-3d Ti-4s O-2p] [--oxidation Sr=+2 Ti=+4 O=-2]): the analysis CrystOD is named after — the crystalline analogue of the crystod-mol --ao-left/--ao-right MO diagram. --co-left/--co-right split the crystal into two fragment sublattices by chemical formula, each treated with its full-electron basis (WIEN2k-style: every core and valence shell; valence with the extended-Hueckel parameters, cores with Slater-rule exponents and the archived neutral-atom PySCF Hartree-Fock/def2-svp levels — one calculation per element in reference/atomic_level_*, generated with the user-standard script/calc_pyscf.py driver via script/generate_atomic_levels.py, collected into crystod/atomic_levels.py with angular-momentum shell assignment and an archive cross-check; beyond Kr the def2 ECP freezes the deep cores, which are omitted exactly as in pseudopotential DFT), and each fragment feels the removed sublattice as a point-charge lattice with the formal oxidation states (pymatgen-guessed or --oxidation) — the Madelung ligand field of the pre-bonding states, entering as exact same-site STO matrix elements (real-spherical-harmonics Laplace expansion in the wigner_D_real convention, closed-form radial integrals, Loewdin-consistent with the on-site metric, near shells explicit + Ewald long-range tail; validated against the NaCl Madelung constant and the exact 6Dq/-4Dq octahedral ratio), so the fragment d states carry the true electrostatic t2g/eg splittings; the background-dependent monopole of the charged sublattice array is omitted (it cancels against the intra-atomic charging energy absent from EHT VSIPs; the jellium-referenced values are printed). At every special k point the Bloch orbitals are symmetry-adapted per little-group irrep, all overlaps are evaluated exactly as Bloch lattice sums of STO integrals (double-zeta d shells, generic sigma/pi/delta Slater-Koster assembly via exact real Wigner-D rotations, validated to machine precision; per-shell-pair cutoffs probed from the actual STO tails — diffuse cation shells reach 30+ bohr), and the Wolfsberg-Helmholz generalized eigenproblem (diagonal carrying the same-orbital neighbour-cell Bloch sums; ligand-field blocks added identically to fragment and crystal Hamiltonians, symmetry invariance of S and H self-checked per k point) implements the COD mixing rule (same irrep -> bonding/antibonding, no partner -> rigorously nonbonding); near-linearly-dependent Bloch combinations of the diffuse cation shells (overlap eigenvalue < 0.2, the extended-Hueckel overlap catastrophe) are removed by canonical orthogonalization and counted in the report. Output: per-k terminal tables (fragment levels labeled by their dominant element+shell) and an interactive HTML with one diagram per k point (k-point buttons, composition-weighted correlation lines, electron arrows, HOMO/LUMO, full-electron filling with --electrons override, energy window opening on -20 .. 10 eV with a Show-all button for the core levels, and — with the optional --atomic-orbital sketch filter — a hover wave-function sketch: Re[psi] of exactly the selected components on the k-commensurate supercell with s/p/d lobes, drag-rotatable with switchable degenerate partners). The extended-Hueckel parameter tables were extended to H-Bi (3d/4d metals with the standard double-zeta d contractions). ScF3 (Sc | F3, 48 electrons) and SrTiO3 (SrTi | O3, 56 electrons with the ECP-28-frozen Sr core) reproduce the symmetry-resolved perovskite bonding picture: flat core levels at the archived atomic energies, fragment t2g below eg in the octahedral anion field, F-2s/Sc-3d(eg) sigma bonds at GM with the t2g GM5+ rigorously nonbonding, and sigma/sigma* eg + pi/pi* t2g splittings with nonbonding R4+/R5- at R. Fixed (2026-07-28): the --atomic-orbital sketches wrote same-l shells of one atom into the same s/p/d slots by plain assignment, so a multi-shell filter (e.g. Sc-p = 2p+3p+4p) drew only the last shell’s raw STO coefficients — a semicore level like Sc 3p came out as the tiny sign-inverted 4p orthogonalization tail, the crystal analogue of the contracted-GTO compression bug in the crystod-mol --pyscf sketches; the shells now accumulate, each weighted by its STO radial amplitude at r0 = 2 bohr, so the drawn lobe phases are the real wave-function phases (regression test in section 3: the X-point Sc-3p/F-2s bonding/antibonding pair of ScF3), and the example diagrams were regenerated.

v0.3.2#

  • Added crystod-mol, a new sectioned command for molecules (XYZ files): --symmetry detects the molecular point group with pymatgen (Schoenflies + Hermann-Mauguin symbols and the symmetry operations by class — the molecular analogue of phonopy --symmetry), and --element EL --orbital s|p|d|f builds molecular SALCs: the site-permutation characters are multiplied by the orbital characters, decomposed into point-group irreps (same character tables and labels as crystod-group), and the explicit SALCs are projected out (via the permutation x real-orbital Wigner-D representation, so p/d/f orbital components mix correctly). --align rotates the molecule into the standard point-group orientation for textbook axis conventions, --show-matrix prints the site-permutation matrices, and --tolerance controls the symmetry detection. --visualize writes the SALCs as the same standalone interactive 3D HTML viewer as crystod --visualize (molecule in a vacuum box at Gamma; no cell edges, x/y/z compass; --output and --bond EL1 EL2 MAX supported). The detected symmetry operations are snapped onto the exact character-table matrices, so the SALC coefficients are clean small integers whenever possible. Adds pymatgen as a dependency.

  • Added crystod-bz --show-kpoint --space-group SG: prints the special (high-symmetry) k points of a space group with their CDML labels (from irreptables, the same k-point definition as the SALC/irrep analyses) in the primitive reciprocal basis, and additionally in the conventional reciprocal basis for centred lattices (F, I, C, A, B, R) such as Fm-3m, where the two definitions differ.

  • testsuite.py gained the crystod-mol sections (32 and 34 in the current numbering) with the molecule files in example/test_XYZs (O2, H2O, NH3, CH4).

  • Fixed: the SALC viewer (crystod --visualize and crystod-mol --visualize) squeezed the structure into the left half of the viewport and opened too zoomed-in, clipping part of the structure. Cause: with the compass added as a second plotly scene, the main scene had no explicit domain and was grid-split to half the width. The main scene now claims the full plot area and the initial camera starts further out, so the whole structure is visible on load (the stored example/05_visualized_basis pages were regenerated accordingly).

v0.3.1#

crystod-phonon --modulation workflow overhaul — corrected frequencies, CDML irrep labels, a mode-table preview, and informative default file names — plus star-arm-aware k-point/irrep labeling across all commands.

  • crystod-phonon --modulation can now be run with --qpoint only (no --mode): it prints the mode table (mode number, frequency, irrep, degeneracy) and the star of q, then exits without generating a structure — useful for inspecting the modes at a q point before choosing which to apply.

  • Fixed: the frequencies in the --modulation mode table were wrong whenever an irrep occurs more than once at the q point (each irrep-projected block was diagonalized on its own, dropping the coupling between blocks carrying equivalent irreps — e.g. every X-point mode of Sr3Ti2O7 except the singly-occurring X1-/X2+ irreps). The dynamical matrix is now block-diagonalized with the same equivalent-irrep clustering as crystod-phonon --vector, the mode vectors are true eigenvectors, and every run is verified internally against the plain phonopy spectrum. Mode numbers at affected q points differ from v0.3.0 accordingly; the frequencies now agree with crystod-phonon --irreps (phonon_irreps.yaml).

  • The --modulation mode table now shows CDML irrep labels (X3-(1), GM5-(2), …) from irreptables — the same labeling as crystod-phonon --vector/--irreps — instead of internal block indices (the column header changed from Irrep Block to Irrep; labels fall back to - when irreptables labeling is unavailable, e.g. at non-special q points).

  • Irrep and k-point labeling now works at EVERY arm of a special-point star, not only at the single arm tabulated in irreptables/seekpath — across ALL label lookups: the SALC analysis (crystod -c ... --element/--orbital), --atomic-orbital hybridization, --visualize, --star-of-k (the header now shows e.g. X [0.0, 0.5, 0.5] instead of custom), crystod-mag, crystod-phonon --vibration, --vector (q label in the report and VESTA file names), and --modulation. E.g. all three X arms (1/2,0,0)/(0,1/2,0)/(0,0,1/2) of Pm-3m, and both X arms (0,0,1/2)/(1/2,1/2,0) of the I4/mmm primitive cell, now get X labels; previously only the tabulated arm was labeled (the SALC decomposition fell back to generic irrep_N names). For the phonon commands the k point is mapped onto the tabulated arm (star-arm spectra are identical band by band); for the SALC/orbital analyses, which compare little-group characters operation by operation, the characters are transported by the conjugation isomorphism h -> g^-1 h g between the little groups, including the Bloch phase from lattice-translation offsets (verified at the X and W stars of nonsymmorphic Fd-3m Si).

  • Fixed: special k points with 1/3-type coordinates (K/H of hexagonal groups, …) were rounded to 0.333333 in the table-derived k-point lists, which silently broke the little-group detection — e.g. the crystod-mag survey printed a wrong decomposition with generic irrep_N labels at K/H. Table-derived coordinates are now snapped to exact fractions, and k/q-point input across the commands accepts fractions (--qpoint 1/3 1/3 0) while near-fraction decimals are snapped (0.333333 -> 1/3).

  • Fixed: crystod-mag exported the SAME spin structure twice (e.g. ..._GM5+_dipole_x_conv.vesta and an identical ..._x_conv_2.vesta) when a 2-dim irrep space came out of the projection in the circular complex basis (x + iy, x - iy): taking the real part folded both partners onto the same vector. Such spaces are now recombined unitarily into a real orthonormal basis (possible whenever the space is closed under complex conjugation), so the two components export as orthogonal partners (..._dipole_x and ..._dipole_y), matching the 2-dim order parameter (found for the in-plane Ni dipoles/quadrupoles of I4/mmm La3Ni2O7).

  • The --modulation default output name is now MPOSCAR_{q}_{mode}_{irrep}_{subgroup} (e.g. MPOSCAR_R_mode1+2+3_R4+_R-3c; one {q}_{mode}_{irrep} group per term in multi-q runs) instead of POSCAR_modulated; --output still overrides it.

  • testsuite.py sections are renumbered by command group (1 library core, 2-6 crystod, 7-13 crystod-group, 14-16 crystod-bz, 17-23 crystod-phonon, 24-25 crystod-mag, 26-27 crystod-md; each group closes with that command’s CLI regression section), and every example/ directory is prefixed with its section number (example/24_phonon_vector <-> python testsuite.py 24). New example directories with captured output were added for the previously undocumented features: 01_wigner_d (library demo), 04_star_of_k, 07_direct_product, 10_basis_function, 11_generate_basis_function, 12_show_coset, and 19_bz_supercell. See example/README for the full map.

v0.3.0#

CrystOD has been restructured into sectioned commands (phonopy style: one main crystod command plus per-domain crystod-* commands), with phonopy-aligned option names (-c/--cell for the structure file, with --poscar kept as an alias). The old crystod --<mode> flat forms were removed; invoking one prints an error with the equivalent sectioned command.

  • Mode numbering is now 1-based across ALL commands and outputs: crystod-phonon --vector --mode, --modulation --mode/--modeN, --vibration --mode-index/--component-index (the component default is now 1), crystod --visualize --mode-index, and every printed mode table / Mode Space / component listing. A 0 or out-of-range number is rejected with a clear 1-based message.

  • crystod --visualize HTML redesigned as an interactive viewer modeled after Henrique Miranda’s phonon website (BSD-3-Clause; design imitated, no code copied): sidebar with structure summary, irreducible decomposition, a clickable SALC-basis table replacing the dropdown, lobe-opacity / cell / atom display controls, and a credit link in the page. New --conventional displays the SALC in the conventional cell (primitive-to-conventional matrix from the detected centring, as in crystod-phonon --vector). New --bond EL1 EL2 MAX (repeatable) draws bonds within the cutoff and VESTA-style translucent coordination polyhedra around the EL1 atoms, with boundary atoms completed so polyhedra at the cell edge are not cut off.

  • crystod --visualize now always writes the interactive 3D HTML, auto-named SALC_{element}_{orbital}_{kpoint}.html when --output is omitted (previously nothing was written without --output).

  • crystod-phonon --vector usability update: mode numbering is now 1-based, matching the printed table (--mode 0 is rejected with a clear message); the q-point mode table (space group, q-points, frequencies, irreps) is automatically saved as phonon_modes_<formula>_<qlabel>.txt; and omitting --mode now exports ALL modes as individual VESTA files (--mode still selects specific modes, summed into one file). VESTA file names now carry the irrep label and zero-padded mode numbers (POSCAR_<formula>_<qlabel>_mode<NN>_<irrep>.vesta) so that ls lists them in order.

  • Redesigned the main crystod command: the flagship SALC analysis runs without a mode flag (crystod -c POSCAR --element Ti --orbital d; hybridization via --atomic-orbital), --visualize merges the former --visualize-basis, and --star-of-k stays as the symmetry-information option. --kpoint now also accepts high-symmetry labels (GM/X/M/R) in --star-of-k/--visualize mode — the documented --kpoint M form previously failed to parse. Added --version; crystod --help lists all sectioned commands.

  • Added crystod-bz (first sectioned command), merging --bz and --bz-supercell: with the default identity --trans-mat it plots the unit-cell BZ with an automatic (seekpath) or manual (--band/--band-labels) k-path; with any non-identity --trans-mat it plots the unit-cell BZ together with the folded BZ of the transformed (super)lattice.

  • Added crystod-md (MD section) with two modes: --adp (replacing --xdatcar2adp) writes atomic displacement parameters as a CIF; --summary (based on script/md_summary.py) reports time-averaged lattice parameters and cell volume with standard deviations over a step range (--start-step/--end-step). --dim now also accepts a nine-value diagonal matrix (quoted or unquoted); --format selects the trajectory format (vasp; LAMMPS planned). A missing trajectory file is reported cleanly instead of a traceback.

  • Added crystod-phonon (phonon section), merging the six flat phonon modes as --irreps (old --phonon-irrep, renamed to match phonopy), --fatband, --lt, --vector, --modulation, and --vibration (force-data-free). --dim accepts three values or a nine-value diagonal matrix, quoted or unquoted; --band-labels is the primary spelling (--label kept as alias); numbered --qpoint1/--mode1/... arguments still work in --modulation.

  • Added crystod-group (representation-theory section) for point AND space groups, merging six flat modes: --product (old --direct-product), --table (character-table display, formerly --direct-product --show-irrep-table without irreps), --decompose (old --decompose-irrep), --ligand-field ORBITAL (old --ligand-field-split --orbital), --basis (old --basis-function), --generate-basis (old --generate-basis-function), and --coset (old --show-coset). --pg/--sg are provided as short aliases of --point-group/--space-group; labels starting with a minus sign (-43m, -1, …) are handled.

  • crystod-mag --conventional exports the symmetry-adapted spin structures in the conventional cell (primitive-to-conventional matrix from the detected centring; _conv-suffixed VESTA files).

  • Added crystod-mag (magnetism section), replacing --spin-basis: symmetry-adapted spin bases as the default action, with per-atom spin directions and the noncollinear magnetization input printed by default (no --show-spin-direction needed). New --format vasp|qe selects the printed input: a VASP MAGMOM line, or the Quantum ESPRESSO &SYSTEM counterpart (noncolin, per-type starting_magnetization/angle1/angle2, with the magnetic element split into one type per spin direction).

  • Removed the old flat forms of all sectioned modes — --salc, --visualize-basis, --bz, --bz-supercell, --xdatcar2adp, --spin-basis, --phonon-irrep, --phonon-fatband, --phonon-lt, --phonon-vector, --modulation, --vibration, --direct-product, --decompose-irrep, --ligand-field-split, --basis-function, --generate-basis-function, and --show-coset. Invoking one prints an error naming the sectioned replacement.

v0.2.3#

  • Added --phonon-vector: phonon-eigenvector visualization as VESTA files with displacement arrows, from the same POSCAR + FORCE_SETS/FORCE_CONSTANTS inputs as --phonon-irrep. Modes are listed with frequencies and irrep labels; the commensurate supercell and Bloch phase factors are applied automatically for fractional q-points. Degenerate modes are exported as symmetry-adapted eigenvectors (aligned along symmetry-dictated directions, as in --modulation) rather than arbitrary eigensolver combinations. --conventional switches the output from the primitive to the conventional cell.

  • Added --decompose-irrep: interactive decomposition of a reducible representation into point-group irreps from user-entered characters (based on script/decomose_to_irreps.py by Hiroki Koiso), with a non-interactive --characters option.

  • Added --ligand-field-split: ligand-field splitting of an atomic orbital (s-i) into point-group irreps (based on script/ligand_field_spliting.py by Hiroki Koiso).

  • Added --xdatcar2adp: time-averaged structure and symmetry-constrained ADPs (U_ij) from an MD XDATCAR trajectory as a CIF (based on script/xdatcar_to_adp.py by Ko Sato), with a fast built-in XDATCAR parser instead of pymatgen.

  • Added --phonon-fatband: element-projected phonon fatbands (PDF per element) along an automatic seekpath k-path, computed directly from POSCAR + FORCE_SETS/FORCE_CONSTANTS via the phonopy API. Colored with VESTA default element colors; --nac applies the non-analytical term correction (LO/TO splitting) from a BORN file.

  • Added --bz-supercell: interactive 3D plot of the unit-cell BZ together with the BZ of a transformed (super)lattice, tiled at the |det T| folded supercell reciprocal-lattice points (based on script/supercell_BZ.py by Hiroki Koiso).

  • Added --phonon-lt: phonon bands colored by longitudinal/transverse character with an optional --nac correction (based on script/LT_phonon_band.py, after Qijing Zheng), using the exact eigenvector projection onto the propagation direction so that diagonal path segments are also colored correctly.

  • Added --spin-basis: symmetry-adapted spin bases (cluster multipoles / SAMM, after M.-T. Suzuki et al.) with FM/AFM separation, multipole-rank naming, per-atom spin directions with a VASP MAGMOM line (--show-spin-direction), VESTA export, and automatic magnetic supercells for q != 0. Omitting --qpoint lists the spin-multipole irreps at all special k points (as in the crystod SALC survey).

  • --basis-function now supports the axial-vector components Rx, Ry, Rz (transforming with det(R) R, like magnetic moments), including mixed polar-axial products such as the toroidal x*Ry - y*Rx. In space-group mode, omitting --kpoint now analyzes all special k points automatically (as in the crystod SALC survey).

  • Fixed point-group labels starting with a minus sign (-43m, -42m, -6m2, -1, …) being rejected by the argument parser in all --point-group/--subgroup modes; both --point-group -43m and --point-group="-43m" now work.

  • --dim now also accepts unquoted values (--dim 4 4 4) in all modes.

  • Fixed --phonon-irrep with phonopy >= 2.21, where the removed IrReps.frequencies property caused an AttributeError.

v0.2.2#

  • Added --bz: interactive 3D Brillouin-zone plot (HTML) with automatic space-group detection and a seekpath-generated high-symmetry k-path, or a manual path via --band/--label. Based on script/brillouin_zone_plot.py by Hiroki Koiso.

  • Added seekpath and scipy to the package dependencies.

v0.2.1#

  • Added --visualize-basis: SALC coefficients and interactive 3D HTML visualization, with the --real-coefficient option for real-type degenerate irreps.

  • Added --star-of-k as a standalone command, and automatic star-of-q display in --modulation.

  • Added --show-coset: left-coset decomposition G = sum g_i H (point groups) and right-coset decomposition G = sum G_k g_i (space groups, one coset per star arm).

  • Added --generate-basis-function: automatic 1st-3rd order polynomial basis functions classified by irrep.

  • Rewrote the Wigner-D routines in operations.py with pure NumPy (no sympy) and fixed several bugs: the parity factor for improper operations (det vs det^l), the beta = pi Euler-angle branch, the p_y row sign in the complex-to-real transform, and a float-determinant perturbation that broke the Euler-angle extraction.

  • Fixed numerical-noise blowup in space-group basis-function coefficients (--basis-function / --generate-basis-function).

  • Improved compatibility with recent phonopy PhonopyAtoms (removed remaining ASE-style getter calls).