Skip to content

Quantum Cloud Computing

This tutorial covers how to connect to OriginQ's quantum cloud platform, submit quantum programs to real quantum processors and cloud simulators, track job status, and retrieve results using the pyqpanda3.qcloud module.

Prerequisites: Simulation -- you should be comfortable building quantum programs and running them on a local simulator before moving to cloud execution.


Table of Contents


1. Introduction to Quantum Cloud Computing

1.1 Why Use Cloud Quantum Computing

Local simulators are excellent for learning, debugging, and developing algorithms. However, they face fundamental limitations:

  • The memory required to simulate n qubits grows as O(2n), making it impractical to simulate more than roughly 30 qubits on a typical workstation.
  • Simulators model ideal quantum behavior, which differs significantly from real quantum processors affected by noise, decoherence, and gate errors.

Cloud quantum computing addresses both problems:

AspectLocal SimulatorCloud Quantum Computing
Qubit countLimited by RAM (typically ~25-30 qubits)Up to 72+ qubits on real hardware
Noise modelArtificial, user-specifiedReal, inherent to the physical device
SpeedFast for small circuits, exponential scalingConstant for any circuit depth on real QPU
CostFreeConsumes cloud credits or subscription
AvailabilityAlways availableSubject to queue times and maintenance
FidelityPerfect (noiseless) by defaultLimited by hardware quality

Cloud platforms also provide cloud-hosted simulators that can handle larger circuits than your local machine, along with specialized backends for full-amplitude simulation, partial amplitude computation, and expectation value estimation.

1.2 OriginQ Cloud Platform Overview

The OriginQ cloud platform provides access to several categories of quantum backends:

  • Real quantum processors (QPUs) -- Superconducting quantum chips with 6 to 72+ qubits. These are physical devices that execute your circuit on actual quantum hardware.
  • Full-amplitude simulators -- Cloud-hosted statevector simulators that compute all 2n amplitudes. Useful for verification and algorithm development.
  • Partial amplitude simulators -- Compute only specific amplitudes of the statevector, enabling simulation of larger circuits.
  • Specialized backends -- Backends for state tomography, expectation value computation, and other analysis tasks.

The cloud platform is accessed through the pyqpanda3.qcloud module, which provides a Pythonic interface for authentication, backend discovery, job submission, and result retrieval.


2. QCloudService — Connecting to the Cloud

The QCloudService class is the main entry point for interacting with the OriginQ cloud platform. It handles authentication, backend discovery, and logging configuration.

2.1 Creating a Service Instance

To create a QCloudService, you need an API key. The optional url parameter defaults to the OriginQ cloud server.

python
from pyqpanda3.qcloud import QCloudService

# Connect using the default OriginQ cloud URL
service = QCloudService("your_api_key_here")

# Or specify a custom URL (e.g., for a private cloud deployment)
# service = QCloudService("your_api_key_here", url="https://custom-cloud.example.com")

The API key is a string that authenticates your account. You can obtain one by registering on the OriginQ cloud platform. Treat your API key like a password -- do not share it or commit it to version control.

A recommended pattern is to store the API key in an environment variable:

python
import os
from pyqpanda3.qcloud import QCloudService

# Read the API key from an environment variable
api_key = os.environ["QPANDA3_API_KEY"]
service = QCloudService(api_key)

Then set the environment variable before running your script:

bash
export QPANDA3_API_KEY="your_api_key_here"
python your_script.py

2.2 Configuring Logging

The cloud service supports configurable logging for debugging and monitoring. Use setup_logging to enable it:

python
from pyqpanda3.qcloud import QCloudService, LogOutput, LogLevel

service = QCloudService("your_api_key_here")

# Log to the console (default)
service.setup_logging(output=LogOutput.CONSOLE)

# Log to a file instead
service.setup_logging(output=LogOutput.FILE, file_path="cloud_debug.log")

The setup_logging method accepts two parameters:

ParameterTypeDefaultDescription
outputLogOutputLogOutput.CONSOLEWhere to send log output
file_pathstr""File path when output=LogOutput.FILE

Logging is optional. If you do not call setup_logging, the service operates silently.

2.3 Listing Available Backends

The backends() method returns a dict mapping backend names to availability flags:

python
from pyqpanda3.qcloud import QCloudService

service = QCloudService("your_api_key_here")
backends = service.backends()

print("Available backends:")
for name, available in backends.items():
    status = "available" if available else "unavailable"
    print(f"  - {name}: {status}")

Example output:

Available backends:
  - origin_wukong: available
  - origin_72: available
  - full_amplitude: available
  - partial_amplitude: unavailable

The available backends depend on your account permissions and the current state of the cloud platform. Real quantum processors may be temporarily unavailable during calibration or maintenance.

