Native Molecular Geometry Optimizer¶
See also the tutorial for the ASE-driven BFGS walkthrough this page’s native optimizer complements.
vibe-qc ships a standalone molecular geometry optimizer that needs no
ASE. It wraps analytic SCF nuclear gradients where they are validated and
uses central finite differences of the full energy for wavefunction methods
without an FD-tight analytic gradient. It is available both as a library
function and through run_job. A conservative steepest-descent + Brent
line-search backend is also available for systems where the Hessian
approximation misbehaves.
Status. Geometry optimisation is production for molecules on every
method that supplies a gradient: analytic where one exists, full-energy
central finite differences otherwise, and the .out always states which.
Two caveats concern which surface is walked. run_job(optimize=True)
with a post-SCF method (mp2, ccsd, the dlpno-* family, ovgf and the
coupled-cluster variants) relaxes the mean-field reference surface, not
the correlated one: the runner collapses such a request to its RHF/UHF
reference for the walk, and the .xyz header records that reference as
energy_method so that an MP2 job’s geometry is not mistaken for an
MP2-optimised one. fci, nevpt2, caspt2 and mrci refuse the ASE
backend and need optimizer_backend="native" or "brent" to optimise on
the correlated surface by finite differences (GitLab #357, detailed under
Backends). The label vocabulary is defined in
Methods: theory and status.
Theory¶
Quasi-Newton steps. The energy is modelled by the quadratic \(E(\mathbf x+\mathbf s) \approx E + \mathbf g^{T}\mathbf s + \tfrac12\mathbf s^{T}\mathbf H\mathbf s\), whose Newton step is \(\mathbf s = -\mathbf H^{-1}\mathbf g\). Quasi-Newton methods update an approximate \(\mathbf H\) from the gradient change \(\mathbf y_k = \mathbf g_{k+1}-\mathbf g_k\) over the step \(\mathbf s_k\); the BFGS update
satisfies \(\mathbf H_{k+1}\mathbf s_k = \mathbf y_k\) and stays positive definite while
\(\mathbf y_k^{T}\mathbf s_k > 0\), which a Wolfe line search guarantees (review, including the SR1,
Powell and Bofill updates of geom_hessian_update=: H. B. Schlegel, WIREs Comput. Mol. Sci.
1, 790 (2011), doi:10.1002/wcms.34). L-BFGS keeps only the last \(m\) pairs and applies
\(\mathbf H^{-1}\) in \(O(mn)\) work (D. C. Liu, J. Nocedal, Math. Program. 45, 503 (1989),
doi:10.1007/BF01589116); L-BFGS-B adds bounds (R. H. Byrd, P. Lu, J. Nocedal, C. Zhu,
SIAM J. Sci. Comput. 16, 1190 (1995), doi:10.1137/0916069).
Step control. A trust-region step minimises the model inside a radius \(\Delta\), \(\mathbf s = -(\mathbf H+\mu\mathbf 1)^{-1}\mathbf g\) with \(\mu \ge 0\) and \(|\mathbf s| \le \Delta\) (Moré, Jorge J. and Sorensen, Danny C. (1983). *Computing a trust region step.* SIAM Journal on Scientific and Statistical Computing, **4**(3), 553-572. doi:[10.1137/0904038](https://doi.org/10.1137/0904038)); \(\Delta\) follows the ratio of actual to predicted energy change. Rational-function optimisation (Banerjee, Amit, Adams, Noah, et al. (1985). *Search for stationary points on surfaces.* Journal of Physical Chemistry, **89**(1), 52-57. doi:[10.1021/j100247a015](https://doi.org/10.1021/j100247a015)) takes the step from the lowest eigenpair of the augmented Hessian,
which stays finite and downhill for an indefinite \(\mathbf H\); partitioned into one uphill and \(n-1\) downhill modes it is the P-RFO transition-state search (Baker, Jon (1986). *An algorithm for the location of transition states.* Journal of Computational Chemistry, **7**(4), 385-395. doi:[10.1002/jcc.540070402](https://doi.org/10.1002/jcc.540070402)). GDIIS (Császár, Pál and Pulay, Péter (1984). *Geometry optimization by direct inversion in the iterative subspace.* Journal of Molecular Structure, **114**, 31-34. doi:[10.1016/S0022-2860(84)87198-7](https://doi.org/10.1016/S0022-2860(84)87198-7)), FIRE (Bitzek, Erik, Koskinen, Pekka, et al. (2006). *Structural relaxation made simple.* Physical Review Letters, **97**(17), 170201. doi:[10.1103/PhysRevLett.97.170201](https://doi.org/10.1103/PhysRevLett.97.170201)) and the dimer method (Henkelman, Graeme and Jónsson, Hannes (1999). *A dimer method for finding saddle points on high dimensional potential surfaces using only first derivatives.* Journal of Chemical Physics, **111**(15), 7010-7022. doi:[10.1063/1.480097](https://doi.org/10.1063/1.480097)) are summarised in the table below.
Convergence. A stationary point has \(\mathbf g = 0\). The optimizers here test
\(\max_i|g_i|\) against a threshold (ASE’s fmax is converted from eV/Å to Ha/bohr); energy-change
and step-length criteria are also common (Schlegel 2011), and the conv column of the progress
trace lists the ones a run applies. A small gradient does not separate a minimum from a saddle
point; a frequency calculation does.
Internal coordinates. Bonds, angles and torsions \(\mathbf q\) couple less strongly than Cartesians. The Wilson matrix \(B_{ij} = \partial q_i/\partial x_j\) (Wilson, E. Bright, Decius, J. C., and Cross, Paul C. (1955). *Molecular Vibrations: The Theory of Infrared and Raman Vibrational Spectra.* McGraw-Hill.) gives \(\mathbf g_q = (\mathbf B^{T})^{+}\mathbf g_x\), and a step \(\Delta\mathbf q\) becomes Cartesian by iterating \(\Delta\mathbf x = \mathbf B^{T}(\mathbf B\mathbf B^{T})^{+}\Delta\mathbf q\). The primitive set is redundant (Peng, Chunyang, Ayala, Philippe Y., et al. (1996). *Using redundant internal coordinates to optimize equilibrium geometries and transition states.* Journal of Computational Chemistry, **17**(1), 49-56. doi:[10.1002/(SICI)1096-987X(19960115)17:1<49::AID-JCC5>3.0.CO;2-0](https://doi.org/10.1002/(SICI)1096-987X(19960115)17:1<49::AID-JCC5>3.0.CO;2-0)); delocalised internal coordinates (Baker, Jon, Kessi, Alain, and Delley, Bernard (1996). *The generation and use of delocalized internal coordinates in geometry optimization.* Journal of Chemical Physics, **105**(1), 192-212. doi:[10.1063/1.471864](https://doi.org/10.1063/1.471864)) use the \(3N-6\) eigenvectors of \(\mathbf G = \mathbf B\mathbf B^{T}\) with nonzero eigenvalues as a non-redundant basis.
Quick start¶
from vibeqc import Molecule, Atom
from vibeqc.molecular_optimize import optimize_molecule
mol = Molecule([
Atom(8, [ 0.00, 0.00, 0.00]),
Atom(1, [ 0.00, 1.43, -0.98]),
Atom(1, [ 0.00, -1.43, -0.98]),
])
result = optimize_molecule(
mol,
basis_name="def2-svp",
method="rks",
functional="PBE",
)
print(result.system) # optimised Molecule (bohr)
print(result.energy) # final energy (Ha)
print(result.n_iter) # number of L-BFGS-B iterations
print(result.converged) # True / False
Through run_job¶
Pass optimizer_backend="native" to bypass ASE:
from vibeqc import run_job
run_job(
mol,
basis="def2-svp",
method="rks",
functional="PBE",
optimize=True,
optimizer_backend="native", # ← no ASE needed
output="h2o_opt",
)
The default optimizer_backend="auto" prefers ASE
(Larsen, Ask Hjorth, Mortensen, Jens Jørgen, et al. (2017). *The atomic simulation environment - a Python library for working with atoms.* Journal of Physics: Condensed Matter, **29**(27), 273002. doi:[10.1088/1361-648X/aa680e](https://doi.org/10.1088/1361-648X/aa680e)) when installed and falls back to the native path otherwise, so existing workflows
are unchanged.
Backends¶
Three geometry-optimizer backends are available through
optimizer_backend= (both via run_job and directly):
Backend |
|
How it works |
Best for |
|---|---|---|---|
ASE BFGS |
|
ASE’s |
Routine use; fastest convergence near minimum |
Native L-BFGS-B |
|
scipy’s L-BFGS-B, limited-memory BFGS with box constraints |
No-ASE workflows; frozen atoms |
Brent steepest-descent |
|
Steepest-descent direction + Brent 1-D line search per step |
Flat/dispersion-bound PESs where Hessian extrapolation misbehaves |
fci, nevpt2, caspt2, and mrci require optimizer_backend="native"
or "brent" explicitly. The ASE calculator has no correlated-surface
route for these four (unlike casci/casscf, which it does route
correctly), so optimizer_backend="ase", including the "auto" default
whenever ASE is installed, raises a ValueError rather than silently
optimizing the mean-field surface and reporting it under the requested
method (GitLab #357). Pass optimizer_backend="native" or "brent" to
reach the correlated FD surface described in
Supported methods below; expect it to be
substantially slower than a mean-field optimization (minutes rather than
seconds even on a small molecule), since each FD gradient costs
6 * n_atoms correlated single points.
The Brent backend never takes an uphill step (each line search is a rigorous 1-D minimisation by Brent’s method, R. P. Brent, Algorithms for Minimization without Derivatives, Prentice-Hall (1973)) and needs no Hessian approximation. Its cost is higher per geometry step (multiple energy evaluations per line search), but it is exceptionally robust on difficult surfaces. Use it when the quasi-Newton backends fail to converge.
# Via run_job:
run_job(mol, basis="def2-svp", method="rks", functional="PBE",
optimize=True, optimizer_backend="brent", output="h2o_brent")
# Direct API:
from vibeqc.molecular_optimize import optimize_molecule_brent
result = optimize_molecule_brent(mol, basis_name="def2-svp", method="rks",
functional="PBE")
When the optimizer does not converge¶
An optimization that stops short of the gradient threshold ends the job on
all three optimizer_backend= backends. The final single point, the
frequencies and the other post-SCF steps do not run, because they would
report the properties of a geometry that is not a stationary point.
For "native" and "brent" the .out closes the optimization with a
verdict. A converged run reads (H2O/STO-3G RHF from a stretched start)
Geometry optimization CONVERGED (native L-BFGS-B, 4 iterations)
final max|g| = 2.328e-05 Ha/bohr (threshold 9.723e-04)
the largest gradient component is within the threshold
Optimized geometry
and a run that did not converge reads
Geometry optimization NOT CONVERGED (native L-BFGS-B, 3 iterations)
final max|g| = 4.543e-03 Ha/bohr (threshold 9.723e-04)
stopped at the step limit (3 of 3 steps)
FATAL: geometry optimization (native L-BFGS-B) did not converge: stopped at the step limit (3 of 3 steps); final max|g| = 4.543e-03 Ha/bohr against the 9.723e-04 Ha/bohr threshold. The last geometry is in the output files.
Last non-converged geometry written to h2o.xyz
final max|g| is the largest gradient component (Ha/bohr) at the last
geometry and the threshold is fmax converted from eV/Å. When scipy stops
for a reason other than the step limit, the line under it quotes that reason.
A run that did not converge also leaves the last geometry in the .xyz
(unless write_xyz_file=False), marks the manifest crashed, and records a
geometry_optimization_nonconverged event. run_job then raises:
Backend |
Error |
|---|---|
|
|
|
|
GeomOptNotConvergedError carries the last geometry as .system and the
whole outcome, as a vibeqc.geomopt.GeomOptResult with the energy, gradient,
iteration count and trajectory, as .result:
from vibeqc.geomopt import GeomOptNotConvergedError
try:
run_job(mol, basis="def2-svp", method="rks", functional="PBE",
optimize=True, optimizer_backend="native", max_opt_steps=20,
output="h2o_opt")
except GeomOptNotConvergedError as err:
print(err.result.gradient) # flat (3N,) gradient, Ha/bohr
restart_from = err.system # last Molecule; continue from here
To get past a capped run, raise max_opt_steps, loosen fmax, restart from
the last geometry, or try another backend (the Brent backend above is slower
per step but takes no uphill step).
The library functions optimize_molecule and optimize_molecule_brent do not
raise. They return a MolecularOptimizeResult whose converged is False
and whose message says why it stopped, so a script that calls them directly
must check converged itself.
Releases before this change handled the ASE backend this way but not the native and Brent backends: a capped run there wrote “Optimized geometry” and went on to the single point and the frequencies as if it had converged.
The native optimizer family (geom_opt=)¶
Beyond the three backends above, run_job exposes the uniform
vibeqc.geomopt framework through geom_opt=. Each optimizer walks
the same provider surface (SCF energy + analytic gradient, with
dispersion and solvation folded in when requested):
|
Algorithm |
How it steps |
Target |
|---|---|---|---|
|
Steepest descent + line search |
walks along the negative gradient with a Brent or backtracking line search ( |
minimum |
|
Conjugate gradient |
conjugate search directions from successive gradients, no Hessian |
minimum |
|
BFGS quasi-Newton |
dense inverse-Hessian update from gradient differences (the |
minimum |
|
Limited-memory BFGS |
the same update kept as a short history of pairs, for large molecules |
minimum |
|
Trust-region Newton |
explicit Hessian model with a step-length trust radius that grows or shrinks on the ratio of predicted to actual descent |
minimum or TS |
|
Rational function optimization |
augmented-Hessian step that stays finite where the Hessian is indefinite |
minimum |
|
Geometry DIIS |
extrapolates the geometry from a history of quasi-Newton steps by minimising the error vector |
minimum |
|
Fast inertial relaxation (FIRE) |
damped molecular dynamics whose velocity is steered along the force |
minimum |
|
Eigenvector following (preview) |
maximises along one Hessian eigenvector and minimises along the rest |
minimum or TS |
|
Partitioned RFO (preview) |
separate RFO steps in the uphill and downhill Hessian subspaces |
transition state |
|
Dimer method (gradient-only saddle search) |
rotates a pair of images to the lowest curvature mode and climbs along it, no Hessian |
transition state |
The spellings "steepest_descent", "conjugate_gradient", "l-bfgs",
"trust_region" and "eigenvector_following" (with hyphen variants) are
accepted aliases; an unknown name raises ValueError naming the available
set.
run_job(mol, basis="def2-svp", method="rks", functional="PBE",
optimize=True, geom_opt="bfgs", fmax=0.01,
output="h2o_native_bfgs")
# Transition-state search (ef / prfo / dimer / trust); ef and prfo are a preview,
# see irc_and_dimer.md for their convergence behaviour from an approximate start:
run_job(mol, basis="def2-svp", method="rks", functional="PBE",
optimize=True, geom_opt="ef", geom_target="transition_state",
output="ts_search")
geom_target="transition_state" selects saddle-point mode;
geom_opt="dimer" requires it (a dimer search of a minimum is
refused with a ValueError). Per-optimizer tuning knobs pass through
geom_opt_options={...}, and the initial-Hessian policy through
geom_hessian_init= / geom_hessian_update=.
geom_hessian_init= accepts "diagonal" (the default: a scaled identity,
hessian_init_scale) and "exact" (an explicit Hessian from a
hessian_provider, when one is supplied). The runner’s docstring also lists
"read" and "model", but no optimiser in vibeqc.geomopt.optimizers,
trust.py or ts.py branches on those two spellings: they fall through to
the diagonal start. geom_hessian_update= selects the update formula
applied after each accepted step: "none" (the run_geomopt default),
"bfgs", "sr1", "powell" or "bofill"; the TS optimisers in
ts.py default to "bofill", which handles the indefinite Hessian a saddle
search needs.
Every optimizer streams a per-step progress table into the job .out
while it runs, so long queue jobs can be monitored live:
step E (Ha) dE max|g| |step| conv
evaluating initial energy and gradient ...
0 -74.96517778 -- 1.9577e-02 -- gmax=1.958e-02 <= 4.500e-04 Ha/bohr [fail]
1 -74.96565587 -4.781e-04 6.9972e-03 2.891e-02 gmax=6.997e-03 <= 4.500e-04 Ha/bohr [fail]
2 -74.96579842 -1.426e-04 4.8506e-03 1.633e-02 gmax=4.851e-03 <= 4.500e-04 Ha/bohr [fail]
3 -74.96589991 -1.015e-04 1.3428e-03 2.670e-02 gmax=1.343e-03 <= 4.500e-04 Ha/bohr [fail]
4 -74.96590107 -1.152e-06 3.2652e-04 1.673e-03 gmax=3.265e-04 <= 4.500e-04 Ha/bohr [pass]
(H2O/STO-3G RHF, geom_opt="bfgs".) The conv column shows each
active convergence criterion’s current value, configured threshold, unit,
and ASCII pass/fail state. Multiple active criteria are separated by
semicolons; rejected markers identify trust-region step rejections. The
optimization stops once all active criteria pass.
When calling run_geomopt directly, progress=True also streams this trace
to the terminal. Pass a ProgressLogger for a caller-controlled live sink;
the persistent .out channel remains independent and no file handle is passed
through the optimizer.
Explicit MANTA RHF/UHF jobs also support geom_opt with independently selected
exact/RI Coulomb and exchange. Their geometry provider retains custom auxiliary
basis content and moves its centers with the owning atoms, including for the
final single point. This route currently uses HCORE at each geometry and refuses
unqualified derivative sources. See selected HF geometry optimization
for configuration and resource limits.
Coordinate systems (geom_coords=)¶
Two coordinate systems are available. geom_coords="cartesian" (the
default) walks the 3N Cartesian coordinates directly, with frozen atoms
removed from the step. geom_coords="dlc" (aliases "internal" and
"delocalized") selects delocalised internal coordinates: bonds, angles
and torsions are generated automatically and combined into a non-redundant
set of 3N-6 linear combinations, which typically converges in fewer steps
for covalently bonded molecules. Any other spelling raises ValueError.
run_job(mol, basis="def2-svp", method="rks", functional="PBE",
optimize=True, geom_opt="bfgs", geom_coords="dlc",
output="h2o_dlc")
The DLC back-transform rebuilds the Wilson B-matrix pseudoinverse at
the current geometry each Newton iteration, wraps torsion residuals
onto the correct 2π branch, and seeds each step from the previous
geometry, so large steps and near-planar torsions (±π branch cut) stay
in the Newton convergence basin. The auto-generated primitive set does
not yet guarantee completeness for every topology: when it spans fewer
than the expected 3N−6 internal degrees of freedom, construction emits
a RuntimeWarning and the optimization may not fully converge; use
the default geom_coords="cartesian" for such systems.
Each step’s SCF warm-starts from the previous step’s converged
density (mean-field methods, gas phase): the provider hands the
prior result to the SCF as an initial_guess=READ restart, cutting
the per-step iteration count substantially since optimizer steps are
small. A warm-started SCF that fails to converge is retried once from
the cold default guess; a still-nonconverged SCF raises a clear error
instead of feeding an unconverged gradient to the optimizer. Opt out
(e.g. to reproduce older runs’ iteration counts) with
MolecularSCFProvider(..., warm_start=False) when driving
run_geomopt directly.
For RKS and UKS, MolecularSCFProvider defaults to
grid_level="orca-defgrid3" and uses that same grid for the SCF energy
and analytic gradient. A caller-provided rks_options= or uks_options=
object keeps its own SCF grid when that grid was customised; an untouched
grid on it receives the grid_level preset (#663). A separate
grid_options= value remains an explicit analytic-gradient override. The
optimize_molecule and optimize_molecule_brent backends use the same
gradient-grid inheritance (the analytic gradient follows the SCF options’
grid). Both backends now accept grid_level="orca-defgrid3" and apply it
to absent or untouched KS options. A custom grid wins; pass
grid_level="legacy" for the old defaults. ROKS options and the selected
level reach every optimizer energy and finite-difference displacement,
including the ASE backend. Molecular NEB, dimer and IRC entry points accept
the same grid_level selection.
Supported methods¶
Method |
Gradient |
Notes |
|---|---|---|
|
Analytic |
All-electron, closed- and open-shell |
|
Analytic / Central FD |
Analytic for LDA, GGA, meta-GGA and global hybrids. Range-separated hybrids (HSE06, ωB97X, CAM-B3LYP, ωB97X-V, …) and VV10-paired functionals (VV10, ωB97X-V, ωB97M-V) fall back to full-energy central FD because the analytic kernel omits their long-range exchange and VV10 terms (GitLab #571; see below) |
|
Central FD |
2-point finite difference on energy ( |
|
Central FD |
2-point FD on energy |
|
Analytic / Central FD |
Validated analytic gradient for state-specific, closed-shell CASSCF (v0.15.0); SA-CASSCF, open-shell, and |
|
Analytic / Central FD |
Analytic IC-CASPT2 Lagrangian for state-specific closed-shell, unshifted, explicit-engine CASSCF references with |
|
Central FD |
Relaxed full-energy central FD for CASSCF-referenced, gas-phase runs with |
|
Central FD |
2-point FD on energy; full-valence unless |
|
Central FD |
2-point FD on energy; CASCI or (with |
fci and mrci reach run_job(optimize=True) only through
optimizer_backend="native" or "brent"; see the note above the
backends table.
Range-separated and VV10 functionals walk the finite-difference
surface (GitLab #571). The analytic RKS / UKS gradient kernel carries
exact exchange through the global fraction only and has no VV10 term, so
for HSE06, ωB97X, ωB97X-D, CAM-B3LYP, VV10, ωB97X-V, ωB97M-V and every other
functional for which vibeqc.functional_gradient_terms_missing(name) is
non-empty, all optimizer backends (ASE/BFGS, native L-BFGS-B, Brent, and
the geom_opt= family through MolecularSCFProvider) differentiate the
full SCF energy by central differences (fd_step_bohr=0.005) instead.
They share one decision (MolecularSCFProvider.has_analytic_gradient is
False and .missing_gradient_terms names the terms), so all of them
walk the same surface, and the .out carries one Note: line naming the
omitted terms and the FD step. Before this fix the optimizers used the
partial analytic gradient silently: on a bent O/H/H molecule in STO-3G it
sits 5.6e-3 (HSE06), 7.0e-4 (VV10) and 5.6e-2 (ωB97X-V) Ha/bohr off the
FD gradient, against a 2.1e-6 PBE noise floor. Frequencies for these
functionals fail closed (the FD Hessian finite-differences the analytic
gradient).
Dispersion corrections (D3-BJ) and implicit solvation (CPCM) are folded into the energy and the gradient on every backend, so a solvated optimization walks the solvated surface rather than the gas-phase one.
Releases before this fix did not: the ASE backend – the default whenever
ASE is installed – was handed no solvent= at all, and the native and
geom_opt= routes solvated the energy while differentiating the
gas-phase surface. On LiF/STO-3G in water, whose bond the reaction field
lengthens by 0.025 bohr, the default backend stopped 9e-06 bohr from the
gas minimum and the other two between the surfaces; the solvated
gradient at those reported geometries was 1.7x to 6.2x the run’s own
convergence threshold. The energy printed was solvated throughout, so
nothing in the output said the geometry was not.
For a functional whose analytic gradient omits terms (the
functional_gradient_terms_missing list above), a solvated run takes the
same full-energy finite-difference route, differentiating the solvated
energy; cpcm_gradient refuses those functionals rather than adding a
reaction field to an incomplete gas-phase kernel.
Implicit solvation composes with the mean-field methods only
(rhf / uhf / rks / uks); requesting solvent=... with the
CAS-family methods or rohf raises a clear ValueError (there is
no CPCM composition for those methods, and silently optimizing the
gas-phase surface instead would misreport the result). For the same
reason optimize=True with solvent= and method="msindo" is refused:
MSINDO’s COSMO route has no nuclear gradient, so its walk would follow
the gas-phase surface. Optimize it in gas phase, or use a mean-field
method with CPCM.
On both backends every per-step energy evaluation receives the
same solver options as the final single point, active_space,
cas_reference, and the wavefunction option structs
(selected_ci_options, dmrg_options, v2rdm_options,
transcorrelated_options, casci_options, casscf_options,
caspt2_options, nevpt2_options), so an
SA-CASSCF optimization (casscf_options=CASSCFOptions(nroots=2))
walks the state-averaged surface its final energy is reported on, and
a selected_ci optimization keeps its active-space truncation at
every FD displacement instead of falling back to full-space CI.
As of v0.15.0 the state-specific CASSCF analytic gradient is a validated
full-energy derivative (examples/regression/casscf_gradient_fd_reproducer.py
passes to ~1e-7 Ha/bohr), and all three molecular backends (the L-BFGS-B
primary, the Brent backend optimizer_backend="brent", and the uniform
geom_opt framework) use it for state-specific, closed-shell, default-
compute_wz CASSCF. They share one decision so they always walk the same
surface. Outside that validated envelope they fall back to full-energy central
FD: state-averaged CASSCF (casscf_options=CASSCFOptions(nroots=2), whose
analytic gradient is only finiteness/translational-invariance checked),
open-shell CASSCF (the kernel is the closed-shell RHF formalism), and an
explicit compute_wz="numerical" request. compute_wz=True is a no-op
alias of the analytic gradient since the experimental W^z correction was
retired (GitLab #516) and stays on the analytic path.
CASPT2 optimizations use the runner-supplied analytic IC-CASPT2 Lagrangian
gradient when the run requests and can produce it: a state-specific,
closed-shell exact-CI CASSCF reference, gas phase, the unshifted explicit
small-space engine, and compute_corr_grad=True. IPEA/imaginary shifts,
engine="cases", direct-large, selected-reference, and open-shell variants
stay on the optimizer’s full-energy central FD route. MS/XMS-CASPT2 keeps its
separate SA-CASSCF analytic response route. NEVPT2 continues to use the
runner-supplied relaxed full-energy central FD when CASSCF-referenced, gas
phase, and opted in.
With the default compute_corr_grad=False the solver only computes the bare
CASSCF reference gradient, so every optimizer falls back to its outer
full-energy central FD rather than walking a surface inconsistent with the
reported PT2 energy. All three backends share the same capability decision.
from vibeqc.solvers import CASPT2Options, CASSCFOptions
result = optimize_molecule(
mol,
basis_name="6-31g",
method="caspt2",
active_space=(2, 2),
casscf_options=CASSCFOptions(),
caspt2_options=CASPT2Options(compute_corr_grad=True), # analytic IC gradient
)
Trajectory collection¶
Set record_trajectory=True (the default) to collect per-step
geometries and energies:
result = optimize_molecule(mol, basis_name="sto-3g", method="rhf")
for i, (frame, e) in enumerate(
zip(result.trajectory_frames, result.trajectory_energies)
):
print(f" step {i}: E = {e:.8f} Ha")
When used through run_job(optimize=True, output_qvf=True), the
trajectory is embedded in the QVF archive for vibe-view’s animation
player, identical behaviour to the ASE backend.
Convergence control¶
Parameter |
Default |
Meaning |
|---|---|---|
|
|
Largest gradient component (Ha/bohr) |
|
|
Energy change tolerance (Ha) |
|
|
Maximum steps |
For DFT jobs where the SCF may struggle at intermediate geometries
(common with PBE + minimal basis sets), pass the appropriate
rks_options / uks_options with increased max_iter:
from vibeqc import RKSOptions
rks_opts = RKSOptions()
rks_opts.max_iter = 80
rks_opts.use_diis = True
result = optimize_molecule(
mol, basis_name="sto-3g", method="rks", functional="PBE",
rks_options=rks_opts,
)
API reference¶
- class vibeqc.molecular_optimize.MolecularOptimizeResult(system, energy, gradient, n_iter, converged, trajectory_frames=None, trajectory_energies=None, message='')[source]¶
Bases:
objectContainer for molecular geometry optimization results.
messagesays in words why the optimizer stopped; it is empty for a result built without one.- Parameters:
- to_geomopt_result(optimizer, *, method=None, basis=None, functional=None)[source]¶
The same outcome as a
vibeqc.geomopt.GeomOptResult.GeomOptResultis the result type thegeom_opt=optimizers return and the one avibeqc.geomopt.GeomOptFailedErrorcarries, so a caller handling a failed optimization sees one shape whichever backend ran.
- vibeqc.molecular_optimize.optimize_molecule(molecule, basis_name, *, method='rhf', functional=None, grid_level='orca-defgrid3', rhf_options=None, uhf_options=None, rks_options=None, uks_options=None, rohf_options=None, roks_options=None, cisd_options=None, selected_ci_options=None, dmrg_options=None, v2rdm_options=None, transcorrelated_options=None, casci_options=None, caspt2_options=None, nevpt2_options=None, casscf_options=None, active_space=None, cas_reference=None, max_iter=100, conv_tol_grad=0.00045, conv_tol_energy=1e-06, gradient_options=None, grid_options=None, dispersion_params=None, solvent=None, record_trajectory=True, progress=False, fd_step_bohr=0.005, freeze_indices=None)[source]¶
Relax molecular geometry using analytic gradients + L-BFGS-B.
- Parameters:
molecule (vibeqc._vibeqc_core.Molecule) – Starting geometry (Cartesian coordinates in bohr).
basis_name (str) – Basis-set name (rebuilt at each geometry step).
method (str) –
"rhf","uhf","rks","uks", or a wavefunction method ("selected_ci","dmrg","v2rdm","transcorrelated_ci","casci","casscf"). Wavefunction methods fall back to central finite differences on the energy.grid_level (str) – Preset for absent or untouched KS options (default
"orca-defgrid3"). Custom grids win; use"legacy"to select the old defaults. Forwarded to every SCF and finite-difference energy evaluation.functional (str | None) – XC functional string for
"rks"/"uks"(e.g."PBE").uks_options (vibeqc._vibeqc_core.UKSOptions | None) – Per-method SCF options. If
None, defaults are used./ (transcorrelated_options / casci_options / caspt2_options)
/
casscf_options (Any) – Wavefunction-solver options, forwarded to every per-step energy evaluation (the FD path) exactly as the final single point receives them – an SA-CASSCF optimization (
casscf_options=CASSCFOptions(nroots=2)) walks the state-averaged surface it reports.active_space (tuple[int, int] | None) –
(n_active_orbitals, n_active_electrons)truncation for the wavefunction methods, applied at every per-step evaluation. Without it aselected_cistep would run full-space CI.cas_reference (str | None) – Reference-orbital choice for the determinant solvers (
"rhf"/"uhf"/"uno").max_iter (int) – Maximum L-BFGS-B iterations.
conv_tol_grad (float) – Gradient convergence tolerance (Ha/bohr). Default 4.5e-4 corresponds to ~0.01 eV/Å – tight enough for routine use.
conv_tol_energy (float) – Energy convergence tolerance (Ha). Controls the scipy
ftolparameter.gradient_options (vibeqc._vibeqc_core.GradientOptions | None) – Options for the analytic gradient kernels (density fitting, COSX, etc.).
grid_options (vibeqc._vibeqc_core.GridOptions | None) – DFT integration grid options (RKS / UKS only).
dispersion_params (Any) – A
D3BJParamsinstance – if provided, the D3-BJ energy and gradient are folded into the objective.solvent (Any) – A
SolventModelor preset string / dict for CPCM implicit solvation (v0.9.0).record_trajectory (bool) – If True (default), collect per-step geometries and energies for downstream visualisation (QVF animation player).
progress (bool | ProgressLogger) – If True, emit per-step energy and gradient norms to stdout. Pass a
ProgressLoggerto select another live sink.fd_step_bohr (float) – Finite-difference step size for wavefunction-method gradients (bohr). Default 0.005 (≈ 0.0026 Å).
freeze_indices (Sequence[int] | None) – Atom indices to hold fixed during the relaxation. Implemented via per-coordinate L-BFGS-B
(fixed, fixed)bounds, mirroringvibeqc.bipole_optimize.relax_atoms(). The SCF + gradient still see every atom; the optimizer simply cannot move the frozen ones, and the reported|grad|excludes them so the convergence metric reflects only the free degrees of freedom.rhf_options (vibeqc._vibeqc_core.RHFOptions | None)
uhf_options (vibeqc._vibeqc_core.UHFOptions | None)
rks_options (vibeqc._vibeqc_core.RKSOptions | None)
uks_options
rohf_options (Any)
roks_options (Any)
cisd_options (Any)
selected_ci_options (Any)
dmrg_options (Any)
v2rdm_options (Any)
transcorrelated_options (Any)
casci_options (Any)
caspt2_options (Any)
nevpt2_options (Any)
- Return type:
- vibeqc.molecular_optimize.optimize_molecule_brent(molecule, basis_name, *, method='rhf', functional=None, grid_level='orca-defgrid3', rhf_options=None, uhf_options=None, rks_options=None, uks_options=None, rohf_options=None, roks_options=None, cisd_options=None, selected_ci_options=None, dmrg_options=None, v2rdm_options=None, transcorrelated_options=None, casci_options=None, caspt2_options=None, nevpt2_options=None, casscf_options=None, active_space=None, cas_reference=None, max_iter=100, conv_tol_grad=0.00045, gradient_options=None, grid_options=None, dispersion_params=None, solvent=None, record_trajectory=True, progress=False, fd_step_bohr=0.005, freeze_indices=None, line_search_step=0.05, line_search_tol=1e-05)[source]¶
Relax molecular geometry using steepest-descent + Brent line search.
At each geometry step the analytic (or finite-difference) gradient defines the steepest-descent direction. A 1-D line search using Brent’s method finds the optimal step length along that direction.
This is a conservative, gradient-driven optimiser that never takes uphill steps. Use
optimizer_backend="brent"inrun_jobto select it from the top-level API.- Parameters:
molecule (vibeqc._vibeqc_core.Molecule)
basis_name (str)
method (str)
functional (str | None)
grid_level (str)
rhf_options (vibeqc._vibeqc_core.RHFOptions | None)
uhf_options (vibeqc._vibeqc_core.UHFOptions | None)
rks_options (vibeqc._vibeqc_core.RKSOptions | None)
uks_options (vibeqc._vibeqc_core.UKSOptions | None)
rohf_options (Any)
roks_options (Any)
cisd_options (Any)
selected_ci_options (Any)
dmrg_options (Any)
v2rdm_options (Any)
transcorrelated_options (Any)
casci_options (Any)
caspt2_options (Any)
nevpt2_options (Any)
casscf_options (Any)
cas_reference (str | None)
max_iter (int)
conv_tol_grad (float)
gradient_options (vibeqc._vibeqc_core.GradientOptions | None)
grid_options (vibeqc._vibeqc_core.GridOptions | None)
dispersion_params (Any)
solvent (Any)
record_trajectory (bool)
progress (bool | ProgressLogger)
fd_step_bohr (float)
line_search_step (float)
line_search_tol (float)
- Return type:
- vibeqc.molecular_optimize.brent_minimize_1d(f, a, b, c, *, tol=1e-05, max_iter=100, progress=False)[source]¶
Brent’s 1-D minimisation without derivatives.
Finds a local minimum of the scalar function
fwithin the bracketing tripleta < b < cwheref(b) < f(a)andf(b) < f(c). The algorithm combines golden-section search with inverse parabolic interpolation.This is the classic Brent (1973) algorithm as described in Numerical Recipes Sec. 10.2.
Returns
(x_min, f_min, n_eval).