Implicit solvation (CPCM / COSMO)¶
vibe-qc ships polarisable-continuum implicit solvation in the
conductor variant (Cossi-Rega-Scalmani-Barone 2003; “C-PCM”). Aqueous
chemistry is most users’ default reaction medium, solvent="water"
is now a one-keyword drop-in for run_job.
CPCM and COSMO energies and their analytic gradients are production
routes. Two things on this page are not: the COSMO-RS / COSMOSPACE
thermodynamic layer is an experimental, library-only surface
(vibeqc.solvation.cosmors), and Direct COSMO-RS
(variant="dcosmo-rs") is an energy-only route whose gradient is
refused; both are labelled below. The label vocabulary is defined in
Methods: theory and status.
Quick-start¶
import vibeqc as vq
mol = vq.Molecule.from_xyz("ethanol.xyz")
result = vq.run_job(
mol,
basis="def2-svp",
method="rks",
functional="pbe0",
solvent="water", # ← the only new ingredient
output="ethanol_aq",
)
sol = result.solvent_result # SolventResult bundle
print(f"E_total (in water) = {sol.energy:.6f} Ha")
print(f"E_total (gas-phase) = {sol.e_gas:.6f} Ha")
print(f"ΔG_solv = {sol.e_solv * 627.509:+.3f} kcal/mol")
print(f"Macro-iters = {sol.n_macro_iter}")
solvent= accepts:
Input form |
Meaning |
|---|---|
|
gas-phase SCF, same as omitting the kwarg. |
|
preset lookup → |
|
preset aliases, case-insensitive. |
|
numeric ε, custom dielectric. |
|
dict → forwarded to :class: |
|
direct construction for full cavity control. |
|
ε = 1, short-circuits to gas-phase (uniform return). |
Supported methods¶
Implicit solvation composes with the mean-field SCFs and with MSINDO:
|
Solvation route |
|---|---|
|
CPCM macro-iteration ( |
|
dedicated COSMO driver (see MSINDO) |
Every other method refuses a non-None solvent= with a clear
ValueError: there is no solvation composition for them yet, and
silently returning a gas-phase energy would misrepresent the result.
CAS family (
casscf,caspt2,nevpt2,casci,mrci) and the other wavefunction solvers (cisd,selected_ci,fci,dmrg,v2rdm,transcorrelated_ci): no CPCM coupling of the correlated density. This includes geometry optimisations: the finite-difference fallback path refuses too, rather than walking the gas-phase surface while claiming solvation (see geometry optimisation).Post-SCF correlation (
mp2and spin-scaled variants,ccsd,ccsd(t), coupled-pair variants,dlpno-*,ovgf): the mean-field reference could run in solvent, but the correlation step’s reported total is not composed against the in-solvent reference, so the combination is gated until a validated post-SCF-in-solvent scheme exists.Semi-empirical (
gfn2_xtb,pm6,om1/om2/om3,dftb0,scc_dftb,ccm): only MSINDO has a COSMO route.MLIPs (
mace): a pre-trained gas-phase surface; no continuum coupling.rohf/roksraiseNotImplementedError(the open-shell Roothaan drivers are not yet coupled to CPCM).
method="auto" with solvent= works whenever the automatic selection
lands on a mean-field method; if it resolves to FCI / selected-CI
(very small systems), the error names the resolution; pass
method="rhf" (or rks + functional) explicitly.
Solvent preset table¶
Static dielectric constants ε at 298 K, matching the Gaussian 16
SCRF=Solvent=… table to ±1 %. The full list of preset keys lives in
python/vibeqc/solvation/presets.py.
Solvent |
ε |
Aliases |
|---|---|---|
water |
78.39 |
|
methanol |
32.61 |
|
ethanol |
24.85 |
|
dmso |
46.83 |
|
dmf |
37.22 |
|
acetonitrile |
35.94 |
|
acetone |
20.49 |
|
thf |
7.43 |
|
dichloromethane |
8.93 |
|
chloroform |
4.71 |
|
benzene |
2.27 |
|
toluene |
2.37 |
|
n-hexane |
1.88 |
|
vacuum |
1.0 |
|
For a solvent outside this table, pass a numeric ε or build a custom
:class:vibeqc.SolventModel:
ionic_liquid = vq.SolventModel(
epsilon=15.6,
name="1-butyl-3-methylimidazolium-BF4",
radii_scale=1.25, # tighter sphere scaling
n_points_per_sphere=302, # tighter Lebedev
variant="cpcm", # "cpcm" (default) or "cosmo"
)
What CPCM actually computes¶
The solute sits in a molecular-shaped cavity built as the union of atom-centred spheres (Bondi vdW radii ×1.20). Outside the cavity, the solvent is treated as a homogeneous dielectric. The boundary carries an apparent surface charge \(q\) that satisfies
where \(A\) is the discrete CPCM matrix (Scalmani-Frisch 2010 diagonal + \(1/|s_i - s_j|\) off-diagonal) and \(\mathbf{V}\) is the molecular electrostatic potential at the cavity tessellation points. The solvation energy is
and the in-solvent total energy is \(E_\text{tot}^\text{solv} = E_\text{tot}^\text{gas} + E_\text{solv}\).
The full coupled problem \(\{q, D\}\) is solved by a macro-iteration loop
(Cossi-Scalmani 2003 § II.B): converge SCF for the current \(V_q\), build a
new \(V_q\) from the new density, repeat until \(|\Delta E_\text{solv}| <\)
tol_e_solv (default 1 µHa). Three to five outer cycles is typical for
small organic molecules in water.
Cavity tessellation¶
Each atomic sphere is discretised by a Lebedev-Laikov quadrature; points sitting inside a neighbouring atomic sphere are smoothly removed via the Scalmani-Frisch continuous switching function (CSC; \(\sigma = 0.5\,a_0\)). This makes the cavity surface, and hence \(E_\text{solv}(R)\), \(C^1\) continuous, which is essential for clean geometry optimisation.
Defaults that match Gaussian’s SCRF=PCM cavity:
Parameter |
Default |
Notes |
|---|---|---|
vdW radii |
Bondi 1964 |
per-element override via |
|
1.20 |
PCM / GEPOL convention |
|
0.0 |
0 = vdW-SES; set 1.385 Å for water SAS |
|
302 |
Lebedev order 29 (Gaussian default) |
|
0.5 |
Scalmani-Frisch CSC width |
|
1e-8 |
Below this switched weight, drop the point |
Supported Lebedev point counts: 6, 14, 26, 38, 50, 74, 86, 110, 146, 170, 194, 230, 266, 302, 350, 434, 590, 770, 974. The PySCF / ORCA default is 110; the Gaussian default is 302; Q-Chem defaults to 194.
Inspecting the cavity¶
from vibeqc.solvation import build_cavity
cav = build_cavity(
atom_positions_bohr=..., # (n_atoms, 3) bohr
atom_numbers=..., # iterable of Z
radii_scale=1.20,
n_points_per_sphere=302,
)
print(f"{cav.n_points} surface points after switching")
print(f"Total area: {cav.total_surface_area_bohr2:.2f} bohr²")
# Dump for visualisation
import numpy as np
np.savetxt("cavity.xyz", np.hstack([cav.points, cav.weights[:, None]]))
Geometry optimisation in solvent¶
vibe-qc ships a closed-form analytic CPCM solvation gradient,
:func:vibeqc.cpcm_gradient. The ASE calculator picks it up
automatically when solvent= is set on the calculator:
from ase.optimize import BFGSLineSearch
from vibeqc.ase import VibeQC
atoms.calc = VibeQC(basis="def2-svp", functional="b3lyp",
solvent="water")
BFGSLineSearch(atoms).run(fmax=0.05)
This covers variant="cpcm", variant="cosmo" and, with a smoothed
hydrogen-bond term, variant="dcosmo-rs"; see
the Direct COSMO-RS gradient for what that
adds and why the published term is refused.
The gradient of the in-solvent total energy \(E_\text{tot} = E_\text{HF}^\text{gas}[D^\text{solv}] + \tfrac{1}{2}q^\mathsf{T}V\) decomposes (envelope theorem at SCF + CPCM convergence) into four pieces, each evaluated in closed form:
the standard gas-phase analytic SCF gradient at the in-solvent density,
\(\tfrac{1}{2f}\,q^\mathsf{T}(\partial A/\partial R)\,q\), the cavity self-interaction-matrix derivative, where \(f\) is the screening factor of the run’s own
variant, the same one that built \(q\),\(q^\mathsf{T}(\partial V^\text{nuc}/\partial R)\), the nuclear-ESP derivative,
\(q^\mathsf{T}(\partial V^\text{elec}/\partial R)\), the electronic-ESP derivative, evaluated with a single libint nuclear-attraction-gradient pass over the cavity points (
compute_external_charge_density_gradient).
No geometry rebuilds, no finite-difference step error. A
finite-difference reference gradient,
:func:vibeqc.cpcm_gradient_fd (full SCF + CPCM re-converge at
\(6\cdot n_\text{atoms}\) displaced geometries), is retained as a
cross-check oracle; cpcm_gradient(..., use_fd_electronic=True)
likewise swaps just the electronic-ESP piece back to a
finite-difference path for verification.
The assembled analytic gradient agrees with the full-energy
finite-difference oracle to FD-truncation level, on water/6-31G the
maximum deviation scales as \(O(h^2)\) down to ~6e-9 Ha/bohr at
\(h = 2\cdot10^{-4}\) bohr. (Releases before v0.12.0 carried a
factor-of-2 error in the off-diagonal \(\partial A/\partial R\) piece,
a systematic ~3e-4 Ha/bohr on water-sized solutes, below typical
fmax optimisation thresholds but large enough to shift tightly
converged minima; fixed and FD-pinned per component since.)
(Releases before v0.15.157 evaluated piece 2 with the CPCM screening
factor regardless of variant, so a variant="cosmo" run differentiated
a different energy than it reported. Because the two factors diverge as
\(\varepsilon\) falls, the error grew in nonpolar solvents: the cavity
contribution was 18 % low at benzene, 0.6 % low at water, and the
analytic-vs-FD residual plateaued at ~5e-5 Ha/bohr instead of converging.
CPCM runs were unaffected. Fixed and pinned in
tests/test_solvation_variant_gradient.py; issue #546.)
Choosing the cavity¶
SolventModel(cavity=...) selects the construction:
"lebedev"(default): each atomic sphere is paved with a Lebedev grid and segments are switched off where spheres overlap. This describes the convex solute surface and leaves the concave crevices between atoms unpaved."fine": the Klamt & Diedenhofen 2018 COSMO FINE Cavity, a pseudo-density iso-surface triangulated by marching tetrahedra (:mod:vibeqc.solvation.fine_cavity). One closed smooth surface, so the concave regions pave automatically.
The default is deliberately unchanged: the two give different segment sets, so switching moves every solvated energy. On water/STO-3G at \(\varepsilon = 78.39\) the measured difference is \(E_\text{solv} = -3.85\) against \(-3.35\) kcal/mol.
The FINE cavity matters most for COSMO-RS, where the screening charge density is the central descriptor: polarization charge accumulating at the edges of paved regions distorts a sigma profile directly.
The analytic gradient does support the FINE cavity. It does not reuse the
Lebedev chain rule – a FINE segment neither rides rigidly on its parent atom
nor carries a switching function – but differentiates the construction itself:
the pseudo-density iso-surface points, the triangle and basis-point areas, the
Becke coarsening onto the segment grid, and the marching box that is anchored
to the molecule (:mod:vibeqc.solvation.fine_cavity_gradient, issues #729 and
#746). On water/STO-3G it agrees with cpcm_gradient_fd to better than
5e-6 Ha/bohr, which is that reference’s own noise floor.
The derivatives hold at fixed segment topology, the same caveat the Lebedev
cavity carries through its drop_threshold.
:func:vibeqc.cpcm_gradient_fd re-converges displaced geometries and so makes
no assumption about the construction at all.
The molecule-fixed frame¶
A solvated energy must not depend on how the solute happens to be oriented in the laboratory – the cavity is a property of the molecule. Until #769 it did, because the marching lattice of step 2 and the geodesic segment directions of step 6 were laid out along the laboratory axes. Measured on water at \(\delta = 0.40\) Å, RHF/STO-3G, \(\varepsilon = 78.39\): rotating the solute about a generic axis moved the energy by 1.0e-05 Ha and changed the segment count between 235 and 239.
The CFC is now built in the solute’s own frame, which is the paper’s workflow step 1 – origin at the centre of the atom positions, \(x\) toward the atom farthest from it, \(y\) from the largest orthogonal component. The cavity rotates rigidly with the solute: the same measurement gives 5.7e-14 Ha, and the segment set is reproduced to 1.3e-14 bohr. Records come back in laboratory coordinates either way; the frame is an internal device.
SolventModel(cavity="fine", fine_frame="lab") restores the old behaviour. It
is worth knowing about for two reasons: it is the right choice for a linear
solute, whose molecule-fixed frame has no nuclear derivative (below), and the
two frames give different segment sets, so a CFC number from before #769 is
reproduced only with "lab".
The analytic gradient supports it. A frame that turns with the solute gives the
marching lattice and every segment direction a derivative of their own, and that
contribution is not small – 2.3e+01 of a cavity gradient whose largest
component is 2.9e+01. Rather than rewrite the chain rule for a turning lattice,
:class:vibeqc.solvation.cavity_derivative.FrameCavityDerivative reverses the
cavity into the frame it was built in, where the existing chain is exactly
valid, and composes it with the frame’s own derivative.
That buys an exact check no finite difference is needed for: a molecule in
free space feels no torque. On the asymmetric solute above, the lab frame’s
analytic gradient has a net torque of 2.9e-05 Ha – the rotational counterpart
of its orientation-dependent energy – and the molecule-fixed frame’s has
1.0e-13. Agreement with cpcm_gradient_fd improves too, from 9.0e-05 to
1.4e-05 Ha/bohr.
Two cases are weaker than the rest, both local and both measured. A linear
solute determines no y-axis, so its azimuth is a discontinuous lab-derived
choice with no nuclear derivative at all – it is refused; use "lab" there,
and note cpcm_gradient_fd cannot rescue it either, since its displacements
cross the same jump. And the frame’s two argmax selections tie at
symmetric geometries, water’s equilibrium structure included, which is the
next subsection.
Symmetric solutes: the frame tie is a cusp, and the gradient is projected¶
At a symmetric solute the frame cannot be symmetric: step 1 picks one of several symmetry-equivalent atoms (water’s two hydrogens, ammonia’s three), so the cavity, and with it the reported energy, belongs to one of several candidates related by the solute’s own symmetry operations. The candidates have different discretization errors and the difference changes linearly with the displacement, so the energy is continuous at the tie but has a cusp there: a V in the antisymmetric coordinate. A scan of Direct COSMO-RS water across the tie shows it as a second difference of 0.808 microhartree at the tie against 0.095 either side, which is a slope change of 1.4e-3 Ha/bohr (#769 had measured the energy’s value across a tie, which is continuous, and concluded it was smooth).
The raw analytic gradient is the slope of one branch of that V. On symmetric
water it reports a force on the oxygen along the tie-breaking direction of
1.9e-05 Ha/bohr for the conductor energy and 1.4e-03 for Direct COSMO-RS, in a
component the mirror symmetry requires to vanish. A central finite difference
reports the average of the two branches, which is what the exact
(continuum) energy has at a symmetric geometry. So cpcm_gradient projects
the gradient onto the totally symmetric subspace of the solute’s point
group (:mod:vibeqc.solvation.point_group) when the cavity was built in the
molecule-fixed frame. The operations are found from the geometry within
SolventModel(gradient_symmetry_tol_bohr=1e-4); 0.0 disables the
projection and returns the raw branch slope.
Measured on symmetric water, RHF/STO-3G, fine cavity at 0.40 Å, against a central difference at h = 1e-3 (Ha/bohr, largest component):
energy |
raw gradient |
projected |
|---|---|---|
conductor |
3.5e-5 |
4.1e-7 |
Direct COSMO-RS |
1.4e-3 |
1.0e-5 |
The projection is a linear average, so it keeps the two exact invariants (zero net force and zero net torque stay zero), it is idempotent, and a solute with no symmetry is returned unchanged. A solute whose only symmetry is its molecular plane changes at roundoff. The cost of a tolerance is that near a symmetric geometry, within the tolerance, the projection also removes the true restoring force of the symmetry-lowering modes, of order \(k\delta\) for a displacement \(\delta\): at the default 1e-4 bohr about 4e-5 Ha/bohr for water’s antisymmetric stretch, far below an optimizer’s threshold, while the artifact it removes is 70 times that for Direct COSMO-RS. If the operations found at the tolerance do not close under composition, nothing is projected, rather than averaging a set that is not a group.
What it fixes in practice is the optimizer. Before the projection a Direct COSMO-RS walk of water reached its minimum at BFGS step 6 and then reported non-convergence: 200 steps, the geometry stable to 1e-4 Å, the energy constant to 1e-12 eV from step 59, and the maximum force fixed at 0.072 eV/Å against a 0.05 eV/Å target, because the walk was sitting on the cusp.
An accidental tie, with no symmetry relating the two atoms, is a different matter: the two candidate frames then give different cavities and the energy itself steps (5.8e-06 Ha), and a projection cannot help since there is no operation to project over. That remains open.
The tetragon split¶
Step 3 cuts four edges when a tetrahedron has two corners inside the surface and two outside, and splits the resulting tetragon into two triangles on its shorter diagonal. That comparison flips where the two diagonals are equal. The quad’s total area survives the flip, but step 4’s “a third of each triangle to each of its three corners” does not: the same total lands on the four corners differently, and each corner’s basis area jumps by about 0.023 bohr². About 40 % of a typical triangulation is tetragon halves.
Step 4 now gives a tetragon’s four corners a quarter of the pair’s area each,
which is the paper’s own principle – the area is “equally assigned to the
corner points” – applied to the figure step 3 actually produced. No diagonal
can change it. Scanning a coordinate through a flip, the second difference of
the solvated energy was 125 % away from its trend and is now 0.074 %; agreement
between the analytic gradient and cpcm_gradient_fd on an asymmetric solute
improves from 7.1e-06 to 1.2e-06 Ha/bohr. Energies move by under 1e-06 Ha
(issue #779).
What remains at a flip is the tetragon’s own non-planarity: two flat triangles spanning four points that are not coplanar have slightly different total areas depending on the diagonal, worth 7e-06 bohr² of surface area and 2e-04 bohr³ of enclosed volume. That is inherent to representing a curved patch with flat triangles, and it shrinks with the grid.
Smooth atomic cells¶
Step 5 assigns each basis point to “the atom of smallest relative distance”,
\(\tau_a = |r - R_a| / \rho_a\). That argmin is discrete, so a point on a
boundary used to flip at an infinitesimal displacement and its whole area moved
to the other atom’s segment grid: 0.19 bohr² at once, a 6.9 % error in the local
slope of the energy, a 2.9e-08 Ha step. Twelve coordinates scanned over
±6e-03 bohr found thirty crossings.
The cells are now smooth (vibeqc.solvation.atom_partition), in the same
spirit as the segment partition one level down. The width of the sharing band is
the marching spacing: whether a basis point falls on one side of a boundary
is only meaningful to within the spacing that placed it, so the smoothing tends
to the hard rule as the grid refines. The per-atom area budget moves 0.9 % from
the hard assignment at \(\delta = 0.40\) Å and 0.33 % at 0.20 Å, and a basis
point feeds 1.8 atoms on average at 0.40 Å and 1.4 at 0.20 Å. The energy’s slope
across a former crossing is smooth to 1.4e-03 instead of 6.9e-02 (issue #771).
This costs roughly a factor of two in cavity construction, and it moved every
cavity="fine" energy by about 7e-06 Ha.
The outward projection¶
Step 6 pushes a segment centre onto its atom’s sphere when the area-weighted mean falls inside – which on a curved surface it always does. The paper states that as a hard rule, and a hard maximum is C0 but not C1: the segment position’s slope jumps where a centre crosses the sphere. The energy is untouched; it is the gradient that steps, so what this cost was finite-difference second derivatives. An area-weighted functional’s slope jumped 0.07 % across one crossing, and crossings arrive about every 0.025 bohr of displacement per coordinate.
The projection is now eased over a band 1e-03 of the sphere radius wide (0.003 bohr) with a softplus, so it is smooth and never places a segment inside the sphere it screens. The band is narrow against what it eases – the projection depth is 0.019 bohr median – and wide enough for a finite difference to resolve, which is the consumer that needed it. It recovers the paper’s rule as the band vanishes: the solvated energy moves 6.6e-07 Ha from the hard rule at this width, 2.1e-09 at a tenth of it and nothing at a hundredth (issue #770).
One side effect is worth knowing: the resolution guard on
fine_grid_spacing_ang fires far less often. Laying the segment directions out
in the laboratory means the surface meets them at an arbitrary angle, so a Becke
cell can be left holding a single basis point and two segments then coincide. On
water it first fired at 0.44 Å; after #769 fixed the direction grid’s
orientation and #771 let a basis point feed more than one atom’s grid, the
first refusal is at 0.70 Å in both frames.
Moving the switching width¶
switching_sigma_bohr widens the Scalmani-Frisch switch, which its own
docstring recommends for close atom pairs where the default 0.5 bohr clip is
too aggressive. Two things had to be fixed before that was safe to do.
The gradient did not know the width. SolventModel.switching_sigma_bohr
reached build_cavity, but the tessellation did not record it, so
cpcm_gradient read a hard-coded 0.5 and differentiated a cavity the run never
built – wrong by 0.35 % of the largest component at \(\sigma = 0.35\), against a
converged finite difference. The cavity now carries its own width and the
gradient reads it; a cavity that switches and cannot say how widely is refused
rather than assumed (issue #745).
What made that survive for so long is worth knowing if you are ever debugging a
gradient: a gradient taken with the wrong width is still self-consistent.
It satisfies translational invariance to 1e-15 and every other exact identity.
Only cpcm_gradient_fd, which rebuilds the energy, can see it.
The second is on the energy side. switching_drop_threshold (default 1e-8)
drops points whose switched weight falls below that fraction of their raw
Lebedev weight. The switching function is smooth across the cutoff but the
point set is not, so a point crossing it steps the energy. At the default
width nothing sits near the cutoff and this is invisible. A wide switch puts
many points there: at \(\sigma = 0.8\) a 2.5e-04 bohr displacement moves the
count from 904 to 905, the local slope reads -0.027 against a trend of +0.016,
and no gradient can match a finite difference across that step.
So if you widen the switch, set switching_drop_threshold=0.0. It costs
zeros in the \(A\) matrix and buys a smooth energy: at \(\sigma = 0.8\) the
analytic gradient goes from 2.1e-02 to 1.2e-07 Ha/bohr against FD.
COSMO-RS and COSMOSPACE thermodynamics¶
Status: experimental
The COSMO-RS / COSMOSPACE layer in vibeqc.solvation.cosmors is an experimental,
library-only route: there is no run_job keyword for it, and nothing it computes
enters an .out file. The only shipped parameter set (klamt1998) was fitted on a
DMol/BPW91/DNP COSMO protocol with 92-segment spheres on its own radii; the cavity
vibe-qc builds (the COSMO FINE Cavity) and whatever method and basis you run are a
different protocol, and every potential records that mismatch in potential.protocol
against params.fitted_protocol. A validation against the FreeSolv hydration set is
pending (#558). Its numbers are research output, not validated results, and it is not
part of the release gate. The label vocabulary is defined in
Methods: theory and status.
Conductor-like screening gives a solute’s energy in an ideal conductor;
COSMO-RS (Klamt, Andreas (1995). *Conductor-like Screening Model for Real Solvents: A New Approach to the Quantitative Calculation of Solvation Phenomena.* The Journal of Physical Chemistry, **99**(7), 2224–2235. doi:[10.1021/j100007a062](https://doi.org/10.1021/j100007a062);
Klamt, Andreas, Jonas, Volker, et al. (1998). *Refinement and Parametrization of COSMO-RS.* The Journal of Physical Chemistry A, **102**(26), 5074–5085. doi:[10.1021/jp980017s](https://doi.org/10.1021/jp980017s)) turns that into
thermodynamics in a real liquid by treating the liquid as an ensemble
of pairwise contacts between surface segments of conductor-screened
molecules. The layer is method-independent by construction: its only
input is a ConductorSurface (segments with areas and screening-charge
densities) plus a named Parameterization, so any method that can
produce a conductor surface gets COSMO-RS. The pipeline is
ConductorSurface
-> segment_descriptors sigma averaging (Klamt 1998 eq. 11, 14)
-> sigma_profile p(sigma) (eq. 16)
-> sigma_potential_* mu(sigma), iterated (eq. 18, 23 to 25)
-> chemical_potential mu*_S^X (eq. 19, 20)
-> gas_phase_chemical_potential, solvation_free_energy,
ln_activity_coefficient, activity_coefficient, log_partition_coefficient
Sigma profiles. Each segment’s raw screening-charge density
\(\sigma = q/a\) is averaged over a radius \(r_{\rm av}\) (0.5 Å) to give
the contact descriptor, and the solute’s surface is binned into the
sigma profile \(p^X(\sigma)\), the area carrying each value of
\(\sigma\); it integrates to the total area \(A^X\)
(sigma_profile, mixture_sigma_profile for a mole-fraction-weighted
solvent).
Contact energies and the sigma potential. Two segments in contact pay a misfit energy for imperfect screening and gain a hydrogen-bond energy when a strongly negative and a strongly positive segment meet,
per effective contact area \(a_{\rm eff}\) (misfit_energy,
hydrogen_bond_energy, interaction_energy; the segment form adds
the \(\sigma_\perp\) correlation term with \(f_{\rm corr}\)). The sigma
potential of a solvent \(S\) is the chemical potential of a surface
segment of charge density \(\sigma\) in that ensemble, the solution of
the self-consistent equation
iterated to a fixed point (sigma_potential_profile on a profile,
sigma_potential_segments on descriptor sets with mole fractions,
solvent_sigma_potential straight from a conductor run of the pure
solvent). Water’s potential is positive near \(\sigma = 0\) (the
hydrophobic penalty) and negative at both wings (hydrogen-bond
donors and acceptors).
Chemical potentials and derived quantities. The solute’s chemical
potential in the solvent is the integral of its profile against the
solvent’s potential plus a combinatorial size term,
\(\mu^{X}_S = \int p^X(\sigma)\, \mu_S(\sigma)\, d\sigma - \lambda\, kT \ln A^S\)
(chemical_potential, returning the two parts separately). The
gas-phase reference \(\mu^X_{\rm gas} = -\Delta^X - \sum_k \gamma_k A^X_k - \omega\, n_{\rm ring} - \eta RT\)
(gas_phase_chemical_potential) needs the ideal screening energy
\(\Delta^X = E_{\rm gas} - E_{\rm conductor}\), per-element dispersion
constants \(\gamma_k\) (H, C, N, O, Cl only in klamt1998; other
elements are refused, not zeroed) and the ring and entropy constants.
From these, \(\Delta G_{\rm solv} = \mu^X_S - \mu^X_{\rm gas}\)
(solvation_free_energy),
\(\ln \gamma^X_S = (\mu^X_S - \mu^X_X)/RT\) against the pure solute
(ln_activity_coefficient, activity_coefficient) and
\(\log_{10} K_{B/A} = (\mu^X_A - \mu^X_B)/(RT \ln 10) + \log_{10}(V_A/V_B)\)
(log_partition_coefficient, molar-volume ratio supplied by the
caller).
COSMOSPACE. The sigma-potential equation is the
independent-patch approximation. COSMOSPACE
(Klamt, Andreas, Krooshof, Gerard J. P., and Taylor, Ross (2002). *COSMOSPACE: Alternative to conventional activity-coefficient models.* AIChE Journal, **48**(10), 2332–2349. doi:[10.1002/aic.690481023](https://doi.org/10.1002/aic.690481023)) solves the same segment-pairing
problem exactly as a lattice of segments with a Staverman-Guggenheim
combinatorial term: tau_matrix and exchange_energy_matrix build the
Boltzmann factors of the contact energies, segment_activity_coefficients
iterates the segment activity coefficients \(\Gamma_i\) from
\(\ln \Gamma_i = -\ln \sum_j \theta_j\, \Gamma_j\, \tau_{ij}\), and
residual_ln_activity_coefficient plus staverman_guggenheim_ln_gamma
assemble \(\ln\gamma\) (binary_segment_activity_coefficient for the
two-segment case). It is exposed beside COSMO-RS rather than in place
of it: the klamt1998 constants were fitted with the sigma-potential
form, so the two are not interchangeable at fixed parameters.
A worked call. Water as both solvent and solute, RHF/STO-3G on the FINE cavity at the parameter set’s own radii (vibe-qc 0.17.10 development build on an Apple silicon Mac, 4 s wall):
import math
import vibeqc as vq
from vibeqc.solvation import cosmors as rs
from vibeqc.solvation.surface import build_conductor_surface
K = rs.KLAMT_1998
water = vq.Molecule(
[vq.Atom(8, [0.0, 0.0, 0.0]),
vq.Atom(1, [0.0, 1.498, -1.159]),
vq.Atom(1, [0.0, -1.498, -1.159])],
)
conductor = vq.SolventModel(
epsilon=math.inf, variant="cosmo", cavity="fine",
radii=dict(K.cavity_radii), radii_scale=1.0, solvent_probe_radius_ang=0.0,
)
run = vq.run_cpcm_scf(water, vq.BasisSet(water, "sto-3g"), method="rhf",
solvent=conductor)
# the solvent: sigma potential of pure water
potential = rs.solvent_sigma_potential(run, K, method="rhf", basis="sto-3g")
print(potential.protocol, K.fitted_protocol) # rhf/sto-3g/fine-cfc dmol/bpw91/dnp/cosmo-inf/nspa92
# the solute (here water again): surface -> descriptors -> profile
surface = build_conductor_surface(run, method="rhf", basis="sto-3g",
cavity_kind="fine-cfc",
energy_conductor=float(run.energy))
desc = rs.segment_descriptors(surface, K.r_av)
profile = rs.sigma_profile(desc, label="water")
mu_solv = rs.chemical_potential(profile, potential, K,
solvent_area=profile.total_area)
mu_gas = rs.gas_phase_chemical_potential(surface, desc, K)
print(mu_solv.total, mu_gas) # kcal/mol
print(rs.solvation_free_energy(mu_solv, mu_gas)) # kcal/mol
print(rs.ln_activity_coefficient(mu_solv, mu_solv)) # 0.0 (pure solute)
The hydration free energy of water that this prints is not a validated
number (experiment is about \(-6.3\) kcal/mol): it is RHF/STO-3G on a cavity and
protocol the constants were never fitted to, which is exactly what the
protocol comparison in the second print is there to make visible. This page
quotes no chemical potential or free energy, because the gas-phase reference
was corrected on the development branch after these examples were first run
(the screening gain is added and \(\eta RT\) subtracted, #435), so values from
older builds differ; print them with your own build. For a run near the fitted
protocol use the BPW91/def2-TZVP recipe in the section on Direct COSMO-RS
below. The zero activity coefficient of the pure solute is the cheapest
available check on a mixture workflow and worth asserting in any script
built on this layer.
Direct COSMO-RS¶
variant="dcosmo-rs" feeds the solvent’s sigma potential back into the
electronic Hamiltonian, so the solute’s wavefunction responds to a real
solvent rather than to a scaled conductor. The method is Sinnecker, Rajendran,
Klamt, Diedenhofen & Neese, J. Phys. Chem. A 110, 2235 (2006),
doi:10.1021/jp056016z, and it is three
equations:
with \(q^{\Delta RS}\) solved from \(\phi^{\Delta RS}\) through the same first equation, iterated to self-consistency.
from vibeqc.solvation import cosmors as rs
potential = rs.sigma_potential_segments(solvent_segments, mole_fractions, params)
sm = vq.SolventModel(
variant="dcosmo-rs",
sigma_potential=potential,
epsilon=float("inf"), # required; see below
cavity="fine", # required; see below
)
The energy is the conductor COSMO energy plus the solute’s own COSMO-RS chemical potential, \(G = E_{\rm gas}[D] + \tfrac12 q\cdot V_{\rm tot} + \sum_t a_t\,\mu_S(\sigma_t)\). The paper states the operator but not the energy; the two are the same statement, because differentiating that third term with respect to the density is exactly what produces the extra charges \(q^{\Delta RS}\).
Getting a solvent potential¶
The sigma potential is the solvent, so it has to come from somewhere. Compute it from the solvent’s own conductor COSMO run:
import math
from vibeqc.solvation import cosmors as rs
K = rs.KLAMT_1998
solvent = vq.run_cpcm_scf(
water, basis, method="rhf",
solvent=vq.SolventModel(
epsilon=math.inf, variant="cosmo", cavity="fine",
radii=dict(K.cavity_radii), radii_scale=1.0, # the set's own radii
solvent_probe_radius_ang=0.0,
),
)
potential = rs.solvent_sigma_potential(solvent, K, method="rhf", basis="sto-3g")
solvent_sigma_potential refuses two mistakes that produce a perfectly
ordinary-looking potential and a wrong number.
A screened run: build_conductor_surface takes the charges as stored, and
those are the ideal \(q^*\) only at \(\varepsilon=\infty\); at finite dielectric
vibe-qc stores \(q = f q^*\), so the profile is scaled by \(f\) and nothing
downstream can detect it.
Foreign cavity radii: a parameter set is fitted against a radius set –
KLAMT_1998 against H 1.30, C 2.00, N 1.83, O 1.72, Cl 2.05 Å – and since
\(\sigma = q/a\), the default scaled-Bondi cavity changes every \(\sigma\) the
constants were fitted to reproduce.
What it cannot refuse is the QC protocol. KLAMT_1998 was fitted on
dmol/bpw91/dnp/cosmo-inf/nspa92, and whatever you compute carries its own
method and basis. That is a real accuracy limit, not a bug, and no code can
repair it – so it is recorded: potential.protocol says what the surface was
and params.fitted_protocol says what the constants expect. Compare them
before believing a number.
Computing the solvent near the fitted protocol¶
vibe-qc cannot run DMol’s numerical DNP basis, but it can match the functional. BPW91 with def2-TZVP is the recommended recipe, for the solvent and for the solute alike:
opts = vq.RKSOptions()
opts.functional = "bpw91"
solvent = vq.run_cpcm_scf(
water, vq.BasisSet(water, "def2-tzvp"), method="rks", options=opts,
solvent=vq.SolventModel(
epsilon=math.inf, variant="cosmo", cavity="fine",
radii=dict(K.cavity_radii), radii_scale=1.0,
solvent_probe_radius_ang=0.0,
),
)
potential = rs.solvent_sigma_potential(solvent, K, method="bpw91", basis="def2-tzvp")
Pass the functional as method so that potential.protocol names it
(bpw91/def2-tzvp/fine-cfc). The level of theory decides how far a surface
polarizes, and every number built on the surface follows. Measured with the
fine cavity at 0.35 A and KLAMT_1998 radii, solute and solvent at the same
level, energies and macro-iterations from Direct COSMO-RS:
RHF/STO-3G |
BPW91/def2-TZVP |
|
|---|---|---|
water sigma, 1st to 99th percentile (e/Ų) |
-0.012 to +0.014 |
-0.017 to +0.018 |
COSMO-RS term, water in water (kcal/mol) |
+0.3 |
-7.7 |
COSMO-RS term, acetate (kcal/mol) |
+11.7 |
-15.0 |
COSMO-RS term, ammonium (kcal/mol) |
-11.0 |
-19.5 |
area beyond the fitted grid, acetate / ammonium |
1.6 / 8.2 % |
6.9 / 8.2 % |
macro-iterations, water / acetate / ammonium |
9 / 9 / 5 |
14 / 15 / 8 |
A minimal basis is too stiff to polarize into the hydrogen-bonding wings of the potential, so at RHF/STO-3G an anion comes out disfavoured by water. At BPW91/def2-TZVP both ions gain, as they should, and water gains in its own solvent.
A computed water potential has the shape water should have: positive near \(\sigma = 0\), which is the cost of putting a nonpolar surface in water, and negative at both wings, where a polarized surface donates or accepts a hydrogen bond. At RHF/STO-3G one consequence looks like a bug and is not: the COSMO-RS term for a small solute in water comes out unfavourable by around half a kcal/mol on top of the conductor energy, because most of the solute’s narrow profile sits at moderate \(\sigma\) where water’s potential is positive. That is the hydrophobic penalty, and at a level that polarizes enough to reach the wings, a polar solute’s hydrogen bonds outweigh it.
What SolventModel(variant="dcosmo-rs") requires¶
The constructor validates the combination up front and refuses anything
else with a ValueError naming the reason:
Field |
Requirement |
Why |
|---|---|---|
|
mandatory, no default |
the solvent is the potential; there is nothing to fall back on |
the potential |
must carry its solvent ensemble ( |
the feedback is the potential’s derivative, and a bare interpolant’s derivative is piecewise constant |
|
|
the switched Lebedev cavity is ill-conditioned for \(\sigma = q/a\) (below) |
|
|
the potential replaces the dielectric scaling rather than composing with it (below) |
The result is energy-only: cpcm_gradient refuses a Direct COSMO-RS
result with NotImplementedError (see
When it will not converge), so
optimize=True and the ASE force path are not available with this
variant.
Two requirements, both measured rather than stylistic¶
epsilon=inf. The sigma potential is the real-solvent physics, so
scaling by \(f(\varepsilon)\) as well would count the solvent twice. The paper is
explicit that the correction acts on “the ideal screening charges appearing in
a conductor”. Direct COSMO-RS replaces the dielectric scaling rather than
composing with it, and the solvent is chosen by which sigma potential you pass.
cavity="fine". The feedback is evaluated at \(\sigma_t = q_t/a_t\), and on
a switched Lebedev cavity that is ill-conditioned: the switching function
shrinks weights continuously, so the smallest segment sits 2.9e+07 below the
median and \(|\sigma|\) reaches 640 e/Ų against a potential parameterized on
±0.025. Dropping the tail does not help – there is no clean cut, and a
vanishing segment’s energy does not vanish with it, since
\(a\,\mu(\sigma) \sim a\sigma^2 \sim 1/a\). The COSMO FINE Cavity’s segments
all carry real area and put the 99th percentile of \(|\sigma|\) at 0.028, inside
the parameterization.
When it will not converge¶
Klamt’s hydrogen-bond term is piecewise linear in each density, so \(\mu_S'\) jumps where a segment’s \(\sigma\) crosses \(\pm\sigma_{hb}\), and the Boltzmann average over solvent partners does not damp it: every partner steps at the same \(\sigma\). A segment parked on that corner sees a discontinuous potential from one macro-iteration to the next.
result.direct_feedback reports both failure modes, and they are the first
things to read when the outer loop will not settle:
meaning |
|
|---|---|
|
segments sitting on the discontinuity (2 to 15 percent measured on small solutes; see below) |
|
area whose sigma is extrapolated beyond the fitted range |
The extrapolation is through the misfit term, which is quadratic and defined everywhere, so it is smooth rather than a clamp – but it is still extrapolation, and a large share means the surface and the parameterization disagree about what a polar segment looks like.
Measured at RHF/STO-3G, the loop is robust. Across RHF/STO-3G solutes in a
water potential computed the same way (fine cavity at 0.35 A, KLAMT_1998
radii), methanol, acetic acid, acetate, ammonium and benzene converge in 5 to
11 macro-iterations, and water in a computed methanol potential in 10. Every
run converges monotonically, including water in methanol with 15 percent of its
surface on the hydrogen-bond corner. Those counts are for the plain loop; the
accelerated loop described below takes as many or one fewer on every solute
here, reaches the same energy within 3e-13 Ha, and can alternate in sign once
the change is below 1e-7. The loop stops on the conductor term
alone, |delta e_solv| < tol_e_solv, yet the reported total, COSMO-RS energy
included, is converged far inside that tolerance: at 1e-4 to 1e-8 it sits
within 6e-6 of the tolerance of a tightly converged run.
At a realistic level of theory a polar solute overshoots. With solute and
solvent both at BPW91, acetate’s feedback oscillates: at def2-SVP the change
in e_solv flips sign on every one of its last ten macro-iterations and a
plain fixed-point loop needs 26 of them, and at def2-TZVP it needs 60, twice
the default max_macro_iter of 30. The loop therefore extrapolates the
feedback potential over its last four iterates (Anderson acceleration;
Anderson, J. ACM 12, 547 (1965)). That brings acetate to 18
macro-iterations at def2-SVP and 15 at def2-TZVP, leaves the monotone runs
above where they were, and converges to the same energy to 1e-13 Ha. Fixed
damping was measured and rejected: a factor of 0.7 helps acetate (15 at
def2-SVP) but nearly doubles every run that was already monotone (water 9 to
16).
Ions are the caveat, and it is not a convergence one. An ion’s surface
polarization runs past the range a sigma potential is fitted over, and there
the potential is extrapolated through the misfit term. At RHF/STO-3G ammonium
puts 8 percent of its area beyond the grid, with sigma up to 0.38 e/Ų, and
acetate 1.6 percent, and the extrapolation dominates their energies (the table
above). Computing solvent and solute at BPW91/def2-TZVP gives both ions the
sign they should have, but it does not remove the extrapolation: acetate, now
polarizing further, puts 6.9 percent of its area beyond the grid and ammonium
8.2. That is what an ion’s surface is, at both levels measured. Read
fraction_outside_grid before trusting an ion’s number.
Smoothing the hydrogen-bond corner. The discontinuity is in Klamt’s
hydrogen-bond term itself, c_hb max[0, acc - sigma_hb] min[0, don + sigma_hb].
A positive Parameterization.hb_smoothing replaces its two corners with
softplus ramps of that width, in e/Ų, giving an interaction energy that is
smooth in \(\sigma\) everywhere:
import dataclasses
from vibeqc.solvation.cosmors.parameters import KLAMT_1998
smoothed = dataclasses.replace(KLAMT_1998, hb_smoothing=5e-4)
The default is 0.0, which is the published form exactly: every published
COSMO-RS number is unchanged unless you ask for smoothing. The smoothed term
tends to the published one linearly in the width, and it is what makes the
Direct COSMO-RS nuclear gradient below available. It departs from the
published functional form by a measured amount. On RHF/STO-3G solutes in a
computed water potential, a width of 5e-4 (a sixteenth of sigma_hb) moves the
COSMO-RS energy by less than 0.05 kcal/mol and 1e-3 by up to 0.25, and neither
changes how many macro-iterations the loop needs.
The Direct COSMO-RS gradient¶
With the published corner the feedback is discontinuous in \(\sigma\), so a
Direct COSMO-RS energy is not smooth in the geometry: it has no derivative
wherever a segment sits on \(\pm\sigma_{hb}\), and a finite difference steps
wherever a segment crosses it. cpcm_gradient therefore refuses a result
computed with hb_smoothing = 0 (NotImplementedError, naming the setting).
v0.17.1, where Direct COSMO-RS first shipped, did not refuse it: it returned
the conductor COSMO gradient, without the COSMO-RS energy term or the
\(q^{\Delta RS}\) operator. That gradient passes translational invariance to
1e-15 and is wrong by up to 1.4e-03 Ha/bohr on water.
With a smoothed term the energy is smooth and cpcm_gradient differentiates
it. The run reports
\(G = E_{\rm gas}[D] + \tfrac12 q\cdot V + \sum_t a_t\,\mu_S(\sigma_t)\)
with \(q\) the conductor charges, \(A q = -V\), and \(\sigma_t = q_t/a_t\); its SCF
is stationary for \(G\) under the operator of \(q + q^{\Delta RS}\), so the
envelope theorem applies and, at fixed density, with
\(\phi_t = \mu_S'(\sigma_t)\) and \(q^{\Delta RS} = -A^{-1}\phi\),
The second line is the derivative of the COSMO-RS term through the charges: \(d(\sum_t a_t \mu_S)/dq = \phi\) and \(dq/dR = -A^{-1}(\partial_R A\, q + \partial_R V)\), so the correction charges contract the same \(\partial A/\partial R\) and \(\partial V/\partial R\) the conductor charges do. The last term is the explicit area dependence with the charge held fixed. Every piece is a contraction through the fine cavity’s own chain rule, so the gradient inherits the fine-cavity caveats above, fixed segment topology and the frame tie at symmetric geometries, and Direct COSMO-RS amplifies the second, because the COSMO-RS term is far more sensitive than the conductor energy to how the lattice cuts the surface. The projection of Symmetric solutes removes the tie from the gradient; this is what it does here.
Measured on water at RHF/STO-3G in a computed water potential (fine cavity at
0.40 Å, hb_smoothing = 5e-4), the largest component of the analytic gradient’s
disagreement with cpcm_gradient_fd at three central-difference steps
(Ha/bohr, #535). “Raw” is gradient_symmetry_tol_bohr=0, “projected” the
default:
geometry |
energy |
frame |
h = 2e-3 |
1e-3 |
5e-4 |
|---|---|---|---|---|---|
asymmetric |
conductor |
molecular |
4.6e-7 |
1.2e-7 |
2.9e-8 |
asymmetric |
Direct COSMO-RS |
molecular |
3.7e-6 |
6.9e-7 |
1.6e-7 |
asymmetric |
Direct COSMO-RS |
lab |
1.3e-3 |
2.6e-3 |
5.2e-3 |
symmetric |
conductor |
molecular, raw |
3.5e-5 |
3.5e-5 |
3.5e-5 |
symmetric |
conductor |
molecular, projected |
6.1e-7 |
4.1e-7 |
2.6e-7 |
symmetric |
Direct COSMO-RS |
molecular, raw |
1.4e-3 |
1.4e-3 |
1.4e-3 |
symmetric |
Direct COSMO-RS |
molecular, projected |
2.1e-5 |
1.0e-5 |
5.0e-6 |
symmetric |
Direct COSMO-RS |
lab |
3.4e-1 |
1.0 |
1.3 |
Read the rows by how they scale with the step. Truncation error shrinks as \(h^2\): the asymmetric molecular-frame rows, which is where the gradient is validated. A constant offset is the frame tie, the raw symmetric rows: 3.5e-5 for the conductor energy, 1.4e-3 for Direct COSMO-RS. The projected symmetric rows shrink linearly with the step, which is what the central difference’s own error across a cusp does, so the projected gradient is the \(h \to 0\) limit of the finite difference and what remains at finite \(h\) is the reference’s. An error that grows as \(h\) shrinks is an energy that is not continuous: the Direct COSMO-RS energy in the lab frame has steps (about 5e-6 Ha on asymmetric water, and of order 1e-3 Ha at the symmetric geometry), so a finite difference cannot validate it and an optimizer sees the same steps. The lab frame is therefore not a remedy for the tie when the energy is Direct COSMO-RS, although it is for the conductor energy.
The practical consequence, before the projection, was that a Direct COSMO-RS
optimization ending at a symmetric geometry reported non-convergence against
run_job’s default 0.05 eV/Å target although the energy was converged:
measured on water, 200 BFGS steps with the geometry stable to 1e-4 Å, the
energy at its minimum from step 6 on, and the reported maximum force fixed at
0.072 eV/Å. With the projection the same walk converges at the default
threshold (pinned in tests/test_solvation_cosmors.py). If you disable the
projection, or the geometry is further from symmetric than the tolerance, a
force threshold above the tie offset is the fallback: fmax=0.1 stopped that
walk after 13 s at O-H 0.9856 Å and H-H 1.5130 Å, E = -74.971945 Ha, the same
minimum. fmax=0.1 eV/Å is loose, so compare the converged energy and
geometry against the trajectory rather than trusting the threshold alone.
Measured on asymmetric water at RHF/STO-3G in a computed water potential with
hb_smoothing = 5e-4 (fine cavity at 0.40 Å): the analytic gradient agrees with
cpcm_gradient_fd, which re-converges the whole Direct COSMO-RS calculation at
displaced geometries, to better than 5e-6 Ha/bohr, the floor that reference
has on the conductor gradient too; the net force is zero to 1e-9 and, in the
molecule-fixed frame, the net torque to 1e-7. A solvent with no interactions
reproduces the conductor gradient to 1e-10, the exact limit. The gradient is
pinned in tests/test_solvation_cosmors.py.
A geometry optimization in a real solvent is then one call:
import dataclasses
import vibeqc as vq
from vibeqc.solvation import cosmors as rs
smoothed = dataclasses.replace(rs.KLAMT_1998, hb_smoothing=5e-4)
potential = rs.solvent_sigma_potential(water_run, smoothed, method="bpw91", basis="def2-tzvp")
result = vq.run_job(
mol, basis="def2-tzvp", method="rks", functional="bpw91", optimize=True,
solvent=vq.SolventModel(variant="dcosmo-rs", sigma_potential=potential,
epsilon=float("inf"), cavity="fine",
radii=dict(rs.KLAMT_1998.cavity_radii), radii_scale=1.0,
solvent_probe_radius_ang=0.0),
)
Build the potential with the smoothed parameter set: the gradient reads the
width off the run’s feedback, and a potential built with the published set
is refused. cpcm_gradient_fd differentiates the Direct COSMO-RS energy
itself at any width, subject to the steps above when the width is zero.
COSMO-RS solvation free energies¶
run_job computes a COSMO-RS solvation free energy for a solute in a pure
solvent when you pass cosmors=vq.CosmoRSOptions(...). The solute runs as an
ideal conductor on the fine cavity at the parameterization’s radii, and the
COSMO-RS chain (Klamt 1998 eq. 19 to 21) is evaluated on that surface. The
solvent enters either by name or as its sigma potential.
By name. The simplest form names a solvent from the library:
result = vq.run_job(
methanol, basis="def2-tzvp", method="rks", functional="bpw91",
cosmors=vq.CosmoRSOptions(solvent="water", n_ring_atoms="auto"),
)
print(result.cosmors.dg_solv) # kcal/mol
The solvent’s conductor surface is computed at the solute’s own method and
basis, on the same fine cavity at the same radii, so the two sigma
distributions are comparable by construction (the refusal below cannot
fire). That is one conductor run of the solvent molecule, and it is the same
for every solute at that protocol (the methanol job above, with water’s
surface computed in it, took 21 s on 4 threads; larger solvents take longer), so the surface is cached under $XDG_CACHE_HOME/vibeqc/cosmors
(~/.cache/vibeqc/cosmors by default; CosmoRSOptions(cache_dir=...)
overrides it). What is cached is the ConductorSurface record with its
provenance, keyed by the solvent’s geometry, the protocol, the
parameterization’s identity and radii, the cavity settings, the record’s
schema and the vibe-qc version, so a changed input is a new file and nothing
is read back under a key it was not written with. The .out says whether the
surface was computed or read back. CosmoRSOptions(temperature_k=...) sets
the temperature the potential is built at (the surface does not depend on
it), and parameter_set= the parameter set, default klamt1998.
vibeqc.solvation.cosmors.available_solvents() lists the library: the common
solvents the klamt1998 set covers (H, C, N, O, Cl), each a vibe-qc geometry
optimized in the gas phase at BPW91/def2-SVP by
scripts/cosmors_solvent_library.py, which records the protocol on every
entry (the script can re-optimize them as ideal conductors, at the price of a
fine-cavity gradient per step). A solvent outside the library takes the
second form.
By potential. Build the solvent’s potential as in Getting a solvent potential and pass it; the potential then fixes the solvent, the protocol, the temperature and the parameter set:
import math
import vibeqc as vq
from vibeqc.solvation import cosmors as rs
K = rs.KLAMT_1998
conductor = vq.SolventModel(
epsilon=math.inf, variant="cosmo", cavity="fine",
radii=dict(K.cavity_radii), radii_scale=1.0, solvent_probe_radius_ang=0.0,
)
opts = vq.RKSOptions()
opts.functional = "bpw91"
water_run = vq.run_cpcm_scf(
water, vq.BasisSet(water, "def2-tzvp"), method="rks", options=opts,
solvent=conductor,
)
potential = rs.solvent_sigma_potential(water_run, K, method="bpw91", basis="def2-tzvp")
result = vq.run_job(
methanol, basis="def2-tzvp", method="rks", functional="bpw91",
cosmors=vq.CosmoRSOptions(potential),
)
print(result.cosmors.dg_solv) # kcal/mol
Either way, leave solvent unset: the job builds the conductor model itself.
The .out gets a ## COSMO-RS block with the solvent’s and the solute’s
chemical potentials, dG_solv, the temperature, the parameterization and its
fitted protocol, the solute’s and the solvent’s protocols, where the solvent
came from, and the share of the solute’s surface beyond the fitted sigma
range. result.cosmors carries the same numbers, and the run cites the
COSMO-RS papers.
Reference states. dG_solv is the free energy of transfer from the ideal
gas at 1 bar to the solvent at 1 mol/mol, the reference states the
parameterization was fitted in. The usual 1 mol/L to 1 mol/L value is lower
by RT ln(rho V_gas / M) of the solvent, 4.28 kcal/mol for water at 298 K
(Klamt 1998 section 4). The job does not convert, so that a number cannot
change reference state unannounced.
What it refuses, before any SCF.
A solute at another level of theory than the solvent potential. Both sigma distributions must come from the same method and basis, or they are not comparable and nothing downstream can tell. Functionals are compared by name, so build the potential with the name you run the solute with (
method="bpw91"above). Restricted and unrestricted Hartree-Fock compare equal.A
solvent=that cannot give the fitted surface: finite dielectric, the Lebedev cavity, foreign radii, a radius scale or a probe radius.Anything but a mean-field single point (rhf, uhf, rks, uks). A geometry optimization combined with
cosmors=is refused: optimize first, in the real solvent if you like (the Direct COSMO-RS gradient), then run the COSMO-RS single point at the optimized geometry. COSMO-RS on the Direct COSMO-RS density is later work.Elements the parameterization has no constants for, in the solute or in the solvent.
Ring atoms. The gas reference carries Klamt’s ring correction, -0.21
kcal/mol per ring atom, “ring atoms” being the atoms that belong to a ring
(Klamt 1998 section 5.1). Pass the count, CosmoRSOptions(..., n_ring_atoms=6)
for benzene, or ask for it: n_ring_atoms="auto" perceives bonds from
covalent radii and counts the atoms on a cycle of the bond graph, so a fused
system counts every atom once and a chain between two rings none
(vibeqc.solvation.cosmors.ring_atom_count). The default is 0, and the
output states the value used and whether it was perceived.
Accuracy. Pure-liquid vapour pressures test the whole chain, since
RT ln(p_vap / bar) is a liquid’s solvation free energy in itself. Eight
liquids from Klamt 1995 Table 2 (water, methanol, ethanol, propanone,
benzene, ethane, cyclobutane, tetrachloromethane) come out with an rms error
of 1.2 kcal/mol at RHF/STO-3G, and six cases including methane’s Henry
constant in water with 2.0 kcal/mol at BPW91/def2-TZVP. That is a consistency
check on one property class, not a benchmark; ions extrapolate beyond the
fitted sigma range at every level measured, and the output says how much.
Until #435 the gas reference had two signs wrong, as Klamt 1998 eq. 21 is
printed: every nonpolar liquid came out about 10 kcal/mol too volatile, while
water, where the two errors cancel, looked right. A dG_solv computed with
gas_phase_chemical_potential before that fix is not usable.
Comparing a solvated run against another code¶
Two quantities of a solvated run can be compared with another program, and they behave differently.
The in-solvent total (
result.energy, printed asIn-solvent total) compares only against the other code’s in-solvent total, and carries every gas-phase convention difference on top of the solvation ones.The solvent-response component (
sol.e_solv, printed asSolvation energy (1/2 q.V_tot)) is the more robust one: it is a difference of two energies on the same functional and grid, so gas-phase conventions largely cancel. ORCA prints the same quantity asCPCM Dielectricin itsTOTAL SCF ENERGYblock.
Do not compare a solvated total against a gas-phase total, and do not
compare either against an inner-SCF energy. Before v0.15.138 vibe-qc’s banner
printed the inner SCF’s \(E_\text{SCF}^{w/V_q}\) rather than the in-solvent
total (archived tracker vibeqc#148) – 23-42 mHa away from it. A harvester that read the banner
reported a 54.6 mHa cross-code disagreement on the case below that does not
exist; the run’s own solvation block was right all along.
Total solvation shift (in-solvent minus gas-phase) is not e_solv: the
solute density also relaxes in the field, and the block prints that
polarisation cost separately. Compare like with like.
Measured against ORCA¶
CH\(_3\)COOH / def2-TZVP / B3LYP, water, on one geometry, against an archived
ORCA 6.1.1 ! B3LYP def2-TZVP TightSCF CPCM(water) reference
(in-solvent total \(-229.0790600869\), CPCM Dielectric \(-0.0126977537\) Ha):
vibe-qc setting |
in-solvent total |
response component |
component vs ORCA |
|---|---|---|---|
defaults ( |
\(-229.0799875548\) |
\(-0.0139868457\) |
\(-1.29\) mHa |
ORCA’s \(\varepsilon\) and radii |
\(-229.0821294531\) |
\(-0.0164592598\) |
\(-3.76\) mHa |
The in-solvent totals agree to 0.93 mHa, so raw solvated totals are comparable once both sides quote the same quantity.
The second row is the surprise, and it is why this section exists: making the named parameters agree makes the answer worse. ORCA’s hydrogen radius is 1.32 A against vibe-qc’s 1.44 A, and the tighter cavity deepens the response past ORCA’s own value. What remains is the surface itself – vibe-qc switches a Lebedev grid per sphere (1895 points here), ORCA lays down a Gaussian-charge surface at constant charge density (830 points). Two cavities that carry the same radii are still not the same cavity, and that sets the floor on any cross-code component comparison. Matching \(\varepsilon\) and radii is therefore not a recipe for agreement; it is only a way to remove those two variables when attributing a difference.
Maintainer decision (2026-09-17): this floor is accepted. It is expected cross-code behaviour, not a defect to tune away, so a component comparison should quote it rather than chase it. vibe-qc does not offer a Gaussian-charge cavity to match ORCA’s construction exactly; that would be a new feature, and nothing shipped today needs it.
The geometry has to be identical, not similar. Re-running the same case on a geometry whose C=O bonds differ by at most 0.07 A moves the component by 2.5 mHa – twice the entire cross-code gap. A component comparison across codes is meaningless unless both sides ran the same coordinates.
For reference, ORCA 6.1’s CPCM(water) defaults against vibe-qc’s:
knob |
vibe-qc default |
ORCA 6.1 |
|---|---|---|
dielectric |
78.39 ( |
80.1510 |
C / O radius |
2.040 / 1.824 A (Bondi x 1.20) |
2.0400 / 1.8240 A |
H radius |
1.44 A |
1.3200 A |
probe |
|
VDW surface |
discretisation |
switched Lebedev, per sphere |
Gaussian VDW, constant charge density |
screening |
|
|
orca_like = vq.SolventModel(
epsilon=80.1510, # ORCA's water, not 78.39
variant="cpcm",
radii={1: 1.10, 6: 1.70, 8: 1.52}, # pre-scaling, Angstrom
radii_scale=1.2, # -> H 1.32, C 2.04, O 1.824 A
)
Choosing CPCM vs COSMO¶
The two are not separate formulas. Klamt & Schuurmann 1993 p. 800 gives the family both belong to,
and the variant= field just selects \(x\):
"cpcm"(default): \(x = 0\), so \(f(\varepsilon) = (\varepsilon - 1)/\varepsilon\). Converges smoothly to the conductor limit at \(\varepsilon \to \infty\)."cosmo": \(x = 1/2\), so \(f(\varepsilon) = (\varepsilon - 1)/(\varepsilon + 0.5)\) (Klamt 1993 p. 801, which extends the exact conductor result to finite \(\varepsilon\) with a relative error below \(\varepsilon^{-1}/2\)).
vibeqc.solvation.ScreeningModel carries the resolved factor, and
SolventResult.screening is the one that actually built the apparent charges.
Read f from there rather than recomputing it from (epsilon, variant): that
recomputation is what made COSMO gradients differentiate the wrong energy
(issue #546). A gas-phase result carries screening = None, which is how
“no reaction field” is expressed; epsilon = inf gives exactly \(f = 1\).
The two agree to better than 1 % above \(\varepsilon \approx 30\), for water and most polar solvents the choice is inconsequential (\(\Delta E_\text{solv} \lesssim 0.1\) kcal/mol). For very low-dielectric solvents (e.g. n-hexane, \(\varepsilon \approx 1.9\)) the choice can shift \(E_\text{solv}\) by ~5 %.
Density fitting and RIJCOSX¶
The CPCM macro-iteration drives the inner SCF through whatever
Fock-build path the method options select, there is nothing
solvation-specific about the two-electron build. Set density_fit
(and optionally cosx) on the method options and the CPCM SCF picks
up RI-JK / RIJCOSX automatically:
import vibeqc as vq
opts = vq.RHFOptions()
opts.density_fit = True # RI-JK
# opts.aux_basis = "def2-svp-jk" # optional — autodetected if omitted
# opts.cosx = True # RIJCOSX (RI-J + chain-of-spheres K)
result = vq.run_job(mol, basis="def2-tzvp", method="rhf",
solvent="water", rhf_options=opts)
When aux_basis is left empty, it is autodetected from the orbital
basis name via vibeqc.default_aux_basis_for(orbital, kind="jk") and
filled in on the options struct (so the gas-phase bootstrap SCF, the
macro-iteration inner SCFs, and the JKBuilder all share one aux
basis). Density fitting changes the in-solvent total energy by the
usual fitting error, ~1e-4 Ha for def2-svp-jk, and leaves
E_solv essentially unchanged (~1e-6 Ha).
The CPCM analytic gradient currently uses conventional (non-DF) two-electron integral derivatives for the gas-phase SCF piece even when the SCF itself was density-fitted; the resulting DF/conventional mismatch is within the DF fitting error and below typical optimisation tolerances. A DF-consistent CPCM gradient is a future refinement.
Known limitations¶
Molecules only, periodic CPCM is out of scope until v1.x; the conductor model needs adaptation to handle the periodic image problem cleanly.
No non-electrostatic terms, vibe-qc’s CPCM is currently electrostatic-only. The cavitation, dispersion, and repulsion (CDR) terms that a full SMD-style model adds are on the future roadmap.
Theoretical references¶
The implementation follows the standard CPCM literature:
Klamt, A. & Schüürmann, G. J. Chem. Soc. Perkin Trans. 2, 799 (1993), COSMO conductor model.
Cossi, M., Rega, N., Scalmani, G. & Barone, V. J. Comp. Chem. 24, 669 (2003), CPCM as the practical numerical variant; cavity + matrix layout used here.
Scalmani, G. & Frisch, M. J. J. Chem. Phys. 132, 114110 (2010), continuous-surface-charge (CSC) formulation; the Scalmani-Frisch switching function and the \(A_{ii} = 1.0694\sqrt{4\pi / w_i}\) diagonal.
Tomasi, J., Mennucci, B. & Cammi, R. Chem. Rev. 105, 2999 (2005), comprehensive PCM-family review.
Lange, A. W. & Herbert, J. M. J. Chem. Phys. 133, 244111 (2010), modern Scalmani-style analytic CPCM gradient.
Sinnecker, S., Rajendran, A., Klamt, A., Diedenhofen, M. & Neese, F. J. Phys. Chem. A 110, 2235 (2006), Direct COSMO-RS. A
variant="dcosmo-rs"run cites it together with the COSMO-RS (Klamt 1995, 1998) and COSMO FINE Cavity (Klamt & Diedenhofen 2018) papers. Scalmani and Frisch are left out, because their diagonal is the Lebedev cavity’s.Klamt, A. & Diedenhofen, M. J. Comput. Chem. 39, 1648 (2018), the COSMO FINE Cavity (
cavity="fine").York, D. M. & Karplus, M. J. Phys. Chem. A 103, 11060 (1999), the Gaussian surface-charge kernel the fine cavity uses.
The citations a run writes follow the cavity as well as the variant. A fine-cavity run cites Klamt & Diedenhofen and York & Karplus, and not Scalmani and Frisch, whose switched point-charge diagonal belongs to the Lebedev cavity.
For citation in published work, see citation discipline.