DLPNO methods (MP2, CCSD, CCSD(T))¶
DLPNO-MP2 is local second-order Møller-Plesset theory in the domain-based pair-natural-orbital framework of Pinski, Riplinger, Valeev and Neese (J. Chem. Phys. 143, 034108 (2015)). Occupied orbitals are Foster-Boys localised, every occupied pair gets a compact virtual space of pair natural orbitals built inside its own projected atomic-orbital domain, and the MP2 residual equations are solved with the localised-orbital Fock coupling. The result tracks canonical RI-MP2 at a fraction of the asymptotic cost, with controllable truncation thresholds.
At a glance¶
Entry point |
|
Reference |
closed-shell RHF, or UHF for open shells (auto-routed to DLPNO-UMP2) |
Auxiliary basis |
auto-resolved correlation (“ri”) fit of the orbital basis |
Options |
|
Accuracy (defaults) |
≥ 99.8 % of canonical RI-MP2 E_corr (TCutPNO = 1e-8) |
Exactness limit |
thresholds → 0 reproduces canonical RI-MP2 to ≤ 1 µHa |
Quick start¶
from vibeqc import run_job
from vibeqc._vibeqc_core import Atom, Molecule
mol = Molecule(
[
Atom(8, [0.000, 0.000, 0.000]),
Atom(1, [0.000, 1.499, -1.160]),
Atom(1, [0.000, -1.499, -1.160]),
], # bohr
charge=0,
multiplicity=1,
)
result = run_job(mol, basis="def2-svp", method="dlpno-mp2", output="h2o")
print(result.dlpno_mp2.e_corr) # DLPNO-MP2 correlation energy (Ha)
print(result.energy_total) # RHF + correlation
The .out file reports the energy decomposition, iterated pair
energies, the semicanonical PNO-truncation correction, and the
distant-pair dipole estimate:
DLPNO-MP2 (Pinski 2015; RI: def2-svp-rifit)
------------------------------------------------------------------------------
E(RHF reference) = -75.9547597194 Ha
pairs kept / screened = 15 / 0 (frozen core: 0)
avg PNOs per pair = 14.9
E(iterated pairs) = -0.2062668853 Ha
E(PNO truncation corr) = -0.0000032714 Ha
E(distant-pair est.) = 0.0000000000 Ha
E(DLPNO-MP2 corr) = -0.2062701567 Ha
E(DLPNO-MP2 total) = -76.1610298761 Ha
Thresholds and options¶
from vibeqc.dlpno.mp2 import DLPNOMP2Options
opts = DLPNOMP2Options(
n_frozen=1, # freeze core orbitals (before localisation)
tcut_pno=1e-9, # tighter PNO truncation (default 1e-8)
tcut_pairs=1e-6, # screen distant pairs via the dipole estimate
)
result = run_job(mol, basis="def2-svp", method="dlpno-mp2",
dlpno_options=opts, output="h2o_tight")
Option |
Default |
Meaning |
|---|---|---|
|
1e-8 |
PNO occupation cutoff (strong pairs). Tighter = more PNOs = closer to canonical. 1e-8 recovers ≈ 99.87 % of E_corr on H₂O/def2-SVP, 1e-9 ≈ 99.93 %. |
|
1e-7 |
PNO cutoff for weak pairs. |
|
1e-3 |
Mulliken threshold for domain atoms (0 = full domains). |
|
1e-6 |
Distant-pair screening on the semicanonical dipole-dipole estimate, keeps the pair list O(N) for extended systems; screened pairs contribute through |
|
0 |
Frozen-core orbitals, validated against a canonical frozen-core reference. |
|
|
|
|
|
Domain-restricted (local) density fitting, see below. |
|
4.0 |
Fit-domain extension (bohr) when |
Setting every threshold to zero is a supported validation mode: the
energy then reproduces canonical RI-MP2 with the same fitting basis to
≤ 1 µHa (this is asserted in tests/test_dlpno_mp2.py).
Local density fitting (experimental, reduced-scaling)¶
With local_df=True, each pair’s exchange integrals are refit using
only the auxiliary functions within fit_buffer bohr of the pair’s
domain, instead of the global RI metric, the per-pair fit cost then
stays bounded by the local neighbourhood as the molecule grows (the
foundation of linear scaling; result.dlpno_mp2.fit_dim_per_pair
records the per-pair fit dimension). A full fit domain reproduces the
global RI exactly. This is the first reduced-scaling increment; it
currently complements rather than replaces the global path (default
local_df=False), and the end-to-end speedup arrives with the
integral-direct local build (see handovers/HANDOVER_GATED_ITEMS.md,
G-CORR-005).
Result object¶
result.dlpno_mp2 carries the full decomposition:
e_corr = e_corr_iterated + e_pno_correction + e_distant, per-pair
energies (pair_energies, absolute occupied indices), PNO counts per
pair (pno_per_pair), per-pair fit dimensions (fit_dim_per_pair),
screening statistics, and the iteration trace.
DLPNO-CCSD and DLPNO-CCSD(T)¶
method="dlpno-ccsd" runs the reduced-scaling local solver
(dlpno.ccsd_local_solver): each occupied pair’s CCSD residual is
evaluated in its own PNO basis, with amplitudes projected between pair
domains. It is FCI-anchored, in the full-domain limit it reproduces
canonical closed-shell CCSD bit-for-bit (≤ 1 µHa; on H₂ that equals FCI),
and default PNO truncation recovers ≈ 99.9 % of the correlation energy
(tests/test_dlpno_ccsd_solver.py).
from vibeqc import run_job
from vibeqc.dlpno.ccsd_local_solver import LocalCCSDOptions
result = run_job(mol, basis="def2-svp", method="dlpno-ccsd",
dlpno_ccsd_options=LocalCCSDOptions(tcut_pno=1e-7),
output="h2o_cc")
print(result.dlpno_ccsd.e_corr, result.energy_total)
Each pair couples only to occupied orbitals within coupling_radius (bohr)
of either pair index, the lever that takes the occupied coupling sums from
O(N⁴) to O(N²). The 12-bohr default is a bit-identical no-op on any
molecule under ~12-bohr extent (the common case); on larger systems it
keeps the dropped long-range coupling well below the PNO truncation error
(~7 µHa on a 25-bohr H₂ chain vs the ~0.1% PNO error, the same
controlled-locality bargain as the default sparse pair list). Set
coupling_radius=0 for full coupling, the exact reference the full-domain
ratchet pins. result.dlpno_ccsd reports avg_coupled_occ, the mean
local-set size, which saturates as the system grows at fixed radius
(tests/test_dlpno_ccsd_solver.py::TestSparseCoupling).
method="dlpno-ccsd(t)" runs the same local solver plus the DLPNO-(T1)
(dlpno.triples_local) on the converged amplitudes: the (T) correction is
evaluated per occupied triple in a TNO domain, with the off-diagonal
localised Fock coupling restored iteratively via DLPNO-(T1) (Guo, Riplinger
et al., J. Chem. Phys. 148, 011101 (2018)) – the same exact-(T) accuracy
as the canonical (T) at TNO-domain scaling. Set triples_mode="local"
for the older DLPNO-(T0) (diagonal localised Fock, ~0.1 kcal/mol looser).
At full domains with canonical occupieds it reproduces canonical CCSD(T)
exactly (<= 1 nHa, the (T) parity ratchet). The (T) vanishes
identically for two-electron systems.
Larger systems: the SCF reference and memory¶
DLPNO is a reduced-scaling correlation method, but it runs on top of a mean-field SCF reference, and that reference is what sets the memory footprint on larger systems:
The DLPNO step itself is reduced-scaling (per-pair PNO domains, no dense
n_occ^2 n_virt^2orn_mo^4tensor). n-octane / cc-pVTZ (492 basis functions) DLPNO-MP2 peaks at a few GB, not hundreds.Use a density-fitted SCF reference for larger systems. Set
density_fit=True(with anaux_basis) on the reference options. A conventional in-core 4-index SCF would materialise then_basis^4ERI tensor (~436 GB at 492 functions); the integral-direct SCF (the default above 200 functions) avoids it, and density fitting is faster still:from vibeqc import RHFOptions, run_job o = RHFOptions(density_fit=True, aux_basis="cc-pvtz-jkfit") run_job(mol, basis="cc-pvtz", method="dlpno-mp2", rhf_options=o) # ~13 GB
The O(N^6) pilot (DLPNOCCSDPilotOptions, opt-in) is a different
animal: it is the small-system correctness oracle the local solver is
validated against, and it forms dense full-virtual-space integrals (memory
~ n_mo^4, set by the basis size and independent of tcut_pno). It is
hard-capped at max_nbf=64 for that reason. Do not use it for production
runs; the default run_job(method="dlpno-ccsd") already uses the
reduced-scaling local solver, which has no such cap.
Accuracy¶
Against canonical CCSD(T) on a 7-molecule set (def2-SVP, all-electron) the
defaults (tcut_pno=1e-7 + DLPNO-(T1) + tcut_pairs=1e-4) give a mean
absolute error of 0.37 kcal/mol, within chemical accuracy:
from vibeqc.dlpno.ccsd_local_solver import LocalCCSDOptions, run_local_dlpno_ccsd
r = run_local_dlpno_ccsd(mol, basis, hf, df, LocalCCSDOptions(compute_triples=True))
# MAE 0.37 kcal/mol vs canonical CCSD(T); tcut_pno=1e-8 → 0.17 (ORCA: 0.16)
Tightening to tcut_pno=1e-8 (the compiled C++ residual keeps it
affordable) brings the MAE to 0.17 kcal/mol, matching ORCA 6.1’s 0.16.
An earlier tcut_pno=1e-7 + DLPNO-(T0) pairing gave MAE 0.30 only via an
accidental cancellation (CCSD over-recovery against the (T0) error); (T1)
removes the (T0) error honestly and 1e-8 removes the over-recovery, so they
target different errors and combine to the cancellation-free 0.17.
triples_mode="local" selects DLPNO-(T0) for fast scans and
triples_mode="exact" the O(N⁷) (T) oracle.
Pair screening (tcut_pairs=1e-4, ORCA’s TCutPairs, default-on)
treats a pair whose full-virtual MP2 energy is below the threshold at MP2
level instead of CCSD: accuracy-neutral on compact molecules (a no-op on
5-occupied cases, the weak ~10-14 % tail on larger ones) and a ~2x
linear-scaling speedup on extended systems; set tcut_pairs=0 to
disable. The reproducible benchmark and ORCA comparison are
examples/molecular/benchmark-dlpno-ccsd-t.py.
Closed-shell (T1): choosing tcut_tno¶
LocalCCSDOptions.tcut_tno defaults to 0.0, the full TNO union span, so a
default dlpno-ccsd(t) job performs no triples-domain truncation. If you opt
into a positive value, these are the measured trade-offs on H2O/def2-SVP
(2026-07-29; full domain e_t = -0.003009059087680666 Ha at 19.0 TNOs per
triple, which is the entire virtual space):
|
error vs full domain |
mean TNOs |
smallest domain |
collapsed triples |
|---|---|---|---|---|
|
|
18.52 |
7 |
0 |
|
|
18.20 |
4 |
0 |
|
|
17.80 |
4 |
0 |
|
|
15.68 |
1 |
1 |
|
|
10.68 |
1 |
1 |
The closed-shell route tolerates much coarser thresholds than the open-shell
engine: 1e-6 costs under 0.1 µHa here, where the open-shell (T1) costs
30 to 64 µHa at the same setting.
The error curve breaks exactly where the first triple collapses. A spatial
triple excitation promotes three electrons into three distinct virtuals, so
a domain retaining fewer than three cannot host one and contributes exactly
zero rather than approximately. Between 1e-6 and 1e-5 one triple falls to
a single virtual and the error jumps 400-fold. LocalCCSDResult reports this
directly:
result = run_local_dlpno_ccsd(mol, basis, rhf, df, options)
result.avg_tno # mean retained domain per distinct triple
result.n_degenerate_tno_triples # triples truncated below three virtuals
result.tno_per_triple # per-triple domain sizes
Treat a nonzero n_degenerate_tno_triples as a signal that tcut_tno has
truncated past the point where part of the correction exists, rather than as
a merely coarse setting. triples_mode="exact" expands to the full virtual
space and builds no TNO domain, so it reports an empty tno_per_triple.
A run_job(method="dlpno-ccsd(t)") run reports the same thing without any
Python bookkeeping. The .out block carries a domain line beside the pair
line, and a collapsed domain raises a warning:
pairs / avg PNOs = 15 / 19.0 (frozen core: 0)
E(CCSD correlation) = -0.2130338714 Ha
triples / avg TNOs = 25 / 15.7 (smallest domain: 1)
E((T) correction) = -0.0029756723 Ha
...
WARNING: tcut_tno truncated 1 of 25 triples below three virtuals; those
contribute exactly zero to (T), so part of the correction is missing rather
than approximated. Lower tcut_tno (0 keeps the full domain).
coupling_radius is the occupied-side counterpart: it drops whole occupied
triple keys rather than shrinking each domain, and it can be far more
aggressive than it looks. On an H10 chain at 1.8 bohr spacing (16.2 bohr long,
strongly coupled occupieds) a coupling_radius of 5 bohr drops 16 of the 25
distinct triple keys and takes 90% of the triples energy with them. That
is a property of the system rather than a defect: correlation in a delocalized
chain is genuinely long-ranged, so a locality cutoff is the wrong
approximation there. The count is now reported so the choice is visible:
triples / avg TNOs = 9 / 5.0 (smallest domain: 5) (16 screened by coupling_radius)
LocalCCSDResult.n_triple_keys and .n_triple_keys_screened carry the same
numbers. Screening behaves comparably in both triples modes: measured against
its own unscreened reference it costs 0.074 µHa under (T1) and 0.071 µHa
under (T0) on H2O + H2 at 8 Å, and 1459 µHa against 1142 µHa on the H10
chain.
The dlpno_ccsd_done structured-log event carries n_tno_triples,
avg_tno_per_triple, min_tno_per_triple, n_degenerate_tno_triples and
n_triple_keys_screened alongside the existing pair fields. The open-shell route runs the UCCSD(T)
pilot, which builds no TNO domain, so the line is omitted there rather than
printed as zeros.
Current limitations¶
DLPNO-MP2 runs on closed-shell RHF and open-shell UHF references:
run_job(method="dlpno-mp2")auto-routes a multiplicity > 1 system to UHF + DLPNO-UMP2 (spin-channel resolved αα/ββ/αβ;res.dlpno_ump2). Pass aDLPNOUMP2Optionsasdlpno_optionsto control its thresholds (localise,tcut_pno,tcut_pairs,n_frozen). DLPNO-CCSD/(T) also run on open-shell UHF references:run_job(method="dlpno-ccsd"| "dlpno-ccsd(t)")auto-routes a multiplicity > 1 system to UHF + the spin-orbital DLPNO-UCCSD(T) pilot (res.dlpno_ccsd; pass aDLPNOUCCSDPilotOptionsasdlpno_ccsd_options). The open-shell path is the O(N⁶) correctness pilot (capped atmax_nbf=64), not the reduced-scaling closed-shell local solver; a near-linear-scaling open-shell engine is the roadmap item. The first opt-in engine rung is available directly asrun_local_dlpno_uccsd: it evaluates native UCCSD residuals in per-pair PNO/local-occupied domains and is exact against the pilot at full domains. Its default-offtcut_pairsoption screens weak spin-orbital pairs using their full-virtual MP2 energies and retains those MP2 contributions; compact OH and a separated H + H2 doublet pin the exact default and the lossless distant-pair limit. It is not wired intorun_jobyet. A second default-off option,tcut_mkn, selects each pair’s atoms from the two occupied-orbital Mulliken populations and builds separate alpha/beta semicanonical PAOs on their union. A separated OH + H2 doublet pins the reduced pair-virtual span and exact fragment limit. The default-offtcut_pno_singlesoption sums each occupied orbital’s full-virtual MP2 pair-density contributions and retains same-spin singles-specific natural orbitals above the threshold, optionally inside that occupied orbital’s PAO atom domain. The zero default keeps the full spin-virtual singles space. A separated OH + H2 doublet pins both the domain reduction and its exact fragment limit. The same direct solver has an experimental, default-off open-shell DLPNO-(T0) path: setcompute_triples=Trueand control the triple-natural-orbital occupation cutoff withtcut_tno. It streams each distinct occupied triple through the native spin-orbital kernel, reportse_tplus triple/TNO diagnostics, and reproduces the dense UCCSD(T) pilot when every threshold is zero andlocalise="none". This direct API is not yet a public production route. Settriples_mode="t1"to opt into spin-block semicanonical occupied coupling: it removes the(T0)localized occupied-Fock error while retaining per-triple TNO streaming. That rotation takes the occupied indices out of the localised basis, where a weak-pair label or an occupied distance has no meaning, so(T1)cannot carry a screened triple list. It is decided on the realised screened set rather than on the raw thresholds:tcut_pairsorcoupling_radiusvalues that screen no triple leave(T1)reachable and exact on the full triple list, while any setting that actually removes one fails closed with the offending count. Usetriples_mode="t0"for a genuinely screened open-shell triples run. Broader radical benchmarks and public wiring remain gated.Positive
tcut_tnounder(T1)is calibrated on OH/def2-SVP (2026-07-29; full domaine_t = -1.778474224055274e-03Ha at 29.0 spin virtuals per triple). Truncation only ever loses correlation, so every entry is a positive error:tcut_tnoerror vs full domain
mean TNOs/triple
1e-12+11.27µHa26.07
1e-9+12.39µHa24.73
1e-8+20.05µHa23.49
1e-7+34.39µHa21.85
1e-6+63.37µHa19.92
1e-5+156.16µHa16.77
1e-4+759.06µHa10.55 (collapsing; see below)
1e-9, theT_CutTNOdefault of Guo et al. (2018), costs12 µHa(0.008 kcal/mol) for a 15% smaller triples domain and is the recommended starting point. There is no free threshold: the smallest value tested already costs11 µHa, sotcut_tno=0remains the only exact setting.The same sweep on two more radicals, including a high-spin case, gives the same picture (error vs full domain;
d= triples that collapsed below three spin virtuals):system
multiplicity
1e-91e-61e-4OH
2
+12.39µHa+63.37µHa+759.06µHa,d=13NH2
2
+7.62µHa+37.02µHa+1177.91µHa,d=9CH2
3
+5.73µHa+30.25µHa+706.18µHa,d=10Across all three,
1e-9stays within12µHa and1e-6within64µHa, and the collapse sets in between1e-6and1e-4in every case. The triplet is not an outlier, so the recommendation does not depend on spin multiplicity over this (small) sample.1e-6is the coarsest calibrated value on every system tested. Through1e-6every triple keeps at least three spin virtuals (smallest domain 8), so the error is pure truncation. By1e-4the smallest domain is a single virtual and 13 of the 84 triples have collapsed to identically zero, so that row’s+759µHa mixes truncation with collapse and should not be read as a truncation error.Warning
Do not calibrate this threshold on a minimal basis. OH/STO-3G retains only 3.0 TNOs per triple at full domain, and a triple excitation needs three distinct spin virtuals, so any positive
tcut_tnodrops 75 of its 84 triples below three ande_tcollapses from-2.29e-07Ha to order1e-46: the correction vanishes identically instead of degrading. The newLocalUCCSDResult.n_degenerate_tno_triplescounts triples in that state; a nonzero value means the threshold has truncated past the point where the correction exists, not that it is merely small.Energies only, no analytic gradients.
dlpno-ccsd(t)runs the local solver + local DLPNO-(T1) on the converged amplitudes (exact == canonical CCSD(T) at full domains); the (T) uses a spatial closed-shell kernel (no spin-orbital redundancy), validated to machine precision against the spin-orbital reference. The per-pair CCSD residual runs in compiled C++ when the core provides it (vibeqc::dlpno_pair_residual, bit-for-bit identical to the numpy kernel, 4-8× faster and growing with PNO size), with an automatic numpy fallback; the (T) is numpy (already BLAS-bound). The local CCSD solver couples all pairs by default (reduced-scaling) with an opt-incoupling_radiusfor linear scaling on extended systems. Remaining production follow-ups are tracked inhandovers/HANDOVER_GATED_ITEMS.md, G-CORR-005.
Citations¶
Jobs running dlpno-mp2 emit the method papers into the
.references / .bibtex outputs automatically: Møller-Plesset 1934,
Feyereisen 1993 (RI), Pinski 2015 (DLPNO-MP2), and Foster-Boys 1960
(localisation).
See also¶
-
worked walkthrough with the TCutPNO convergence experiment.
examples/molecular/input-h2o-dlpno-mp2.pyand…-dlpno-ccsd-t.pyready-to-run scripts.
MP2 and double hybrids, canonical MP2, SCS/SOS variants, double-hybrid functionals.
Density fitting, auxiliary-basis families and auto-resolution.
AICCM A-namespace correlation APIs:
run_ccm_mp2andrun_ccm_ccsdwithmethod="aiccm2026dev-a"are union-and-weight Γ-CCM methods. Theccm_dlpno_mp2andccm_dlpno_ccsdAPIs instead use a neutral fitted-torus control; their A-namespace location does not make them Γ-CCM or χ-CCM results.χ-CCM / aiccm2026dev-b: periodic DLPNO-MP2 and DLPNO-CCSD(T) on the finite torus (χ-CCM, 3-D).