Variational Optimization

MIMIQ includes a lightweight workflow to optimize symbolic circuits against a scalar cost accumulated in the Z-register (e.g., the expectation value of a Hamiltonian). This enables a broad class of Variational Quantum Algorithms (VQAs), including the Variational Quantum Eigensolver (VQE) and related variational tasks.

Concepts

Variational Quantum Algorithms (VQAs) are hybrid quantum–classical methods that optimize a parameterized quantum circuit against a classical cost function. Different choices of cost define different algorithms (e.g., QAOA, variational classifiers), and VQE is one such example.

Variational Quantum Eigensolver (VQE) approximates the ground-state energy of a Hamiltonian \(H\) and is a specific instance of the broader VQA family.

The method relies on:

  • A parameterized ansatz \(|\psi(\vec{\theta})\rangle\) prepared by a quantum circuit

  • A cost function given by the energy expectation value

\[E(\vec{\theta}) \;=\; \langle \psi(\vec{\theta}) \,|\, H \,|\, \psi(\vec{\theta}) \rangle\]

The parameters \(\vec{\theta}\) are updated by a classical optimizer to minimize \(E(\vec{\theta})\). At convergence, the variational principle guarantees

\[E(\vec{\theta}^*) \;\geq\; E_0\]

where \(E_0\) is the true ground-state energy of \(H\).

Quick Start: VQE in MIMIQ

We use the 1D Ising Hamiltonian and define a simple RX/RY ansatz. Then we define the cost as the energy \(\langle H \rangle\) and optimize it.

Build the Ising Hamiltonian

>>> from mimiqcircuits import *
>>> from symengine import *
>>> N = 4; J = 1.0; h = 0.5
>>> H = Hamiltonian()
>>> for j in range(N - 1):
...     _ = H.push(-J, PauliString("ZZ"), j, j + 1)
>>> for j in range(N):
...     _ = H.push(-h, PauliString("X"), j)
>>> H
4-qubit Hamiltonian with 7 terms:
├── -1.0 * ZZ @ q[0,1]
├── -1.0 * ZZ @ q[1,2]
├── -1.0 * ZZ @ q[2,3]
├── -0.5 * X @ q[0]
├── -0.5 * X @ q[1]
├── -0.5 * X @ q[2]
└── -0.5 * X @ q[3]

Define a symbolic ansatz \(|\psi(\theta)\rangle\)

>>> x0, x1, x2, x3, y0, y1, y2, y3 = symbols("x0 x1 x2 x3 y0 y1 y2 y3")
>>> c = Circuit()
>>> for q, (x, y) in enumerate([(x0,y0),(x1,y1),(x2,y2),(x3,y3)]):
...     _ = c.push(GateRX(x), q)
...     _ = c.push(GateRY(y), q)

Append the cost (accumulate ⟨H⟩ into z[0])

>>> c.push_expval(H, *range(N))
4-qubit, 7-zvar circuit with 23 instructions:
├── RX(x0) @ q[0]
├── RY(y0) @ q[0]
├── RX(x1) @ q[1]
├── RY(y1) @ q[1]
├── RX(x2) @ q[2]
├── RY(y2) @ q[2]
├── RX(x3) @ q[3]
├── RY(y3) @ q[3]
├── ⟨ZZ⟩ @ q[0,1], z[0]
├── z[0] *= -1.0
├── ⟨ZZ⟩ @ q[1,2], z[1]
├── z[1] *= -1.0
├── ⟨ZZ⟩ @ q[2,3], z[2]
├── z[2] *= -1.0
├── ⟨X⟩ @ q[0], z[3]
├── z[3] *= -0.5
├── ⟨X⟩ @ q[1], z[4]
├── z[4] *= -0.5
├── ⟨X⟩ @ q[2], z[5]
⋮   ⋮
└── z[0] += 0.0 + z[1] + z[2] + z[3] + z[4] + z[5] + z[6]

Create the OptimizationExperiment

An OptimizationExperiment specifies:

  • the symbolic quantum circuit,

  • the initial parameter values,

  • the classical optimizer to use,

  • and additional experiment settings (such as label, maximum iterations, and the Z-register index where the cost is accumulated).

>>> init = {x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0, x2: 0.0, y2: 0.0, x3: 0.0, y3: 0.0}
>>> exp = OptimizationExperiment(circuit=c, initparams=init,
...                              optimizer="COBYLA", maxiters=50, zregister=0)
>>> exp
OptimizationExperiment:
├── optimizer: COBYLA
├── label: optexp_python
├── maxiters: 50
├── zregister: 0
└── initparams:
    ├── x0 => 0.0
    ├── y0 => 0.0
    ├── x1 => 0.0
    ├── y1 => 0.0
    ├── x2 => 0.0
    ├── y2 => 0.0
    ├── x3 => 0.0
    └── y3 => 0.0

Running Optimization on Cloud

You can submit a single OptimizationExperiment or a list of them to the MIMIQ Cloud. The return type depends on the history flag:

  • If history=True you will receive an OptimizationResults, which contains the best run and the full history of runs.

  • If history=False (default), you will receive only the best OptimizationRun.

An OptimizationRun represents a single evaluation of the cost function during optimization. It contains:

  • the final cost value for the given parameters,

  • the parameter values used in that evaluation,

  • and the raw execution results (QCSResults) from the quantum simulation or hardware.

An OptimizationResults collects all runs into a history and tracks the best run found during the optimization.

The snippet below assumes you already constructed exp as in the Ising example above.

Connect to the cloud

conn = MimiqConnection()
conn.connect()

Submit the optimization job (choose backend/algorithm)

job = conn.optimize(exp, algorithm="mps", history=True, label="ising_vqe")

Retrieve results (blocks until the job finishes)

optres = conn.get_result(job)   # use get_results if submitting a batch

Inspect best run and history

best = optres.get_best()
print(best)

Optional. Access raw results objects for each evaluation

history_results = optres.get_resultsofhistory()
print(len(history_results))