Code Walkthrough
1. Initialization and Setup:
Define the backend as IBM Osaka.
2. Quantum Circuit Definition:
We construct a parameterized quantum circuit (quantum neuron) with one qubit and one classical bit.
The circuit includes:
A Hadamard gate to create superposition.
A parameterized rotation around the z-axis R_z * (θ)).
Another Hadamard gate.
Measurement of the qubit state.
We define the parameter θ as a Parameter object for optimization purposes.
3. Execution Function:
We create a function to execute the parameterized circuit on the backend.
We then bind the parameter θ to the circuit and transpile it for the specified backend with an optimization level of 3.
We use the Sampler from qiskit_ibm_runtime to run the circuit with 512 shots per run.
Then we extract and return the counts of measurement results from the quasi_dists in the results.
4. Objective Function:
First we define an objective function to minimize. The goal is to achieve an expectation value ⟨Z⟩ close to 1.
The expectation value ⟨Z⟩ is calculated as P(0) − P(1), where P(0) and P(1) are the probabilities of measuring the qubit in states '0' and '1', respectively.
The objective function is (⟨Z⟩ − 1)^2.
5. Gradient Calculation:
We implement a numerical gradient calculation using finite differences to estimate the gradient of the objective function with respect to the parameter
Then we define a small perturbation ϵ and compute the gradient as the difference in the objective function values at θ and θ + ϵ.
6. Optimization:
Use the BFGS method from scipy.optimize.minimize to perform gradient descent optimization on the objective function.
The maximum number of iterations to 8 to fit within time constraints (~ 2 minutes).
7. Backend Execution and Result Collection:
We perform the optimization for 8 separate runs.
For each run, execute the optimized circuit on the backend, collect the counts, and save the results to individual json files.
Each json file includes the quasi_dists, metadata fields with the measurement results, and backend information.
8. Data Analysis:
We load all result json files from the runs.
Then we extract the measurement counts from the quasi_dists field in each file and combine them (code also below).
Normalize the combined counts to ensure they sum to 1.
Then we calculate the average optimized θ and objective values if available, but note that these fields may not be present in all files.
9. Plotting and Interpretation:
We plot the combined counts using Qiskit’s histogram plotting function.
Lastly analyze the combined results to evaluate the performance of the quantum gradient descent.
Combined Counts:
'0': 0.9149142447621135
'1': 0.08508575523788647
These combined counts represent the normalized measurement results from multiple runs of the quantum circuit.
Measurement Probabilities:
The probability of measuring the qubit in the '0' state is approximately 91.5%.
The probability of measuring the qubit in the '1' state is approximately 8.5%.
Quantum State and Expectation Values:
Given that we used a parameterized circuit involving a rotation around the z-axis and Hadamard gates, these probabilities indicate that the optimized parameter θ likely resulted in a state that is heavily biased towards the '0' state.
The expectation value of the Z observable can be computed as ⟨Z⟩ = P(0) − P(1), which in this case is 0.914914 − 0.085086 = 0.829828.
Optimization Insight:
The goal of our optimization was to find a parameter θ that minimizes the objective function, which was defined as (⟨Z⟩ − 1)^2.
An expectation value of 0.829828 suggests that the objective function value is (0.829828 − 1)^2 = 0.02896.
This result indicates that our optimization process brought the expectation value close to 1, but not perfectly. The circuit parameter was likely near the optimal point but may not have fully converged within the allowed iterations.
Summary:
The combined measurement results from multiple runs of the experiment showed the following probabilities:
Measuring the qubit in the '0' state: 91.5%
Measuring the qubit in the '1' state: 8.5%
The expectation value of the Z observable, calculated as ⟨Z⟩ = P(0) − P(1), was found to be approximately 0.8298. The objective function value, (0.8298 − 1)^2, was approximately 0.0290. These results indicate that the optimization process was effective, bringing the expectation value close to the desired target of 1. However, the results suggest that further refinement, such as increasing the number of iterations or shots per iteration, could improve accuracy.
In the end, the experiment demonstrated the feasibility of implementing quantum gradient descent on IBM's quantum hardware.
Code:
# Imports
import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler
from qiskit.circuit import Parameter
from scipy.optimize import minimize
import json
import logging
# Set up logging and IBM Quantum service
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
API_KEY = 'Your_IBMQ_Key_O-`' # Replace w/ IBMQ key
service = QiskitRuntimeService(channel='ibm_quantum', token=API_KEY)
backend = service.backend('ibm_osaka')
# Define a parameterized quantum neuron circuit
theta = Parameter('θ')
qc = QuantumCircuit(1, 1) # One qubit and one classical bit for output
qc.h(0)
qc.rz(theta, 0)
qc.h(0)
qc.measure(0, 0)
# Define the function to execute the circuit and retrieve the result
def execute_circuit(param_value):
qc_temp = qc.assign_parameters({theta: param_value})
transpiled_circuit = transpile(qc_temp, backend=backend, optimization_level=3)
with Session(service=service, backend=backend) as session:
sampler = Sampler()
job = sampler. run(circuits=[transpiled_circuit], shots=512)
result = job.result()
counts = result.quasi_dists[0].binary_probabilities()
return counts
# Compute the expectation value of the Z observable
def compute_expectation_value(counts):
shots = sum(counts.values())
expectation = (counts.get('0', 0) - counts.get('1', 0)) / shots
return expectation
# Define the objective function for optimization
def objective_function(params):
counts = execute_circuit(params[0])
exp_val = compute_expectation_value(counts)
return (exp_val - 1) ** 2
# Numerical gradient calculation using finite differences
def gradient_function(params):
epsilon = 1e-6
grad = np.zeros_like(params)
for i in range(len(params)):
params_eps = np.copy(params)
params_eps[i] += epsilon
grad[i] = (objective_function(params_eps) - objective_function(params)) / epsilon
return grad
# Perform the backend runs and save each result to a separate JSON file
initial_point = np.array([0.0])
number_of_runs = 7 # Number of runs to perform
for run in range(1, number_of_runs + 1):
# Perform gradient descent optimization
result = minimize(objective_function, initial_point, jac=gradient_function, method='BFGS', options={'maxiter': 5})
optimized_theta = result.x
# Transpile and run the optimized circuit
optimized_circuit = qc.assign_parameters({theta: optimized_theta[0]})
transpiled_circuit = transpile(optimized_circuit, backend=backend, optimization_level=3)
with Session(service=service, backend=backend) as session:
sampler = Sampler()
job = sampler. run(circuits=[transpiled_circuit], shots=512)
result = job.result()
# Analyze results
counts = result.quasi_dists[0].binary_probabilities()
logging. info(f"Counts from run {run}: {counts}")
# Save results to JSON
results_data = {
'optimized_theta': optimized_theta.tolist(),
'counts': {k: float(v) for k, v in counts.items()},
'objective_value': float(objective_function(optimized_theta))
}
file_path = f'/Users/Documents/QuantumNeuronHW{run}.json'
with open(file_path, 'w') as file:
json.dump(results_data, file, indent=4)
Run Analysis Code:
import json
import os
import numpy as np
from qiskit.visualization import plot_histogram
import matplotlib.pyplot as plt
# Directory containing the result files
results_dir = '/Users/Documents/QuantumNeuronRuns/'
# List all result files in the directory
result_files = [f for f in os.listdir(results_dir) if f.endswith('.json')]
# Function to load and combine results from JSON files
def load_and_combine_results(files):
combined_counts = {}
combined_theta = [] # Placeholder if we find optimized_theta in some files
combined_objective_values = [] # Placeholder if we find objective_value in some files
for file in files:
with open(os.path.join(results_dir, file), 'r') as f:
data = json.load(f)
# Debugging: Print out the keys in each file
print(f"Processing file: {file}")
print(f"Keys in file: {list(data.keys())}")
# Extract counts from quasi_dists
if 'quasi_dists' in data:
for dist in data['quasi_dists']:
for key, value in dist.items():
if key in combined_counts:
combined_counts[key] += value
else:
combined_counts[key] = value
# Normalize combined counts
total_counts = sum(combined_counts.values())
if total_counts > 0:
for key in combined_counts:
combined_counts[key] /= total_counts
# For the purpose of this task, optimized_theta and objective_value are not present
# Return None for these values
avg_theta = None
avg_objective_value = None
return avg_theta, avg_objective_value, combined_counts
# Load and combine results
avg_theta, avg_objective_value, combined_counts = load_and_combine_results(result_files)
# Print the combined results
print(f'Average Optimized Theta: {avg_theta}')
print(f'Average Objective Value: {avg_objective_value}')
print(f'Combined Counts: {combined_counts}')
# Save the combined results to a new JSON file
combined_results_data = {
'optimized_theta': avg_theta.tolist() if avg_theta is not None else None,
'counts': {k: float(v) for k, v in combined_counts.items()},
'objective_value': float(avg_objective_value) if avg_objective_value is not None else None
}
combined_file_path = os.path.join(results_dir, 'QuantumNeuronHW_combined.json')
with open(combined_file_path, 'w') as file:
json.dump(combined_results_data, file, indent=4)
# Plot the combined results if there are counts to plot
if combined_counts:
plot_histogram(combined_counts)
plt. show()
else:
print("No valid counts data to plot.")
# end
Code to Analyze Runs (post hardware run):
import json
import os
import numpy as np
from qiskit.visualization import plot_histogram
import matplotlib.pyplot as plt
# Directory containing the result files
results_dir = '/Users/Documents/QuantumNeuronJobs/'
# List all result files in the directory
result_files = [f for f in os.listdir(results_dir) if f.endswith('.json')]
# Function to load and combine results from JSON files
def load_and_combine_results(files):
combined_counts = {}
expectation_values = []
objective_values = []
for file in files:
with open(os.path.join(results_dir, file), 'r') as f:
data = json.load(f)
print(f"Processing file: {file}")
print(f"Keys in file: {list(data.keys())}")
if 'quasi_dists' in data:
for dist in data['quasi_dists']:
for key, value in dist.items():
if key in combined_counts:
combined_counts[key] += value
else:
combined_counts[key] = value
# Calculate expectation value
P0 = combined_counts.get('0', 0)
P1 = combined_counts.get('1', 0)
total = P0 + P1
if total > 0:
expectation_value = (P0 - P1) / total
expectation_values.append(expectation_value)
# Calculate objective value
objective_value = (expectation_value - 1) ** 2
objective_values.append(objective_value)
# Normalize combined counts
total_counts = sum(combined_counts.values())
if total_counts > 0:
for key in combined_counts:
combined_counts[key] /= total_counts
return expectation_values, objective_values, combined_counts
# Load and combine results
expectation_values, objective_values, combined_counts = load_and_combine_results(result_files)
# Print the combined results
print(f'Expectation Values: {expectation_values}')
print(f'Objective Values: {objective_values}')
print(f'Combined Counts: {combined_counts}')
# Save the combined results to a new JSON file
analysis_results = {
'expectation_values': expectation_values,
'objective_values': objective_values,
'counts': {k: float(v) for k, v in combined_counts.items()}
}
analysis_file_path = '/Users/Documents/QNeuralAnalysis.json'
with open(analysis_file_path, 'w') as file:
json.dump(analysis_results, file, indent=4)
# Plot the combined results
if combined_counts:
plot_histogram(combined_counts)
plt.title("Combined Measurement Outcomes")
plt. show()
# Plot expectation values over iterations
if expectation_values:
plt.plot(expectation_values, label='Expectation Value')
plt.xlabel('Iteration')
plt.ylabel('Expectation Value')
plt.title('Expectation Value Over Iterations')
plt.legend()
plt. show()
# Plot objective values over iterations
if objective_values:
plt.plot(objective_values, label='Objective Function Value')
plt.xlabel('Iteration')
plt.ylabel('Objective Function Value')
plt.title('Objective Function Value Over Iterations')
plt.legend()
plt. show()
# end
Subplots of Expectation Values and Objective Function Values Code:
import json
import matplotlib.pyplot as plt
import numpy as np
# Load the analysis results
file_path = '/Users/Documents/QNeuralAnalysis.json'
with open(file_path, 'r') as file:
data = json.load(file)
expectation_values = data['expectation_values']
objective_values = data['objective_values']
iterations = np.arange(1, len(expectation_values) + 1)
# Set plot style
plt. style.use('dark_background')
# Subplots of Expectation Values and Objective Function Values
fig, axs = plt.subplots(2, 1, figsize=(12, 10), sharex=True)
fig.suptitle('Subplots of Expectation Values and Objective Function Values', color='white')
axs[0].plot(iterations, expectation_values, 'o-', color='white', label='Expectation Value')
axs[0].set_ylabel('Expectation Value', color='white')
axs[0].grid(True, which='both', linestyle='--', linewidth=0.5)
axs[0].legend()
axs[1].plot(iterations, objective_values, 's-', color='blue', label='Objective Function Value')
axs[1].set_xlabel('Iteration', color='white')
axs[1].set_ylabel('Objective Function Value', color='white')
axs[1].grid(True, which='both', linestyle='--', linewidth=0.5)
axs[1].legend()
plt.savefig('/Users/Documents/Subplots_Expectation_Objective.png')
plt. show()
# end
Parallel Coordinates Plot Code:
import pandas as pd
import matplotlib.pyplot as plt
from pandas.plotting import parallel_coordinates
# Load the analysis results
file_path = '/Users/Documents/QNeuralAnalysis.json'
with open(file_path, 'r') as file:
data = json.load(file)
expectation_values = data['expectation_values']
objective_values = data['objective_values']
iterations = np.arange(1, len(expectation_values) + 1)
# Create a DataFrame
df = pd.DataFrame({
'Iteration': iterations,
'Expectation Value': expectation_values,
'Objective Function Value': objective_values
})
# Set plot style
plt. style.use('dark_background')
# Parallel Coordinates Plot
plt.figure(figsize=(12, 8))
parallel_coordinates(df, class_column='Iteration', colormap='coolwarm')
plt.title('Parallel Coordinates Plot of Iterations, Expectation Values, and Objective Function Values', color='white')
plt.xlabel('Attributes', color='white')
plt.ylabel('Values', color='white')
plt.savefig('/Users/Documents/Parallel_Coordinates.png')
plt. show()
Radial Heatmap Code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.projections import PolarAxes
from matplotlib.spines import Spine
from matplotlib.projections.polar import PolarTransform
from matplotlib.ticker import FixedLocator
# Load the analysis results
file_path = '/Users/Documents/QNeuralAnalysis.json'
with open(file_path, 'r') as file:
data = json.load(file)
expectation_values = data['expectation_values']
objective_values = data['objective_values']
iterations = np.arange(1, len(expectation_values) + 1)
# Normalize data for heatmap
normalized_expectation = (expectation_values - np.min(expectation_values)) / (np.max(expectation_values) - np.min(expectation_values))
normalized_objective = (objective_values - np.min(objective_values)) / (np.max(objective_values) - np.min(objective_values))
# Set plot style
plt. style.use('dark_background')
# Radial Heatmap
fig, ax = plt.subplots(figsize=(12, 8), subplot_kw=dict(polar=True))
theta = np.linspace(0, 2 * np.pi, len(iterations))
bars = ax. bar(theta, normalized_expectation, width=0.3, color='blue', alpha=0.7, label='Expectation Value')
bars = ax. bar(theta + 0.3, normalized_objective, width=0.3, color='red', alpha=0.7, label='Objective Function Value')
ax.set_xticks(theta)
ax.set_xticklabels([f'Iteration {i}' for i in iterations], color='white')
ax.set_yticklabels([])
ax.set_title('Radial Heatmap of Expectation and Objective Values', color='white')
ax.legend(loc='upper right')
plt.savefig('/Users/Documents/Radial_Heatmap.png')
plt. show()
# end.