Sigmoid Adiabatic State PreparationĀ¶
Sigmoid Adiabatic State Preparation is a variation of adiabatic evolution where the transition between the initial and target Hamiltonians follows a sigmoid curve. This smooth, gradual transformation avoids abrupt changes and allows the system to closely follow the ground state path, as required by the adiabatic theorem.
In this example, the Hamiltonian $ H(t) $ is adjusted over time using a sigmoid function:
- Initial Hamiltonian, $ H_z $: Encodes the initial ground state. Here, $ H_z = I \otimes Z + Z \otimes I $.
- Target Hamiltonian, $ H_{xx} $: Represents the desired end state. Here, $ H_{xx} = X \otimes X $, which applies $X$-basis interactions.
The time-dependent Hamiltonian is given by:
$$ H(t) = -(1 - \text{sigmoid}) \cdot H_z - \text{sigmoid} \cdot H_{xx} $$
where $\text{sigmoid} = \frac{1}{1 + e^{-0.5 \cdot (t - 10)}}$ is a sigmoid function that smoothly shifts the weight from $ H_z $ to $ H_{xx} $ as $ t $ progresses. Initially, $ H_z $ dominates; over time, the system increasingly aligns with $ H_{xx} $.
In this setup, a two-qubit system evolves under the AnalogGate
defined by $ H(t) $ over 30 units of time. The gradual transition led by the sigmoid function allows the state to evolve gently towards the target ground state of $ H_{xx} $, which is captured by measure()
at the end, approximating the desired solution.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import itertools
import warnings
warnings.filterwarnings("ignore")
from oqd_core.interface.math import *
from oqd_core.interface.analog.operator import *
from oqd_core.interface.analog.operation import *
from oqd_core.backend.metric import *
from oqd_core.backend.task import Task, TaskArgsAnalog
from oqd_analog_emulator.qutip_backend import QutipBackend
X, Y, Z, I = PauliX(), PauliY(), PauliZ(), PauliI()
Hxx = X @ X
Hz = I @ Z + Z @ I
sigmoid = MathStr(string=f"1/(1+{np.e}**(-(0.5*(t-10))))")
H = -(1 - sigmoid) * Hz + -sigmoid * Hxx
gate = AnalogGate(hamiltonian=H)
n = 2 # number of qubits
circuit = AnalogCircuit()
circuit.evolve(gate=gate, duration=30)
circuit.measure()
args = TaskArgsAnalog(
n_shots=100,
fock_cutoff=4,
metrics={
"Z": Expectation(operator=((I @ Z + Z @ I) * -0.5)),
"XX": Expectation(operator=(X @ X) * -1),
},
dt=1e-2,
)
task = Task(program=circuit, args=args)
backend = QutipBackend()
results = backend.run(task=task)
fig, ax = plt.subplots(1, 1, figsize=[6, 3])
colors = sns.color_palette(palette="crest", n_colors=4)
for k, (name, metric) in enumerate(results.metrics.items()):
ax.plot(results.times, metric, label=f"$\\langle {name} \\rangle$", color=colors[k])
ax.legend();
fig, axs = plt.subplots(4, 1, sharex=True, figsize=[5, 9])
state = np.array([basis.real + 1j * basis.imag for basis in results.state])
bases = ["".join(bits) for bits in itertools.product("01", repeat=n)]
counts = {basis: results.counts.get(basis, 0) for basis in bases}
ax = axs[0]
ax.bar(x=bases, height=np.abs(state) ** 2, color=colors[0])
ax.set(ylabel="Probability")
ax = axs[1]
ax.bar(x=bases, height=list(counts.values()), color=colors[1])
ax.set(ylabel="Count")
ax = axs[2]
ax.bar(x=bases, height=state.real, color=colors[2])
ax.set(ylabel="Amplitude (real)")
ax = axs[3]
ax.bar(x=bases, height=state.imag, color=colors[3])
ax.set(xlabel="Basis state", ylabel="Amplitude (imag)", ylim=[-np.pi, np.pi]);