Amazon Braket
Amazon Braket 🔗 provides a development environment for you to explore and build quantum algorithms, test them on quantum circuit simulators, and run them on different quantum hardware technologies.
The strangeworks-braket SDK provides a drop-in replacement for code written for Amazon Braket devices.
Pricing is based on the number of quantum tasks executed.
pip install -U pip strangeworks-braket
Authentication
First, authenticate via the Strangeworks SDK with your api-key taken from the Portal homepage:
import strangeworks as sw
from strangeworks_braket import StrangeworksDevice
sw.authenticate(api_key)
Backends
devices = StrangeworksDevice.get_devices(statuses=["ONLINE"])
print("Available devices:")
for device in devices:
print(f" - {device.name} ({device.arn})")
For more accurate and up-to-date information, please refer to the Amazon Braket documentation
Quickstart
Hello World
import strangeworks as sw
from braket.circuits import Circuit
from strangeworks_braket import StrangeworksDevice
sw.authenticate(api_key)
# create a simple quantum circuit
bell_state = Circuit().h(0).cnot(0, 1)
# Choose a device (an AWS-hosted simulator in this case)
tn1 = StrangeworksDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
# Execute the circuit
print("\n🤖 Executing Circuit...\n")
task = tn1.run(bell_state, 1000)
# Add a list of tags to the task to help with organization
sw.add_tags(task.id, ["bell-state"])
# At this point, the job is running on the Strangeworks Platform.
# You can check the status of the job in the Portal.
print(f"⏳ Job {task.id} submitted!\n")
# Lots of good info in here
result = task.result()
# View the counts (also visible in the Portal in a chart 📊)
print(f"🎉 📊 Counts: {result.measurement_counts}\n")
🥳 Success! You may view your job in the portal.
😅 Something went wrong? Contact us at support@strangeworks.com for assistance.
Get a Task
from strangeworks_braket import StrangeworksQuantumTask
task = StrangeworksQuantumTask.from_strangeworks_slug(task_slug)
result = task.result()
Cancel a Task
You can cancel a task using the cancel method. This will stop the task and prevent further charges although you may still be charged for the time the task was running.
from strangeworks_braket import StrangeworksQuantumTask
task = StrangeworksQuantumTask.from_strangeworks_slug(task_slug)
# Cancel the job
task.cancel()
Hybrid Jobs
Hybrid jobs allow for the integration of classical and quantum computing tasks. The execution of these tasks on the Strangeworks Platform is similar to Braket Hybrid Jobs.
The StrangeworksQuantumJob.create() method offers a user-friendly alternative to AwsQuantumJob.create(), streamlining the job submission process by focusing on essential parameters. This makes it easier for users to create jobs on the Strangeworks platform, with the source module path and hyperparameters passed directly as arguments.
While this simplification enhances usability, it is important to note that some advanced AWS-specific features, such as specifying input data or controlling job completion waiting, are not directly exposed.
Job Script
Do NOT run this script directly! This script is uploaded to the Strangeworks Platform and executed remotely. You should only save this code as a file (e.g., braket_hybrid_script.py) and reference it in the submission code shown in the "Run a Job" section below.
To ensure your script runs correctly, follow these guidelines:
- Define a single
mainfunction with no inputs:def main(). Useload_hyperparameters()to read inputs within this function. - Import the Braket tracker:
from braket.tracking import Tracker. - Start the tracker immediately after
def main():or any comments following it. - Import the save job result function:
from braket.jobs import save_job_result. - Save the tracker's results using
save_job_resultwith"task summary": t.quantum_tasks_statistics(),in the output dictionary. - Ensure
save_job_resultis the last line in themainfunction.
An example script is shown below which will run a simple bell state circuit a number of times given by the input parameter num_iter. Save this code as a file (e.g., braket_hybrid_script.py) - you can also download it from braket_hybrid_script.py. This file will be uploaded to the platform when you submit the job using the code in the next section.
# WARNING: Do NOT run this script directly!
# This script is uploaded to the Strangeworks Platform and executed remotely.
# Save this code as a file (e.g., braket_hybrid_script.py) and reference it
# in the submission code shown in the "Run a Job" section.
import json
import os
from braket.aws import AwsDevice
from braket.circuits import Circuit
from braket.jobs import save_job_result
from braket.tracking import Tracker
def main():
"""
Example Hybrid Job File
"""
t = Tracker().start()
device_arn = os.environ["AMZN_BRAKET_DEVICE_ARN"]
# Initialise device
device = AwsDevice(device_arn)
print(f"Using device {device}")
hyperparams = load_hyperparameters()
print("Hyperparameters are:", hyperparams)
shots = int(hyperparams.get("shots"))
num_iter = int(hyperparams.get("num_iter"))
counts = []
bell = Circuit().h(0).cnot(0, 1)
for count in range(num_iter):
task = device.run(bell, shots=shots)
print(task.result().measurement_counts)
counts.append(task.result().measurement_counts)
# return results as strings
save_job_result(
{
"counts": json.dumps(counts),
"task summary": t.quantum_tasks_statistics(),
"estimated cost": t.qpu_tasks_cost() + t.simulator_tasks_cost()
}
)
def load_hyperparameters():
"""Load the Hybrid Job hyperparameters"""
hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
with open(hp_file) as f:
hyperparams = json.load(f)
return hyperparams
Run a Job
This is the code you should run! Execute this code in your local Python environment to submit the hybrid job. Make sure you've saved the job script from the previous section as a file (e.g., braket_hybrid_script.py) in the same directory.
StrangeworksQuantumJob.create() takes:
- source_module: The file containing the code to run.
- hyperparameters: A dictionary of hyperparameters.
- device: The ARN of the device to run the job on.
- job_name: The name of the job, added as a tag.
StrangeworksQuantumJob.create() does not take:
- entry_point (always uses
source_module::main) - input_data
- wait_until_complete
Here's how to submit the hybrid job using the Strangeworks SDK. Run this code in your local environment:
# This is the code you should run in your local Python environment!
# Make sure you've saved the job script (braket_hybrid_script.py) in the same directory.
import strangeworks as sw
from strangeworks_braket import StrangeworksQuantumJob
# Authenticate with your Strangeworks API key
sw.authenticate(api_key)
sw_job = StrangeworksQuantumJob.create(
source_module="./braket_hybrid_script.py",
input_data={}, # No input data needed for this example
hyperparameters={
"shots": 100,
"num_iter": 5
},
device="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
job_name="Bell State Experiment"
)
# Add a list of tags to the job to help with organization
sw.add_tags(sw_job.id, ["quantum-computing"])
# Retrieve the job result
result = sw_job.result()
print(result)
🥳 Success! You may view your job in the portal.
😅 Something went wrong? Contact us at support@strangeworks.com for assistance.
Get a Job
from strangeworks_braket import StrangeworksQuantumJob
job = StrangeworksQuantumJob.from_strangeworks_slug(job_slug)
result = job.result()
Cancel a Job
For hybrid jobs, the cancel command terminates the classical hybrid job container immediately and does a best effort to cancel all of the related quantum tasks that are still in a non-terminal state. This will stop the job and prevent further charges although some charges may still apply for the work that has already been done. The job status will be updated to CANCELLED in the portal.
from strangeworks_braket import StrangeworksQuantumJob
job = StrangeworksQuantumJob.from_strangeworks_slug(job_slug)
# Cancel the job
job.cancel()
Input Data
Hybrid jobs can also accept input data files that are made available to your job script. This is useful for providing datasets, configuration files, or other resources that your quantum algorithm needs.
Job Script with Input Data
Do NOT run this script directly! This script is uploaded to the Strangeworks Platform and executed remotely. You should only save this code as a file and reference it in the submission code shown in the "Run a Job with Input Data" section below.
Here's an example script that demonstrates how to load and use input data files. Save this code as a file (e.g., hybrid_job_with_input_data.py):
# WARNING: Do NOT run this script directly!
# This script is uploaded to the Strangeworks Platform and executed remotely.
# Save this code as a file (e.g., hybrid_job_with_input_data.py) and reference it
# in the submission code shown in the "Run a Job with Input Data" section.
import json
import os
import pandas as pd
import numpy as np
from braket.aws import AwsDevice
from braket.circuits import Circuit
from braket.jobs import save_job_result
from braket.tracking import Tracker
def main():
"""
Example Hybrid Job File with Input Data
"""
t = Tracker().start()
# Load input data from the environment variable
input_dir = os.environ["AMZN_BRAKET_INPUT_DIR"]
# Load CSV data
flight_df = pd.read_csv(f"{input_dir}/flight-data/flight_data.csv")
flight_ids = flight_df.index.tolist()
capacity = {idx: row["Plane Capacity"] for idx, row in flight_df.iterrows()}
# Load JSON data
with open(f"{input_dir}/quantum-params/quantum_parameters.json", "r") as f:
quantum_data = json.load(f)
device_arn = os.environ["AMZN_BRAKET_DEVICE_ARN"]
device = AwsDevice(device_arn)
print(f"Using device {device}")
hyperparams = load_hyperparameters()
print("Hyperparameters are:", hyperparams)
shots = int(hyperparams.get("shots"))
num_iter = int(hyperparams.get("num_iter"))
# Use the input data in your quantum algorithm
print(f"Loaded {len(flight_df)} flight records from CSV")
print(f"Loaded quantum parameters: {list(quantum_data.keys())}")
counts = []
bell = Circuit().h(0).cnot(0, 1)
for count in range(num_iter):
task = device.run(bell, shots=shots)
print(task.result().measurement_counts)
counts.append(task.result().measurement_counts)
# Save results including input data summary
save_job_result(
{
"counts": json.dumps(counts),
"input_data_summary": {
"flight_records": len(flight_df),
"quantum_params": list(quantum_data.keys()),
"sample_capacity": list(capacity.values())
},
"task summary": t.quantum_tasks_statistics(),
"estimated cost": t.qpu_tasks_cost() + t.simulator_tasks_cost(),
}
)
def load_hyperparameters():
"""Load the Hybrid Job hyperparameters"""
hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
with open(hp_file) as f:
hyperparams = json.load(f)
return hyperparams
Run a Job with Input Data
This is the code you should run! Execute this code in your local Python environment to submit the hybrid job with input data. Make sure you've saved the job script from the previous section as a file in the same directory.
To submit a hybrid job with input data, use the input_data parameter in StrangeworksQuantumJob.create(). Run this code in your local environment:
# This is the code you should run in your local Python environment!
# Make sure you've saved the job script (hybrid_job_with_input_data.py) in the same directory.
import strangeworks as sw
from strangeworks_braket import StrangeworksQuantumJob
# Authenticate with your Strangeworks API key
sw.authenticate(api_key)
# Create input data files (these would typically be your actual data files)
# Example: quantum_parameters.json
quantum_params = {
"rotation_angles": [0.048, 0.265, 0.462, 0.905, 1.276, 1.534, 0.730, 0.226, 1.439, 0.566],
"gate_sequence": [0.084, 0.254, 0.414, 1.078, 0.801, 1.487, 0.213, 0.575, 0.510, 0.353],
"optimization_weights": [1.093, 1.025, 1.483, 0.350, 1.435, 0.095, 0.833, 0.570, 0.604, 1.031]
}
import json
with open("quantum_parameters.json", "w") as f:
json.dump(quantum_params, f)
# Example: flight_data.csv
import pandas as pd
flight_data = {
"Plane Capacity": [5000, 7000, 10000],
"Cost of Transport Per Cargo": [15, 30, 45]
}
df = pd.DataFrame(flight_data)
df.to_csv("flight_data.csv", index=False)
# Submit the hybrid job with input data
sw_job = StrangeworksQuantumJob.create(
source_module="./hybrid_job_with_input_data.py",
input_data={
"quantum-params": "quantum_parameters.json",
"flight-data": "flight_data.csv"
},
hyperparameters={
"shots": 100,
"num_iter": 5
},
device="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
job_name="Hybrid Job with Input Data"
)
# Add tags for organization
sw.add_tags(sw_job.id, ["hybrid-job", "input-data", "data-processing"])
print(f"⏳ Job submitted with ID: {sw_job.id}")
# Retrieve the job result
result = sw_job.result()
print("📊 Job Results:")
print(f"Quantum counts: {result['counts']}")
print(f"Input data summary: {result['input_data_summary']}")
Key Points for Input Data
- File Access: Input data files are available in the
AMZN_BRAKET_INPUT_DIRenvironment variable - File Structure: Files are organized in subdirectories based on the keys you provide in
input_data - Supported Formats: CSV, JSON, text files, and other data formats
- File Size Limits: Be mindful of file size limits for your input data
- Data Processing: Use the input data to parameterize your quantum algorithms or provide classical data for hybrid approaches
🥳 Success! You may view your job in the portal.
😅 Something went wrong? Contact us at support@strangeworks.com for assistance.
PennyLane
Hybrid jobs can be seamlessly integrated with PennyLane, a versatile library for quantum machine learning, quantum computing, and quantum chemistry. For detailed guidance on running hybrid jobs with PennyLane, including the QAOA algorithm, please refer to the Amazon Braket documentation.
Please note that the Amazon Braket PennyLane plugin is not supported for use within local notebooks on the Strangeworks Platform.
Example Script
Do NOT run this script directly! This script is uploaded to the Strangeworks Platform and executed remotely. You should only save this code as a file and reference it in the submission code shown in the "Run the Script" section below.
Here is an example script that integrates PennyLane. Save this code as a file (e.g., pennylane_hybridjob.py) - you can also download it from pennylane_hybridjob.py:
# WARNING: Do NOT run this script directly!
# This script is uploaded to the Strangeworks Platform and executed remotely.
# Save this code as a file (e.g., pennylane_hybridjob.py) and reference it
# in the submission code shown in the "Run the Script" section.
import os
import json
import time
import networkx as nx
import pennylane as qml
from pennylane import numpy as np
from braket.jobs import save_job_result
from braket.jobs.metrics import log_metric
from braket.tracking import Tracker
def init_pl_device(device_arn, num_nodes, max_parallel):
device_prefix = device_arn.split(":")[0]
if device_prefix == "local":
prefix, device_name = device_arn.split("/")
print("Using local simulator: ", device_name)
device = qml.device(
device_name,
wires=num_nodes,
custom_decomps={"MultiRZ": qml.MultiRZ.compute_decomposition}
)
else:
print("Using AWS managed device: ", device_arn)
device = qml.device(
"braket.aws.qubit",
device_arn=device_arn,
wires=num_nodes,
parallel=True,
max_parallel=max_parallel
)
return device
def main():
"""
Example Hybrid Job File
"""
# Start tracking the job
t = Tracker().start()
# Read environment variables
hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
device_arn = os.environ["AMZN_BRAKET_DEVICE_ARN"]
# Load hyperparameters
with open(hp_file, "r") as f:
hyperparams = json.load(f)
print("Hyperparameters: ", hyperparams)
# Problem setup hyperparameters
n_nodes = int(hyperparams["n_nodes"])
n_edges = float(hyperparams["n_edges"])
n_layers = int(hyperparams["n_layers"])
# Training hyperparameters
seed = int(hyperparams["seed"])
iterations = int(hyperparams["iterations"])
stepsize = float(hyperparams["stepsize"])
diff_method = hyperparams["diff_method"]
max_parallel = int(hyperparams["max_parallel"])
# Initialize the device
device = init_pl_device(device_arn, n_nodes, max_parallel)
# Set up the graph
g = nx.gnm_random_graph(n_nodes, n_edges, seed=seed)
# Define the QAOA problem
cost_h, mixer_h = qml.qaoa.max_clique(g, constrained=False)
print("Number of observables: ", len(cost_h._ops))
def qaoa_layer(gamma, alpha):
qml.qaoa.cost_layer(gamma, cost_h)
qml.qaoa.mixer_layer(alpha, mixer_h)
def circuit(params):
for i in range(n_nodes):
qml.Hadamard(wires=i)
qml.layer(qaoa_layer, n_layers, params[0], params[1])
@qml.qnode(device, diff_method=diff_method)
def cost_function(params):
circuit(params)
return qml.expval(cost_h)
# Optimization process
print("Starting optimization...")
np.random.seed(seed)
params = np.random.uniform(size=[2, n_layers])
opt = qml.AdamOptimizer(stepsize=stepsize)
for i in range(iterations):
t0 = time.time()
# Perform a gradient step
params, cost_before = opt.step_and_cost(cost_function, params)
cost_before = float(cost_before)
t1 = time.time()
if i == 0:
print("Initial cost:", cost_before)
else:
print(f"Cost at step {i}:", cost_before)
# Log the current loss as a metric
log_metric(
metric_name="Cost",
value=cost_before,
iteration_number=i,
)
print(f"Completed iteration {i + 1}")
print(f"Time to complete iteration: {t1 - t0} seconds")
final_cost = float(cost_function(params))
log_metric(
metric_name="Cost",
value=final_cost,
iteration_number=iterations,
)
# Save the job result
save_job_result(
{
"params": params.tolist(),
"cost": final_cost,
"task summary": t.quantum_tasks_statistics(),
"estimated cost": t.qpu_tasks_cost() + t.simulator_tasks_cost(),
}
)
Run the Script
This is the code you should run! Execute this code in your local Python environment to submit the PennyLane hybrid job. Make sure you've saved the job script from the previous section as a file (e.g., pennylane_hybridjob.py) in the same directory.
To submit the PennyLane hybrid job, run this code in your local environment:
# This is the code you should run in your local Python environment!
# Make sure you've saved the job script (pennylane_hybridjob.py) in the same directory.
import strangeworks as sw
from strangeworks_braket import StrangeworksQuantumJob
sw.authenticate(api_key)
sw_job = StrangeworksQuantumJob.create(
source_module="./pennylane_hybridjob.py",
input_data={}, # No input data needed for this example
hyperparameters = {
"n_nodes": 6,
"n_edges": 10,
"n_layers": 3,
"iterations": 10,
"stepsize": 0.1,
"seed": 42,
"diff_method": "parameter-shift",
"max_parallel": 30
},
device="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
job_name="Pennylane QAOA"
)
# Retrieve the job result
result = sw_job.result()
print(result)
QAOA for Max-Cut
This example demonstrates how to implement the Quantum Approximate Optimization Algorithm (QAOA) for solving the Max-Cut problem using Strangeworks Hybrid Jobs. The algorithm combines classical optimization with quantum circuits to find approximate solutions to combinatorial optimization problems.
The QAOA approach uses a parameterized quantum circuit that is optimized classically to minimize a cost function. This example implements the Max-Cut problem, which seeks to partition graph vertices into two sets to maximize the number of edges between them.
Do NOT run this script directly! This script is uploaded to the Strangeworks Platform and executed remotely. You should only save this code as a file and reference it in the submission code shown in the next section below.
First, save the following script as a file (e.g., qaoa_maxcut_hybrid.py) - you can also download it from qaoa_maxcut_hybrid.py:
# WARNING: Do NOT run this script directly!
# This script is uploaded to the Strangeworks Platform and executed remotely.
# Save this code as a file (e.g., qaoa_maxcut_hybrid.py) and reference it
# in the submission code shown in the next section.
import json
import os
import time
import networkx as nx
import pennylane as qml
from pennylane import numpy as np
from braket.jobs import save_job_result
from braket.jobs.metrics import log_metric
from braket.tracking import Tracker
def create_maxcut_graph(n_nodes, n_edges, seed):
"""Create a random graph for the Max-Cut problem."""
np.random.seed(seed)
graph = nx.gnm_random_graph(n_nodes, n_edges, seed=seed)
return graph
def maxcut_cost_hamiltonian(graph):
"""Generate the cost Hamiltonian for Max-Cut problem."""
coeffs = []
obs = []
for edge in graph.edges():
coeffs.append(0.5)
obs.append(qml.PauliZ(edge[0]) @ qml.PauliZ(edge[1]))
# Add constant term (number of edges / 2)
for i in range(len(coeffs)):
coeffs[i] = -coeffs[i] # Minimize instead of maximize
return qml.Hamiltonian(coeffs, obs)
def mixer_hamiltonian(n_qubits):
"""Generate the mixer Hamiltonian (sum of X gates)."""
coeffs = [1.0] * n_qubits
obs = [qml.PauliX(i) for i in range(n_qubits)]
return qml.Hamiltonian(coeffs, obs)
def qaoa_circuit(params, cost_ham, mixer_ham, n_layers):
"""QAOA circuit with alternating cost and mixer layers."""
gammas, betas = params
# Initial superposition
for i in range(len(mixer_ham.ops)):
qml.Hadamard(wires=i)
# QAOA layers
for layer in range(n_layers):
# Cost layer
qml.templates.ApproxTimeEvolution(cost_ham, gammas[layer], 1)
# Mixer layer
qml.templates.ApproxTimeEvolution(mixer_ham, betas[layer], 1)
def init_device(device_arn, n_qubits):
"""Initialize PennyLane device for the given device ARN."""
if device_arn.startswith("local"):
device = qml.device("default.qubit", wires=n_qubits)
else:
device = qml.device(
"braket.aws.qubit",
device_arn=device_arn,
wires=n_qubits,
shots=1000
)
return device
def main():
"""Main QAOA optimization function."""
# Start tracking
t = Tracker().start()
# Load hyperparameters
hp_file = os.environ["AMZN_BRAKET_HP_FILE"]
device_arn = os.environ.get("AMZN_BRAKET_DEVICE_ARN", "local/default.qubit")
with open(hp_file, "r") as f:
hyperparams = json.load(f)
print("Hyperparameters:", hyperparams)
# Extract hyperparameters
n_nodes = int(hyperparams["n_nodes"])
n_edges = int(hyperparams["n_edges"])
n_layers = int(hyperparams["n_layers"])
n_iterations = int(hyperparams["n_iterations"])
learning_rate = float(hyperparams["learning_rate"])
seed = int(hyperparams.get("seed", 42))
print(f"Solving Max-Cut on {n_nodes} nodes, {n_edges} edges with {n_layers} QAOA layers")
# Create the graph
graph = create_maxcut_graph(n_nodes, n_edges, seed)
print(f"Created graph with {graph.number_of_edges()} edges")
# Set up Hamiltonians
cost_ham = maxcut_cost_hamiltonian(graph)
mixer_ham = mixer_hamiltonian(n_nodes)
# Initialize device
device = init_device(device_arn, n_nodes)
# Create QNode
@qml.qnode(device)
def cost_function(params):
qaoa_circuit(params, cost_ham, mixer_ham, n_layers)
return qml.expval(cost_ham)
# Initialize parameters
np.random.seed(seed)
initial_gammas = np.random.uniform(0, 2*np.pi, n_layers)
initial_betas = np.random.uniform(0, np.pi, n_layers)
params = [initial_gammas, initial_betas]
# Classical optimizer
optimizer = qml.AdamOptimizer(stepsize=learning_rate)
print("Starting QAOA optimization...")
costs = []
for iteration in range(n_iterations):
start_time = time.time()
# Optimization step
params, cost = optimizer.step_and_cost(cost_function, params)
cost = float(cost)
costs.append(cost)
end_time = time.time()
iteration_time = end_time - start_time
print(f"Iteration {iteration + 1}/{n_iterations}: Cost = {cost:.4f}, Time = {iteration_time:.2f}s")
# Log metrics
log_metric(
metric_name="Cost",
value=cost,
iteration_number=iteration
)
log_metric(
metric_name="IterationTime",
value=iteration_time,
iteration_number=iteration
)
# Final evaluation
final_cost = float(cost_function(params))
print(f"Final optimized cost: {final_cost:.4f}")
# Calculate the classical solution for comparison
classical_cut = max_cut_classical(graph)
print(f"Classical max-cut value: {classical_cut}")
# Save results
save_job_result({
"final_cost": final_cost,
"optimal_params": {
"gammas": params[0].tolist(),
"betas": params[1].tolist()
},
"cost_history": costs,
"classical_max_cut": classical_cut,
"graph_info": {
"n_nodes": n_nodes,
"n_edges": graph.number_of_edges(),
"edges": list(graph.edges())
},
"task summary": t.quantum_tasks_statistics(),
"estimated_cost": t.qpu_tasks_cost() + t.simulator_tasks_cost()
})
def max_cut_classical(graph):
"""Calculate classical max-cut using brute force (for small graphs)."""
if graph.number_of_nodes() > 10:
return "Graph too large for brute force"
max_cut = 0
n_nodes = graph.number_of_nodes()
# Try all possible cuts (2^n possibilities)
for i in range(2**n_nodes):
cut_set = []
for j in range(n_nodes):
if (i >> j) & 1:
cut_set.append(j)
# Count edges between the two sets
cut_value = 0
for edge in graph.edges():
if (edge[0] in cut_set) != (edge[1] in cut_set):
cut_value += 1
max_cut = max(max_cut, cut_value)
return max_cut
if __name__ == "__main__":
main()
Now run the QAOA Max-Cut optimization:
This is the code you should run! Execute this code in your local Python environment to submit the QAOA Max-Cut hybrid job. Make sure you've saved the job script from the previous section as a file (e.g., qaoa_maxcut_hybrid.py) in the same directory.
# This is the code you should run in your local Python environment!
# Make sure you've saved the job script (qaoa_maxcut_hybrid.py) in the same directory.
import strangeworks as sw
from strangeworks_braket import StrangeworksQuantumJob
# Authenticate with Strangeworks
sw.authenticate(api_key)
# Submit the QAOA hybrid job
sw_job = StrangeworksQuantumJob.create(
source_module="./qaoa_maxcut_hybrid.py",
input_data={}, # No input data needed for this example
hyperparameters={
"n_nodes": 6, # Number of graph nodes
"n_edges": 8, # Number of graph edges
"n_layers": 2, # Number of QAOA layers (p parameter)
"n_iterations": 50, # Classical optimization iterations
"learning_rate": 0.1, # Adam optimizer learning rate
"seed": 42 # Random seed for reproducibility
},
device="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
job_name="QAOA Max-Cut Optimization"
)
# Add tags for organization
sw.add_tags(sw_job.id, ["qaoa", "max-cut", "optimization", "hybrid-algorithm"])
print(f"⏳ QAOA job submitted with ID: {sw_job.id}")
print("🔗 View progress at: https://portal.strangeworks.com")
# Get results when complete
result = sw_job.result()
print("📊 QAOA Results:")
print(f"Final cost: {result['final_cost']}")
print(f"Classical benchmark: {result['classical_max_cut']}")
print(f"Optimal parameters: {result['optimal_params']}")
This example demonstrates:
- Graph Generation: Creates a random graph for the Max-Cut problem
- Hamiltonian Construction: Builds cost and mixer Hamiltonians for QAOA
- Quantum Circuit: Implements the QAOA ansatz with parameterized layers
- Classical Optimization: Uses Adam optimizer to minimize the cost function
- Performance Tracking: Logs metrics and compares against classical solutions
- Comprehensive Results: Saves optimization history, parameters, and performance metrics
The algorithm iteratively optimizes the quantum circuit parameters to find cuts that maximize the number of edges between graph partitions, showcasing the hybrid quantum-classical approach fundamental to QAOA.
🥳 Success! You may view your job in the portal.
😅 Something went wrong? Contact us at support@strangeworks.com for assistance.
Advanced Features
Error Mitigation
Error mitigation techniques on IonQ Aria
import strangeworks as sw
from strangeworks_braket import StrangeworksDevice
from braket.circuits import Circuit
from braket.error_mitigation import Debias
sw.authenticate(api_key)
device = StrangeworksDevice("ionq_aria")
circuit = Circuit().h(0).cnot(0, 1)
task = device.run(circuit, shots=2500, device_parameters={"errorMitigation": Debias()})
sw.add_tags(task.id, ["debias"])
result = task.result()
print(result.measurement_counts)
🥳 Success! You may view your job in the portal.
😅 Something went wrong? Contact us at support@strangeworks.com for assistance.
Pulse Control
Pulse control on Braket is supported by using StrangeworksDevice
Qubit Spectroscopy
The following example demonstrates how to perform qubit spectroscopy using a Gaussian waveform.
import strangeworks as sw
from strangeworks_braket import StrangeworksDevice
from braket.pulse import GaussianWaveform, PulseSequence
from braket.parametric import FreeParameter
sw.authenticate(api_key)
device = StrangeworksDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2")
if device.status == "ONLINE":
device_name = "ankaa"
experiment_configuration = {
"ankaa": {
"device_arn": "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2",
"qubit": 3,
"drive_frame": "Transmon_3_charge_tx",
"readout_frame": "Transmon_3_readout_rx",
"spectroscopy_wf": GaussianWaveform(100e-9, 25e-9, 0.1, True),
"rabi_wf": GaussianWaveform(
FreeParameter("length"), FreeParameter("length") * 0.25, 0.2, True
),
},
}
drive_frame = device.frames[experiment_configuration[device_name]["drive_frame"]]
readout_frame = device.frames[experiment_configuration[device_name]["readout_frame"]]
waveform = experiment_configuration[device_name]["spectroscopy_wf"]
frequency = FreeParameter("frequency")
pulse_sequence = (
PulseSequence()
.set_frequency(drive_frame, frequency)
.play(drive_frame, waveform)
.capture_v0(readout_frame)
)
span = 75e6
N_shots = 100
qubit_spectroscopy_sequences = pulse_sequence(frequency=drive_frame.frequency - span / 2)
task = device.run(qubit_spectroscopy_sequences, shots=N_shots)
sw.add_tags(task.id, ["qubit-spectroscopy"])
print(task.result().measurement_counts)
Custom Gate with Pulse
The following example demonstrates how to create a custom single-qubit gate using Gaussian waveforms.
import strangeworks as sw
from strangeworks_braket import StrangeworksDevice
import braket.circuits.circuit as circuit
from braket.pulse import GaussianWaveform, PulseSequence
from braket.parametric import FreeParameter
from braket.circuits import Circuit
sw.authenticate(api_key)
device = StrangeworksDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2")
if device.status == "ONLINE":
qubit = 3
drive_frame = device.frames[f"Transmon_{qubit}_charge_tx"]
width = 5e-9
length = 40e-9
x90_amplitude = 0.9
x90 = GaussianWaveform(length, width, x90_amplitude, False)
lambda_ = FreeParameter("lambda_")
theta = FreeParameter("theta")
phi = FreeParameter("phi")
U_sequence = (
PulseSequence()
.shift_phase(drive_frame, np.pi / 2 - lambda_)
.play(drive_frame, x90)
.shift_phase(drive_frame, theta - np.pi)
.play(drive_frame, x90)
.shift_phase(drive_frame, np.pi / 2 - phi)
)
@circuit.subroutine(register=True)
def U_pulses(theta, phi, lambda_):
return Circuit().pulse_gate(
[qubit],
pulse_sequence=U_sequence(theta=theta, phi=phi, lambda_=lambda_),
)
nb_shots = 500
task = device.run(
Circuit().rx(4, np.pi / 2).rz(4, np.pi / 2).U_pulses(2 * np.pi, 0, 0),
shots=nb_shots,
disable_qubit_rewiring=True,
)
sw.add_tags(task.id, ["custom-gate"])
print(task.result().measurement_counts)
🥳 Success! You may view your job in the portal.
😅 Something went wrong? Contact us at support@strangeworks.com for assistance.
Mid-Circuit Measurement
Mid-circuit measurement is a powerful feature that allows you to perform measurements during the execution of a quantum circuit and use the results to conditionally apply subsequent operations. This capability is available on certain quantum devices through Amazon Braket's experimental features.
For detailed information about mid-circuit measurement capabilities, limitations, and supported devices, refer to the AWS Quantum Computing Blog.
Key Features and Limitations
- Supported Devices: Currently available on IQM Garnet and other select devices
- Conditional Operations: Use measurement results to control subsequent quantum operations
- Feedback Loops: Enable dynamic circuit execution based on intermediate results
- Experimental Feature: Requires enabling experimental capabilities
Example: Mid-Circuit Measurement with IQM Garnet
This example demonstrates how to create a simple circuit that uses mid-circuit measurement to conditionally apply operations. The circuit will:
- Prepare a qubit in the |1⟩ state
- Measure it mid-circuit
- Conditionally flip it back to |0⟩ if the measurement result was 1
import strangeworks as sw
from strangeworks_braket import StrangeworksDevice
from braket.circuits import Circuit
from braket.experimental_capabilities import EnableExperimentalCapability
from math import pi
# Authenticate with Strangeworks
sw.authenticate(api_key)
# Use IQM Garnet device for mid-circuit measurement
device = StrangeworksDevice("arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet")
if device.status == "ONLINE":
print("🤖 Creating circuit with mid-circuit measurement...")
# Enable experimental capabilities for mid-circuit measurement
with EnableExperimentalCapability():
circuit = Circuit()
# Step 1: Prepare qubit 1 in the |1⟩ state using a π rotation
circuit.prx(1, pi, 0)
# Step 2: Measure qubit 1 mid-circuit and store result with feedback key 0
circuit.measure_ff(1, feedback_key=0)
# Step 3: Conditionally apply a π rotation if the measurement was 1
# This effectively flips the qubit back to |0⟩ if it was measured as |1⟩
circuit.cc_prx(1, pi, 0, feedback_key=0)
# Wrap the circuit in a verbatim box to preserve the dynamic structure
circuit = Circuit().add_verbatim_box(circuit)
print("🚀 Executing circuit...")
# Execute the circuit with 100 shots
task = device.run(circuit, shots=100)
# Add tags for organization
sw.add_tags(task.id, ["mid-circuit-measurement", "tutorial"])
print(f"⏳ Task submitted with ID: {task.id}")
# Get results
result = task.result()
print(f"📊 Final measurement counts: {result.measurement_counts}")
# The result should show mostly |0⟩ states because:
# - We prepare |1⟩, measure it, then flip it back to |0⟩ if it was |1⟩
# - Any |1⟩ states in the final result are due to quantum noise
print("✅ Mid-circuit measurement completed successfully!")
else:
print("❌ Device is not available. Please try again later.")
Understanding the Circuit Components
prx(qubit, angle, phase): Applies a rotation gate around the X-axismeasure_ff(qubit, feedback_key): Performs a mid-circuit measurement and stores the resultcc_prx(qubit, angle, phase, feedback_key): Applies a conditional rotation based on the measurement resultadd_verbatim_box(): Preserves the dynamic circuit structure for the quantum device
Use Cases
Mid-circuit measurement enables various advanced quantum algorithms:
- Quantum Error Correction: Detect and correct errors during computation
- Adaptive Algorithms: Modify circuit execution based on intermediate results
- Quantum Machine Learning: Implement feedback mechanisms in quantum neural networks
- Quantum Control: Create dynamic control sequences for quantum systems
Mid-circuit measurement is an experimental feature and may have limitations depending on the specific quantum device. Always check the device documentation for current capabilities and restrictions.
Qiskit Integration
While it's generally recommended to write circuits directly in Braket syntax for full compatibility and feature support, you can quickly convert existing Qiskit circuits to work with Amazon Braket using the qiskit-braket-provider package.
Important Limitations: Not all Qiskit circuits will be supported when converted to Braket. Complex quantum operations, custom gates, or advanced Qiskit features may not translate properly. For production use, consider rewriting circuits directly in Braket syntax for better reliability and full feature access.
Installation
Install the qiskit-braket-provider package:
pip install qiskit-braket-provider
Quick Circuit Conversion
The simplest approach is to use the to_braket conversion function to convert Qiskit circuits to Braket format:
import strangeworks as sw
from qiskit import QuantumCircuit
from qiskit_braket_provider import to_braket
from strangeworks_braket import StrangeworksDevice
# Authenticate with Strangeworks
sw.authenticate(api_key)
# Create a Qiskit quantum circuit
qc = QuantumCircuit(2)
qc.h(0) # Apply Hadamard gate to qubit 0
qc.cx(0, 1) # Apply CNOT gate with control=0, target=1
qc.measure_all() # Add measurements
print("Original Qiskit Circuit:")
print(qc.draw())
# Convert Qiskit circuit to Braket circuit
braket_qc = to_braket(qc)
print("\nConverted Braket Circuit:")
print(braket_qc)
# Execute on Braket through Strangeworks
device = StrangeworksDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
task = device.run(braket_qc, shots=1000)
# Add tags for organization
sw.add_tags(task.id, ["qiskit-conversion", "bell-state"])
print(f"⏳ Task submitted with ID: {task.id}")
# Get results
result = task.result()
print(f"📊 Measurement counts: {result.measurement_counts}")
More Complex Example
Here's an example with a more complex Qiskit circuit:
import strangeworks as sw
from qiskit import QuantumCircuit
from qiskit.circuit.library import QFT
from qiskit_braket_provider import to_braket
from strangeworks_braket import StrangeworksDevice
import numpy as np
sw.authenticate(api_key)
# Create a more complex Qiskit circuit with parameterized gates
qc = QuantumCircuit(3)
# Add some rotations and entanglement
qc.ry(np.pi/4, 0)
qc.ry(np.pi/3, 1)
qc.cx(0, 1)
qc.cx(1, 2)
qc.rz(np.pi/6, 2)
qc.barrier()
# Add QFT to the circuit
qft = QFT(num_qubits=3)
qc.compose(qft, inplace=True)
qc.measure_all()
print("Complex Qiskit Circuit:")
print(qc.draw())
try:
# Convert to Braket
braket_qc = to_braket(qc)
print("\n✅ Successfully converted to Braket!")
# Execute on Braket
device = StrangeworksDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
task = device.run(braket_qc, shots=1000)
sw.add_tags(task.id, ["qiskit-conversion", "qft-circuit"])
result = task.result()
print(f"📊 Results: {result.measurement_counts}")
except Exception as e:
print(f"❌ Conversion failed: {e}")
print("💡 Consider rewriting this circuit in native Braket syntax")
Best Practices for Qiskit-Braket Conversion
- Test Conversions: Always test converted circuits on simulators first before running on hardware
- Simple Circuits: Start with simple circuits and gradually increase complexity
- Avoid Custom Gates: Standard Qiskit gates work best; avoid custom or composite gates
- Check Gate Support: Verify that all gates in your Qiskit circuit are supported in Braket
- Consider Rewriting: For production applications, rewrite complex circuits in native Braket syntax
Supported Gates
The conversion typically supports standard gates like:
- Single-qubit gates: H, X, Y, Z, RX, RY, RZ, S, T
- Two-qubit gates: CNOT, CZ, SWAP
- Multi-qubit gates: Toffoli (CCX)
- Measurement operations
When Conversion Might Fail
- Custom or user-defined gates
- Advanced Qiskit circuit features (conditional operations, etc.)
- Some parameterized composite gates
- Circuits with complex classical logic
Alternative: Direct Braket Implementation
For better control and guaranteed compatibility, consider implementing circuits directly in Braket:
import strangeworks as sw
from braket.circuits import Circuit
from strangeworks_braket import StrangeworksDevice
sw.authenticate(api_key)
# Equivalent Bell state circuit in native Braket syntax
braket_circuit = Circuit().h(0).cnot(0, 1)
device = StrangeworksDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
task = device.run(braket_circuit, shots=1000)
sw.add_tags(task.id, ["native-braket", "bell-state"])
result = task.result()
print(f"📊 Results: {result.measurement_counts}")
🥳 Success! You may view your job in the portal.
😅 Something went wrong? Contact us at support@strangeworks.com for assistance.