2.4 Getting a Specific Backend

Once you know the name of a backend, retrieve it with backend():

python
from pyqpanda3.qcloud import QCloudService

service = QCloudService("your_api_key_here")

# Get a real quantum processor backend
qpu_backend = service.backend("origin_wukong")

# Get a cloud simulator backend
sim_backend = service.backend("full_amplitude")

The returned QCloudBackend object provides methods for submitting jobs, querying chip information, and computing expectation values. We explore it in detail in the next section.


3. QCloudBackend — The Execution Target

A QCloudBackend represents a specific quantum computing resource -- either a real quantum processor or a cloud-hosted simulator. You obtain one through QCloudService.backend().

3.1 Backend Overview

python
from pyqpanda3.qcloud import QCloudService

service = QCloudService("your_api_key_here")
backend = service.backend("origin_wukong")

# Get the backend name
print(f"Backend name: {backend.name()}")

3.2 Running a Quantum Program

The simplest way to run a program is with the run() method, which takes a QProg and a shot count:

python
from pyqpanda3 import core
from pyqpanda3.qcloud import QCloudService

# Build a Bell state circuit
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

# Connect and submit
service = QCloudService("your_api_key_here")
backend = service.backend("full_amplitude")

# Submit the job with 3000 shots
job = backend.run(prog, shots=3000)
print(f"Submitted job: {job.job_id()}")

The run() method returns a QCloudJob object immediately. The actual execution happens asynchronously on the cloud server.

3.3 Running with Options

For more control over compilation and execution, use QCloudOptions:

python
from pyqpanda3 import core
from pyqpanda3.qcloud import QCloudService, QCloudOptions

prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

service = QCloudService("your_api_key_here")
backend = service.backend("origin_wukong")

# Configure execution options
options = QCloudOptions()
options.set_amend(True)       # Enable amendment (error mitigation)
options.set_mapping(True)     # Enable qubit mapping
options.set_optimization(True) # Enable circuit optimization

# Submit with options
job = backend.run(prog, shots=3000, options=options)

The QCloudOptions class (covered in detail in Section 6) lets you control compilation behavior on the server side.

3.4 Running with Noise Model

When submitting to a cloud simulator (not a real QPU), you can specify a noise model to simulate realistic hardware behavior:

python
from pyqpanda3 import core
from pyqpanda3.qcloud import (
    QCloudService, QCloudNoiseModel, NOISE_MODEL
)

prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

service = QCloudService("your_api_key_here")
backend = service.backend("full_amplitude")

# Create a depolarizing noise model
noise = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001],   # single-qubit gate noise parameter
    [0.01]     # two-qubit gate noise parameter
)

# Submit with noise model
job = backend.run(prog, shots=3000, model=noise)

Note that noise models are only relevant for cloud simulators. Real quantum processors already have inherent noise -- you cannot add a software noise model on top.

3.5 Chip Information

For real QPU backends, you can query detailed information about the chip's physical characteristics:

python
from pyqpanda3.qcloud import QCloudService

service = QCloudService("your_api_key_here")
backend = service.backend("origin_wukong")

# Get chip information
chip_info = backend.chip_info()

# Number of qubits on the chip
print(f"Qubits: {chip_info.qubits_num()}")

# Chip identifier
print(f"Chip ID: {chip_info.chip_id()}")

# Available qubits (not all physical qubits may be usable)
available = chip_info.available_qubits()
print(f"Available qubits: {available}")

# High-frequency qubits
hf_qubits = chip_info.high_frequency_qubits()
print(f"High-frequency qubits: {hf_qubits}")

# Basic gate set supported by this chip
gates = chip_info.get_basic_gates()
print(f"Basic gates: {gates}")

# Chip topology (connectivity between qubits)
topology = chip_info.get_chip_topology()
print(f"Topology edges ({len(topology)} connections):")
for edge in topology[:5]:  # Show first 5 edges
    print(f"  {edge[0]} <-> {edge[1]}")

Each qubit on the chip has individual characteristics:

python
# Get per-qubit information
single_info_list = chip_info.single_qubit_info()
for info in single_info_list[:3]:  # Show first 3 qubits
    print(f"Qubit {info.get_qubit_id()}:")
    print(f"  T1 (relaxation): {info.get_t1():.2f} us")
    print(f"  T2 (coherence):  {info.get_t2():.2f} us")
    print(f"  Gate fidelity:   {info.get_single_gate_fidelity():.4f}")
    print(f"  Readout fidelity: {info.get_readout_fidelity():.4f}")
    print(f"  Frequency:       {info.get_frequency():.2f} GHz")

Two-qubit gate information is also available:

