Here's the code for qGAN, that creates 3 qubit circuit (setting first one for fixed angle). Also, creates a generator and discriminator that chooses two-wire different layer structure. Then, defining cost function and optimizing generator for fixed parameters, train generator. For optimized generator calculating the the probability for the discriminator to be fooled should be close to 1. At last, comparing the states of the real data and generator.
First i tried to do without using pennylane however, i faced many import issues mostly related with qiskit-aer, qiskit execute, QuantumInstance etc. Then i asked chatgpt for help and also its answers has same issues. So firstly, i'll share my working code using pennylane and then converted version of my code by gpt. Asking help for how to rewrite some parts with using qiskit tools instead of pennylane.
In short, I want to do the things I do in the code without using pennylane.
working pennylane code:
!pip install pennylane
!pip install pennylane-cirq
!pip install qiskit
!pip install qiskit-algorithms
!pip install qiskit-machine-learning
!pip install qiskit-Aer
!pip install qiskit-qulacs
!pip install matplotlib
!pip install pylatexenc
import numpy as np
import pennylane as qml
import tensorflow as tf
dev = qml.device('cirq.simulator', wires=3)
def real(angles, **kwargs):
qml.Hadamard(wires=0)
qml.Rot(*angles, wires=0)
def generator(w, **kwargs):
qml.Hadamard(wires=0)
qml.RX(w[0], wires=0)
qml.RX(w[1], wires=1)
qml.RY(w[2], wires=0)
qml.RY(w[3], wires=1)
qml.RZ(w[4], wires=0)
qml.RZ(w[5], wires=1)
qml.CNOT(wires=[0, 1])
qml.RX(w[6], wires=0)
qml.RY(w[7], wires=0)
qml.RZ(w[8], wires=0)
def discriminator(w):
qml.Hadamard(wires=0)
qml.RX(w[0], wires=0)
qml.RX(w[1], wires=2)
qml.RY(w[2], wires=0)
qml.RY(w[3], wires=2)
qml.RZ(w[4], wires=0)
qml.RZ(w[5], wires=2)
qml.CNOT(wires=[0, 2])
qml.RX(w[6], wires=2)
qml.RY(w[7], wires=2)
qml.RZ(w[8], wires=2)
@qml.qnode(dev)
def real_disc_circuit(phi, theta, omega, disc_weights):
real([phi, theta, omega])
discriminator(disc_weights)
return qml.expval(qml.PauliZ(2))
@qml.qnode(dev)
def gen_disc_circuit(gen_weights, disc_weights):
generator(gen_weights)
discriminator(disc_weights)
return qml.expval(qml.PauliZ(2))
def prob_real_true(disc_weights):
true_disc_output = real_disc_circuit(phi, theta, omega, disc_weights)
# convert to probability
prob_real_true = (true_disc_output + 1) / 2
return prob_real_true
def prob_fake_true(gen_weights, disc_weights):
fake_disc_output = gen_disc_circuit(gen_weights, disc_weights)
# convert to probability
prob_fake_true = (fake_disc_output + 1) / 2
return prob_fake_true
def disc_cost(disc_weights):
cost = prob_fake_true(gen_weights, disc_weights) - prob_real_true(disc_weights)
return cost
def gen_cost(gen_weights):
return -prob_fake_true(gen_weights, disc_weights)
phi = np.pi / 6
theta = np.pi / 2
omega = np.pi / 7
np.random.seed(0)
eps = 1e-2
init_gen_weights = np.array([np.pi] + [0] * 8) + \
np.random.normal(scale=eps, size=(9,))
init_disc_weights = np.random.normal(size=(9,))
gen_weights = tf.Variable(init_gen_weights)
disc_weights = tf.Variable(init_disc_weights)
opt = tf.keras.optimizers.SGD(0.4)
opt.build([disc_weights, gen_weights])
cost = lambda: disc_cost(disc_weights)
for step in range(50):
with tf.GradientTape() as tape:
cost_value = disc_cost(disc_weights)
gradients = tape.gradient(cost_value, [disc_weights])
opt.apply_gradients(zip(gradients, [disc_weights]))
if step % 5 == 0:
print("Step {}: cost = {}".format(step, cost_value.numpy()))
print("Prob(real classified as real): ", prob_real_true(disc_weights).numpy())
print("Prob(fake classified as real): ", prob_fake_true(gen_weights, disc_weights).numpy())
cost = lambda: gen_cost(gen_weights)
for step in range(50):
with tf.GradientTape() as tape:
cost_value = gen_cost(gen_weights)
gradients = tape.gradient(cost_value, [gen_weights])
opt.apply_gradients(zip(gradients, [gen_weights]))
if step % 5 == 0:
print("Step {}: cost = {}".format(step, cost_value.numpy()))
print("Prob(fake classified as real): ", prob_fake_true(gen_weights, disc_weights).numpy())
print("Discriminator cost: ", disc_cost(disc_weights).numpy())
obs = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)]
@qml.qnode(dev)
def bloch_vector_real(angles):
real(angles)
return [qml.expval(o) for o in obs]
@qml.qnode(dev)
def bloch_vector_generator(angles):
generator(angles)
return [qml.expval(o) for o in obs]
print(f"Real Bloch vector: {bloch_vector_real([phi, theta, omega])}")
print(f"Generator Bloch vector: {bloch_vector_generator(gen_weights)}")
Modified not working code:
from qiskit_aer import Aer
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.utils import QuantumInstance
from qiskit.opflow import Z, I, StateFn
from qiskit.algorithms.optimizers import GradientDescent
import numpy as np
import tensorflow as tf
# Set up simulator
simulator = Aer.get_backend('aer_simulator')
# QuantumInstance for the Qiskit runtime environment
quantum_instance = QuantumInstance(backend=simulator)
# Define the real circuit
def real_circuit(angles):
phi, theta, omega = angles
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.u(phi, theta, omega, 0)
return circuit
# Define the generator circuit
def generator_circuit(w):
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.rx(w[0], 0)
circuit.rx(w[1], 1)
circuit.ry(w[2], 0)
circuit.ry(w[3], 1)
circuit.rz(w[4], 0)
circuit.rz(w[5], 1)
circuit.cx(0, 1)
circuit.rx(w[6], 0)
circuit.ry(w[7], 0)
circuit.rz(w[8], 0)
return circuit
# Define the discriminator circuit
def discriminator_circuit(w):
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.rx(w[0], 0)
circuit.rx(w[1], 2)
circuit.ry(w[2], 0)
circuit.ry(w[3], 2)
circuit.rz(w[4], 0)
circuit.rz(w[5], 2)
circuit.cx(0, 2)
circuit.rx(w[6], 2)
circuit.ry(w[7], 2)
circuit.rz(w[8], 2)
return circuit
# Define function to calculate expectation value of Z on qubit 2
def expectation_value(circuit):
observable = Z ^ I ^ I # Expectation on the second qubit (q2)
state_fn = StateFn(observable, is_measurement=True) @ StateFn(circuit)
return np.real(state_fn.eval())
# Probability conversion functions
def prob_real_true(disc_weights):
circuit = real_circuit([phi, theta, omega])
circuit.compose(discriminator_circuit(disc_weights), inplace=True)
output = expectation_value(circuit)
return (output + 1) / 2
def prob_fake_true(gen_weights, disc_weights):
circuit = generator_circuit(gen_weights)
circuit.compose(discriminator_circuit(disc_weights), inplace=True)
output = expectation_value(circuit)
return (output + 1) / 2
# Define the cost functions
def disc_cost(disc_weights):
return prob_fake_true(gen_weights, disc_weights) - prob_real_true(disc_weights)
def gen_cost(gen_weights):
return -prob_fake_true(gen_weights, disc_weights)
# Initialize parameters
phi, theta, omega = np.pi / 6, np.pi / 2, np.pi / 7
np.random.seed(0)
eps = 1e-2
init_gen_weights = np.array([np.pi] + [0] * 8) + np.random.normal(scale=eps, size=(9,))
init_disc_weights = np.random.normal(size=(9,))
gen_weights = tf.Variable(init_gen_weights)
disc_weights = tf.Variable(init_disc_weights)
opt = tf.keras.optimizers.SGD(0.4)
# Training Discriminator
for step in range(50):
with tf.GradientTape() as tape:
cost_value = disc_cost(disc_weights)
gradients = tape.gradient(cost_value, [disc_weights])
opt.apply_gradients(zip(gradients, [disc_weights]))
if step % 5 == 0:
print("Step {}: cost = {}".format(step, cost_value.numpy()))
# Display probability outputs after discriminator training
print("Prob(real classified as real): ", prob_real_true(disc_weights))
print("Prob(fake classified as real): ", prob_fake_true(gen_weights, disc_weights))
# Training Generator
for step in range(50):
with tf.GradientTape() as tape:
cost_value = gen_cost(gen_weights)
gradients = tape.gradient(cost_value, [gen_weights])
opt.apply_gradients(zip(gradients, [gen_weights]))
if step % 5 == 0:
print("Step {}: cost = {}".format(step, cost_value.numpy()))
# Display results
print("Prob(fake classified as real): ", prob_fake_true(gen_weights, disc_weights))
print("Discriminator cost: ", disc_cost(disc_weights).numpy())
# Bloch vector functions
def bloch_vector_real(angles):
circuit = real_circuit(angles)
return [np.real(StateFn(op @ circuit).eval()) for op in [Z ^ I ^ I, I ^ Z ^ I, I ^ I ^ Z]]
def bloch_vector_generator(angles):
circuit = generator_circuit(angles)
return [np.real(StateFn(op @ circuit).eval()) for op in [Z ^ I ^ I, I ^ Z ^ I, I ^ I ^ Z]]
print(f"Real Bloch vector: {bloch_vector_real([phi, theta, omega])}")
print(f"Generator Bloch vector: {bloch_vector_generator(gen_weights)}")
```