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:

  1. Loading and saving your API KEY
  2. Creating your first quantum circuit
  3. Running simulations
  4. Executing on real QpiAI Indus quantum hardware using our Qpiai-quantum-SDK
  5. Analyzing results

Let's dive in!

Configure your API key¶

Steps to access your API Key:

  1. Go to the dropdown menu in the top right corner and click on API Token
  2. Under the Tokens section, copy your Main API Token
  3. Paste the Copied API token as: API_KEY="paste your API KEY here"

Auntentication the USER¶

In [1]:
# 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)
Out[1]:
'API key stored. It will be validated on first cloud access.'

Lets verify that the API key is indeed set

In [2]:
QpiAIQuantumAuth.me()
Out[2]:
{'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:

In [3]:
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.

In [4]:
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!

In [5]:
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
In [6]:
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()
In [7]:
circuit.show()  # Draw the circuit
No description has been provided for this image

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()
In [8]:
circuit.depth()  # Display the depth of the circuit
Out[8]:
1

Number of Qubits¶

This shows the total number of quantum bits (qubits) in your circuit.

# Display the number of qubits  
qc.num_qubits
In [9]:
circuit.num_qubits  # Get the number of qubits in the circuit
Out[9]:
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
In [10]:
circuit.num_clbits  # Get the number of classical bits in the circuit
Out[10]:
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()
In [11]:
circuit.list_gates()  # List all gates in the circuit
Out[11]:
{'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:¶

  1. Log in to the QpiAI platform at https://app.qpiai-quantum.tech
  2. Navigate to the Experiments section from the main dashboard
  3. Click on "Create New Experiment" or the "+" button
  4. Enter the experiment name - this must match the experiment_name parameter in your code
    • In our example below, you need to create: sdk_test_exp
  5. Configure additional settings as needed (optional)
  6. 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=True is 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.

In [12]:
res = circuit.run(shots=10000, experiment_name="plus_state", device_type="qpu")
To Extract the Counts¶
In [13]:
print(f"Counts : {res.get_counts()}")
Counts : {'0': 4962, '1': 5038}
To Extract the probablities¶
In [14]:
print(f"Probablities : {res.get_probabilities()}")
Probablities : {'0': 0.4962, '1': 0.5038}
To Extract the JOB ID¶
In [15]:
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!

In [16]:
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
In [17]:
bell_circuit.show()  # Draw the circuit
No description has been provided for this image
To extract counts in big-endian format (the default is little-endian), use reverse_bits=True.¶
res = circuit.run(
    reverse_bits=True
)
In [18]:
# 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}

Analyzing Execution Results¶

Once your circuit execution is complete, you can extract and visualize the measurement results using the built-in methods provided by the QpiAI SDK.

Visualizing Results¶

The SDK provides a convenient plotting method to visualize your measurement results as a histogram:

In [19]:
res.plot() # Plot the measurement results as a histogram
No description has been provided for this image

Camparison Plot features on Histogram¶

In [20]:
# Plot comparison between Little-endian and Big-endian results using the custome Labels
res_l.plot(rres_b, labels=['Little-endian', 'Big-endian'])
No description has been provided for this image
In [21]:
# Plot comparison between Little-endian and Big-endian results by deafault labels
res_l.plot(rres_b)
No description has been provided for this image

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 - Hadamard
  • x, y, z - Pauli gates
  • s, t - Phase gates
  • sx, ry, rz - Phase gates

Two-Qubit Gates:

  • cx - CNOT (controlled-X)
  • cz - Controlled-Z
  • swap - SWAP gate
  • rzz - 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!

In [22]:
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
)
In [23]:
rand_circ = generator.generate()
rand_circ.show()
No description has been provided for this image
No description has been provided for this image

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.

In [24]:
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}
In [25]:
res.plot() # Plot the measurement results as a histogram
No description has been provided for this image

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()
In [26]:
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.

In [27]:
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:

In [28]:
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!

In [29]:
import qpiai_quantum
print(qpiai_quantum.__version__)
0.1.9
In [ ]: