Classical Minimizers¶
In the near-term many quantum computational chemistry algorithms use a combination of classical and quantum
computational resources. Typically, hybrid quantum-classical approaches to finding Hamiltonian eigenvalues and
eigenstates involve the minimization of a cost function, which is often the energy. The variables of the cost function are updated
on a classical device, and the value of the cost function at each iteration is evaluated on a quantum device. To
facilitate algorithms of this type, a variety of minimization methods are available in the InQuanto package. Each
of the minimizers
contains a minimize()
method,
which can be called by the user or by algorithms
objects as part of the workflow.
In this section, we will implement a bespoke state-vector VQE routine with gradients to showcase a few of the available minimizers.
MinimizerScipy¶
The simplest minimizer option available in InQuanto is the InQuanto MinimizerScipy
class,
which wraps the SciPy suite of minimizer classes into an InQuanto object. Below, we implement and optimize a VQE
objective function using the conjugate gradient method to demonstrate the functionality of the minimizer classes, and
the additional control that familiarity with these objects can provide. First, we load in a Hamiltonian from the
express module:
from inquanto.express import load_h5
from inquanto.spaces import FermionSpace
from inquanto.mappings import QubitMappingJordanWigner
h2_sto3g = load_h5("h2_sto3g.h5")
hamiltonian = h2_sto3g["hamiltonian_operator"]
space = FermionSpace(4)
state = space.generate_occupation_state_from_list([1, 1, 0, 0])
qubit_hamiltonian = QubitMappingJordanWigner().operator_map(hamiltonian)
We now choose an ansatz (UCCSD), and prepare functions to compute the energy and energy gradient, which will be the VQE objective and VQE gradient functions respectively:
from inquanto.ansatzes import FermionSpaceAnsatzUCCSD
from inquanto.computables import ExpectationValue, ExpectationValueDerivative
from inquanto.core import dict_to_vector
from inquanto.protocols import SparseStatevectorProtocol
from pytket.extensions.qiskit import AerStateBackend
import numpy as np
ansatz = FermionSpaceAnsatzUCCSD(space, state)
parameters = ansatz.state_symbols.construct_zeros()
sv_protocol = SparseStatevectorProtocol(AerStateBackend())
ev = ExpectationValue(kernel=qubit_hamiltonian, state=ansatz)
evg = ExpectationValueDerivative(ansatz, qubit_hamiltonian, ansatz.free_symbols_ordered())
def vqe_objective(variables):
parameters = ansatz.state_symbols.construct_from_array(variables)
sv_evaluator = sv_protocol.get_evaluator(parameters)
return ev.evaluate(sv_evaluator).real
def vqe_gradient(variables):
parameters = ansatz.state_symbols.construct_from_array(variables)
sv_evaluator = sv_protocol.get_evaluator(parameters)
gradient_dict = evg.evaluate(sv_evaluator)
return dict_to_vector(ansatz.free_symbols_ordered(), gradient_dict)
print("VQE energy with parameters at [0, 0, 0]:", vqe_objective(np.zeros(3)))
print("Gradients of parameters at [0, 0, 0]:", vqe_gradient(np.zeros(3)))
VQE energy with parameters at [0, 0, 0]: -1.117505884204331
Gradients of parameters at [0, 0, 0]: [0.359 0. 0. ]
It should be noted that the minimizers work with NumPy arrays. The parameter objects compatible with the
computables
objects can be constructed with the
construct_from_array()
method as shown above.
We now initialize and execute the conjugate gradient minimizer. The underlying SciPy object can be configured
by passing an options dict
to the MinimizerScipy
constructor, according to the
solver-specific guidance in the
SciPy user manual. With this,
one may define, for example, a maximum number of iterations. With the
minimize()
method, we can leave the gradient
argument empty to
compute the gradient numerically, or pass the gradient function we defined above:
from inquanto.minimizers import MinimizerScipy
minimizer = MinimizerScipy("CG", disp=True)
minimizer.minimize(function=vqe_objective, initial=np.zeros(3))
Optimization terminated successfully.
Current function value: -1.136847
Iterations: 2
Function evaluations: 20
Gradient evaluations: 5
(-1.1368465754720545, array([-0.107, 0. , 0. ]))
minimizer = MinimizerScipy("CG", disp=True)
min, loc = minimizer.minimize(function=vqe_objective, initial=np.zeros(3), gradient=vqe_gradient)
print("Objective function minimum is {}, located at {}".format(min, loc))
Optimization terminated successfully.
Current function value: -1.136847
Iterations: 2
Function evaluations: 5
Gradient evaluations: 5
Objective function minimum is -1.1368465754720543, located at [-0.107 0. 0. ]
As one might expect, we observe that the optimizer converges to the same value in both cases, but requires less evaluations of the objective function when gradient information is provided.
MinimizerRotosolve¶
The Rotosolve minimizer [58], is a gradient-free optimizer
designed for minimization of VQE-like objective functions, and is available in the
MinimizerRotosolve
class. With this minimizer, one may define a maximum number of
iterations and convergence threshold on initialization.
A short example using rotosolve with an algorithm object is shown below.
from inquanto.algorithms import AlgorithmVQE
from inquanto.minimizers import MinimizerRotosolve
minimizer=MinimizerRotosolve(max_iterations=10, tolerance=1e-6, disp=True)
vqe = AlgorithmVQE(
objective_expression=ev,
minimizer=minimizer,
initial_parameters=ansatz.state_symbols.construct_zeros()
)
vqe.build(protocol_objective=SparseStatevectorProtocol(AerStateBackend()))
vqe.run()
print("Minimizer report:", vqe.generate_report()["minimizer"])
print("VQE Energy:", vqe.generate_report()["final_value"])
# TIMER BLOCK-0 BEGINS AT 2025-09-24 18:30:49.733478
ROTOSOLVER – A gradient-free optimizer for parametric circuits
Iteration 1
fun = -1.1368465754720543 variance = 0.011499023526666223 p-norm = 0.10723350002059162
Iteration 2
fun = -1.1368465754720543 variance = 0.0 p-norm = 0.10723350002059162
Optimizer Converged
nit = 2 nfun = 19
final fun = -1.1368465754720543 final variance = 0.0 final p-norm = 0.10723350002059162
# TIMER BLOCK-0 ENDS - DURATION (s): 0.4503408 [0:00:00.450341]
Minimizer report: {'n_iterations': 3, 'function_evaluations': 19, 'final_value': -1.1368465754720543, 'final_parameters': array([-0.107, -0. , -0. ])}
VQE Energy: -1.1368465754720543
Notice that the generate_report()
method of the algorithm class includes the
minimizer report, inheriting it from the chosen minimizer class. This is true for all minimizers and the algorithms
that use them. This allows users to interrogate the number of iterations and function and/or gradient evaluations
that the classical minimizer performed.
MinimizerSGD¶
The Stochastic Gradient Descent (SGD) approach to functional optimization is
available in the MinimizerSGD
class. This minimizer takes bespoke input arguments for the
learning_rate
and decay_rate
parameters, defined in [59].
A short example using MinimizerSGD
is shown below.
from inquanto.minimizers import MinimizerSGD
minimizer=MinimizerSGD(learning_rate=0.25, decay_rate=0.5, max_iterations=10, disp=True)
vqe = AlgorithmVQE(
objective_expression=ev,
minimizer=minimizer,
initial_parameters=ansatz.state_symbols.construct_zeros(),
gradient_expression=evg
)
vqe.build(
protocol_objective=SparseStatevectorProtocol(AerStateBackend()),
protocol_gradient=SparseStatevectorProtocol(AerStateBackend()),
)
vqe.run()
print("VQE Energy:", vqe.generate_report()["final_value"])
# TIMER BLOCK-1 BEGINS AT 2025-09-24 18:30:50.190347
Optimizer Stochastic Gradient Descent
Iteration 0
fun = -1.117505884204331
p-norm = 0.0
g-norm = 0.35933735912603115
Iteration 1
fun = -1.132153514601271
p-norm = 0.054487281372526786
g-norm = 0.17778365523875422
Iteration 2
fun = -1.1357239648371473
p-norm = 0.08144509079704804
g-norm = 0.0870438927078119
Iteration 3
fun = -1.1365789761834755
p-norm = 0.09464378821405425
g-norm = 0.042508542578840264
Iteration 4
fun = -1.1367828405791485
p-norm = 0.10108947180749589
g-norm = 0.020746679121849076
Iteration 5
fun = -1.1368313985785874
p-norm = 0.10423534605115126
g-norm = 0.01012412847800237
Iteration 6
fun = -1.1368429616408462
p-norm = 0.10577049463234582
g-norm = 0.004940280683155357
Iteration 7
fun = -1.1368457149778974
p-norm = 0.10651960255782578
g-norm = 0.0024106935679378827
Iteration 8
fun = -1.1368463705791982
p-norm = 0.10688514244785736
g-norm = 0.0011763364084181327
Iteration 9
fun = -1.1368465266849066
p-norm = 0.10706351347231782
g-norm = 0.0005740118592296173
Optimizer Converged
nit = 9 nfun = 9 njac = 9
final fun = -1.1368465266849066
final p-norm = 0.10715055242023318
final g-norm = 0.0005740118592296173
# TIMER BLOCK-1 ENDS - DURATION (s): 0.4398573 [0:00:00.439857]
VQE Energy: -1.1368465266849066
Minimizer SPSA¶
Simultaneous Perturbation Stochastic Approximation (SPSA) [60] is available in the
MinimizerSPSA
class. This minimizer is especially efficient in high dimensional
problems. SPSA approximates function gradients using
only two objective function measurements and is robust to noisy measurements of this objective function.
A short example using MinimizerSPSA
is shown below.
from inquanto.minimizers import MinimizerSPSA
minimizer=MinimizerSPSA(max_iterations=5, disp=True)
vqe = AlgorithmVQE(
objective_expression=ev,
minimizer=minimizer,
initial_parameters=ansatz.state_symbols.construct_zeros()
)
vqe.build(protocol_objective=SparseStatevectorProtocol(AerStateBackend()))
vqe.run()
print("VQE Energy:", vqe.generate_report()["final_value"])
# TIMER BLOCK-2 BEGINS AT 2025-09-24 18:30:50.636446
Starting SPSA minimization.
Result at iteration 0: [-0.174 0. 0. ].
Result at iteration 1: [-0.174 -0.09 0. ].
Result at iteration 2: [-0.174 -0.09 -0.1 ].
Result at iteration 3: [-0.219 -0.09 -0.1 ].
Result at iteration 4: [-0.219 -0.081 -0.09 ].
Finishing SPSA minimization.
# TIMER BLOCK-2 ENDS - DURATION (s): 0.5131056 [0:00:00.513106]
VQE Energy: -1.1042371202692995
Minimizer BasinHopping¶
Basin-hopping is available in the MinimizerBasinHopping
[61] class. Rather than being a minimizer in the
traditional sense, this is a global optimization algorithm which uses
MinimizerScipy
as its minimizer under the hood.
It is a stochastic algorithm, in which each iteration does the following:
Randomly perturb the input variables.
Locally minimize the target function.
Accept or reject these new variables based on the result of the minimization.
Traditionally, the new variables would only be accepted if the function is minimized. However, this can cause issues if the function is stuck in a local minimum. This is alleviated by using the Metropolis–Hastings condition which allows the function to jump out of a “basin”.
As this inherits from MinimizerScipy
, arguments for that minimizer can
be passed to MinimizerBasinHopping
as well. Important keyword arguments for
this include niter
, which is the number of iterations of this full algorithm (subtly different
from the number of iterations of the minimizer within each step), T
, the temperature which
indicates how high a barrier can be crossed in the function landscape, ideal value being the spacing
between local minima in the function, and stepsize
indicating thewidth of the uniform
distribution to change the input variables.
Further details can be found at SciPy Basin-hopping.
A short example using MinimizerBasinHopping
is shown below.
from inquanto.minimizers import MinimizerBasinHopping
minimizer=MinimizerBasinHopping(disp=True, niter=5, T=1e-3)
vqe = AlgorithmVQE(
objective_expression=ev,
minimizer=minimizer,
initial_parameters=ansatz.state_symbols.construct_zeros()
)
vqe.build(protocol_objective=SparseStatevectorProtocol(AerStateBackend()))
vqe.run()
print("VQE Energy:", vqe.generate_report()["final_value"])
# TIMER BLOCK-3 BEGINS AT 2025-09-24 18:30:51.155761
basinhopping step 0: f -1.13685
basinhopping step 1: f -1.13685 trial_f -1.13685 accepted True lowest_f -1.13685
basinhopping step 2: f -1.13685 trial_f -1.13685 accepted True lowest_f -1.13685
basinhopping step 3: f -1.13685 trial_f -1.13685 accepted True lowest_f -1.13685
basinhopping step 4: f -1.13685 trial_f -1.13685 accepted True lowest_f -1.13685
basinhopping step 5: f -1.13685 trial_f -1.13685 accepted True lowest_f -1.13685
# TIMER BLOCK-3 ENDS - DURATION (s): 7.1166961 [0:00:07.116696]
VQE Energy: -1.1368465754720543