python
# Get two-qubit gate information
double_info_list = chip_info.double_qubits_info()
for info in double_info_list[:3]:
    qubits = info.get_qubits()
    print(f"Qubits ({qubits[0]}, {qubits[1]}): "
          f"fidelity = {info.get_fidelity():.4f}")

This information is valuable for:

  • Choosing which qubits to use for your circuit
  • Understanding the connectivity constraints (which qubit pairs support two-qubit gates)
  • Estimating the expected fidelity of your circuit on the real hardware

3.6 Expectation Values

The cloud backend can directly compute expectation values of Hamiltonians or Pauli operators without requiring you to manually parse measurement results:

python
from pyqpanda3 import core
from pyqpanda3.qcloud import QCloudService, QCloudOptions
from pyqpanda3.hamiltonian import Hamiltonian, PauliOperator

# Build a circuit
prog = core.QProg()
prog << core.X(0)
prog << core.CNOT(0, 1)
prog << core.SWAP(1, 2)

# Define a Hamiltonian
hamiltonian = Hamiltonian({"X0 Y1": 1.1, "X0 Z1 I2": 2.1, "I1 Z2": 3.1})

# Or equivalently, a Pauli operator
pauli_op = PauliOperator({"X0 Y1": 1.1, "X0 Z1 I2": 2.1, "I1 Z2": 3.1})

service = QCloudService("your_api_key_here")

# Compute expectation on a real QPU
backend = service.backend("origin_wukong")
expval = backend.expval_hamiltonian(prog, hamiltonian, QCloudOptions())
print(f"Hamiltonian expectation (QPU): {expval}")

# Compute on cloud simulator
sim_backend = service.backend("full_amplitude")
expval_sim = sim_backend.expval_hamiltonian(prog, hamiltonian)
print(f"Hamiltonian expectation (sim): {expval_sim}")

# Pauli operator expectation
expval_pauli = backend.expval_pauli_operator(prog, pauli_op, QCloudOptions())
print(f"Pauli operator expectation: {expval_pauli}")

The expval_hamiltonian and expval_pauli_operator methods also accept a shots parameter and an optional noise model:

python
# With explicit shots and noise model on simulator
from pyqpanda3.qcloud import QCloudNoiseModel, NOISE_MODEL

noise = QCloudNoiseModel(NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR, [0.001], [0.01])
expval = sim_backend.expval_hamiltonian(prog, hamiltonian, shots=5000, noise_model=noise)

4. QCloudJob — Tracking Execution

After submitting a quantum program to the cloud, you receive a QCloudJob object that represents the asynchronous computation. The job goes through several states before completing.

4.1 Job Lifecycle

When you submit a job, it progresses through the following states:

StateDescription
QUEUINGThe job has been received and is waiting in the execution queue
WAITINGThe job is waiting to be assigned to a compute resource
COMPUTINGThe job is actively being executed on the quantum processor or simulator
FINISHEDThe job has completed successfully and results are available
FAILEDThe job encountered an error and did not complete

4.2 Creating a Job Reference

A QCloudJob is created in two ways:

From a submission -- the backend.run() method returns a QCloudJob:

python
job = backend.run(prog, shots=3000)

From a job ID -- if you saved a job ID from a previous session, you can reconstruct the job reference:

python
from pyqpanda3.qcloud import QCloudJob

# Reconstruct a job reference from a known job ID
job = QCloudJob("ABC123DEF456")

This is useful when you submitted a long-running job and want to check its status in a new Python session.

4.3 Checking Job Status

Use the status() method to check the current state of a job:

python
from pyqpanda3.qcloud import JobStatus

status = job.status()
print(f"Current status: {status}")

# Compare against known states
if status == JobStatus.FINISHED:
    print("Job completed successfully!")
elif status == JobStatus.FAILED:
    print("Job failed. Check error messages.")
elif status == JobStatus.QUEUING:
    print("Job is in the queue. Please wait.")
elif status == JobStatus.COMPUTING:
    print("Job is currently executing.")
elif status == JobStatus.WAITING:
    print("Job is waiting to be processed.")

4.4 Querying Job Information

The query() method retrieves detailed information about the job without fetching the full result. It returns a QCloudResult with metadata:

python
result_info = job.query()
print(f"Job ID: {result_info.job_id()}")
print(f"Job status: {result_info.job_status()}")

If the job has failed, you can retrieve the error message:

python
if result_info.job_status() == JobStatus.FAILED:
    print(f"Error: {result_info.error_message()}")

4.5 Retrieving Job Results

The result() method blocks until the job completes (or the server reports a final status) and returns a QCloudResult:

python
# Wait for completion and get results
result = job.result()

# Access the data
probs = result.get_probs()
counts = result.get_counts()

print(f"Probabilities: {probs}")
print(f"Counts: {counts}")

