Constrained DFT
Charge- and spin-constrained DFT, and the electron-transfer couplings that follow from it.
Run it
Python and the Rust library; there is no CLI section.
run_cdft(mol, basis_set, constraints, functional=None, ...)returns aCdftResult: a constrained UHF solve, or UKS whenfunctionalnames a libxc functional other than"HF"(Noneand"HF", any case, give UHF).CdftConstraint(atoms, target, kind="charge")defines one fragment constraint.atomsare 0-based atom indices.targetis the electron population on the fragment, the Becke-weighted trace \( \mathrm{Tr}[W D] \), not a net charge: \( N_\alpha + N_\beta \) forkind="charge"and \( N_\alpha - N_\beta \) forkind="spin". A neutral He atom has a charge population of 2.0; He⁺ has 1.0.cdft_coupling(state_a, state_b)returns aCdftCouplingResultwith the Wu–Van Voorhis couplingh_ab, the determinant overlaps_aband the two diabat energiese_a,e_b. The raw element uses each state's free energy F = E + λN, which makes the coupling independent of a constant shift of the constraint operator.
In Rust the entry points are ferric_scf::cdft_driver::solve_cdft_uhf and
ferric_scf::cdft_coupling::coupling_hab; see the
Rust API.
This example reproduces the HeNe⁺ constrained solution that
crates/ferric-scf/tests/cdft_outer_loop.rs pins (E = −130.4021906 Ha,
λ = −2.754 Ha per electron), with the He fragment held at 2.0 electrons:
import ferric
# HeNe+ doublet at 2.0 Å; hold the He atom (index 0) at 2.0 electrons.
mol = ferric.Molecule.from_xyz_string(
"2\nHeNe+\nHe 0.0 0.0 0.0\nNe 0.0 0.0 2.0\n", 1, 2
)
r = ferric.run_cdft(
mol,
ferric.BasisSet.bundled("def2-svp"),
[ferric.CdftConstraint([0], 2.0, kind="charge")],
guess="hcore",
level_shift=0.5,
scf_max_iter=400,
lambda_tol=1e-5,
max_outer=40,
stability_descent=False,
grid_radial=99,
grid_angular=302,
)
print(r.converged)
print(f"{r.energy:.6f}")
print(f"{r.populations[0]:.5f}")
True
-130.402190
2.00000
r.energy is the ordinary UHF/UKS energy at the constrained density, without
the constraint term. r.lambdas holds the multipliers (Ha per electron) and
r.weight_matrix(i) the AO weight operator of constraint i.
What to know before using it:
- A returned result has a converged λ loop. When the outer loop exceeds
max_outer,run_cdftraisesRuntimeError. The inner SCF at the final λ can still be unconverged, so checkr.converged: it is true only when the inner SCF converged and every constraint is met tolambda_tol. - A constraint is applied at least once before it counts as met. The
loop starts at λ = 0, where the state is the unconstrained one. If its
population is within
lambda_tolof the target but more than 1e-3 electrons away, the loop still takes a Newton step rather than returning the unconstrained state as if it were constrained. Within 1e-3 the unconstrained state is returned with λ = 0: that is the measured spread of one state's population across grids, level shifts and guesses (up to 8.9e-5), with a margin. A loose tolerance can therefore return a state that is only partly constrained, so checkr.max_constraint_error(or the per-constraintr.residuals) to see how far from the target it is. The stability-descent restarts follow the same rule. stability_descentdefaults to True here (inrun_uhfit defaults to False). After the λ loop converges, a constrained saddle point is followed downhill to a lower state that still meets the constraint. The example turns it off to reproduce the pinned solution; with it on, this HeNe⁺ case ends about 0.0245 Ha lower. The descent is skipped for a KS reference.- The weight grid defaults to 99 × 302. It must resolve populations below
lambda_tol; 75 × 110 resolves them only to about 1e-4. Settinggrid_radialorgrid_angularuses that grid for the XC quadrature too. - The UKS path is the one compared against another code. Constrained
UKS/PBE matches NWChem 7.2.2 (see Accuracy). The UHF path
(
functional=Noneor"HF") has no comparison for energies along a target scan or for couplings: NWChem's standalone SCF (Hartree–Fock) module has no cDFT, because the Becke weight operator is built on the XC grid of its DFT module. Constrained UHF state selection on HeNe⁺ and LiH⁺ is compared through that DFT module, with Hartree–Fock exchange and a vanishing Slater term extrapolated to zero (ferric-scf/tests/validation_cdft_state.rs). - One constraint is the tested case. For one constraint the outer loop is a Newton step kept inside a sign-change bracket. It backs off from a λ whose inner SCF does not converge, and it discards a finite-difference derivative whose two inner solves landed in different SCF states. With several constraints the outer loop is a plain k × k Newton step without these safeguards.
cdft_couplinghas strict preconditions and raisesValueErrorwhen one fails. Each state carries exactly onekind="charge"constraint, both are converged, and both come from the same molecule, geometry, basis, charge and multiplicity, and the same Hamiltonian: functional,df_j_aux/df_k_aux,k_builder, XC grid, point charges and external field. Two states that are the same determinant (\( |S_{ab}| \to 1 \)) also raise. The sign ofh_abis a determinant-phase convention; compare \( |H_{ab}| \).
Accuracy
Constrained UKS/PBE energies and multipliers are compared against NWChem 7.2.2
cdft ... pop becke on LiH, HF and H2O⁺ (charge and spin constraints) at
6-31G and def2-SVP (ferric-scf/tests/validation_cdft.rs): E(N) −
E_unconstrained to 2.0e-7 Ha and λ to 1.1e-6 against NWChem's grid limit, and
dE/dN = −λ to 2.3e-12 Ha (this identity is checked at def2-SVP, on the first
target of each constraint kind); numbers are on Capabilities and
validation. No external reference value
is stated for UHF-cDFT energies apart from the HeNe⁺ and LiH⁺ states in
validation_cdft_state.rs. On HeNe⁺/def2-SVP at R = 2.0 Å the hole-on-Ne
state (N_He = 2) is NWChem's lower state once NWChem's Becke partition is
given ferric's He/Ne radius ratio (0.667; NWChem's own is 0.700). Against
NWChem's native partition it sits 0.563 eV higher. The offset comes from the
partition: with NWChem's radii patched into
ferric's radius table (a one-off measurement, not a test), ferric reaches
−130.447404 Ha against NWChem's native −130.447401 Ha. The He₂⁺ coupling
ingredients (determinant overlap, one- and two-electron transition elements,
|V|) match NWChem's et module to ≤ 6e-11 Ha on NWChem's own determinants,
and the KS diabats and couplings match end to end when started from NWChem's
λ, except def2-SVP at 3.50 Å, where inner SCF solves within 1e-9 in λ of the
root do not converge in 100 iterations (see Capabilities and
validation). These diabats are validated only
from NWChem's λ, set through the Rust RhfConfig::cdft_lambda_init;
run_cdft starts from λ = 0, where the symmetric pair is delocalized and the
inner SCF is bistable. The tests also check the coupling kernel on synthetic
matrices and He₂⁺ identities (ferric-scf/tests/cdft_coupling.rs). On HeNe⁺,
an asymmetric pair, the same file checks that both diabats carry the requested
charge, that the hole-on-He state is reached through either fragment (3.9e-6
Ha apart), that the hole prefers Ne, the lower-IP atom (gap 1.7646 eV with the
default stability descent; 1.0975 eV is a saddle point),
and that H_ab is invariant under W → W − cS. These run at R = 2.0 Å only: from
the hcore start, at 2.5, 3.5 and 4.0 Å the outer loop does not converge or
returns the unconstrained state, and from MINAO at 2.5 Å the two routes to the
hole-on-Ne state disagree by 6e-4 Ha. |H_ab| is not asserted on HeNe⁺;
cdft_coupling_hene.rs probes it over a distance series. The tests also check
exact identities on LiH/def2-SVP (cdft_uhf.rs): the constraint is satisfied,
λ = 0 reproduces plain UHF, and the constraint composes with an external point
charge. The Python tests (crates/ferric-python/tests/test_cdft.py) rerun
those configurations through the bindings and check cdft_coupling against an
independent transition-density construction. Apart from the HeNe⁺ and LiH⁺
states above, treat UHF-cDFT energies as unvalidated against other codes; see
Capabilities and validation.
The response connection
A cDFT constraint couples a Lagrange multiplier \( \lambda \) to a fragment-weighted density operator. The derivative
\[ \frac{\partial N}{\partial \lambda} \]
is the charge moved per unit constraint potential, a susceptibility. cDFT therefore probes the same object as RPA and GW and attenuated MP2, through a different coupling.
Implementation
- Fragment charge and spin constraints via a grid-Becke weight operator
- A nested Lagrange-multiplier solve (Wu–Van Voorhis): an inner SCF at fixed \( \lambda \), an outer Newton iteration on \( \lambda \) itself
Each outer step is a full converged inner solve, so cDFT costs several plain SCFs.
Electron-transfer coupling
The coupling \( H_{ab} \) between two charge-localized diabatic states follows from a non-orthogonal determinant overlap, computed via Löwdin biorthogonalization. It is the matrix element governing electron-transfer rates in Marcus theory.
Tolerance and coupling error
The λ convergence tolerance (lambda_tol in Python, cdft_lambda_tol in
Rust, default 1e-5 electrons) affects the coupling: a loosely converged
\( \lambda \) produces diabatic states that miss their targets, and
\( H_{ab} \) inherits that error. Tighten it before trusting a coupling. The exception is a
constraint whose population barely responds to \( \lambda \), such as He₂⁺
at the localized-hole plateau with a UHF reference: on He₂⁺/def2-SVP UHF at
2.5–3.5 Å the outer loop does not converge in 40 iterations at 1e-5, while at
1e-2 (what the tests use) it stops after 3 with λ ≈ 0.91 and residuals of
1.6e-3 to 7.8e-3 electrons, so check r.max_constraint_error before trusting
the state. With a UKS/PBE reference the He₂⁺ diabats do converge from λ = 0 at
1e-10 (validation_cdft_et.rs).
Cite
Wu & Van Voorhis 2006 (electron-transfer coupling from cDFT); Becke 1988 (the fragment weight partition). Full entries in References.