|
Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
|
Present-state capability inventory for Q3, grouped into three user-facing areas: Science (what you can model and compute), Performance (how it runs), and Engineering (how you work with it). This file describes what exists now; planned work lives in TODOs.md and codebase metrics in STATISTICS.md.
Status legend
| Symbol | Meaning |
|---|---|
| ✅ | Validated — cross-checked against an established plane-wave DFT code (LibXC-enabled) and confirmed to agree |
| 🟡 | Implemented — present in code, not yet validated against a reference |
| — | Not applicable for this pseudopotential type |
A feature graduates from TODOs.md into the matching area here once it ships.
What Q3 can model and the physical quantities it produces. NC, US and PAW ground-state SCF are implemented and validated, including spin polarization, forces and stress. PAW accepts UPF v2 datasets; further dataset and input formats plug in as build-time add-ons (§3.1). Parallel PAW backend validation remains planned (see TODOs.md §1.1).
| Feature | Status |
|---|---|
| All 14 Bravais lattice types (ibrav) | ✅ |
| Free / explicit lattice vectors (ibrav = 0) | ✅ |
General-lattice input: YAML type + a/b/c/α/β/γ or lattice.vectors; QE CELL_PARAMETERS | ✅ |
| Symmetry detection (spglib) | ✅ |
| Monkhorst-Pack k-grid + offsets | ✅ |
| IBZ reduction (spglib) | ✅ |
| Density symmetrization over the full space group (supercell translations included) | ✅ |
| Gamma-only | ✅ |
Generated Monkhorst-Pack dimensions and 0/1 shifts survive both symmetry reduction implementations and k-point-pool distribution. Explicit k-point lists retain zero grid metadata, so downstream consumers can distinguish the two forms. A real shifted
4x4x4 -> 10silicon reduction gates the BerkeleyGW WFN header'skgridandkshiftfields.control.disable_symmetryswitches all of it off — no mesh reduction and no symmetrization of the density, forces or stress, QE'snosym+noinv— so a shifted mesh then gives the raw-mesh energy rather than the symmetrized one.
| Feature | NC | US | PAW | Status |
|---|---|---|---|---|
| Local potential V_loc(G) | ✅ | ✅ | ✅ | Validated |
| KB projectors β(G) | ✅ | ✅ | ✅ | Validated |
| (-i)^l phase convention | ✅ | ✅ | ✅ | Validated |
| D^ion matrix | ✅ | ✅ | ✅ | Validated |
| S-matrix (overlap) | — | ✅ | ✅ | Validated |
| Q-function augmentation | — | ✅ | ✅ | Validated |
| NLCC core charge | ✅ | ✅ | ✅ | Validated |
| Atomic charge ρ_at(G) | ✅ | ✅ | ✅ | Validated |
| All-electron one-center package | — | — | ✅ | Validated |
The columns above compose within one cell. Species carrying different pseudopotential families can be combined in a single calculation — an ultrasoft nickel alongside a norm-conserving carbon, say — which matters whenever one element needs a particular dataset and the others do not. The augmentation machinery is built whenever any species asks for it, and each species is then asked individually: one without augmentation charges contributes q_ij = 0 to the overlap operator and no augmentation charge to the density, while its projectors still enter the nonlocal potential normally.
PawPseudopotentialholds the all-electron and pseudo partial waves, core densities, one-center potentials, occupations, and compensation multipoles alongside the ultrasoft augmentation apparatus it extends. UPF v2 supplies radial augmentation functions directly. A dataset format that supplies other data plugs in as a build-time add-on, together with the one-center convention its datasets use (§3.1).Serial PBE validation: Q3+UPF agrees with the LibXC-enabled QE 7.5 build within 1 mRy/atom for Si, Cu, and diamond. UPF v1 PAW remains rejected at read time.
All functionals use LibXC. Named functionals (PZ, LDA, PBE, PBEsol, BLYP, revPBE, RPBE) map to LibXC functional IDs in the factory; arbitrary LDA/GGA combinations may be selected via libxc:X:C or QE-style XC-... identifiers. Names are matched case-insensitively, and each half of a libxc: pair may be given as a LibXC id or as the name LibXC knows it by, so libxc:117:130 and libxc:gga_x_rpbe:gga_c_pbe select the same functional. Every exchange and correlation component is classified during construction. Meta-GGA and hybrid families are refused before SCF with the identifier, detected family, and missing Q3 capability in the error; this also prevents BerkeleyGW vxc.dat from representing a semilocal-only fragment under a nonlocal functional name.
An input that names no functional runs the one its pseudopotentials were generated with, as pw.x does without input_dft, whether the deck is Q3 YAML or QE-format. Datasets that disagree, or that state a functional Q3 does not map, are refused by name rather than approximated — see io_reference.md §1.2.
| Functional | Type | Unpolarized | LSDA | Status |
|---|---|---|---|---|
| PZ | LDA | ✅ | 🟡 | ✅ |
| PBE | GGA | ✅ | ✅ | ✅ |
Any via libxc:X:C | LDA/GGA | ✅ | 🟡 | 🟡 |
| Spin mode | nspin | NC | US | PAW | Status |
|---|---|---|---|---|---|
| Unpolarized | 1 | ✅ | ✅ | ✅ | ✅ |
| LSDA | 2 | ✅ | ✅ | ✅ | ✅ |
LSDA supports per-species
starting_magnetizationthrough both parsers (AFM-capable, e.g.Cr1/Cr2sublattice labels) and per-atom starting moments — YAMLmethod.scf.atomic_starting_magnetization(one value per atom) — so a same-species antiferromagnet needs no relabeling; such sites are also kept symmetry-inequivalent, and the per-atom AFM Cr cell matches the two-species formulation (tests/validation/test_per_atom_starting_magnetization.py). QE input stays per species, as in QE itself. LSDA reproduces the unpolarized energy at zero moment to ~1e-9 Ry (tests/validation/test_lsda_consistency.py). Cross-code validated (NC, PBE) against QE on three Window 1 roadmap systems — Fe BCC-FM (ΔE = 0.11 mRy, Δmoment = 0.005 μB), Ni FCC-FM (ΔE = 0.013 mRy, Δmoment = 0.005 μB), Cr BCC-AFM (ΔE = 0.25 mRy, genuine antiferromagnetic order confirmed via both codes' absolute-vs-total magnetization) — all well inside the roadmap's ΔE < 1 mRy / Δmoment < 0.02 μB gates (tests/validation/test_qe_energy_comparison.py::test_qe_lsda_energy_and_moment_comparison, reference inputs/outputs inexamples/total_energy--metal--magnetic--{iron,nickel,chromium}/reference/).Ultrasoft and PAW spin polarization was fixed and validated 2026-08-08. The nonlocal D matrix is now built per spin channel and the compensation charge per channel from that channel's own occupancies; previously both were spin-averaged, which is exact for a non-magnetic cell and collapses the moment in a magnetic one. Ferromagnetic iron with a GBRV ultrasoft dataset — a path that had never been measured — went from 0.0144 to 2.2482 μB and from 473 to 0.06 meV/atom against QE. Non-magnetic cells at
nspin = 2reproduce theirnspin = 1energies bit-for-bit (tests/validation/test_spin_polarized_nonlocal.py).
The augmentation force and stress became spin-resolved 2026-08-09, completing that work. They had kept integrating the spin-_averaged_ potential against the spin-_summed_ occupancies while their nonlocal partners carried a spin index, dropping ∫(V↑−V↓)(ρ↑−ρ↓) dQ/dR / 2 — zero for a non-magnetic cell, which is why unpolarized tests never saw it. The per-spin quantities already existed in the iterator; only the SCF state handed to layer 5 lacked them. Gated by a finite-difference check against Q3's own energy on ferromagnetic bcc iron (ultrasoft, conventional two-atom cell, one atom displaced), with a companion assertion that the cell carries a moment so the gate cannot pass vacuously.
| Feature | NC | US | PAW | Status | Notes |
|---|---|---|---|---|---|
| Total energy | ✅ | ✅ | ✅ | Validated | PAW validated against QE |
| Atomic ρ superposition | ✅ | ✅ | ✅ | Validated | Initial density |
| Hartree potential | ✅ | ✅ | ✅ | Validated | |
| Ewald energy (ion-ion) | ✅ | ✅ | ✅ | Validated | |
| Dispersion (DFT-D3 BJ) | ✅ | 🟡 | 🟡 | Validated | Energy + forces + stress (NC validated vs QE) |
| Augmentation charge | — | ✅ | ✅ | Validated | add_augmentation_charges |
| Effective D-matrix | — | ✅ | ✅ | Validated | D_ij = D^ion + ∫ V_eff Q_ij dG; PAW includes one-center correction |
| Charged cells (net Q) | ✅ | 🟡 | 🟡 | Validated | total_charge input, QE tot_charge convention |
| Makov-Payne correction | ✅ | — | — | Validated | Cubic lattices only; isolated_system_correction: makov-payne |
| Martyna-Tuckerman correction | ✅ | — | — | Validated | Any Bravais lattice; post-SCF only (see note below); isolated_system_correction: martyna-tuckerman |
| DFT+U (Dudarev) | ✅ | ✅ | 🟡 | Implemented | Atomic and ortho-atomic energy/force/stress for all three dataset families (see note) |
DFT-D3(BJ) additionally validated on AB (Bernal) graphite (roadmap Window 2 pinned system): the isolated dispersion term matches QE to <2e-9 Ry at every scanned interlayer spacing (
D3BJDispersionCalculator.Graphite*intest_d3bj.cpp, sub-millisecond, runs on every PR). The equilibrium interlayer spacing and interlayer binding energy were extracted from a c-scan (fixed a=2.464 Å, quadratic fit near the discrete minimum) in both Q3 and QE: equilibrium spacing Δ = 0.0001 Å (roadmap gate: ≤ 0.01 Å) and binding energy Δ = 0.54 meV/atom (roadmap gate: ≤ 1 meV/atom) (tests/validation/test_qe_graphite_d3_dispersion.py, tier-2/slow, ~2 min).Forces and stress (
D3BJDispersionCalculator::compute_gradients()) cover both the direct pairwise term and the coordination-number chain rule (C6 depends on each atom's full coordination environment, not just the pair in question). Validated by finite-difference unit tests against the already-validated energy (test_d3bj.cpp, forces to 1e-7 Ry/bohr, stress to 1e-8 Ry/bohr³) and, independently, against QE forces on a distorted graphite geometry via a two-run differencing trick (QE'sdft-d3module requires plain ‘input_dft='pbe’, so the two runs — with and withoutvdw_corr— are differenced to cancel every non-dispersion force term): agreement to 1e-8 Ry/bohr (test_dispersion_forces_match_qe_via_differencing`, tier-2/slow).
total_charge(InputData::total_charge, YAMLmethod.scf.total_charge, QEtot_charge): positive = electrons removed (cationic), negative = electrons added (anionic), matching QE's sign convention exactly. Needs no compensating-background term — the Ewald ion-ion sum and the Hartree G=0 skip are both keyed only to the fixed ionic charge (verified against QE, whose Ewald energy has notot_chargedependence at all), so only the electron count changes. Validated on a charged FCC Si cell (tot_charge = </blockquote>1.0, Gaussian smearing — required, since an odd electron count has nofixed-occupation representation): Q3 vs QE agreement 0.0002 mRy, deep inside the 0.05 mRy tolerance (
tests/validation/test_qe_energy_comparison.py::test_qe_charged_cell_energy_comparison). The isolated-system correction for comparing charged-cell energies across cell sizes (control.isolated_system_correction: makov-payne,MakovPayneCorrectionCalculator) reproduces QE's exact convention (both the 1st-order Madelung/monopole and 2nd-order dipole+quadrupole terms — the 2nd order is 3.3x larger than the 1st for this system, not a small refinement, so an implementation without it would be badly misleading). Validated on the same charged FCC Si cell above: 1st order matches QE to all 8 printed significant figures (0.27948401 Ry); 2nd order agrees to ~0.08% (-0.91521864 Ry vs. QE's -0.91595649 Ry), consistent with the expected gauge-dependence of Makov-Payne's un-wrapped ionic moments (Q3 and the QE reference run need not share the exact same atomic-position gauge) rather than a formula error. Cubic lattices only (SC/FCC/BCC); see TODOs.md §1.3 for the exact algorithm and full derivation notes.The Martyna-Tuckerman isolated-system correction (
control.isolated_system_correction: martyna-tuckerman,MartynaTuckermanCorrectionCalculator) is the second scheme through the same factory: an exact reciprocal-space kernel-difference sum (every multipole order at once, not a truncated series), valid for any Bravais lattice (no cubic-only gate, unlike Makov-Payne). This is the post-SCF (non-self-consistent) evaluation only — evaluated once on the density already converged under the ordinary periodic Coulomb kernel, which differs from QE's self-consistent ‘assume_isolated='m-t’(density not relaxed under the MT-corrected potential); see the class docstring for the full scope statement. Validated by two unit-test limits derived from first principles and confirmed both analytically and in an independent numpy prototype before being wired into C++: (1) a neutral, concentrically symmetric synthetic charge distribution (point ion + isotropic Gaussian electron cloud at the same center) gives a correction consistent with zero to ~1e-9 Ry, matching the classical shell-theorem expectation; (2) for a net-charged compact synthetic density, the correction's ratio to Makov-Payne's first-order (Madelung monopole) term on the same state trends monotonically toward 1 as the cell grows (1.07 → 1.03 → 1.01 across three increasing cell sizes), confirming both the sign and the expected large-cell limit against an independently-validated scheme. End-to-end regression anchors on the same charged FCC Si cell used above are self-measured (no QEassume_isolated='m-t'` cross-check yet — QE's own version is self-consistent, so a literal Q3-vs-QE total-energy comparison would conflate the post-SCF-vs-self-consistent scope difference with any formula difference); see TODOs.md §1.3 for the exact algorithm, its literature grounding, and what a future self-consistent version would need.DFT+U (
method.scf.hubbard) is Dudarev's rotationally invariant formulation, applied self-consistently as a second nonlocal-projector term. A shell may be named by its dataset label (3D) or by a bare orbital letter (d), which selects by angular momentum and occupancy. Q3 logsTr[n]per manifold and spin so a missing or misidentified shell is visible.Energy and potential. Atomic projectors work for norm-conserving, ultrasoft, and PAW datasets; the latter two use the all-electron inner product
⟨φ|S|ψ⟩. Reading QE's own committed ultrasoft Hubbard input, Q3 matches the total energy to 4.5e-7 Ry andE_Uto 3e-8 Ry. The gate covers theTr[V_U n]double-counting correction, spatial and time-reversal averaging of the occupation matrix, and irreducible-wedge versus full-zone consistency.Forces. Atomic-projector forces cover all three dataset families. Their ultrasoft/PAW path includes both
dφ/dRanddS/dR, so a Hubbard force can appear on an atom with no Hubbard manifold. Silicon ultrasoft agrees with the derivative of Q3's own energy to 0.044%; zincblende SiC PAW agrees to 0.013%, and removing the carbon-sidedS/dRterm makes that discriminating contribution exactly zero. Gamma-only energy agrees with Q3's explicit full-sphere result to 8e-9 Ry and its force agrees with a central difference to about 3e-5 relative.
method.scf.hubbard_projectors: "atomic"|"ortho-atomic"selects QE's two projector conventions. Ortho-atomic projectors use one joint Löwdin transform over every orbital in the cell. Norm-conserving datasets use⟨φ|φ⟩; ultrasoft and PAW use the generalized⟨φ|S|φ⟩metric from the same production overlap operator as Davidson. Their derivatives included(O^-1/2)/dR,d(O^-1/2)/dε, and, where present,dS/dRanddS/dε.The ultrasoft silicon ortho-atomic total and Hubbard energies match LibXC QE to 5.8e-7 Ry and 1.1e-8 Ry, with occupation traces within 5e-5. On PAW SiC, the complete force agrees with Q3's own energy derivative within 2.4e-6 Ry/bohr, and all six stress components agree within 0.05 kbar. The corresponding norm-conserving gates agree to 4.9e-5 relative for force and 0.05 kbar for stress; removing only the transform derivative misses by 36.3 kbar. Gamma half/full parity is gated for energy, force, total stress, augmentation stress, and Hubbard stress.
Stress. Atomic-projector Hubbard stress is implemented for every dataset family. Its isolated term matches QE's printed decomposition on norm-conserving and ultrasoft silicon and ultrasoft copper, including Gamma-only and both p and d manifolds. PAW exercises the same ultrasoft branch and produces a non-zero term, but lacks an independent QE gate because the committed, currently supported PAW datasets omit the orbital labels QE requires. Atomic and ortho-atomic PAW stress are therefore gated against the full tensor of Q3's own energy derivatives; the ortho-atomic test covers two strain steps before committing its tolerance.
The remaining limits fail visibly. The GPU applicator and direct diagonalizer refuse DFT+U until they have parity coverage. Ultrasoft ortho-atomic energy and the insulating norm-conserving topology gates retain the repository's
1e-8 Rystandard. The metallic nickel norm-conserving ortho-atomic gate uses a5e-8 Ryallowance for MPI reduction-order noise at a1e-16density threshold and enables deterministic FFT planning soFFTW_MEASUREplan selection is not folded into the topology comparison.
| Execution path | Atomic projector | Ortho-atomic projector | Contract |
|---|---|---|---|
| Serial CPU + Davidson | NC, US, PAW | NC, US, PAW | Energy, forces, and stress enabled |
| MPI k-point pools + Davidson | NC, US, PAW | NC, US, PAW | Energy, forces, and stress enabled |
| MPI band groups + Davidson | NC, US, PAW | NC, US, PAW | Energy and forces enabled; stress refused before property evaluation |
| GPU applicator | Refused | Refused | Rejected before GPU backend initialization |
| Direct diagonalizer | Refused | Refused | Rejected before SCF because this path bypasses the Hubbard Hamiltonian applicator |
The mandatory PAW gates use redistributable committed UPF data, and no committed input embeds a host-specific path. Older PAW UPFs that use l_max_aug are parsed, but a PAW UPF declaring a core_energy reference constant is refused during loading until Q3 applies and validates that absolute-energy convention. This prevents the known PSLibrary subset from entering SCF with a silently wrong one-center energy. A dataset with f projector channels is refused for a related reason: the augmentation expansion would need spherical harmonics to degree 6 and Q3 has closed forms to 4, so the Gaunt table it would be built from cannot be formed exactly — see TODOs.md §1.2.
| SCF machinery | Options | Status |
|---|---|---|
| Diagonalization | Full LAPACK (direct, default), Davidson (davidson) | ✅ |
| Density mixing | Broyden (reciprocal, default), Anderson (real-space) | ✅ |
| Occupations | Fixed, Gaussian, Methfessel-Paxton (order N ≥ 1), Marzari-Vanderbilt, Fermi-Dirac smearing | ✅ |
Both diagonalizers handle the generalized eigenvalue problem for US (S-matrix via OverlapOperator). Mixing parameters: mixing_beta (default 0.7), mixing_ndim (default 8); for US, mixing acts on total ρ (smooth + augmentation). Broyden's G-space metric dot products and gamma-correction updates are BLAS-accelerated (metric weights precomputed once per history entry rather than per pairwise access; ZAXPY for the correction/mixing steps).
Methfessel-Paxton (order 1), Marzari-Vanderbilt ("cold smearing"), and Fermi-Dirac smearing cross-validated (NC, PBE, Cu FCC, matched
degauss) against QE: ΔE = 0.00018, 0.00018, and 0.00019 mRy respectively, all inside the roadmap's ΔE < 1e-6 Ry gate (tests/validation/test_qe_energy_comparison.py::test_qe_energy_comparison,Cu-NC-MethfesselPaxton/Cu-NC-MarzariVanderbilt/Cu-NC-FermiDirac).Methfessel-Paxton of any order N ≥ 1 (Phys. Rev. B 40, 3616) is selected by
method.scf.smearing.orderin YAML; order 1 is the default and is bit-identical to the original closed form. Quantum ESPRESSO's pw.x exposes order 1 only, so higher orders are validated against the expansion's defining identities (vanishing moments of δ_N up to x^{2N}, dS_N/dx = −x δ_N) intests/unit/layer_4_computation/iterator/internal/smearing/test_methfessel_paxton.cppand end-to-end on Cu intests/validation/test_methfessel_paxton_order.py.
Forces (Hellmann-Feynman)
| Component | NC | US | PAW | Status |
|---|---|---|---|---|
| Local potential | ✅ | ✅ | ✅ | Validated |
| Ewald (ion-ion) | ✅ | ✅ | ✅ | Validated |
| Nonlocal (KB) | ✅ | ✅ | ✅ | Validated |
| NLCC (core charge) | ✅ | ✅ | ✅ | Validated |
| Augmentation (dQ_ij/dτ) | — | ✅ | ✅ | Validated |
Stress tensor (Nielsen-Martin)
| Component | NC | US | PAW | Status | Notes |
|---|---|---|---|---|---|
| Kinetic | ✅ | ✅ | ✅ | Validated | |
| Hartree | ✅ | ✅ | ✅ | Validated | |
| Exchange-correlation | ✅ | ✅ | ✅ | Validated | Includes NLCC core correction |
| Ewald | ✅ | ✅ | ✅ | Validated | |
| Local pseudopotential | ✅ | ✅ | ✅ | Validated | Finite-difference dV_loc/dG |
| Nonlocal (KB) | ✅ | ✅ | ✅ | Validated | Analytical + finite-difference dβ/dq |
| Core correction | ✅ | ✅ | ✅ | Validated | |
| Augmentation | — | ✅ | ✅ | Validated |
Low-symmetry and Gamma stress gained a dedicated no-U baseline on 2026-08-11. On an inversion-only triclinic cell, the symmetry-reduced and explicit full-zone tensors now agree to
1e-7 kbar; every kinetic, Hartree, XC, Ewald, local, nonlocal, and core-correction component agrees with the LibXC-enabled QE reference to0.03 kbar. All six tensor components agree with central derivatives of Q3's own total energy to0.006 kbar, and the Ewald tensor agrees with the Ewald-energy derivative to0.001 kbar.The audit fixed two independent defects. Ewald reciprocal-space shear was written into the lower triangle while its real-space part used the upper triangle, so final symmetrization discarded the reciprocal shear. At a single Gamma point, the stress calculators did not consistently reconstruct the omitted
-Ghalf sphere while keepingG=0single-counted; the nonlocal term additionally needed full-sphere projector overlaps before contraction. Gamma half/full-sphere energy, total stress, and every component now agree, with Ewald energy and stress held to numerical roundoff bytests/validation/test_stress_baseline.py. Full contribution tensors are serialized understress_contributionsinresults.json.PAW forces and stress were completed 2026-08-08. Two force terms had been missing: the augmentation dQ_ij/dR contribution was assembled in the wrong index space (radial beta pairs against lm-expanded channels), and the generalized-eigenvalue term — ultrasoft and PAW solve H|ψ⟩ = εS|ψ⟩, so the force contracts (D_ij − ε_nk q_ij) — was absent from the force while present in the stress. The stress separately had a translation-invariance bug in its core-correction term that made the pressure depend on the choice of basis origin.
Force and stress terms are gated against the numerical derivative of Q3's own total energy, which is what localizes a defect — a force disagreeing with an external code could be either the force or the energy, while one disagreeing with its own energy can only be the force. On the committed silicon PAW dataset, the pressure is gated against its volume derivative on a fixed basis, the stress against a shift of the origin, and the force at a single Γ point (
tests/validation/test_paw_pressure_and_gamma_forces.py); the PAW force away from Γ is gated by the SiC DFT+U case (tests/validation/test_paw_forces_and_stress.py).Gamma-only forces were fixed in the same pass and are covered by their own gate, which reads 0.99991 of Q3's own dE/dR on the committed silicon PAW dataset (
test_gamma_only_forces_are_the_gradient_of_q3s_own_energyintests/validation/test_paw_pressure_and_gamma_forces.py). At a single unshifted Γ point Q3 keeps only the positive half of G-space, and the local, NLCC, Ewald and augmentation force sums each lacked the resulting factor of two, while the nonlocal term double counted G = 0. Silicon's Γ force was 0.637 of Q3's own dE/dR with a norm-conserving dataset and is now 1.00004; energies were correct throughout.
Structural Relaxation & Band Structure
| Feature | Status | Notes |
|---|---|---|
| Geometry Relaxation | ✅ | High-level workflow in q3.workflows.relax (uses ASE optimizers); runs a Q3 YAML input or a pw.x relax deck as is (any ibrav, if_pos honoured) on q3, q3_mpi or a GPU binary, with an optional execution_policy |
| Variable-Cell Relaxation | ✅ | q3.workflows.vc_relax; ASE FrechetCellFilter + BFGS, QE-validated; a pw.x vc-relax deck runs as is, on any binary; use_symmetry (FixSymmetry) supported, matching ionic-only relaxation |
| Band Structure | ✅ | High-level workflow in q3.workflows.band_structure (crystal path) |
| Density of States | ✅ | High-level workflow in q3.workflows.dos |
| Projected DOS (PDOS) + Löwdin charges | ✅ | q3.workflows.pdos; ortho-atomic (Löwdin) ‘|⟨φ’_i|S|ψ_nk⟩|²projections, taken in the S metric for ultrasoft/PAW \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyLeft"> Γ-Point Phonons \ilinebr </td> <td class="markdownTableBodyCenter"> 🟡 \ilinebr </td> <td class="markdownTableBodyLeft">q3.workflows.phonons; finite-difference, both blocking Q3 bugs fixed (below) \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyLeft"> Phonon dispersions + thermodynamics \ilinebr </td> <td class="markdownTableBodyCenter"> 🟡 \ilinebr </td> <td class="markdownTableBodyLeft">q3.workflows.phonons's Phonopy path; displaced supercells → band paths, free energy / entropy / C_v (below) \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyLeft"> Wannier interpolation \ilinebr </td> <td class="markdownTableBodyCenter"> 🟡 \ilinebr </td> <td class="markdownTableBodyLeft">q3.workflows.interpolation,q3.workflows.wannier+q3_utility interpolate‘; isolated manifolds, band selection, projectability disentanglement for entangled windows, maximally localized Wannier functions (Marzari-Vanderbilt spread minimisation with guiding centres), and the protocol around them: the mesh from a 0.2 Å⁻¹ spacing, the trial orbitals from the datasets’ own shells less an augmented dataset's repeated empty channels, a quality report (per-function spreads and their variance, centre–atom and centre–bond distances, H(R) decay), the frozen window resolved from the bands with the singular-gauge exclusion reported, the band distance η against a reference calculation, and a retry ladder that accepts, retries or rejects unattended; a campaign driver runs a fixed roster and reports one table. Still hand-set: leaving semicore shells out, and SCDM (TODOs.md §1.3) |
| Equation of state (Birch–Murnaghan) | ✅ | q3.workflows.equation_of_state; volume scan through Q3ASECalculator → ASE's third-order fit (q3.properties.equation_of_state) → V0, B0, B0' |
| Elastic constants | 🟡 | q3.workflows.elastic; cubic C11/C12/C44 as stress–strain slopes over ± strains (q3.properties.elastic_constants), clamped-ion C44 (TODOs.md §4) |
| Target-pressure relaxation | ✅ | run_vc_relax_workflow(target_pressure_gpa=…); minimises E + PV, the QE &CELL press analog |
Relaxation, band structure, and density of states are Python-orchestrated multi-step workflows, not single engine flavors: each chains
self_consistent_field/non_self_consistent_fieldruns (main.cpp's dispatch comment states this directly). The YAMLcalculation: </blockquote>relaxation/band_structure("bands")/density_of_states("dos")convention
examples/{relaxation,band_structure,density_of_states}--*/uses is consumed by the matching Python workflow, which reads the same file and the workflow-specificcontrolkeys it sets (band_pathandband_path_points_per_segmentfor band structure); it is not a calculation type theq3binary runs on its own –create_flavor()(src/layer_4_computation/flavors/factory.hpp) raises a specific error naming the rightq3.workflowsfunction if one of these three strings reaches it directly, e.g. from running an example file with./build/q3 <file>.ymlinstead of through the workflow.
run_relax_workflow()andrun_dos_workflow()read the matchingcontrol.relax_max_steps/relax_force_thresholdandcontrol.dos_broadening/dos_energy_min/dos_energy_max/dos_energy_stepskeys from their own input file as defaults when the caller doesn't pass them explicitly (relax_force_thresholdconverts Ry/bohr to eV/Å for ASE'sfmax) — the same treatmentrun_band_structure_workflowapplies toband_path/band_path_points_per_segment.relax_energy_thresholdis schema-accepted and set by everyexamples/relaxation--*/file but has no effect on this ASE-BFGS-based workflow, which only has a force-based convergence criterion; the workflow now warns instead of silently ignoring it when a file sets it.Equation of state, elastic constants and target pressure. All three are Python workflows over repeated SCF runs on deformed cells, driven through ASE and
Q3ASECalculator(which writes each point's ownmaterial.json+input.yml, so any point can be re-run by hand). The workflows hold only therun_*orchestration: the strain isq3.structure.strain, the fits and the stress conversions areq3.properties, marked to move to mat3ra-made and mat3ra-prode respectively (TODOs.md §4). The Birch–Murnaghan fit is ASE'sEquationOfState; V0 and E0 come back as the ESSEvolumeandtotal_energyproperties, while B0, B0' and the C_ij have no ESSE schema.Measured on the silicon norm-conserving PBE example (48 Ry, 4×4×4 k-grid,
tests/validation/test_equation_of_state.pyandtests/validation/test_elastic_constants.py):
quantity Q3 PBE literature experiment V0 40.891 ų — — B0 88.1 GPa 88–89 GPa 97.9 GPa B0' 4.29 ~4.2 ~4.2 C11 152.6 GPa ~153 GPa 165.7 GPa C12 56.2 GPa ~57 GPa 63.9 GPa C44 99.8 GPa (clamped ion) ~100 GPa 79.6 GPa V0 returns to the input cell — itself a PBE-relaxed MaterialsProject structure — to 0.007 %. Two things cross-check without any external reference. The bulk modulus from the elastic constants, (C11 + 2C12)/3 = 88.3 GPa, agrees with the Birch–Murnaghan B0 to 0.2 %, though one fits energies across volume and the other differentiates the stress with respect to strain. And the fitted P(V) reproduces Q3's own computed stress at every scanned point to better than 0.05 GPa over ±12 % in volume, although the fit never sees a stress.
C44 is the clamped-ion value: fractional coordinates are held fixed, so the sublattice sliding a shear induces in the diamond structure is not included. That relaxation is the whole of the difference between ~100 GPa and the ~75 GPa that compares with experiment — see TODOs.md §4.
The strain set is applied in both signs of every magnitude, which is the method rather than a refinement of it. Deforming the cell changes the symmetry-reduced k-point set, adding a stress term that is even in the strain: measured at 0.985 GPa in silicon's shear component at 4×4×4 — twice the physical response — and constant in the magnitude to four digits, which is how it is told apart from third-order elasticity (which scales as the square, as the uniaxial pattern's own even part does). A zero-intercept fit over a sign-symmetric set removes every even term from the estimate exactly (the reference cell's own stress included), so C44 is k-converged at 4×4×4 (99.82 GPa against 99.78 at 6×6×6) while a one-sided difference at 2×2×2 would have overestimated it sevenfold.
Target-pressure relaxation is Q3's answer to QE's
&CELL press, which the QE parser refuses while naming this parameter.run_vc_relax_workflowhands ASE's cell filter the enthalpy E + PV, so the converged cell is the one whose own stress balances the target rather than the one whose stress vanishes. Validated against the equation of state, which reaches the same volume through the fitted P(V) and a different quantity: on silicon at 10 GPa the two agree to 0.03 % in volume (tests/validation/test_vc_relaxation.py).cell_dofree(constrained/partial cell relaxation) stays refused.Γ-point phonons (
run_gamma_phonons_workflow, central-difference forces viaQ3ASECalculator-> mass-weighted dynamical matrix -> ASR -> diagonalize at q=0, using ASE'sPhononsclass) are implemented and unit-tested on their own math (tests/python/test_properties_phonons.py), and the acoustic sum rule correctly zeroes the three acoustic modes on real Q3 forces (tests/validation/test_gamma_phonons.py::test_silicon_acoustic_modes_vanish_after_asr). Validating this workflow found and fixed a real Q3 bug: forces on any structure with reduced (non-maximal) point-group symmetry had spurious components that must be exactly zero by symmetry, traced toSymmetryAnalyzer::symmetrize_vectorapplying the crystal rotation matrix un-transposed to a covariant quantity (forces) — invisible for the full 48-operation cubic point group every other validated system in this codebase uses, not for the reduced subgroups a displaced structure has. Fixed infeature/2026-07-26-0-fix-reduced-symmetry-forces; seetests/unit/layer_1_structure/symmetry/test_symmetry.cppfor a direct regression test.A second, deeper issue was found immediately after (validating the first fix against the full Γ-phonon calculation, not just a single displacement) and is now also fixed:
SymmetryAnalyzer::reduce_kpoints_internal— the default automatic Monkhorst-Pack + IBZ reduction path — built k-point equivalence classes from the Bravais LATTICE's holohedral point group (lattice vectors only), not the crystal's actual, atomic-basis-aware point group. For any structure whose basis reduces the true symmetry below the lattice's (any finite-difference-displaced structure), that group is too large, silently treating k-points as equivalent under rotations that are not real symmetries of the decorated crystal and corrupting the k-point-summed forces and stress. Fixed infeature/2026-07-27-0-ibz-vector-reconstructionby reusingget_symmetry_operations()(the same spglib-backed, basis-aware detection already used for density symmetrization) in place of the hand-rolled Bravais-only group, and itsreciprocal_rotationfield (k-vectors are reciprocal-space quantities; a crystallographic rotation matrix in a generally oblique fractional basis is not its own inverse-transpose). Verified two ways: (a) on the exact displaced-Si case TODOs.md's prior investigation had already checked against an established plane-wave DFT code, the fixed default path now reproduces both the QE-cross-checked force (+0.0048Ry/bohr) and stress exactly, matching an independent spglib-based reduction and a fully unreduced explicit k-point list to displayed precision — where it previously gave a sign-flipped force and a qualitatively wrong stress tensor; (b)test_silicon_optical_modes_triply_degeneratenow passes outright (wasxfail) — seetests/unit/layer_1_structure/symmetry/test_symmetry.cpp'sReduceKpointsInternalUsesActualCrystalSymmetryNotBravaisLatticefor the regression guard, and TODOs.md §1.3 for the full writeup.test_silicon_gamma_phonons_match_qe(the direct, wired-in QE comparison) still needs a run with an established reference DFT code available to confirm formally — not possible in every environment — so this row stays 🟡 pending that run, rather than the DFT+U-style "partially validated" language this legend deliberately has no tier for.Phonon dispersions and thermodynamics (
run_phonopy_workflow, same module) drive Phonopy as a library: it generates the symmetry-reduced displaced supercells,Q3ASECalculatorevaluates the forces on each, the force sets go back as force constants (acoustic sum rule through Phonopy's ownsymmetrize_force_constants), and Phonopy answers at any q — a dispersion along aq3.workflows.kpathband path, the density of states, and harmonic free energy, entropy and heat capacity from a q-mesh. The dispersion, the density of states and the zero-point energy come back as ESSE properties (phonon_dispersionsin THz, one series per branch); ESSE has no temperature-dependent schema for the thermal properties, which come back as its abstract two-dimensional plot against temperature, and none for force constants, which stay inphonopy_params.yamlnamed by an ESSEfile_contentproperty (TODOs.md §4). Silicon in a 2x2x2 supercell costs two 16-atom SCF runs: Phonopy reduces the displacement set to one direction, doubled to ±u. On thenc.ymlexample's own settings it gives a Γ-optical mode of 14.38 THz (against 15.53 THz measured), TA(X) = 4.15 THz and TA(L) = 2.94 THz (4.49 and 3.45 measured), C_v(1000 K) = 48.9 J/K/mol of unit cells against the Dulong–Petit 49.9, and a zero-point energy of 0.058 eV/atom.The ~7% shortfall is not k-point convergence, as first thought: QE through the same Phonopy, on the same nominal mesh, gives 15.22 THz. On the displaced supercell (space group Cm) the 1x1x1 shifted mesh is one k-point in Q3 and three in QE, which completes each mesh point's star under the lattice's rotations; given QE's three points explicitly, Q3's forces match QE's to 6e-4 eV/Å. The difference is the engine's mesh generation, tracked in TODOs.md §1.3.
The central difference is not optional here, and finding that out is what the validation bought. Phonopy's default displacement set uses one sign wherever site symmetry makes ±u equivalent, which holds for the exact energy surface but not for a k-sampled one: on that same 2x2x2 shifted mesh, silicon's ±0.01 Å forces are −0.00887 and +0.00150 Ry/bohr, an antisymmetric half that is the physics and a symmetric half of −0.00368 Ry/bohr that is sampling error — one that falls to +0.00028 by 6x6x6 and vanishes identically on a Γ-centred mesh. A forward difference keeps all of it and reports a Γ-optical mode of 4.9 THz instead of 14.4, with no diagnostic anywhere; the central difference cancels it by construction, which is why the Γ-only path above never saw it. It is the same sampling effect as above: the residual vanishes with QE's k-points, and what the central difference cannot cancel is the response itself coming out low.
tests/validation/test_phonopy_phonons.pycovers the workflow against the real binary, and carries the Si-vs-QE+Phonopy comparison the dispersions need to graduate to ✅: the same Phonopy displaced supercells pushed throughpw.x, so a disagreement is a disagreement about forces and nothing else. It needs the pinned reference build and self-skips without it. Run, it fails by 0.84 THz at Γ for the mesh reason above, and isxfail(strict=True)until the engine samples the star, so this row stays 🟡.Variable-cell relaxation (Si, +2% isotropic strain) recovers the unstrained equilibrium lattice constant to 0.14% and agrees with an independent QE
vc-relaxrun on the identical starting geometry to 0.004% (tests/validation/test_vc_relaxation.py). This workflow had three compounding bugs before this validation existed and none of them were caught by any prior test: (1)Q3ASECalculatornever wrote the optimizer's updated cell back into the per-step material JSON at all, so Q3 silently re-solved the ORIGINAL cell on every step regardless of what the ASE cell filter did; (2) the Q3-to-ASE stress conversion was missing a sign flip between Q3's Nielsen-Martin convention and ASE's tensile-positive convention, which would have driven the cell filter the wrong way once (1) was fixed; (3)_material_json_to_ase_atoms's FCC/BCC cell construction used the ESSE JSON's primitive lattice length directly instead of converting it to the conventional celldm(1) Q3 itself uses, making the cell fed back to Q3 too small by1/√2(FCC) or√3/2(BCC). All three were dormant and invisible for ionic-only relaxation (Q3 never saw the ASE cell at all there) and only became consequential once (1) was fixed.Q3ASECalculator's bindings mode (q3_corenanobind path) has no API to update the solver's cell at all, so it now raises rather than silently ignoring a mismatched or changing cell;run_vc_relax_workflowforces subprocess mode accordingly.Wannier interpolation is native end to end: an opt-in NSCF pass (
control.interpolation) writes the overlap, projection and eigenvalue matrices as an interpolation record, andq3_utility interpolatebuilds the projection gauge U = A(A†A)^(−1/2), transforms to a real-space H(R) on the Wigner-Seitz supercell, and interpolates bands and DOS at arbitrary k-points. Verified at three levels: the k → R → k round trip reproduces the input eigenvalues to 1e-14 eV at the coarse mesh points (an exact identity there); an analytic cubic band is reproduced off-mesh to 1e-10; and against Q3's own direct calculation on a Γ–X–L path the interpolation error falls from 67.8 meV rms on a 4×4×4 grid to 10.0 meV on 8×8×8 — the convergence being what distinguishes interpolation error from a bookkeeping bug. Against Quantum ESPRESSO on the same cell and grid, Q3's NSCF eigenvalues agree to 0.006 meV, and the interpolation from Q3's own interface matrices agrees with the one frompw2wannier90.x's to 0.6 meV rms along Γ–X–L–Γ — the residual being the two codes' projection-gauge conventions (Q3 orthonormalizes the trial orbitals before projecting, pw2wannier90 does not), a hundredth of the interpolation error they share (tests/validation/test_wannier_interpolation.py,examples/band_structure--semiconductor--silicon--wannier/, tutorial 08).The construction is layered as a basis-agnostic local-orbital representation (
LocalOrbitalHamiltonian, layer 3: H(R) blocks, optional S(R), weighted Bloch sums) consumed by generic band-structure and DOS calculators, so an atomic-orbital or Slater-Koster Hamiltonian would run through the same code. Trial orbitals are a strategy behind one interface (the pseudopotentials' atomic wavefunctions, or hydrogenic s orbitals atcontrol.interpolation.projection_centers). Ultrasoft and PAW datasets are dispatched to the augmented interface kernels — the S metric in A_mn(k) and the augmentation term in M_mn(k,b) — which pass their internal identities; only the norm-conserving path is cross-validated against Quantum ESPRESSO.Entangled windows are handled by projectability disentanglement (
control.interpolation.disentanglement), and the gauge inside the chosen subspace is minimised toward maximum localization by default (control.interpolation.localization), with guiding centres fixing the branch of Im ln so the minimisation does not stall where a diagonal overlap crosses the negative real axis. Six examples cover isolated, offset, entangled, symmetry-free and mixed-chemistry cases, gated by awannier-tier2CI job. The norm-conserving construction is validated against Wannier90 4.0.1 run as a black box on Q3's own exported matrices (tests/validation/test_wannier90_parity.py): Ω_I agrees to 10⁻⁶ Ų or better on all four norm-conserving cases, and the minimised Ω to 10⁻⁶ Ų on the isolated manifolds and 8×10⁻⁴ Ų on the disentangled alloy.The augmented construction is validated on silicon, not on copper. The same harness runs four PAW and ultrasoft cases: silicon agrees with Wannier90 on both families, Ω_I, Ω and the centres, while on copper the localisers do not reach the same minimum — Q3's stalls short of Wannier90's on ultrasoft, and both stall on PAW's eighteen functions. See KNOWN-ISSUES.md, and treat a copper PAW or ultrasoft Wannier model as unvalidated.
The protocol around those mechanics is in
q3.workflows.wannier: the mesh from a 0.2 Å⁻¹ spacing (the lever that matters — the same automatic recipe on silicon gives 826 / 110 / 12.6 meV at 4×4×4 / 6×6×6 / 8×8×8 against at most 1.4× from the gauge), the published E_F + 2 eV frozen window with the singular-gauge exclusion reported, a quality report the run writes itself, the band distance η against a reference calculation, and a retry ladder that accepts, retries or rejects unattended. 🟡 because its 10 / 100 meV gates have not yet been re-set from a population — the campaign (scripts/validation/run_wannier_campaign.py) has run its 40-row roster, and nothing on it lands between 9.7 and 87.6 meV, so the 10 meV gate is untested there — and because leaving semicore shells out of the trial set and SCDM are not built. Tracked in TODOs.md §1.3.The construction is composed, not branched:
WannierHamiltonianBuilderchecks a record's inputs once, asks aSubspaceSelection(the window as it stands, or projectability disentanglement) and aGaugeChoice(the projection gauge, or the Marzari-Vanderbilt minimum), each chosen by a factory from oneWannierConstructionSettings, and Fourier-transforms; the kernels underneath work on a layer-0 column-majorDenseMatrixwith onezgemm, one Hermitian eigensolver and one SVD behind them. Every failure a driver can act on carries aWannierDiagnosticcode thatq3_utilityprints on the line after the prose, and the results file has one schema (schemas/q3/interpolation/results.json) that generates the writer and is mirrored by the Python reader under a parity test. The protocol's retry ladder is one class per rung over a frozen state, routed on the code with the prose as the fallback for an older binary; its campaign-scale tests are tier 3, run monthly by the scheduled workflow, which keeps the table.PDOS/Löwdin charges cross-validated against QE's
projwfc.xon Si (insulator, non-magnetic) and Ni (FCC, LSDA-FM): max Löwdin charge Δ = 0.00024 e (Si) / 0.00097 e (Ni), both well inside the roadmap's Δ < 0.01 e gate; spilling parameter agrees to Δ ≤ 0.0001; per-shell PDOS integrals agree to <1% with correlation 0.97–0.999 across every shell, including Ni's two same-_l_ shells (3S, 4S) (tests/validation/test_qe_projwfc_comparison.py). Both codes independently converge SCF, then run a matched-resolution NSCF (explicit full Monkhorst-Pack grid) before projecting — an end-to-end agreement check, not a comparison against a frozen reference.Ultrasoft and PAW datasets are projected in the S metric: those schemes give up norm conservation, so the states solve
H|ψ⟩ = ε S|ψ⟩withS = 1 + Σ q_ij |β_i⟩⟨β_j|and the projection that conserves charge is ‘⟨φ’_i|S|ψ_n⟩, with the atomic orbitals Löwdin-orthogonalized in the same metric (Vanderbilt, PRB 41, 7892 (1990); Blöchl, PRB 50, 17953 (1994); Kresse & Joubert, PRB 59, 1758 (1999)). The sum rule that follows — the Löwdin charges accounting for the valence electron count, up to the basis-set spilling — is checked for a silicon ultrasoft and a silicon PAW case intests/validation/test_pdos_s_metric.py: on a 4×4×4 grid and against 8 valence electrons the charges sum to 7.947881 e (spilling 0.0065) and 7.935842 e (spilling 0.0080), beside norm-conserving silicon's 7.930530 e (0.0087). The cross-code comparison againstprojwfc.x` runs on the norm-conserving cases above (TODOs.md §1.3).
| System | PP Type | ecutwfc (Ry) | k-grid | Functional | Δ Total Energy vs reference |
|---|---|---|---|---|---|
| Si FCC | NC | 48 | 2×2×2 (shifted) | PBE | < 0.001 Ry |
| Si FCC | US | 40 | 2×2×2 (shifted) | PBE | < 0.001 Ry |
| Cu FCC | NC | 120 | 4×4×4 (shifted) | PBE | < 0.001 Ry |
| Cu FCC | US | 40 | 4×4×4 (shifted) | PBE | < 0.001 Ry |
| Fe BCC (LSDA-FM) | NC | 90 | 12×12×12 (shifted) | PBE | 0.11 mRy (Δmoment 0.005 μB) |
| Ni FCC (LSDA-FM) | NC | 60 | 12×12×12 (shifted) | PBE | 0.013 mRy (Δmoment 0.005 μB) |
| Cr BCC (LSDA-AFM) | NC | 90 | 12×12×12 (shifted) | PBE | 0.25 mRy |
Shared memory
| Feature | Status | Notes |
|---|---|---|
| OpenMP threading | ✅ | Threaded across bands in the Davidson H|ψ⟩ loop and in density accumulation, and across grid slices in the XC functional; the sphere/grid scatter and gather run inside the band-parallel region. Team sizes come from layer_0_core/execution/threading.hpp, which returns one thread for a loop too short to pay for a team, so OMP_NUM_THREADS=1 forks nothing and matches a build without OpenMP bit for bit |
| FFTW3 with OpenMP threads | 🟡 | fftw_plan_with_nthreads, now used only where band-level threading is not available. With more than one thread the serial grid plans single-threaded transforms and gives each thread its own scratch, so threads go to whole transforms rather than inside one |
| Wavefunction-specific FFT grid | ✅ | Smooth grid (2×ecutwfc) vs dense density grid; QE-format input, and YAML with control.use_exact_wavefunction_grid, get the exact 4×ecutwfc grid pw.x builds |
| ZGEMM-batched KB projectors | ✅ | Level-3 GEMM across all bands (serial + GPU) |
| Kerker residual preconditioning | ✅ | Auto-enabled for smeared/metallic systems |
| Local Thomas-Fermi preconditioning | 🟡 | mixing.preconditioning: local-tf; screening built from the local density, solved by conjugate gradients. Unit-tested (reduces to Kerker for a uniform density); the convergence gain on inhomogeneous cells is not yet measured |
MPI (distributed memory)
| Feature | Status | Notes |
|---|---|---|
| K-point pool parallelism | ✅ | --pools N or manifest setting; omitted/zero means one pool |
| Band-group parallelism | ✅ | Wavefunction G-vectors distributed by FFT stick within a pool, gamma-only kept; opt-in via --pools, no measured speedup |
| Band distribution | ✅ | --distribute-bands splits states across a pool at the full G-sphere; divides work, not storage |
| Non-self-consistent runs under MPI | ✅ | q3_mpi and q3_mpi_gpu_cuda run NSCF as well as SCF, on one rank per k-point pool; eigenvalues and projections gathered in list order |
| One execution policy for a whole workflow | ✅ | q3.binary.ExecutionPolicy (ranks, pools, launcher) passed to any q3.workflows function reaches every engine run it makes: SCF, NSCF, each relaxation step |
| Per-rank memory report | ✅ | Estimate of the named arrays before the SCF loop, measured peak resident set at the end |
| Davidson basis size | ✅ | method.scf.davidson.basis_expansion_factor (default 2, minimum 2; 4 for a non-self-consistent run unless set) sizes the working basis |
| Fermi bisection without per-step collectives | ✅ | Eigenvalue table gathered once per call and bisected locally in global k-point order, so E_F is bit-identical across pool topologies for identical eigenvalues |
| ScaLAPACK subspace diagonalizer | 🟡 | Used when band_size > 1; redundant-serial fallback otherwise |
The 10⁻⁸ Ry topology invariance gate is confirmed for norm-conserving silicon, localized Makov-Payne and Martyna-Tuckerman corrections, LSDA nickel, atomic and ortho-atomic DFT+U, ultrasoft silicon, and PAW silicon across serial and one/two/four-rank runs. The matrix records actual pool/band topology, binary hashes, all reported energy components (including Hubbard and PAW one-center terms), stable result metadata, and repeated-run spread. LSDA + smearing under k-point-pool parallelism is also validated (all four smearing schemes, 1/2/4 ranks) after fixing a nondeterministic MPI collective-desync bug: the SCF convergence decision was made per-rank from a residual whose per-rank
FFTW_MEASUREFFTs made it bit-divergent across pools, desyncing the pools' collective call sequences (crashes or corrupted totals); the residual is nowMPI_MAX-synchronized across pools before any branch consumes it — see TODOs.md §2.3 for the full root-cause writeup. For metallic soft-moment systems the genuine cross-topology converged-energy difference is ~10⁻⁸–10⁻⁷ Ry (stopping-point and summation-order differences), and hot Fermi-Dirac smearing only pins the energy to its flat moment valley (~10⁻⁴ Ry) — the LSDA smearing tests assert per-scheme calibrated tolerances accordingly.control.use_deterministic_fft_planning: trueopts a run intoFFTW_ESTIMATEplanning (fixed heuristic, no runtime timing) in place of the defaultFFTW_MEASURE, making repeated runs of even the soft-moment Fermi-Dirac case bit-identical — see KNOWN-ISSUES.md.
GPU offloading — Davidson H|ψ⟩ (FFT local potential, KB nonlocal) + subspace operations; density/mixing/XC remain on CPU. Selectable via --precision double|mixed.
| Backend | Target | Status | Notes |
|---|---|---|---|
| CUDA (cuBLAS/cuSOLVER/cuFFT) | q3_gpu_cuda | ✅ | Validated on Tesla T4; 6.6× vs serial (GPU-resident Davidson) |
| CUDA across several GPUs | q3_mpi_gpu_cuda | ✅ | One rank per device, one k-point pool each; measured on Perlmutter A100s |
| Metal / Accelerate | q3_gpu | ✅ | Apple Silicon; energies bit-identical to serial |
| HIP / ROCm | q3_gpu_hip | 🟡 | Compiles in CI; not yet run on AMD hardware |
| SYCL / oneAPI | q3_gpu_sycl | 🟡 | Runs on CPU-fallback device; FFT not yet on Intel GPU |
The GPU-resident Davidson pipeline (device-resident workspace + CUDA scatter/gather/potential kernels) has shipped — see PERFORMANCE.md. Roadmap: MPI+GPU (NCCL), native SYCL/Metal FFT, and cluster-scale benchmarking. See TODOs.md §2.
Benchmark tracking
| Feature | Status | Notes |
|---|---|---|
| Benchmark harness | ✅ | scripts/benchmark/run_benchmarks.py runs the decks in tests/regression/benchmark_matrix.yml and writes one JSON record per run — per-stage times and work counts from the engine's own counters, wall time, machine, commit and binary digest |
| Regression comparison | ✅ | scripts/benchmark/compare_benchmarks.py reduces repeated runs to a median and a spread, then fails on a tracked stage that slowed by more than 10%, or by more than three times its measured spread where that is wider |
benchmark workflow | ✅ | .github/workflows/benchmark.yml runs the CI-tagged decks on x86-64 and arm64 GitHub-hosted runners, each against its own runner class's baseline (docs/benchmarks/baseline-github-*.json), and reports on the run summary; non-blocking by design — a shared runner's clock cannot carry a hard gate |
| Generated PERFORMANCE.md section | ✅ | scripts/benchmark/generate_performance.py renders the committed baselines into PERFORMANCE.md between generated markers; the rest of that document stays hand-written |
Timings are only comparable within one machine: every baseline carries the machine it was taken on and the comparison refuses to cross that line. A scheduled head-to-head against Quantum ESPRESSO is not part of this — see TODOs.md §2.4.
| Feature | Status | Notes |
|---|---|---|
| YAML input | ✅ | Primary input format |
| YAML manifest unknown-key rejection | ✅ | model/method/control fail fast on a typo instead of silently defaulting |
| Namelist input (established DFT code format) | ✅ | Alternative input format; unknown keys, namelists and cards refused; HUBBARD card, celldm(2..6), A/B/C, &ions/&cell read |
| Reference-code input drop-in case set | ✅ | 10 cases where a reference code's own input file is fed to Q3 and its own printed numbers are the reference — tests/validation/test_reference_input_drop_in.py |
--check (parse and report, do not run) | ✅ | Answers "will Q3 read this deck, and as what?" without an SCF |
control.verbosity (default / high) | ✅ | high (or --log-level debug) adds the per-iteration and per-phase diagnostics a default run leaves out; QE's verbosity maps onto it |
| UPF v1 (tag-based) / UPF v2 (XML) readers | ✅ | NC + US; PAW in UPF v2 |
| JSON results output | ✅ | Canonical <prefix>_results.json from CLI and bindings, with legacy unprefixed lookup; self-describing units, execution topology, and §3.2's full record |
| Plane-wave binary output (established code format) | ✅ | wfcN.dat / charge-density.dat in Quantum ESPRESSO's record layout (packed header, interleaved Miller, 1/Bohr); non-spin-polarized only — see TODOs.md §3.1 |
| Binary checkpoint save/restore (wfn + ρ) | ✅ | Serial, and MPI with one rank per k-point pool (the automatic topology), each pool writing its own wfc<N>.dat; shared pools refuse — TODOs.md §3.1 |
| BerkeleyGW mean-field export (WFN + RHO + vxc.dat) | ✅ | mean_field_export: berkeleygw; ZnO G0W0 through BerkeleyGW 4.0 within 1 meV of QE + pw2bgw.x; gaps — TODOs.md §3.1 |
BerkeleyGW VXC, VSC and VKB (sigma, parabands) | ✅ | VKB on request (mean_field_export_kleinman_bylander_projectors); parabands on Q3's VSC + VKB reproduces pw.x's eigenvalues to 0.004 meV on silicon; norm-conserving only — TODOs.md §3.1 |
Mean-field record + standalone converter (q3_utility convert) | ✅ | <prefix>.save/mean-field.q3mf (written when mean_field_export is set); converting it reproduces the in-process WFN, RHO, vxc.dat and VXC byte for byte |
Volumetric field export (cube, .xsf) | ✅ | q3_utility volumetric: ρ, V_eff and a band's |ψ|² off a finished run's save set; the written density integrates to the valence electron count |
Plane-wave charge-density reader (charge-density.dat) | ✅ | Reads a density back, Quantum ESPRESSO's layout and the older Q3 one; a saved band (wfc<k>.dat) is readable too, in Quantum ESPRESSO's layout |
| Build-time add-ons: input, dataset and volumetric format plug-ins | ✅ | src/addons/<name>/, compiled in when present and never named by core; listed by q3 --version and in provenance.generator.addons (note below) |
Build-time add-ons plug into core through registries — input formats, each with the companion outputs it writes after a run it parsed; pseudopotential formats; PAW one-center conventions; and volumetric formats — so core names none of them.
q3 --versionprints a secondadd-ons:line naming each one compiled in, and everyresults.jsonlists them inprovenance.generator.addons, empty for a build without any (io_reference.md §3 and §4.1, architecture.md).
The drop-in claim is tested against the reference codes' own files. Every other comparison test generates the reference input from Q3's YAML, which leaves one loophole: a wrong translation in the generator makes both sides agree on the wrong calculation. In
tests/validation/test_reference_input_drop_in.pythe reference file is primary and Q3 consumes it byte for byte – 10 QE cases (NC, ultrasoft, PAW, Gamma-only, DFT+U via theHUBBARDcard, ferromagnetic Fe and Ni, antiferromagnetic Cr) against the outputs QE itself produced.Measured: NC 2e-6 Ry, Gamma-only 4e-6, ultrasoft 2e-5, DFT+U total and E_U within 2e-4, PAW 2.9e-4 to 6.9e-4 (the known one-center residual), Cr AFM 2.5e-4 Ry with |m| 2.5929 against QE's 2.60. PAW and chromium carry their own tolerances, set from those measurements with the reason recorded beside each.
Its first run found defects in files that had been committed all along: ‘mixing_mode = 'plain’
(QE's default) killed Q3 in the mixer factory, and Q3 reported no _absolute_ magnetization -- the only number that separates a converged antiferromagnet from one whose moments collapsed. </blockquote> <blockquote>‍YAML unknown-key rejection coversmodel/method/controland their sub-objects, where Q3 owns the full key set; inline YAML-embeddedmaterial<tt>(as opposed to an external material JSON file, which every example uses) is deliberately not covered — see [TODOs.md](TODOs.md) §3.1. The QE namelist parser refuses unknown keys, unknown namelists and unsupported cards, and names the reason for anything it recognizes but cannot honour (qe_input::'s four tables inio/input/parser/qe.hpp). Keys stating a QE default that switches a feature off —noinv =
.false.,tot_magnetization = -10000— are accepted, since they ask for what Q3 already <blockquote>‍does. 96 of 150 of QE's ownscf` regression inputs parse; every refusal names a real unimplemented feature (hybrids, noncollinear magnetism, electric fields, explicit occupations, forced FFT grids).
| Feature | Status |
|---|---|
| C++ types generated from JSON schemas | ✅ |
ESSE total_energy scalar property | ✅ |
Published results.json schema | ✅ |
**
results.jsoncarries every number the console report does**, andschemas/q3/results.jsonsays so: a closed root that names every key, with$refs into ESSE for the unit enums and array shapes the two share. The energy breakdown down to the dispersion term, the per-atom force decomposition beside the total, the scalar pressure, both magnetizations, DFT+UTr[n]per manifold, and the band edges with the gap between them are all keys of the file, filled by every flavor into the one struct generated from the schema (io/output/results.hpp) and validated against real runs intests/regression/test_results_schema.py. That is what lets the test suite read results rather than scrape stdout. The console report remains, and one smoke test keeps it honest, but nothing depends on its layout any more.
| Feature | Status | Notes |
|---|---|---|
q3 Python package | ✅ | src/py/q3/, installed via pip install -e src/py |
python -m q3 input.yml CLI | ✅ | Thin wrapper around the q3 binary (--binary, --no-capture) |
q3.workflows.relax / vc_relax | ✅ | ASE BFGS ionic / FrechetCellFilter (ExpCellFilter fallback) variable-cell |
q3.workflows.equation_of_state / elastic | ✅ | run_* orchestration over Q3ASECalculator; the strain is q3.structure.strain, the fits q3.properties |
q3.calculators.ase_calculator | ✅ | Mode A (subprocess, any cell) + Mode B (nanobind q3_core, fixed cell only) |
q3_core nanobind module | ✅ | Stateful SCFSolver with wavefunction restart |
| Per-step relaxation audit trail | ✅ | step_NNNN/{input.yml, material.json, output/} for full reproducibility |
| Unit conversions | ✅ | All factors from mat3ra.code.constants.Coefficients; no magic numbers |
Header-heavy library built with CMake. The default target is the serial q3 executable; opt-in flags add q3_mpi (Q3_WITH_MPI) and the GPU executables q3_gpu / q3_gpu_cuda / q3_gpu_hip / q3_gpu_sycl (Q3_WITH_GPU, Q3_WITH_CUDA, Q3_WITH_HIP, Q3_WITH_SYCL). LAPACK comes from the system, and so does FFTW3 for the default FFT backend; -DQ3_FFT_BACKEND=pocketfft swaps the serial FFT grid to the fetched, header-only, BSD-3-Clause PocketFFT, and then FFTW is neither searched for nor linked, so the serial q3, q3_utility and the tests build without any GPL library (MPI and GPU builds still require the FFTW backend; CMake refuses the combination); LibXC is used from the system when it is from a tested series (5.2 or 7.0), else 7.0.0 is built from source; the core third-party libraries are fetched via CMake FetchContent; parallel/GPU dependencies come from the system toolchain and are required only for their respective opt-in targets.
| Library | Purpose | Provided via |
|---|---|---|
| LibXC | XC functionals | System or CMake FetchContent |
| spglib | Symmetry detection | CMake FetchContent |
| FFTW3 | FFT | System (find_library) |
| PocketFFT | FFT (alternative) | CMake FetchContent |
| LAPACK | Linear algebra | System (find_package) |
| spdlog | Logging | CMake FetchContent |
| yaml-cpp | YAML parsing | CMake FetchContent |
| nlohmann_json | JSON I/O | CMake FetchContent |
| GoogleTest | Testing | CMake FetchContent |
| OpenMP | Shared-memory | System (Q3_WITH_OPENMP) |
| MPI, FFTW3-MPI, ScaLAPACK | Distributed memory | System (Q3_WITH_MPI) |
| CUDA (cuBLAS/cuSOLVER/cuFFT) | NVIDIA GPU | System (Q3_WITH_CUDA) |
| ROCm (hipBLAS/hipSOLVER/hipFFT) | AMD GPU | System (Q3_WITH_HIP) |
| oneMKL | Intel GPU (SYCL) | System (Q3_WITH_SYCL) |
| Accelerate / Metal / MPS | Apple Silicon GPU | System (Q3_WITH_GPU) |
Release archives. Every GitHub release carries one archive per tier-1 platform (Linux x86_64, Linux arm64, macOS arm64), built by .github/workflows/release.yml and assembled by scripts/repo/package_release.py.
| What a release carries | Status |
|---|---|
| Native archives, three platforms | ✅ |
| Smoke test of each archive outside its build job | ✅ |
SHA256SUMS over every asset | ✅ |
| CycloneDX SBOM, in the archive and beside it | ✅ |
Licenses of everything compiled in, in licenses/ | ✅ |
| Build-provenance attestation (public repo only) | ✅ |
The SBOM is built from the build's own dependency pins and the binaries' own link tables, not by a scanner: a C++ executable has no package manifest inside it, and a generic scan finds almost nothing. That is also how the packager notices a system LibXC compiled in from Ubuntu's static
libxc.a, whichlddcannot see. The Linux smoke test runs in a cleanubuntu:24.04container with only the packages the archive'sINSTALL.txtnames, then an SCF with the unpacked binary, so a wrong install line fails the release rather than the first user. Two packagings of one build are byte-identical.
| Suite | Framework | Status |
|---|---|---|
| Unit & integration (C++) | GoogleTest / CTest | ✅ |
| Regression (binary outputs) | pytest | ✅ |
| Validation (vs reference) | pytest | ✅ |
| CPU↔GPU energy parity | pytest | ✅ |
| LSDA / lattice / supercell-folding self-consistency | pytest | ✅ |
| MPI topology / band invariance | pytest (mpirun) | ✅ |
Type-query ratchet (dynamic casts, has_* flags) | pre-commit | ✅ |
| Public-API docstring ratchet (C++ headers, Python) | pre-commit | ✅ |
| A/B parity of a pull request against its merge base | parity workflow | ✅ |
Two ratchets run in the
lintjob with no C++ build: per-file counts may only fall, and a fall must be committed to the baseline in the same change.scripts/repo/check_cpp_type_queries.pycountsdynamic_cast/typeid, downcasts to ultrasoft/PAW types,as_ultrasoft()/as_paw(), pseudopotential-familyhas_*()queries, otherhas_*()branch conditions, and string-literal physics switches (121 at baseline,--reportfor the breakdown).scripts/repo/check_docstring_coverage.pycounts public header declarations and publicq3Python definitions without a doc comment (50% of 2046 C++ and 93% of 197 Python declarations documented at baseline). Driving both down is tracked in TODOs.md §3.3.
The
parityworkflow (.github/workflows/parity.yml) answers the one question a tolerance cannot: did a change that claimed to move no numbers move any? It builds the pull request's merge base and its head in the same runner, runs the twenty-three cases oftests/regression/parity_matrix.ymlwith both binaries over the head checkout's inputs (scripts/validation/run_parity_matrix.py), and compares every numeric leaf of every*_results.jsonplus every byte of every.save/checkpoint exactly (scripts/validation/compare_results_json.py). Only wall-clock timings, peak resident memory and the engine's own version and commit are exempt, because those differ between two runs of the same binary. The matrix spans the three pseudopotential families, Γ-only and shifted meshes, the three smearing schemes, LSDA on Fe/Ni/Cr, DFT+U with both projector schemes on both families, LDA and GGA, dispersion, a charged cell, two band-path NSCF passes, and the ortho-atomic projections on norm-conserving and ultrasoft silicon. A change that is meant to move numbers carries thenumerics-changelabel and anumerics-change: <reason>line in its body, which switches the comparison to 1e-12 relative and echoes the reason into the run log. A change that moves physics further names the matrix rows it moves in anumerics-change-rows:line: those rows are reported rather than failed, every other row is still held to 1e-12, and a pattern that names no row is refused. The workflow also runs when a label or the body changes, so the escape takes effect without a new commit.
CPU↔GPU parity (
tests/validation/test_gpu_energy_parity.py: FP64 ≤ 1e-10 Ry, mixed ≤ 1e-5 Ry; NC, Γ-only, US and PAW cases, plus LSDA nspin=2 on Fe/Ni/Cr and Methfessel-Paxton/Fermi-Dirac/Marzari-Vanderbilt smearing on Cu — ten cases at both precisions, every run with deterministic FFT planning so that the comparison sees the backends rather than FFTW's timing-selected plans) runs in the CUDA workflow and locally against Metal. Machine-validated internal-consistency suites cover LSDA-vs-unpolarized (test_lsda_consistency.py), lattice-representation equivalence (test_lattice_equivalence.py), supercell folding (test_supercell_folding_consistency.py: a 2×2×2 silicon cell on a shifted 2×2×2 mesh must give eight times the primitive cell on the shifted 4×4×4 mesh it unfolds onto — the check no external reference can make, since the primitive cell agrees with QE either way) and screw axes (test_screw_axis_wedge_consistency.py: wurtzite reduced on a mesh odd along c must keep the pairs its 6₃ screw makes degenerate and match the full mesh's energy). MPI validation tests (tests/validation/test_mpi_*.py) run in thempi-validationCI job (1/2/4 ranks) and now also cover LSDA + DFT+U (Hubbard) under band parallelism, plus the committed reproducibility matrix spanning isolated corrections and norm-conserving/ultrasoft/PAW datasets reliably — the first real-MPI-hardware confirmation of thereduce_within_groupband-parallelism fix inaccumulate_hubbard_occupation_and_potential(previously validated only by code-reading symmetry, no MPI build available at the time). Plain LSDA + smearing under k-point-pool parallelism is also covered for all four smearing schemes (formerlyxfail): writing that case surfaced a real nondeterministic MPI collective-desync bug, since root-caused by instrumented per-rank tracing and fixed by synchronizing the SCF convergence residual across pools — see TODOs.md §2.3 andcompute_residual_between's docstring. The smearing cases assert per-scheme calibrated tolerances (§2 MPI note above).