If the job has not finished, result() may return a result with a non-FINISHED status. Always check the status:

python
result = job.result()
if result.job_status() == JobStatus.FINISHED:
    counts = result.get_counts()
    print(f"Measurement counts: {counts}")
else:
    print(f"Job not finished. Status: {result.job_status()}")

5. QCloudResult — Processing Output

The QCloudResult class provides multiple ways to access the output of a completed cloud job.

5.1 Getting Probabilities

The get_probs() method returns the estimated probability distribution over measurement outcomes:

python
from pyqpanda3.qcloud import DataBase

result = job.result()

# Get probabilities as binary strings (default)
probs_binary = result.get_probs(base=DataBase.Binary)
print("Probabilities (binary):", probs_binary)
# Example: {'00': 0.4987, '11': 0.5013}

# Get probabilities as hexadecimal strings
probs_hex = result.get_probs(base=DataBase.Hex)
print("Probabilities (hex):", probs_hex)
# Example: {'0x0': 0.4987, '0x3': 0.5013}

For batch submissions (multiple circuits), use get_probs_list() which returns a list of probability dictionaries:

python
# When running multiple programs in a batch
probs_list = result.get_probs_list()
for i, probs in enumerate(probs_list):
    print(f"Circuit {i} probabilities: {probs}")

5.2 Getting Measurement Counts

The get_counts() method returns the raw measurement counts from each shot:

python
from pyqpanda3.qcloud import DataBase

result = job.result()

# Counts as binary strings (default)
counts = result.get_counts(base=DataBase.Binary)
print("Counts (binary):", counts)
# Example: {'00': 1496, '11': 1504}

# Counts as hexadecimal strings
counts_hex = result.get_counts(base=DataBase.Hex)
print("Counts (hex):", counts_hex)
# Example: {'0x0': 1496, '0x3': 1504}

Similarly, get_counts_list() returns results for batch submissions (only available when using batch run() with multiple circuits):

python
counts_list = result.get_counts_list(base=DataBase.Binary)
for i, counts in enumerate(counts_list):
    total = sum(counts.values())
    print(f"Circuit {i}: {total} total shots, counts = {counts}")

5.3 Accessing Raw Data

The origin_data() method returns the raw JSON response from the cloud server. This is useful for debugging or accessing fields not exposed through the Python API:

python
raw_json = result.origin_data()
print("Raw server response:")
print(raw_json)

5.4 Additional Result Methods

The QCloudResult class provides several additional methods for specialized output types:

python
result = job.result()

# Job ID from the result
job_id = result.job_id()
print(f"Job ID: {job_id}")

# Job status from the result
status = result.job_status()
print(f"Status: {status}")

# Error message (if job failed)
error = result.error_message()
if error:
    print(f"Error: {error}")

# Amplitudes (for partial amplitude backend)
amplitudes = result.get_amplitudes()
print(f"Amplitudes: {amplitudes}")

# State fidelity (for state tomography backend)
fidelity = result.get_state_fidelity()
print(f"State fidelity: {fidelity}")

# State tomography density matrix (returns list[list[complex]])
density = result.get_state_tomography_density()
print(f"Density matrix rows: {len(density)}, cols: {len(density[0])}")

Not all methods are available for all job types. For example, get_state_fidelity() and get_state_tomography_density() are only available when the job was submitted using run_quantum_state_tomography(). Similarly, get_amplitudes() is only available for partial amplitude jobs.


6. QCloudOptions — Configuring Execution

The QCloudOptions class provides fine-grained control over how the cloud server compiles and executes your circuit.

6.1 Creating Options

python
from pyqpanda3.qcloud import QCloudOptions

# Create with all defaults
options = QCloudOptions()

# Print current settings
options.print()

6.2 Compilation Options

When submitting to a real QPU, the cloud server performs several compilation steps. You can enable or disable each one:

python
from pyqpanda3.qcloud import QCloudOptions

options = QCloudOptions()

# Amendment: applies error mitigation based on calibration data
options.set_amend(True)

# Mapping: maps logical qubits to physical qubits based on chip topology
options.set_mapping(True)

# Optimization: simplifies the circuit (gate cancellation, decomposition)
options.set_optimization(True)

# Point label: specifies the calibration point to use
options.set_point_label(0)

# Specified block: restricts execution to a specific block on the chip
options.set_specified_block([1])

# Probability vs counts: controls the output format
options.set_is_prob_counts(True)

Query the current settings:

python
print(f"Amendment: {options.is_amend()}")
print(f"Mapping: {options.is_mapping()}")
print(f"Optimization: {options.is_optimization()}")
print(f"Point label: {options.point_label()}")
print(f"Is prob counts: {options.is_prob_counts()}")
print(f"Specified block: {options.specified_block()}")

