DLPNO-CCSD(T) on a crystal¶
This tutorial runs coupled-cluster correlation on periodic systems with one
call, run_periodic_job(..., method="dlpno-ccsd(t)"). You start from a
molecular crystal of H2 at the Gamma point, converge the energy per cell
against the k mesh, repeat it in a polarised basis, move to an ionic crystal
(rock-salt LiH), and finish with an open-shell cell on a UHF reference. Along
the way you learn what the route folds, what it reports per cell and what it
refuses. The user guide has
the full option tables and the theory in more depth.
Status: preview
The periodic DLPNO route is reachable, maintained and tested, and at full
domains it reproduces canonical k-point CCSD(T) (PySCF KRCCSD(T)) to the fit
tolerance. Its accuracy at the shipped thresholds on crystals with a real
band structure, and its cost against cell size, are not measured yet, so the
.out block says the route “is not production-gated yet”. The label
vocabulary is defined in
Methods: theory and status.
Prerequisites. A working vibe-qc install (installation), the molecular DLPNO tutorial for what PNOs and the thresholds do, and the k-point tutorial for meshes and Bloch sums. Run every script from an empty scratch directory: vibe-qc writes its outputs into the current directory.
All numbers on this page were measured with the vibe-qc 0.17.15 development
build (main, 2026-10-11) at the shipped defaults (NormalPNO, published
frozen core, triples_mode="t1" for RHF and "t0" for UHF). Wall times come
from a loaded laptop shared with other jobs and show orders of magnitude
only.
The H2 molecular crystal at the Gamma point¶
The test crystal is a simple cubic lattice of H2 molecules: a 6 bohr cube with one molecule of bond length 1.4 bohr along \(z\). It is small enough to run in seconds and has a real gap, which the route needs.
import numpy as np
import vibeqc as vq
a, d = 6.0, 1.4 # bohr
system = vq.PeriodicSystem(3, np.diag([a, a, a]), [
vq.Atom(1, [a / 2, a / 2, a / 2 - d / 2]),
vq.Atom(1, [a / 2, a / 2, a / 2 + d / 2]),
])
basis = vq.BasisSet(system.unit_cell_molecule(), "sto-3g")
res = vq.run_periodic_job(system, basis, method="dlpno-ccsd(t)",
output="h2_gamma", progress=False)
c = res.correlation
print(f"E_HF/cell {c.e_hf_per_cell:.10f}")
print(f"E_corr/cell {c.e_corr_per_cell:.10f}")
print(f"E_tot/cell {c.e_total_per_cell:.10f}")
print(c.kmesh, c.n_cells, c.source_kind, c.same_source,
c.exchange_q0, c.localisation, c.backend)
print("pairs", res.dlpno_ccsd.n_pairs, "iterations", res.dlpno_ccsd.n_iter)
E_HF/cell -1.2385562896
E_corr/cell -0.0094929996
E_tot/cell -1.2480492892
(1, 1, 1) 1 gamma-gdf True BvK-ewald pipek-mezey periodic-gdf-dlpno-ccsd(t)
pairs 1 iterations 3
The call ran in about two seconds. Reading the last line from left to right:
(1, 1, 1)and1. The mesh is the Gamma point, so the supercell is the unit cell.gamma-gdf. The correlation source is the Gamma adapter: the reference is the multi-k GDF engine at one k point, with its classic RSGDF fit retained.True.same_sourcemeans the correlation factors are that same fit, so the reference and the correlation see one Hamiltonian.BvK-ewald. The exchange zero mode of the reference Fock operator is the Born-von Karman Ewald convention, and the amplitude equations keep it.pipek-mezey. The adapter localises the active occupied orbitals with Pipek-Mezey, which is safe on a torus.periodic-gdf-dlpno-ccsd(t). The citation route key, which also names the engine in the.systemmanifest.
One occupied orbital per cell gives one pair, so this first run is a consistency check rather than a local-correlation calculation. The (T) correction is zero to machine precision: in a minimal basis a two-electron cell has no triples to speak of.
Converge the energy per cell against the k mesh¶
A Gamma-only calculation on a 6 bohr cube is far from the bulk limit. Pass
a Gamma-centred mesh with kpoints=; the route runs the unreduced mesh,
folds it to the real BvK supercell of \(N_k = n_1 n_2 n_3\) cells, and solves
one real supercell problem.
for mesh in [(1, 1, 2), (2, 2, 2), (3, 3, 3)]:
tag = "h2_sto-3g_" + "x".join(map(str, mesh))
res = vq.run_periodic_job(system, basis, method="dlpno-ccsd(t)",
kpoints=mesh, output=tag, progress=False)
c, cc = res.correlation, res.dlpno_ccsd
print(mesh, c.n_cells, f"{c.e_hf_per_cell:.10f}",
f"{c.e_corr_per_cell:.10f}", f"{c.e_t_per_cell:.2e}",
f"{c.e_total_per_cell:.10f}", cc.n_pairs, cc.n_screened, cc.n_iter)
Mesh |
Cells |
E_HF per cell, Ha |
E_corr (CCSD) per cell, Ha |
E(T) per cell, Ha |
E_total per cell, Ha |
Pairs kept (screened) |
Wall, s |
|---|---|---|---|---|---|---|---|
Gamma |
1 |
-1.2385562896 |
-0.0094929996 |
0 |
-1.2480492892 |
1 (0) |
2 to 8 |
(1, 1, 2) |
2 |
-1.1725050397 |
-0.0156121011 |
0 |
-1.1881171408 |
3 (0) |
2 |
(2, 2, 2) |
8 |
-1.1203818266 |
-0.0158587108 |
0 |
-1.1362405374 |
12 (24) |
27 |
(3, 3, 3) |
27 |
-1.1177121108 |
-0.0172763688 |
-7.1e-07 |
-1.1349891876 |
54 |
46 |
Three things stand out.
The reference moves most. The Hartree-Fock energy per cell rises by 0.12 Ha from Gamma to (3, 3, 3). Exact exchange on a finite mesh carries an error of order \(1/N_k\), and the Gamma point in a 6 bohr cube puts the images of each molecule only 6 bohr away. The multi-k SCF guide explains this tail of the BvK-ewald gauge and how to extrapolate it.
The correlation energy grows in magnitude with the supercell. It goes from -9.5 mHa at Gamma to -17.3 mHa on (3, 3, 3). On a small supercell the virtual space is a coarse sample of the Brillouin zone, so a pair misses part of its excitation space, and pairs between neighbouring molecules only appear once the supercell holds them. Converge the correlation energy per cell on its own mesh series; it does not converge at the same rate as the reference.
Pairs are screened on the larger supercells. On (2, 2, 2) the 8 occupied orbitals give 36 pairs; 12 are kept and 24 fall below the pair prescreening threshold
tcut_pairs. Their dropped energy is estimated from the semicanonical MP2 pair energies. This is where local correlation starts to pay off on a crystal.
The output block¶
Every run writes the correlation block to the .out file. This is the
(2, 2, 2) STO-3G run:
DLPNO-CCSD(T) local PNO-space (M3c) on the periodic GDF reference (RI: def2-svp-jk)
-----------------------------------------------------------------------------------
reference = KRHF/GDF, fit_source=classic; the retained fit is the correlation factors (same source)
k-mesh / cells = (2, 2, 2) / 8 (Gamma-centred, folded to the real BvK supercell)
correlation source = bvk-supercell, identity d87dad4272cf3a96... (full in .system)
exchange q=0 = BvK-ewald
occupied localisation = pipek-mezey (active block, by the adapter)
E(RHF reference) / cell = -1.1203818266 Ha
Threshold convention = NormalPNO
Truncation thresholds = tcut_pairs 0.0001 tcut_pno 3.33e-07 tcut_mkn 0.001 tcut_tno 0.0
Residual domain = pno
PNO density norm = mp2
PNO MP2 correction = on
frozen core / cell = 0 orbitals (ORCA 6.1 published count-only default)
Triples algorithm = rotated-occupied (T1) compatibility correction (triples_mode='t1')
pairs / avg PNOs = 12 / 1.3
E(PNO truncation corr) = 0.0000000000 Ha
E(CCSD correlation) / cell = -0.0158587108 Ha
triples / avg TNOs = 92 / 8.0 (smallest domain: 8)
E((T) correction) / cell = -0.0000000000 Ha
E(DLPNO-CCSD(T) corr) / cell = -0.0158587108 Ha
E(DLPNO-CCSD(T) total) / cell = -1.1362405374 Ha
supercell E(corr) / E(total) = -0.1268696862 Ha / -9.0899242989 Ha (8 cells)
The source line now reads bvk-supercell: the per-k overlap, Fock matrix
and retained fit were inverse-transformed to the real supercell and the
supercell orbitals rebuilt from the folded Fock matrix. The identity hash
names that exact source; the full value is in the .system manifest as
dlpno_source_identity, next to dlpno_kmesh, dlpno_n_cells,
dlpno_e_corr_per_cell and the rest of the dlpno_* fields. The last line
gives the supercell totals, which are \(N_k\) times the per-cell values.
A polarised basis¶
STO-3G is a teaching basis. In def2-SVP each H carries a p shell, the pairs get real PNO spaces and the triples correction becomes visible:
basis = vq.BasisSet(system.unit_cell_molecule(), "def2-svp")
for mesh in [(1, 1, 1), (1, 1, 2), (2, 2, 2)]:
... # same loop as above
Mesh |
E_HF per cell, Ha |
E_corr (CCSD) per cell, Ha |
E(T) per cell, Ha |
E_total per cell, Ha |
Pairs |
Avg PNOs |
Wall, s |
|---|---|---|---|---|---|---|---|
Gamma |
-1.2561489570 |
-0.0191910611 |
0 |
-1.2753400181 |
1 |
9.0 |
7 |
(1, 1, 2) |
-1.1889076672 |
-0.0262675726 |
-6.2e-05 |
-1.2152368592 |
3 |
11.7 |
26 |
(2, 2, 2) |
-1.1321596850 |
-0.0285352331 |
-3.4e-05 |
-1.1607285144 |
20 |
12.0 |
40 |
The correlation energy per cell is now about twice the STO-3G value and still growing on (2, 2, 2). The average of 11.7 PNOs per pair on (1, 1, 2) shows what the PNO compression buys: each pair keeps a dozen virtual directions out of the supercell’s virtual space.
An ionic crystal: rock-salt LiH¶
The same call works for any gapped 3-D cell. Rock-salt LiH in its primitive fcc cell, \(a = 4.084\) Å:
BOHR_PER_ANGSTROM = 1.0 / 0.529177210903
a = 4.084 * BOHR_PER_ANGSTROM
lattice = 0.5 * a * np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=float)
centre = 0.5 * lattice.sum(axis=0)
lih = vq.PeriodicSystem(dim=3, lattice=lattice, unit_cell=[
vq.Atom(3, [0.0, 0.0, 0.0]), vq.Atom(1, centre.tolist())])
basis = vq.BasisSet(lih.unit_cell_molecule(), "sto-3g")
for mesh in [(1, 1, 1), (2, 2, 2)]:
res = vq.run_periodic_job(lih, basis, method="dlpno-ccsd(t)",
kpoints=mesh, output="lih_" + "x".join(map(str, mesh)),
progress=False)
c = res.correlation
print(mesh, f"{c.e_hf_per_cell:.10f}", f"{c.e_corr_per_cell:.10f}",
f"{c.e_t_per_cell:.2e}", f"{c.e_total_per_cell:.10f}")
Mesh |
E_HF per cell, Ha |
E_corr (CCSD) per cell, Ha |
E(T) per cell, Ha |
E_total per cell, Ha |
Pairs / avg PNOs |
Wall, s |
|---|---|---|---|---|---|---|
Gamma |
-8.3351540806 |
-0.0068114824 |
-2.5e-06 |
-8.3419680624 |
3 / 4.0 |
40 |
(2, 2, 2) |
-7.9222475699 |
-0.0162333820 |
-2.8e-04 |
-7.9387617758 |
92 / 14.1 |
77 |
The published count-only frozen-core convention (ORCA 6.1) freezes no orbital on lithium, so both occupied orbitals of the cell, the Li 1s and the hydride pair, are correlated: three pairs at Gamma, 92 on the eight-cell supercell. Two lessons carry over from H2 and are sharper here. The Gamma-point reference of a compact ionic cell is 0.41 Ha per cell away from the (2, 2, 2) value: never correlate a Gamma-only reference of a small primitive cell and call it a crystal. And the triples correction is a hundred times larger on the (2, 2, 2) supercell than at Gamma, because the supercell holds triples that span neighbouring cells.
An open-shell cell¶
A cell with multiplicity > 1 runs on a KUHF reference and the spin-orbital
DLPNO-UCCSD solver, with spin-pure PNOs and the semicanonical local (T0)
triples. Nothing changes in the call. Here an H3 doublet in an 8 bohr cube:
h3 = vq.PeriodicSystem(3, np.diag([8.0, 8.0, 8.0]), [
vq.Atom(1, [3.3, 4.0, 4.0]), vq.Atom(1, [4.7, 4.0, 4.0]),
vq.Atom(1, [4.0, 6.0, 4.0])], multiplicity=2)
basis = vq.BasisSet(h3.unit_cell_molecule(), "sto-3g")
for mesh in [(1, 1, 1), (1, 1, 2)]:
res = vq.run_periodic_job(h3, basis, method="dlpno-ccsd(t)", kpoints=mesh,
output="h3_" + "x".join(map(str, mesh)), progress=False)
c, pd = res.correlation, res.periodic_dlpno
print(mesh, c.reference_kind, pd.n_occ_alpha, pd.n_occ_beta, c.backend,
f"{c.e_hf_per_cell:.10f}", f"{c.e_corr_per_cell:.10f}")
Mesh |
Reference |
Occupied alpha / beta (supercell) |
E_HF per cell, Ha |
E_corr per cell, Ha |
E_total per cell, Ha |
Wall, s |
|---|---|---|---|---|---|---|
Gamma |
UHF |
2 / 1 |
-1.4867525495 |
-0.0127577114 |
-1.4995102609 |
16 |
(1, 1, 2) |
UHF |
4 / 2 |
-1.4719338411 |
-0.0143995238 |
-1.4863333650 |
13 |
In this minimal basis no triple survives the TNO construction: the .out
reports the triples as “not executed for this result” and the (T) per cell is
zero. In a double-zeta basis the open-shell (T0) runs as on molecules. The
route key becomes periodic-gdf-dlpno-uccsd(t), and the manifest gains
dlpno_reference_kind, dlpno_n_occ_alpha and dlpno_n_vir_alpha. Pass
dlpno_ccsd_options=LocalUCCSDOptions(...) (from
vibeqc.dlpno.uccsd_local_solver) to change the open-shell solver’s
controls; passing it for a singlet cell selects the UHF reference.
What the route refuses¶
Every refusal fires before the reference SCF, so a request the route cannot honour costs nothing. These are the measured messages, abbreviated:
for kw in [dict(functional="pbe"), dict(jk_method="bipole"),
dict(kpoints=(2, 2, 2), symmetry_reduce_k=True),
dict(smearing="fermi", smearing_temperature=0.01),
dict(optimize=True)]:
try:
vq.run_periodic_job(system, basis, method="dlpno-ccsd(t)",
output="refused", progress=False, **kw)
except (ValueError, NotImplementedError) as exc:
print(type(exc).__name__, str(exc)[:110])
Request |
Exception |
Reason in the message |
|---|---|---|
|
|
DLPNO-CCSD(T) correlates a Hartree-Fock reference; drop |
|
|
The front door runs on the GDF route only; a BIPOLE reference has no certified same-source correlation factors. |
|
|
A symmetry-reduced reference keeps its orbitals on the irreducible wedge only. Run the full mesh. |
a |
|
Pass a mesh tuple; a |
smearing |
|
The engine needs an integer-aufbau determinant. |
|
|
DLPNO-CCSD(T) gradients do not exist. |
|
|
The GDF route runs on 3-D cells; embed a chain or slab in a vacuum box. |
|
|
The adapter localises the occupied orbitals itself; the engine’s localiser is not periodic-safe. |
The last one is worth understanding: the molecular engine localises with Boys orbitals, whose dipole integrals are not defined on a torus. The adapter therefore localises with Pipek-Mezey populations, which are, and asks the engine to leave the orbitals alone.
Theory¶
Bloch orbitals and the BvK supercell. A Gamma-centred mesh of \(N_k\) points samples exactly the crystal momenta that are periodic on a supercell of \(N_k\) cells, the Born-von Karman torus. The canonical orbitals of that torus are the Bloch orbitals \(\psi_{n\mathbf k}\) of the mesh, and real supercell orbitals follow from the inverse Bloch transform of the per-k Fock and overlap blocks,
so the folded problem is a real molecular problem on the supercell whose orbital energies equal the union of the per-k spectra. The adapter checks that equality to 1e-6 Ha and refuses a fold that breaks it (Monkhorst, Hendrik J. and Pack, James D. (1976). *Special points for Brillouin-zone integrations.* Physical Review B, **13**(12), 5188–5192. doi:[10.1103/PhysRevB.13.5188](https://doi.org/10.1103/PhysRevB.13.5188) for the meshes, McClain, James, Sun, Qiming, et al. (2017). *Gaussian-Based Coupled-Cluster Theory for the Ground-State and Band Structure of Solids.* Journal of Chemical Theory and Computation, **13**(3), 1209–1218. doi:[10.1021/acs.jctc.7b00049](https://doi.org/10.1021/acs.jctc.7b00049) for coupled cluster on the same torus).
Local correlation on the torus. The occupied supercell orbitals are
localised by maximising the Pipek-Mezey functional
\(\sum_i \sum_A (Q_A^{ii})^2\) of atomic orbital populations
(Pipek, János and Mezey, Paul G. (1989). *A fast intrinsic localization procedure applicable for ab initio and semiempirical linear combination of atomic orbital wave functions.* Journal of Chemical Physics, **90**(9), 4916–4926. doi:[10.1063/1.456588](https://doi.org/10.1063/1.456588)). Pair distances for coupling_radius and
the local triples use the minimum image on the supercell. From there on the
calculation is the molecular DLPNO-CCSD(T) of Riplinger and Neese
(Riplinger, Christoph and Neese, Frank (2013). *An efficient and near linear scaling pair natural orbital based local coupled cluster method.* Journal of Chemical Physics, **138**(3), 034106. doi:[10.1063/1.4773581](https://doi.org/10.1063/1.4773581), Riplinger, Christoph, Sandhoefer, Barbara, et al. (2013). *Natural triple excitations in local coupled cluster calculations with pair natural orbitals.* Journal of Chemical Physics, **139**(13), 134101. doi:[10.1063/1.4821834](https://doi.org/10.1063/1.4821834)):
semicanonical MP2 pair densities define the pair natural orbitals,
PNOs with occupation \(n^{ij}_a\) below tcut_pno are dropped and their MP2
energy is added back, weak pairs below tcut_pairs are kept at the MP2
level, and the CCSD amplitude equations are solved with every term in the
PNO space of the amplitude that carries the contracted index. The default
closed-shell (T1) evaluates the canonical (T) on the rotated amplitudes
(Guo, Yang, Riplinger, Christoph, et al. (2018). *Communication: An improved linear scaling perturbative triples correction for the domain based local pair-natural orbital based singles and doubles coupled cluster method [DLPNO-CCSD(T)].* Journal of Chemical Physics, **148**(1), 011101. doi:[10.1063/1.5011798](https://doi.org/10.1063/1.5011798)); the open-shell route uses the
semicanonical (T0) of Guo, Yang, Riplinger, Christoph, et al. (2020). *Linear scaling perturbative triples correction approximations for open-shell domain-based local pair natural orbital coupled cluster singles and doubles theory [DLPNO-CCSD(T0/T)].* Journal of Chemical Physics, **152**(2), 024116. doi:[10.1063/1.5127550](https://doi.org/10.1063/1.5127550) on the
spin-orbital solver.
Finite-size behaviour. The reference and the correlation energy per cell converge at different rates. The HF exchange error of the BvK-ewald gauge decays as \(1/N_k\) after the Madelung correction of the \(\mathbf q = 0\) singularity (Gygi, F. and Baldereschi, A. (1986). *Self-consistent Hartree-Fock and screened-exchange calculations in solids: Application to silicon.* Physical Review B, **34**, 4405–4408. doi:[10.1103/PhysRevB.34.4405](https://doi.org/10.1103/PhysRevB.34.4405)), while the correlation energy per cell converges as the supercell admits the long-range pairs and the low-momentum excitations, at a rate that depends on the system and is in general different from the reference’s. Use the same mesh for every structure you compare, converge each part on its own series, and extrapolate the two separately when you need a bulk value. Recent work on periodic local correlation discusses these limits on larger cells (Nejad, Arman, Zhu, Andrew, et al. (2025). *DLPNO-MP2 for periodic systems. I. Periodic boundary conditions.* Journal of Chemical Physics, **163**(21), 214107. doi:[10.1063/5.0290816](https://doi.org/10.1063/5.0290816), Zhu, Andrew, Nejad, Arman, et al. (2025). *DLPNO-MP2 for periodic systems. II. Megacell embedding.* Journal of Chemical Physics, **163**(21), 214108. doi:[10.1063/5.0290819](https://doi.org/10.1063/5.0290819)).
Variations¶
Thresholds.
dlpno_thresholds="tight"or"loose"changes the PNO, pair and domain cuts exactly as onrun_job. Compare a tight run with the default on your smallest mesh before trusting a mesh series.Frozen core.
frozen_core=Falsecorrelates all electrons;frozen_core=2freezes two orbitals per cell.Local triples.
dlpno_ccsd_options=LocalCCSDOptions(triples_mode="local")runs the compiled local (T0) kernel, whose cost grows more slowly with the supercell than the canonical(T1).The library route.
vibeqc.periodic_dlpno.run_periodic_dlpno_ccsd(system, "def2-svp", kmesh=(2, 2, 2))returns the same numbers without the output files, which is convenient inside a mesh-convergence script.
Pitfalls¶
A Gamma-only reference of a small cell is not a crystal. The LiH and H2 tables above show reference errors of 0.1 to 0.4 Ha per cell at Gamma.
Supercell cost. The engine solves a supercell of \(N_k\) cells, and the default
(T1)costs a canonical (T) on that supercell. A (3, 3, 3) mesh of a 10-atom cell is a 270-atom molecular problem.Minimal-basis triples. In STO-3G the (T) correction of H2 is zero or close to it. Read triples from at least a double-zeta basis.
Fock convention. The amplitude equations keep the Ewald exchange term of the SCF Fock operator. A code that strips it, such as PySCF’s default
KRCCSD, reports a lower finite-mesh correlation energy with the same thermodynamic limit; compare like with like.
See also¶
Local correlation on crystals, the user guide with the option tables, the adapters and the evidence.
DLPNO methods for thresholds, triples modes and residual domains.
Local correlation with DLPNO, the molecular tutorial.
Multi-k SCF for the reference and its finite-size tail.