QpiAI Quantum SDK¶
Welcome to the QpiAI Quantum SDK! This notebook provides a comprehensive introduction to getting started with quantum computing using QpiAI's platform.
Overview¶
The QpiAI Quantum SDK is a Python library that enables seamless interaction with:
- Quantum Simulators - Test and develop quantum algorithms in a simulated environment
- Quantum Processing Units (QPUs) - Execute quantum circuits on real quantum hardware
Whether you're new to quantum computing or an experienced developer, this SDK provides intuitive tools to design, simulate, and run quantum circuits.
Prerequisites¶
Before getting started, ensure you have:
- Python 3.8 or higher installed
- An active QpiAI account with API access
- Your API key configured (see the registration section above)
Installation¶
Load from Source¶
No external Installation is needed from the User side, as the environment is by default set for the jupyter notebook.
Next Steps¶
In the following sections, we'll cover:
- Loading and saving your
API KEY - Creating your first quantum circuit
- Running simulations
- Executing on real QpiAI Indus quantum hardware using our
Qpiai-quantum-SDK - Analyzing results
Let's dive in!
Configure your API key¶
Steps to access your API Key:
- Go to the dropdown menu in the top right corner and click on API Token
- Under the Tokens section, copy your Main API Token
- Paste the Copied API token as:
API_KEY="paste your API KEY here"
Auntentication the USER¶
# Import the `QpiAIQuantumAuth` class and authenticate the user
import os
import sys
sys.path.append("../")
from dotenv import load_dotenv
load_dotenv()
from qpiai_quantum.authentication import QpiAIQuantumAuth
API_KEY = "PASTE_YOUR_API_KEY"
if not API_KEY:
raise ValueError("API_KEY not found. Please set it in your .env file")
QpiAIQuantumAuth.login(API_KEY)
'API key stored. It will be validated on first cloud access.'
Lets verify that the API key is indeed set
QpiAIQuantumAuth.me()
{'name': 'Quantum Hardware Super Admin',
'email': 'demo@qpiai.tech',
'api_key': 'PASTE_YOUR_API_KEY}
Verify API Key is Valid¶
Before running experiments, let's verify your API key has proper access:
is_valid = QpiAIQuantumAuth.verify_api_key()
if is_valid:
print(" API key is valid and has proper access")
else:
print(" API key validation failed - please check your API key")
print(" Make sure you copied the correct API key from the QpiAI platform")
API key is valid and has proper access
List all available compute resources¶
To list all the compute resources associated with the respected user.
QpiAIQuantumAuth.list_compute_resources()
Available Compute Resources: Backend: QpiAI-Indus-1 Device Type: qpu Usage Rate: 4 credits Backend: QpiAI-QSV-Simulator Device Type: sim Usage Rate: 1 credits Backend: QpiAI-QDM-Simulator Device Type: sim Usage Rate: 2 credits Backend: QpiAI-QTN-Simulator Device Type: sim Usage Rate: 3 credits
Example 1 : Build a Simple Circuit¶
Let's create a simple single-qubit circuit to demonstrate how the circuit builder works. We'll prepare a qubit in the plus state (|+⟩), which is an equal superposition of |0⟩ and |1⟩.
What is a Plus State?¶
The plus state is created by applying a Hadamard gate to a qubit initialized in the |0⟩ state:
- Initial state: |0⟩
- Apply Hadamard gate (H)
- Resulting state: |+⟩ = (|0⟩ + |1⟩)/√2
Understanding the Result¶
When we measure this circuit multiple times:
- Approximately 50% of measurements will yield |0⟩
- Approximately 50% of measurements will yield |1⟩
This demonstrates quantum superposition in action!
from qpiai_quantum import QuantumRegister, ClassicalRegister, Circuit
qreg = QuantumRegister(1, "q") # Define a quantum register with 1 qubits with name "q"
creg = ClassicalRegister(1, "c") # Define a classical register with 1 bits with name "c"
circuit = Circuit(qreg, creg) # Create a quantum circuit object with the above registers
circuit.h(0) # Apply Hadamard gate on qubit 0
circuit.measure([0], [0]) # Measure qubit 0 into classical bit 0
Draw the Circuit¶
Now that we've built our circuit, let's visualize it to understand its structure. The QpiAI SDK provides a convenient .show() method that renders a graphical representation of your quantum circuit.
# Display the circuit diagram
Circuit.show()
circuit.show() # Draw the circuit
Analyzing the Circuit Features¶
Once you've built your quantum circuit, it's useful to analyze its properties and characteristics. The QpiAI SDK provides several methods to inspect circuit details:
Circuit Depth¶
The depth represents the longest path of sequential gates in your circuit. It's a measure of how many time steps are required to execute the circuit.
# Display the depth of the circuit
qc.depth()
circuit.depth() # Display the depth of the circuit
1
Number of Qubits¶
This shows the total number of quantum bits (qubits) in your circuit.
# Display the number of qubits
qc.num_qubits
circuit.num_qubits # Get the number of qubits in the circuit
1
Number of Classical Bits¶
Classical bits store measurement results. This property shows how many classical bits are allocated in your circuit.
# Display the number of classical bits
qc.num_clbits
circuit.num_clbits # Get the number of classical bits in the circuit
1
List of Gates¶
Get a complete list of all quantum gates used in your circuit, helping you understand the operations being performed.
# Display the list of all gates in the circuit
qc.list_gates()
circuit.list_gates() # List all gates in the circuit
{'total_operations': 2,
'total_gates': 1,
'single_qubit_gates': 1,
'two_qubit_gates': 0,
'multi_qubit_gates': 0,
'clifford_gates': 1,
'non_clifford_gates': 0,
'parametric_gates': 0,
'measurements': 1,
'barriers': 0,
'gate_counts': {'H': 1, 'MEASURE': 1}}
Important: Create Your Experiment in the UI First¶
⚠️ CRITICAL: Before executing your circuit, you MUST create an experiment in the QpiAI web interface. Failing to do so will result in a 403 Forbidden error.
Steps to Create an Experiment:¶
- Log in to the QpiAI platform at https://app.qpiai-quantum.tech
- Navigate to the Experiments section from the main dashboard
- Click on "Create New Experiment" or the "+" button
- Enter the experiment name - this must match the
experiment_nameparameter in your code- In our example below, you need to create:
sdk_test_exp
- In our example below, you need to create:
- Configure additional settings as needed (optional)
- Save the experiment
Example:¶
For the code below, create an experiment named sdk_test_exp in the web UI first.
Common Errors:¶
403 Forbidden Error:
- Cause: The experiment doesn't exist, or your API key doesn't have access to it
- Solution: Create the experiment in the QpiAI web UI first with the exact same name
Important Notes:
- ⚠️ The experiment name in your code must exactly match the name in the UI (case-sensitive)
- ⚠️ The experiment must be created under the same account as your API key
- If
overwrite=Trueis set, new executions will replace previous results under the same experiment - You can view and manage all your experiments from the web dashboard
Once your experiment is created in the UI, you're ready to execute your circuit!
Submit the Circuit to backend device¶
Now that we've built and analyzed our circuit, it's time to run it! The QpiAI SDK uses the Run funation to submit circuits to quantum simulators or real quantum hardware.
Understanding the Parameters¶
| Parameter | Description |
|---|---|
shots |
Number of times to run the circuit (10,000 repetitions) |
experiment_name |
A descriptive name for your experiment |
method |
Simulation method - "statevector" provides exact quantum state information |
device_type |
Type of device - "sim" for simulator, "qpu" for real quantum hardware ( QpiAI-Indus ) |
reverse_bits |
Specify the qubit ordering (Big-endian {if True} , else Little-endian {default}) |
Execution Methods¶
The SDK mainly supports statevector methods:
- statevector: Computes the exact quantum state (ideal for small circuits)
Device Types¶
- sim: Quantum simulators - perfect for development and testing
- qpu: Real quantum processing units - for production workloads
Note: Execution time varies based on circuit complexity and device availability. The WebSocket connection provides real-time status updates during execution.
res = circuit.run(shots=10000, experiment_name="plus_state", device_type="qpu")
To Extract the Counts¶
print(f"Counts : {res.get_counts()}")
Counts : {'0': 4962, '1': 5038}
To Extract the probablities¶
print(f"Probablities : {res.get_probabilities()}")
Probablities : {'0': 0.4962, '1': 0.5038}
To Extract the JOB ID¶
print(f"JOB ID: {res.get_job_id()} and it's status {res.job_status}")
JOB ID: e7ec6d95-2d68-4c97-afd4-4cc9d5b3b364 and it's status Success
Example 2 : Build a Simple Circuit¶
Let's create a simple single-qubit circuit to demonstrate how the circuit builder works. We'll prepare a qubit in the plus state (|psi_+⟩), which is an equal superposition of |00⟩ and |11⟩.
What is a Bell State?¶
The plus state is created by applying a Hadamard gate to a qubit initialized in the |0⟩ state:
- Initial state: |0⟩
- Apply Hadamard gate (H)
- Resulting state: |+⟩ = (|00⟩ + |11⟩)/√2
Understanding the Result¶
When we measure this circuit multiple times:
- Approximately 50% of measurements will yield |00⟩
- Approximately 50% of measurements will yield |11⟩
This demonstrates quantum superposition in action!
bell_circuit = Circuit(2, 2) # Create a quantum circuit object with the above registers
bell_circuit.x(1) # Apply X gate on qubit 1 to create |10> state
bell_circuit.barrier(0,1) # Apply barrier on qubits 0 and 1
bell_circuit.h(0) # Apply Hadamard gate on qubit 0
bell_circuit.cx(0,1) # Apply CNOT gate with control qubit 0 and target qubit 1
bell_circuit.measure([0,1], [0,1]) # Measure qubit 0 and 1 into classical bits 0 and 1 respectively
bell_circuit.show() # Draw the circuit
To extract counts in big-endian format (the default is little-endian), use reverse_bits=True.¶
res = circuit.run(
reverse_bits=True
)
# By Default : qpiai_quantum uses Little-endian Format
res_l = bell_circuit.run(shots=10000, experiment_name="Bell", device_type='qpu')
print(f"Little-endian counts : {res_l.get_counts()}")
rres_b = bell_circuit.run(shots=10000, experiment_name="Bell", device_type='qpu', reverse_bits=True)
print(f"Big-endian counts : {rres_b.get_counts()}")
Little-endian counts : {'01': 5090, '10': 4910}
Big-endian counts : {'10': 5000, '01': 5000}
res.plot() # Plot the measurement results as a histogram
Camparison Plot features on Histogram¶
# Plot comparison between Little-endian and Big-endian results using the custome Labels
res_l.plot(rres_b, labels=['Little-endian', 'Big-endian'])
# Plot comparison between Little-endian and Big-endian results by deafault labels
res_l.plot(rres_b)
Example 3: Generate Random Quantum Circuits¶
The RandomCircuitGenerator is a powerful utility for creating random quantum circuits with customizable parameters. This is particularly useful for benchmarking quantum hardware, testing quantum algorithms, and exploring the quantum circuit space.
Importing the Generator¶
from qpiai_quantum.circuit import RandomCircuitGenerator
Class Parameters¶
The RandomCircuitGenerator provides extensive customization options:
| Parameter | Type | Default | Description |
|---|---|---|---|
num_qubits |
int | Required | Number of qubits in the circuit |
num_gates |
int | Required | Total number of gates to apply |
allow_parametric |
bool | True |
Enable parametric gates (with rotation angles) |
single_qubit_gates |
List[str] | None |
List of single-qubit gates to use (e.g., ['h', 'x', 'y', 'z']) |
parametric_single_qubit_gates |
List[str] | None |
Parametric single-qubit gates (e.g., ['rx', 'ry', 'rz']) |
two_qubit_gates |
List[str] | None |
Two-qubit gates to use (e.g., ['cx', 'cz', 'swap']) |
parametric_two_qubit_gates |
List[str] | None |
Parametric two-qubit gates (e.g., 'rrz') |
three_qubit_gates |
List[str] | None |
Three-qubit gates (e.g., ['ccx', 'toffoli']) |
add_measurements |
bool | True |
Automatically add measurements at the end |
single_qubit_probability |
float | 0.6 |
Probability of selecting single-qubit gates (60%) |
two_qubit_probability |
float | 0.3 |
Probability of selecting two-qubit gates (30%) |
parametric_probability |
float | 0.3 |
Probability of using parametric gates (30%) |
Advanced Custom Configuration¶
Create a circuit with specific gate sets and probabilities:
# Generate a circuit with custom gate selections
generator = RandomCircuitGenerator(
num_qubits=5,
num_gates=30,
allow_parametric=True,
single_qubit_gates=['h', 'x', 'y', 'z', 's', 't'],
parametric_single_qubit_gates=['rx', 'ry', 'rz'],
two_qubit_gates=['cx', 'cz', 'swap'],
parametric_two_qubit_gates=['rzz'],
three_qubit_gates=['ccx'],
add_measurements=True,
single_qubit_probability=0.5,
two_qubit_probability=0.4,
parametric_probability=0.2
)
# Generate the random circuit
custom_circuit = generator.generate()
Understanding Gate Probabilities¶
The probability parameters determine the gate distribution:
- Single-qubit probability (0.6): 60% chance of selecting a single-qubit gate
- Two-qubit probability (0.3): 30% chance of selecting a two-qubit gate
- Remaining probability (0.1): Goes to three-qubit gates (if available)
- Parametric probability (0.3): 30% of selected gates will be parametric variants
Note: Probabilities for single and two-qubit gates should sum to ≤ 1.0
Common Gate Types¶
Single-Qubit Gates:
h- Hadamardx,y,z- Pauli gatess,t- Phase gatessx,ry,rz- Phase gates
Two-Qubit Gates:
cx- CNOT (controlled-X)cz- Controlled-Zswap- SWAP gaterzz- Controlled rotation gates (parametric)
Three-Qubit Gates:
ccx- Toffoli (doubly-controlled-X)
Random circuits are an essential tool in the quantum computing toolkit. Let's generate one and execute it!
from qpiai_quantum.circuit import RandomCircuitGenerator
generator = RandomCircuitGenerator(
num_qubits=3,
num_gates=20,
allow_parametric=True,
single_qubit_gates=['h', 'x', 'y', 'z', 's', 't'],
parametric_single_qubit_gates=['rx', 'ry', 'rz'],
two_qubit_gates=['cx', 'cz', 'swap'],
parametric_two_qubit_gates=['rzz'],
three_qubit_gates=['ccx'],
add_measurements=True,
single_qubit_probability=0.5,
two_qubit_probability=0.4,
parametric_probability=0.2
)
rand_circ = generator.generate()
rand_circ.show()
Execute the Circuit¶
Now that we've built and analyzed our circuit, it's time to run it! The QpiAI SDK uses the run function to submit circuits to quantum simulators or real quantum hardware.
res = rand_circ.run(shots=10000, experiment_name="random_circ", device_type='qpu')
print("Counts:", res.get_counts())
Counts: {'010': 43, '011': 43, '110': 4994, '111': 4920}
res.plot() # Plot the measurement results as a histogram
You can control various aspects of this circuit generator by tweaking the parameters. See the complete documentation for full detail.
Intermediate Representations of a Circuit¶
You can export the circuit object in varous intermediate representations like qasm, JSON etc.
From Circuit() object to QASM code¶
Circuit.to_qasm()
qasm = rand_circ.to_qasm()
print(qasm)
OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; cz q[1], q[2]; y q[1]; h q[1]; cx q[1], q[2]; ccx q[0], q[1], q[2]; z q[1]; cx q[1], q[2]; ry(1.7565025258847362) q[2]; swap q[0], q[1]; h q[2]; s q[0]; cz q[1], q[2]; s q[1]; y q[1]; rzz(0.06286749514113806) q[0], q[1]; cz q[0], q[1]; cz q[0], q[1]; cz q[1], q[2]; y q[2]; y q[0]; measure q[0] -> c[0]; measure q[1] -> c[1]; measure q[2] -> c[2];
From Circuit() object to JSON format¶
Circuit.to_json()
Lets export this randomly generated circuit to qasm2 format.
from pprint import pprint
qasm = rand_circ.to_json()
pprint(qasm)
{'cregs': [{'bits': [{'index': 0, 'register': 'c'},
{'index': 1, 'register': 'c'},
{'index': 2, 'register': 'c'}],
'name': 'c',
'size': 3,
'type': 'Classical'}],
'evolve': [{'gate_name': 'CZ',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1, 2]},
{'gate_name': 'Y',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1]},
{'gate_name': 'H',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1]},
{'gate_name': 'CX',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1, 2]},
{'gate_name': 'CCX',
'operation_type': 'n_qubit_non_parametric',
'qubits': [0, 1, 2]},
{'gate_name': 'Z',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1]},
{'gate_name': 'CX',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1, 2]},
{'gate_name': 'RY',
'operation_type': 'n_qubit_parametric',
'params': [1.7565025258847362],
'qubits': [2]},
{'gate_name': 'SWAP', 'operation_type': 'swap', 'qubits': [0, 1]},
{'gate_name': 'H',
'operation_type': 'n_qubit_non_parametric',
'qubits': [2]},
{'gate_name': 'S',
'operation_type': 'n_qubit_non_parametric',
'qubits': [0]},
{'gate_name': 'CZ',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1, 2]},
{'gate_name': 'S',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1]},
{'gate_name': 'Y',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1]},
{'gate_name': 'RZZ',
'operation_type': 'n_qubit_parametric',
'order': [{'gate_name': 'CX',
'operation_type': 'n_qubit_non_parametric',
'qubits': [0, 1]},
{'gate_name': 'RZ',
'operation_type': 'n_qubit_parametric',
'params': [0.06286749514113806],
'qubits': [1]},
{'gate_name': 'CX',
'operation_type': 'n_qubit_non_parametric',
'qubits': [0, 1]}],
'params': [0.06286749514113806],
'qubits': [0, 1]},
{'gate_name': 'CZ',
'operation_type': 'n_qubit_non_parametric',
'qubits': [0, 1]},
{'gate_name': 'CZ',
'operation_type': 'n_qubit_non_parametric',
'qubits': [0, 1]},
{'gate_name': 'CZ',
'operation_type': 'n_qubit_non_parametric',
'qubits': [1, 2]},
{'gate_name': 'Y',
'operation_type': 'n_qubit_non_parametric',
'qubits': [2]},
{'gate_name': 'Y',
'operation_type': 'n_qubit_non_parametric',
'qubits': [0]},
{'clbits': [0],
'gate_name': 'Measure',
'operation_type': 'measure',
'qubits': [0]},
{'clbits': [1],
'gate_name': 'Measure',
'operation_type': 'measure',
'qubits': [1]},
{'clbits': [2],
'gate_name': 'Measure',
'operation_type': 'measure',
'qubits': [2]}],
'metadata': None,
'name': 'Circuit139634305400800',
'num_clbits': 3,
'num_qubits': 3,
'qregs': [{'bits': [{'index': 0, 'register': 'q'},
{'index': 1, 'register': 'q'},
{'index': 2, 'register': 'q'}],
'name': 'q',
'size': 3,
'type': 'Quantum'}]}
Job Management and History¶
Let's explore some of the job management capabilities:
from qpiai_quantum import JobManager
job_manage = JobManager()
job_history = job_manage.get_job_history(period="monthly")
print("=== Recent Jobs ===")
print(f"Total jobs found: {job_history['pagination']['total']}")
completed_jobs = [job for job in job_history['jobs'] if job['status'] == 'Success']
failed_jobs = [job for job in job_history['jobs'] if job['status'] == 'Failed']
running_jobs = [job for job in job_history['jobs'] if job['status'] == 'Running']
print(f"Completed: {len(completed_jobs)}")
print(f"Failed: {len(failed_jobs)}")
print(f"Running: {len(running_jobs)}")
print("\nLast 5 completed jobs:")
for job in completed_jobs[-5:]:
execution_time = job.get('execution_metrics', 0) / 1000
print(f" • {job['id'][-8:]}... - {execution_time:.2f}s - {job.get('credits_used', 0)} credits")
=== Recent Jobs === Total jobs found: 525 Completed: 20 Failed: 0 Running: 0 Last 5 completed jobs: • 8d435eab... - 0.00s - 1 credits • 3669c145... - 0.00s - 1 credits • d96392f5... - 0.00s - 1 credits • efd233c1... - 0.00s - 1 credits • 47142ae0... - 0.00s - 1 credits
Thank you for learning with QpiAI!
import qpiai_quantum
print(qpiai_quantum.__version__)
0.1.9