6.3 Custom Options

For advanced use cases, you can set arbitrary key-value options:

python
from pyqpanda3.qcloud import QCloudOptions

options = QCloudOptions()

# Set custom options of various types
options.set_custom_option("repetitions", 10)         # int
options.set_custom_option("threshold", 0.95)          # double
options.set_custom_option("label", "experiment_1")    # string
options.set_custom_option("verbose", True)            # bool

# Check if a custom option exists
if options.has_custom_option("threshold"):
    print("threshold exists")

# Get all custom options as a dictionary
all_options = options.get_custom_options()
print(f"All custom options: {all_options}")
print(f"Threshold: {all_options['threshold']}")
print(f"Repetitions: {all_options['repetitions']}")
print(f"Label: {all_options['label']}")
print(f"Verbose: {all_options['verbose']}")

7. QCloudNoiseModel — Cloud Noise Simulation

When running on cloud simulators, you can attach a noise model to simulate the behavior of a real quantum processor. The QCloudNoiseModel class defines a noise model with separate parameters for single-qubit and two-qubit gates.

7.1 Creating a Noise Model

python
from pyqpanda3.qcloud import QCloudNoiseModel, NOISE_MODEL

# Default constructor -- no noise (disabled)
clean_model = QCloudNoiseModel()
print(f"Clean model enabled: {clean_model.is_enabled()}")
# Output: Clean model enabled: False

# Construct with a specific noise model and parameters
noisy_model = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001],   # single-qubit gate error rate
    [0.01]     # two-qubit gate error rate
)
print(f"Noisy model enabled: {noisy_model.is_enabled()}")
# Output: Noisy model enabled: True

The constructor takes three arguments:

ParameterTypeDescription
modelNOISE_MODELThe type of noise channel
single_plist[float]Noise parameters for single-qubit gates
double_plist[float]Noise parameters for two-qubit gates

7.2 Inspecting and Modifying Parameters

python
from pyqpanda3.qcloud import QCloudNoiseModel, NOISE_MODEL

noise = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001],
    [0.01]
)

# Get the noise model type
model_type = noise.get_noise_model()
print(f"Noise model: {model_type}")

# Get current parameters
single_params = noise.get_single_params()
double_params = noise.get_double_params()
print(f"Single-qubit params: {single_params}")
print(f"Two-qubit params: {double_params}")

# Update parameters
noise.set_single_params([0.002])
noise.set_double_params([0.02])
print(f"Updated single params: {noise.get_single_params()}")

# Print a human-readable summary
noise.print()

7.3 Comparing Noise Models

Two noise models can be compared for equality and inequality:

python
from pyqpanda3.qcloud import QCloudNoiseModel, NOISE_MODEL

model_a = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001], [0.01]
)

model_b = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001], [0.01]
)

model_c = QCloudNoiseModel(
    NOISE_MODEL.BITFLIP_KRAUS_OPERATOR,
    [0.005], [0.05]
)

print(f"model_a == model_b: {model_a == model_b}")   # True
print(f"model_a != model_c: {model_a != model_c}")   # True

8. Enums Reference

The qcloud module defines several enumerations that control job behavior and data formatting.

8.1 JobStatus

Represents the lifecycle state of a cloud job.

ValueDescription
JobStatus.WAITINGThe job is waiting to be processed
JobStatus.COMPUTINGThe job is actively executing
JobStatus.FINISHEDThe job completed successfully
JobStatus.FAILEDThe job failed with an error
JobStatus.QUEUINGThe job is in the submission queue
python
from pyqpanda3.qcloud import JobStatus

# Status values can be compared directly
status = job.status()
if status == JobStatus.FINISHED:
    result = job.result()

8.2 DataFormat

Specifies how the quantum circuit is encoded when sent to the cloud.

ValueDescription
DataFormat.DEFAULTDefault format, depends on backend implementation
DataFormat.BINARYCompact binary format for efficient transmission
DataFormat.INSTRUCTION_SETExplicit gate instruction sequence

8.3 DataBase

Controls how measurement outcomes are represented in the result.

ValueDescription
DataBase.BinaryBinary representation (e.g., "00", "11")
DataBase.HexHexadecimal representation (e.g., "0x0", "0x3")
python
from pyqpanda3.qcloud import DataBase

result = job.result()

# Binary representation (human-readable)
probs_bin = result.get_probs(base=DataBase.Binary)
# {'00': 0.4987, '11': 0.5013}

# Hexadecimal representation (compact for many qubits)
probs_hex = result.get_probs(base=DataBase.Hex)
# {'0x0': 0.4987, '0x3': 0.5013}

For circuits with many qubits, hex representation is more compact. A 10-qubit outcome that takes 10 characters in binary ("0010110101") takes only 4 characters in hex ("0xB5").

