Create an Orbital Optimizer
Goal
Build and configure an orbital optimizer using the fluent builder API. Choose between Newton and simple optimizers. Optionally, set estimators, minimizers, gradient calculators, number of shots for sampling, and basin-hopping for global optimization.
Prerequisites
A basic understanding of orbital optimization (rotating molecular orbitals to lower the system’s energy)
Overview
Start the builder with:
import qrunch as qc
opt = qc.orbital_optimizer_creator()
Then narrow to a specific algorithm and call .create():
optimizer = (
qc.orbital_optimizer_creator()
.newton() # or .simple()
# .<configure>(...)
.create()
)
Available Optimizers
A Newton-style optimizer that uses a gradient calculator. Supports finite-difference or local (RDM-based) gradients.
import qrunch as qc
optimizer = (
qc.orbital_optimizer_creator()
.newton()
.choose_gradient_calculator()
.local_gradient( # analytical (RDM-based) gradients
estimator=qc.estimator_creator().excitation_gate().create()
)
.with_shots(None) # None = exact estimator for gradients
.with_options( # optional Newton options
options=qc.options.NewtonMinimizerOptions(...)
)
.create()
)
The Newton-style optimizer with a local gradient calculator is a
strong default choice for orbital optimization. It leverages analytical gradients
via reduced density matrices (RDMs), leading to efficient and accurate updates.
Options can be set using a NewtonMinimizerOptions instance.
A lightweight optimizer that uses an estimator and a classical minimizer.
import qrunch as qc
optimizer = (
qc.orbital_optimizer_creator()
.simple()
.with_estimator( # optional: set custom estimator
estimator=qc.estimator_creator().excitation_gate().create()
)
.choose_minimizer() # optional: choose custom minimizer
.scipy(
name="L-BFGS-B",
options=qc.options.ScipyMinimizerOptions(...)
)
.with_shots(None) # None = exact estimator; or an int for finite shots
.create()
)
See Choose a Minimizer for a guide to choosing a minimizer.
Basin-Hopping (Global Optimization)
The orbital optimization energy landscape can contain local minima. The Newton optimizer supports wrapping the local Newton-CG minimization in a basin-hopping global optimizer from SciPy, which repeatedly perturbs the solution and re-minimizes to escape local minima.
Enable basin-hopping with OrbitalOptimizerBasinHoppingOptions:
import qrunch as qc
basin_hopping_options = qc.options.OrbitalOptimizerBasinHoppingOptions(
active=True, # enable basin-hopping
number_of_macro_iterations=5, # basin-hopping steps (default=3)
temperature=0.01, # accept probability for worse solutions
stepsize=0.05, # random displacement size
number_of_successive_failures=4, # stop after this many failures
seed=42, # RNG seed for reproducibility
)
optimizer = (
qc.orbital_optimizer_creator()
.newton()
.choose_gradient_calculator()
.local_gradient(
estimator=qc.estimator_creator().excitation_gate().create()
)
.with_shots(None)
.with_basin_hopping_options(basin_hopping_options)
.create()
)
Basin-hopping is inactive by default (active=False). When to enable it:
Intermittent optimizer (used at every iteration during an adaptive VQE): Basin-hopping is usually not needed because the intermittent optimizer keeps rotations small and close to the global minimum as gates are added.
Final optimizer (used once after the adaptive loop completes): especially when the intermittent optimizer was not used (setting
every_nth_iterationlarger than the maximum number of iterations andforce_full_after_n_non_full_stepsto0).
Intermittent vs. Final Orbital Optimization
When using orbital optimization inside an adaptive VQE (OO-BEAST-VQE or OO-FAST-VQE), two orbital optimizers can be configured independently:
Intermittent orbital optimizer — runs during the adaptive loop, controlled by
IntermittentOrbitalOptimizerAlgorithmOptions. You can use loose convergence criteria here, since the gate parameters will shift on the next iteration anyway.Final orbital optimizer — runs once after the adaptive loop finishes. Use tighter convergence criteria and potentially enable basin-hopping to ensure the global minimum is found.
import qrunch as qc
# Intermittent: fast, loose convergence, no basin-hopping
intermittent_optimizer = (
qc.orbital_optimizer_creator()
.newton()
.choose_gradient_calculator()
.local_gradient(
estimator=qc.estimator_creator().excitation_gate().create()
)
.with_shots(None)
.with_options(qc.options.NewtonMinimizerOptions(
relative_error_tolerance=1e-3,
))
.create()
)
# Final: tight convergence with basin-hopping
final_optimizer = (
qc.orbital_optimizer_creator()
.newton()
.choose_gradient_calculator()
.local_gradient(
estimator=qc.estimator_creator().excitation_gate().create()
)
.with_shots(None)
.with_options(qc.options.NewtonMinimizerOptions(
relative_error_tolerance=1e-6,
))
.with_basin_hopping_options(qc.options.OrbitalOptimizerBasinHoppingOptions(
active=True,
))
.create()
)
Controlling When Intermittent Optimization Runs
Use IntermittentOrbitalOptimizerAlgorithmOptions to
control when and how the intermittent optimizer is invoked:
import qrunch as qc
oo_options = qc.options.IntermittentOrbitalOptimizerAlgorithmOptions(
every_nth_iteration=1, # consider OO every iteration (default)
gradient_threshold=1.0e99, # above this: full OO; below: single Newton step
skip_gradient_threshold=1.0e-16, # below this: skip OO entirely
orbital_change_threshold=1.0e99, # above this orbital change: force full OO
force_full_after_n_non_full_steps=10, # force full OO after 10 consecutive non-full steps
gate_addition_threshold=1.0e-3, # if gate addition changes energy above this: full OO
)
every_nth_iteration: how often to consider an OO step (default1).gradient_threshold: when the orbital gradient norm is above this value, a full converged orbital optimization is performed. When the gradient is below this value but aboveskip_gradient_threshold, only a single Newton step is taken instead — this is roughly as cheap as computing the hessian, but it still updates the orbitals so they track the landscape as new gates are added (default1.0e99, meaning single step is always used).skip_gradient_threshold: when the orbital gradient norm is below this value, the orbital optimization step is skipped entirely (default1.0e-16). Must be ≤gradient_threshold.orbital_change_threshold: if the absolute orbital change from the previous OO step exceeds this value, a full optimization is performed regardless of the gradient norm (default1.0e99).force_full_after_n_non_full_steps: force a full optimization after this many consecutive non-full (single-step or skip) OO decisions (default10). Set to0to disable.gate_addition_threshold: if the absolute energy change due to addition of a gate exceeds this value, a full optimization is performed (default1.0e-3).
Pass these options via .with_intermittent_orbital_optimizer_options(oo_options)
on the VQE calculator builder.
Preset-Based Intermittent Options
For convenience, the VQE calculator builders offer preset-based selection
via choose_intermittent_orbital_optimizer_options().
Each preset configures the IntermittentOrbitalOptimizerAlgorithmOptions
in a single call while leaving the orbital optimizer itself unchanged.
import qrunch as qc
calculator = (
qc.calculator_creator()
.vqe()
.iterative_with_orbital_optimization()
.beast()
.choose_intermittent_orbital_optimizer_options()
.quick() # cheapest: single step every iteration
.create()
)
Available presets:
quick()— Always takes a single Newton step. Never runs a full optimization; the cheapest strategy.force_full_after_n_non_full_stepsis0(disabled),orbital_change_thresholdandgate_addition_thresholdare set very high so they never trigger a full OO step.balanced()— Takes a single Newton step every iteration but runs the full optimizer after 10 consecutive non-full steps. A full optimization is also triggered when a gate addition causes an energy change abovegate_addition_threshold(default1e-3).accurate()— Runs the full optimizer at (nearly) every iteration.gradient_thresholdis set very low (1e-16) so that almost any gradient triggers a full optimization, withforce_full_after_n_non_full_stepsset to1as a safety net. Also triggers full OO when the orbital change exceeds1e-3.
These presets only control when and how often the intermittent optimizer
runs. To change which optimizer is used, combine the preset with
with_orbital_optimizer(...) or with_orbital_optimizer_estimator(...).
The presets can also be used directly as class methods on
IntermittentOrbitalOptimizerAlgorithmOptions:
import qrunch as qc
oo_options = qc.options.IntermittentOrbitalOptimizerAlgorithmOptions.quick()
# or .balanced(), .accurate()
calculator = (
qc.calculator_creator()
.vqe()
.iterative_with_orbital_optimization()
.beast()
.with_intermittent_orbital_optimizer_options(oo_options)
.create()
)
calculator = (
qc.calculator_creator()
.vqe()
.iterative_with_orbital_optimization()
.beast()
.choose_intermittent_orbital_optimizer_options()
.balanced()
.with_orbital_optimizer_estimator(my_custom_estimator)
.create()
)
Next Step
Use the orbital optimizer within VQE calculator variants that support orbital rotations (e.g., OO-BEAST-VQE, OO-FAST-VQE).
See Calculate the Ground State Energy Using the BEAST-VQE and Calculate the Ground State Energy Using the FAST-VQE for full examples.