Using the QiskitTranspiler in Qamomile#
This tutorial demonstrates the usage of the QiskitTranspiler in Qamomile and provides key examples to guide users in applying it effectively.
Translating a Hamiltonian into Qiskit#
We begin by defining a Hamiltonian as a test example and use our transpiler to convert it into a Qiskit-compatible representation. This step shows how seamlessly the Hamiltonian defined in our own library’s format can be translated into operators recognized by Qiskit.
import numpy as np
import qamomile
from qamomile.qiskit.transpiler import QiskitTranspiler
from qamomile.core.operator import Hamiltonian, Pauli, X, Y, Z
from qamomile.core.circuit import QuantumCircuit as QamomileCircuit
from qamomile.core.circuit import (
QuantumCircuit,
SingleQubitGate,
TwoQubitGate,
ParametricSingleQubitGate,
ParametricTwoQubitGate,
SingleQubitGateType,
TwoQubitGateType,
ParametricSingleQubitGateType,
ParametricTwoQubitGateType,
Parameter
)
import jijmodeling as jm
import jijmodeling_transpiler.core as jmt
import networkx as nx
In this snippet, we start from a custom-defined Hamiltonian using various Pauli operators (X, Y, Z) and then employ QiskitTranspiler
to convert it into a format directly suitable for Qiskit. By printing out qiskit_hamiltonian
, we can verify the correctness of the translation.
hamiltonian = Hamiltonian()
hamiltonian += X(0)*Z(1)
hamiltonian += Y(0)*Y(1)*Z(2)*X(3)*X(4)
transpiler = QiskitTranspiler()
qiskit_hamiltonian = transpiler.transpile_hamiltonian(hamiltonian)
qiskit_hamiltonian
SparsePauliOp(['IIIZX', 'XXZYY'],
coeffs=[1.+0.j, 1.+0.j])
Constructing a Parameterized Quantum Circuit#
Next, we build a parameterized quantum circuit using QamomileCircuit. We include single-qubit rotations (e.g., rx, ry, rz) and controlled variants (crx, crz, cry), as well as two-qubit entangling gates (rxx, ryy, rzz). The parameters (theta, beta, gamma) allow for flexible variational adjustments.
qc = QamomileCircuit(3)
theta = Parameter("theta")
beta = Parameter("beta")
gamma = Parameter("gamma")
qc.rx(theta, 0)
qc.ry(beta, 1)
qc.rz(gamma, 2)
qc.crx(gamma, 0 ,1)
qc.crz(theta, 1 ,2)
qc.cry(beta, 2 ,0)
qc.rxx(gamma, 0 ,1)
qc.ryy(theta, 1 ,2)
qc.rzz(beta, 2 ,0)
transpiler = QiskitTranspiler()
qk_circuit = transpiler.transpile_circuit(qc)
Formulating the MaxCut Problem and Converting it into a Quantum Form#
In the following part, we demonstrate how to take a classical optimization problem—MaxCut—and encode it into an Ising-form Hamiltonian. We then construct a QAOA-style ansatz circuit that, when executed and optimized, attempts to solve the MaxCut instance.
def maxcut_problem() -> jm.Problem:
V = jm.Placeholder("V")
E = jm.Placeholder("E", ndim=2)
x = jm.BinaryVar("x", shape=(V,))
e = jm.Element("e", belong_to=E)
i = jm.Element("i", belong_to=V)
j = jm.Element("j", belong_to=V)
problem = jm.Problem("Maxcut", sense=jm.ProblemSense.MAXIMIZE)
si = 2 * x[e[0]] - 1
sj = 2 * x[e[1]] - 1
si.set_latex("s_{e[0]}")
sj.set_latex("s_{e[1]}")
obj = 1 / 2 * jm.sum(e, (1 - si * sj))
problem += obj
return problem
def maxcut_instance():
# Construct a simple graph for a MaxCut instance
G = nx.Graph()
G.add_nodes_from([0, 1, 2, 3, 4])
G.add_edges_from([(0, 1), (0, 4), (1, 2), (1, 3), (2, 3), (3, 4)])
E = [list(edge) for edge in G.edges]
instance_data = {"V": G.number_of_nodes(), "E": E}
pos = {0: (1, 1), 1: (0, 1), 2: (-1, 0.5), 3: (0, 0), 4: (1, 0)}
nx.draw_networkx(G, pos, node_size=500)
return instance_data
problem = maxcut_problem()
instance_data = maxcut_instance()
compiled_instance = jmt.compile_model(problem, instance_data)
# Convert the compiled problem into a QAOA form.
qaoa_converter = qamomile.core.qaoa.QAOAConverter(compiled_instance)
qaoa_converter.ising_encode()
p = 5
qaoa_hamiltonian = qaoa_converter.get_cost_hamiltonian()
qaoa_circuit = qaoa_converter.get_qaoa_ansatz(p=p)