8.4 NOISE_MODEL

Defines the type of noise channel for cloud noise simulation.

ValueDescription
NOISE_MODEL.BITFLIP_KRAUS_OPERATORRandom X (bit-flip) errors
NOISE_MODEL.BIT_PHASE_FLIP_OPERATORCombined bit and phase flip errors
NOISE_MODEL.DAMPING_KRAUS_OPERATORAmplitude damping (T1 decay)
NOISE_MODEL.DECOHERENCE_KRAUS_OPERATORCombined T1 and T2 decoherence
NOISE_MODEL.DEPHASING_KRAUS_OPERATORPhase damping (T2 decay)
NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATORUniform depolarizing noise
NOISE_MODEL.PHASE_DAMPING_OPERATORPure dephasing errors
python
from pyqpanda3.qcloud import NOISE_MODEL, QCloudNoiseModel

# Common choice: depolarizing noise (uniform error model)
noise = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001],  # 0.1% error on single-qubit gates
    [0.01]    # 1% error on two-qubit gates
)

# More realistic: decoherence model (T1 + T2)
decoherence_noise = QCloudNoiseModel(
    NOISE_MODEL.DECOHERENCE_KRAUS_OPERATOR,
    [0.0005],  # single-qubit T1/T2 parameters
    [0.005]    # two-qubit T1/T2 parameters
)

8.5 LogLevel and LogOutput

Control the verbosity and destination of cloud service logs.

LogLevel:

ValueDescription
LogLevel.CLOUD_INFOInformational messages
LogLevel.CLOUD_DEBUGDetailed debugging messages
LogLevel.CLOUD_WARNINGWarning messages
LogLevel.CLOUD_ERRORError messages only

LogOutput:

ValueDescription
LogOutput.CONSOLEPrint logs to standard output
LogOutput.FILEWrite logs to a file
python
from pyqpanda3.qcloud import QCloudService, LogOutput

service = QCloudService("your_api_key_here")

# Console logging for development
service.setup_logging(output=LogOutput.CONSOLE)

# File logging for production
service.setup_logging(output=LogOutput.FILE, file_path="/var/log/qcloud.log")

9. Practical Workflow

This section walks through complete end-to-end examples of using the cloud service.

9.1 Step-by-Step Bell State on Cloud

Here is the complete workflow for running a Bell state on a cloud simulator, from initialization to result analysis.

python
import os
from pyqpanda3 import core
from pyqpanda3.qcloud import (
    QCloudService, QCloudOptions,
    LogOutput, JobStatus, DataBase
)

# ------------------------------------------------
# Step 1: Authenticate with the cloud platform
# ------------------------------------------------
api_key = os.environ["QPANDA3_API_KEY"]
service = QCloudService(api_key)
service.setup_logging(output=LogOutput.CONSOLE)

# ------------------------------------------------
# Step 2: Discover available backends
# ------------------------------------------------
backends = service.backends()
print(f"Available backends: {backends}")

# ------------------------------------------------
# Step 3: Select a backend
# ------------------------------------------------
# Use the full-amplitude simulator for this example
backend = service.backend("full_amplitude")

# ------------------------------------------------
# Step 4: Build the quantum circuit
# ------------------------------------------------
prog = core.QProg()
prog << core.H(0)                    # Superposition on qubit 0
prog << core.CNOT(0, 1)              # Entangle qubits 0 and 1
prog << core.measure([0, 1], [0, 1]) # Measure both qubits

print("Circuit:")
print(prog)

# ------------------------------------------------
# Step 5: Submit the job
# ------------------------------------------------
# Note: full_amplitude simulator uses run(prog, shots) or run(prog, shots, model)
# QCloudOptions is only for real QPU backends
job = backend.run(prog, shots=3000)
job_id = job.job_id()
print(f"Job submitted. ID: {job_id}")

# ------------------------------------------------
# Step 6: Monitor job status
# ------------------------------------------------
import time

while True:
    status = job.status()
    print(f"  Status: {status}")
    if status == JobStatus.FINISHED:
        break
    elif status == JobStatus.FAILED:
        print("Job failed!")
        result = job.result()
        print(f"Error: {result.error_message()}")
        exit(1)
    time.sleep(2)  # Wait before polling again

# ------------------------------------------------
# Step 7: Retrieve and analyze results
# ------------------------------------------------
result = job.result()

# Get probabilities
probs = result.get_probs(base=DataBase.Binary)
print("\nProbabilities:")
for state, prob in sorted(probs.items()):
    print(f"  |{state}>: {prob:.4f}")

# Get raw counts
counts = result.get_counts(base=DataBase.Binary)
print("\nMeasurement counts:")
for state, count in sorted(counts.items()):
    print(f"  |{state}>: {count}")

# Expected output for Bell state:
#   |00>: ~0.50
#   |11>: ~0.50
#   (01 and 10 should be absent or near zero)

9.2 Monitoring a Long-Running Job

When submitting to a real QPU, jobs may spend considerable time in the queue. Here is a robust monitoring pattern:

python
import time
from pyqpanda3.qcloud import QCloudJob, JobStatus

# Reconstruct job reference from a saved ID
saved_job_id = "ABC123DEF456"
job = QCloudJob(saved_job_id)

# Poll with exponential backoff
wait_times = [2, 4, 8, 15, 30, 60]  # seconds
attempt = 0

while True:
    status = job.status()
    print(f"[{time.strftime('%H:%M:%S')}] Job {saved_job_id}: {status}")

    if status == JobStatus.FINISHED:
        result = job.result()
        counts = result.get_counts()
        print(f"Final counts: {counts}")
        break

    if status == JobStatus.FAILED:
        result = job.result()
        print(f"Job failed: {result.error_message()}")
        break

    # Wait with increasing delay
    wait = wait_times[min(attempt, len(wait_times) - 1)]
    attempt += 1
    time.sleep(wait)

9.3 Cloud Computing Workflow Diagram

The following diagram shows the complete workflow from authentication to result retrieval:

9.4 Job Lifecycle Diagram

This diagram shows the state transitions a job undergoes from submission to completion:


10. Advanced Patterns

10.1 Batch Job Submission

You can submit multiple quantum programs in a single batch, which is more efficient than submitting them one at a time:

python
from pyqpanda3 import core
from pyqpanda3.qcloud import QCloudService, QCloudOptions

service = QCloudService("your_api_key_here")
backend = service.backend("origin_wukong")

# Build multiple circuits
progs = []
for angle_idx in range(4):
    prog = core.QProg()
    prog << core.H(0)
    prog << core.RZ(1, angle_idx * 3.14159 / 4)
    prog << core.CNOT(0, 1)
    prog << core.measure([0, 1], [0, 1])
    progs.append(prog)

# Submit all at once
options = QCloudOptions()
options.set_amend(True)
job = backend.run(progs, shots=2000, options=options)

# Get results for all circuits
result = job.result()
probs_list = result.get_probs_list()
for i, probs in enumerate(probs_list):
    print(f"Circuit {i}: {probs}")

10.2 Transpiling Before Cloud Submission

When targeting a real QPU, the circuit must be compatible with the chip's native gate set and qubit connectivity. While the cloud server can handle this automatically via QCloudOptions.set_mapping(True), you may want to transpile locally for more control:

python
import time
from pyqpanda3 import core
from pyqpanda3.qcloud import QCloudService, QCloudOptions
from pyqpanda3.transpilation import Transpiler

service = QCloudService("your_api_key_here")
backend = service.backend("origin_wukong")

# Get the chip's physical configuration
chip_info = backend.chip_info()
chip_backend = chip_info.get_chip_backend()

# Build a circuit
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.CNOT(1, 2)
prog << core.SWAP(0, 2)
prog << core.measure([0, 1, 2], [0, 1, 2])

# Transpile to the chip's native gate set and topology
transpiler = Transpiler()
basic_gates = chip_info.get_basic_gates()  # e.g., ["U3", "CZ"]
topo = chip_info.get_chip_topology()

start = time.time()
transpiled_prog = transpiler.transpile(
    prog,
    chip_backend,
    {},       # compensate angle map
    2         # optimization level
)
elapsed = (time.time() - start) * 1000
print(f"Transpilation took {elapsed:.2f} ms")

# Convert to instruction format for submission
instruction_json = transpiled_prog.to_instruction(chip_backend)

# Submit the transpiled instruction directly
from pyqpanda3.qcloud import QCloudOptions
options = QCloudOptions()
options.set_amend(False)  # Already transpiled, no need for server-side mapping

job = backend.run_instruction(instruction_json, 3000, options)
result = job.result()
print(result.get_probs())

10.3 Partial Amplitude and State Tomography

The cloud platform supports specialized execution modes beyond standard circuit simulation.

Partial amplitude computation -- Compute only specific amplitudes of the statevector:

python
from pyqpanda3 import core
from pyqpanda3.qcloud import QCloudService

service = QCloudService("your_api_key_here")
backend = service.backend("partial_amplitude")

prog = core.QProg()
prog << core.H(0)
prog << core.H(1)
prog << core.H(2)
prog << core.CNOT(0, 1)
prog << core.CNOT(1, 2)

# Request specific amplitudes
job = backend.run(prog, ["000", "111"])
result = job.result()
amplitudes = result.get_amplitudes()
print(f"Amplitudes: {amplitudes}")