from qamomile.core.circuit.drawer import plot_quantum_circuit
plot_quantum_circuit(qaoa_circuit)

We have now translated the MaxCut problem into a cost Hamiltonian suitable for a QAOA-like algorithm. The parameter p
determines the number of layers of problem and mixer Hamiltonians. Each layer’s parameters are variational and will be tuned to minimize the expvalue, ideally leading to a good solution to the MaxCut instance.
Transpiling and Executing the QAOA Circuit in Qiskit#
With the QAOA circuit and Hamiltonian defined, we use the transpiler again, this time to convert the QAOA circuit and cost Hamiltonian into Qiskit forms:
transpiler = QiskitTranspiler()
# Transpile the QAOA circuit to Qiskit
qk_circuit = transpiler.transpile_circuit(qaoa_circuit)
# Transpile the QAOA Hamitltonian to Qiskit
qk_hamiltonian = transpiler.transpile_hamiltonian(qaoa_hamiltonian)
qk_hamiltonian
SparsePauliOp(['IIIZZ', 'IIZIZ', 'IZIZI', 'ZIIZI', 'ZZIII', 'ZIZII', 'IIIII'],
coeffs=[ 0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j, -3. +0.j])
Here, qk_circuit
is the Qiskit circuit generated from the Qamomile QAOA ansatz and qk_hamiltonian
is build from Qamomile Hamiltonian based on the mathematical model.
Optimizing the Parameters#
Finally, we optimize the variational parameter and attempt to find those that yield better results (e.g., lower cost for the MaxCut objective):
import qiskit.primitives as qk_pr
from scipy.optimize import minimize
cost_history = []
# Cost estimator function
estimator = qk_pr.StatevectorEstimator()
def estimate_cost(param_values):
try:
job = estimator.run([(qk_circuit, qk_hamiltonian, param_values)])
result = job.result()[0]
cost = result.data["evs"]
cost_history.append(cost)
return cost
except Exception as e:
print(f"Error during cost estimation: {e}")
return np.inf
# Create initial parameters
initial_params = np.random.uniform(low=-np.pi / 4, high=np.pi / 4, size=2 * p)
# Run QAOA optimization
result = minimize(
estimate_cost,
initial_params,
method="COBYLA",
options={"maxiter": 2000},
)
print(result)
message: Maximum number of function evaluations has been exceeded.
success: False
status: 2
fun: -4.948032219792361
x: [-5.640e-01 -4.659e-01 -3.222e-01 -5.174e-01 3.372e-01
4.496e-01 8.850e-01 1.022e+00 1.040e+00 -8.994e-02]
nfev: 2000
maxcv: 0.0
import matplotlib.pyplot as plt
plt.title("Change of Cost", fontsize=15)
plt.xlabel("Iteration", fontsize=15)
plt.ylabel("Cost", fontsize=15)
plt.xscale("log")
plt.xlim(1, result.nfev)
plt.plot(cost_history, label="Cost", color="#2696EB")
plt.show()

Once the optimized parameters are obtained, we use Qiskit’s StatevectorSampler
to sample from the parameterized quantum circuit to get the circuit counts.
# Run Optimized QAOA circuit
sampler = qk_pr.StatevectorSampler()
qk_circuit.measure_all()
job = sampler.run([(qk_circuit, result.x)], shots=10000)
job_result = job.result()[0]
qaoa_counts = job_result.data["meas"]
# Prepare data for plotting
keys = list(qaoa_counts.get_counts().keys())
values = list(qaoa_counts.get_counts().values())
# Plotting
plt.figure(figsize=(12, 6))
plt.bar(keys, values, width=0.6)
plt.xlabel("State", fontsize=12)
plt.ylabel("Frequency", fontsize=12)
plt.title("Distribution of State", fontsize=14)
plt.xticks(rotation=90, fontsize=10)
plt.tight_layout()
plt.show()

Evaluating the Results#
From the qaoa_counts
obtained earlier, we can transfer them to a sampleset by qaoa_converter.decode
.
The sampleset can select only the feasible solutions and then we examine the distribution of the objective function values.
sampleset = qaoa_converter.decode(transpiler, qaoa_counts)
plot_data = {}
for sample in sampleset.feasibles():
if sample.eval.objective in plot_data:
plot_data[sample.eval.objective] += sample.num_occurrences
else:
plot_data[sample.eval.objective] = sample.num_occurrences
plt.bar(plot_data.keys(), plot_data.values(), width=0.5)
plt.title("cost distribution of feasible solution", fontsize=15)
plt.ylabel("Frequency", fontsize=15)
plt.xlabel("Energy", fontsize=15)
plt.show()