State tomography -- Reconstruct the full density matrix from measurements (get_state_tomography_density() returns list[list[complex]]):

python
from pyqpanda3 import core
from pyqpanda3.qcloud import QCloudService, QCloudOptions

service = QCloudService("your_api_key_here")
backend = service.backend("origin_wukong")

prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)

# Run state tomography
options = QCloudOptions()
job = backend.run_quantum_state_tomography(prog, 5000, options)
result = job.result()

fidelity = result.get_state_fidelity()
density = result.get_state_tomography_density()
print(f"State fidelity: {fidelity}")
print(f"Density matrix ({len(density)}x{len(density[0])}):")
for row in density:
    print(row)

11. Summary

This tutorial covered the complete cloud computing workflow in pyqpanda3:

ComponentPurposeKey Methods
QCloudServiceAuthenticate and discover backends.backends() / .backend() / .setup_logging()
QCloudBackendExecute circuits and query chip info.run() / .chip_info() / .expval_hamiltonian()
QCloudJobTrack and retrieve job status.status() / .result() / .query() / .job_id()
QCloudResultAccess measurement data.get_probs() / .get_counts() / .origin_data()
QCloudOptionsConfigure compilation and execution.set_amend() / .set_mapping() / .set_optimization()
QCloudNoiseModelDefine noise for cloud simulatorsConstructor with NOISE_MODEL and parameters

Key takeaways:

  1. Authenticate first -- Create a QCloudService with your API key before doing anything else.
  2. Choose the right backend -- Use cloud simulators for development and verification; use real QPUs when you need actual quantum hardware.
  3. Jobs are asynchronous -- After submission, poll job.status() until FINISHED or FAILED.
  4. Results have multiple formats -- Use DataBase.Binary for human-readable output and DataBase.Hex for compact representation.
  5. Noise models are for simulators only -- Real hardware has inherent noise; software noise models apply only to cloud simulators.
  6. Transpile locally for more control -- Use the Transpiler class and ChipInfo when you need fine-grained control over circuit compilation.

Next steps:


Quick Reference

python
from pyqpanda3 import core
from pyqpanda3.qcloud import (
    QCloudService, QCloudJob, QCloudOptions,
    QCloudNoiseModel, QCloudResult,
    JobStatus, DataBase, NOISE_MODEL,
    LogOutput, LogLevel,
)
import os

# ===========================
# Authentication
# ===========================
service = QCloudService(os.environ["QPANDA3_API_KEY"])
service.setup_logging(output=LogOutput.CONSOLE)

# ===========================
# Backend Discovery
# ===========================
backends = service.backends()           # dict[str, bool] mapping names to availability
qpu_backend = service.backend("origin_wukong")  # Real QPU
sim_backend = service.backend("full_amplitude") # Cloud simulator

# ===========================
# Chip Information (QPU only)
# ===========================
chip_info = qpu_backend.chip_info()
print(f"Qubits: {chip_info.qubits_num()}")
print(f"Available: {chip_info.available_qubits()}")
print(f"Topology: {chip_info.get_chip_topology()}")

# ===========================
# Submit a Job
# ===========================
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

# Simulator: run(prog, shots) or run(prog, shots, model)
job = sim_backend.run(prog, shots=3000)

# QPU: run(prog, shots, options)
options = QCloudOptions()
options.set_amend(True)
options.set_mapping(True)
job = qpu_backend.run(prog, shots=3000, options=options)

# Simulator with noise model
noise = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001], [0.01]
)
job = sim_backend.run(prog, shots=3000, model=noise)

# ===========================
# Monitor Job
# ===========================
job_id = job.job_id()
status = job.status()

# Reconstruct from saved job ID
job = QCloudJob(job_id)

# ===========================
# Get Results
# ===========================
result = job.result()
probs = result.get_probs(base=DataBase.Binary)
counts = result.get_counts(base=DataBase.Binary)
raw = result.origin_data()

# ===========================
# Expectation Values
# ===========================
from pyqpanda3.hamiltonian import Hamiltonian
h = Hamiltonian({"X0 Y1": 1.0, "Z0 Z1": 0.5})
expval = qpu_backend.expval_hamiltonian(prog, h, QCloudOptions())

# ===========================
# Noise Model
# ===========================
noise = QCloudNoiseModel(
    NOISE_MODEL.DEPOLARIZING_KRAUS_OPERATOR,
    [0.001], [0.01]
)
noise.is_enabled()            # True
noise.get_noise_model()       # DEPOLARIZING_KRAUS_OPERATOR
noise.get_single_params()     # [0.001]
noise.get_double_params()     # [0.01]
noise.set_single_params([0.002])
noise.print()

Released under the MIT License.