Quantum Information
This tutorial covers the pyqpanda3.quantum_info module, which provides tools for representing and manipulating quantum states, operators, channels, and statistical distance metrics. These building blocks are essential for quantum algorithm development, error analysis, and benchmarking quantum hardware.
Prerequisites: Circuit Construction and Simulation -- ensure you are comfortable building circuits and running simulations.
Table of Contents
- 1. Module Overview
- 2. StateVector
- 3. DensityMatrix
- 4. Unitary
- 5. Matrix
- 6. Quantum Channels
- 7. Distance and Similarity Metrics
- 8. Complete Example
- 9. Mathematical Background
- 10. API Reference
1. Module Overview
The quantum_info module exposes the following components:
from pyqpanda3.quantum_info import (
StateVector,
DensityMatrix,
Unitary,
Matrix,
Kraus,
Chi,
Choi,
SuperOp,
PTM,
hellinger_distance,
hellinger_fidelity,
KL_divergence,
)These components fall into four categories:
| Category | Classes / Functions | Purpose |
|---|---|---|
| Quantum States | StateVector, DensityMatrix | Represent pure and mixed quantum states |
| Operators | Unitary, Matrix | Represent quantum operations and general matrices |
| Quantum Channels | Kraus, Choi, SuperOp, Chi, PTM | Model noise and open-system evolution |
| Distance Metrics | hellinger_distance, hellinger_fidelity, KL_divergence | Compare probability distributions |
2. StateVector
A state vector is the most fundamental representation of a pure quantum state. For an
where
2.1 Constructing a StateVector
Default constructor -- creates a single-qubit state
from pyqpanda3.quantum_info import StateVector
sv = StateVector()
print(sv)
# Output: [1+0j 0+0j]From qubit count -- initializes the all-zeros state
sv = StateVector(3) # 3 qubits, state |000>
print(sv.dim()) # 8
print(sv.is_valid()) # True (normalized)From a list or numpy array of complex amplitudes:
import numpy as np
# Bell state (|00> + |11>) / sqrt(2)
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
print(sv)
print(sv.is_valid()) # TrueFrom a dictionary mapping index to amplitude:
sv = StateVector({0: 1.0, 3: 1.0})
# Non-normalized: two non-zero entries at indices 0 and 3
print(sv.dim()) # 4 (since max index is 3)2.2 Accessing Data
Use ndarray() to retrieve the underlying data as a numpy array, at(i) for a single element, and dim() for the dimension:
import numpy as np
sv = StateVector([1, 0, 0, 0])
print(sv.ndarray()) # numpy array: [1+0j, 0+0j, 0+0j, 0+0j]
print(sv.at(0)) # (1+0j)
print(sv.dim()) # 42.3 Evolving a State Vector
The evolve method applies a QCircuit to the state vector and returns a new StateVector without modifying the original. The update_by_evolve method does the same but updates the object in place:
from pyqpanda3.quantum_info import StateVector
from pyqpanda3.core import QCircuit, X, SWAP, CP
sv = StateVector(3) # |000>
cir = QCircuit()
cir << X(0) << SWAP(1, 2) << CP(1, 0, 3.14)
# Functional style: returns a new StateVector
sv_evolved = sv.evolve(cir)
print("Original:", sv)
print("Evolved: ", sv_evolved)
# In-place evolution
sv.update_by_evolve(cir)
print("After update:", sv)2.4 Density Matrix and Purity
Every pure state has a corresponding density matrix get_density_matrix() to compute it:
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
dm = sv.get_density_matrix()
print(dm)For a state vector representing a pure state, the purity is always 1:
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
print(sv.purity()) # (1+0j)Two StateVector objects can be compared using ==:
sv1 = StateVector([1, 0, 0, 0])
sv2 = StateVector(sv1)
print(sv1 == sv2) # True
sv3 = StateVector([0, 1, 0, 0])
print(sv1 == sv3) # False3. DensityMatrix
A density matrix (or density operator) provides the most general description of a quantum state, encompassing both pure and mixed states:
where
3.1 Constructing a DensityMatrix
Default constructor -- single qubit in state
from pyqpanda3.quantum_info import DensityMatrix
dm = DensityMatrix()
print(dm)From qubit count -- initializes the all-zeros state for n qubits:
dm = DensityMatrix(3) # 3 qubits
print(dm.dim()) # 8From a 2D numpy array:
import numpy as np
# Maximally mixed state for 2 qubits
data = np.eye(4, dtype=complex) / 4
dm = DensityMatrix(data)
print(dm)From a StateVector -- computes
from pyqpanda3.quantum_info import StateVector, DensityMatrix
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
dm = DensityMatrix(sv)
print(dm)Copy constructor:
dm_copy = DensityMatrix(dm)
print(dm == dm_copy) # True3.2 Accessing Data
Use ndarray() to retrieve data as a numpy array, at(i, j) for individual elements, and dim() for the dimension:
data = np.eye(4, dtype=complex) / 4
dm = DensityMatrix(data)
print(dm.ndarray()) # 4x4 numpy array
print(dm.at(0, 0)) # (0.25+0j)
print(dm.at(1, 2)) # 0j
print(dm.dim()) # 43.3 Purity and Validation
Purity measures how "mixed" a state is. For pure states,
# Pure state
sv = StateVector([1, 0])
dm_pure = DensityMatrix(sv)
print(dm_pure.purity()) # (1+0j)
# Maximally mixed state
dm_mixed = DensityMatrix(np.eye(2, dtype=complex) / 2)
print(dm_mixed.purity()) # (0.5+0j)The is_valid() method checks whether the density matrix satisfies the three physical requirements: Hermiticity, positive semidefiniteness, and unit trace:
data = np.eye(4, dtype=complex) / 4
dm = DensityMatrix(data)
print(dm.is_valid()) # True
# An invalid density matrix
bad = DensityMatrix(np.ones((4, 4), dtype=complex))
print(bad.is_valid()) # False3.4 Evolving a Density Matrix
Like StateVector, DensityMatrix supports both functional and in-place evolution with a QCircuit:
from pyqpanda3.core import QCircuit, X, SWAP, CP
dm = DensityMatrix(3)
cir = QCircuit()
cir << X(0) << SWAP(1, 2) << CP(1, 0, 3.14)
# Functional: returns new DensityMatrix, original unchanged
dm_evolved = dm.evolve(cir)
print("Original:", dm)
print("Evolved: ", dm_evolved)
# In-place
dm.update_by_evolve(cir)
print("After update:", dm)3.5 Converting to StateVector
For a pure-state density matrix, to_statevector() extracts the corresponding state vector:
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
dm = DensityMatrix(sv)
recovered = dm.to_statevector()
print(recovered)Note: this method assumes the density matrix represents a pure state. The result may not be physically meaningful for mixed states.
4. Unitary
The Unitary class represents a unitary operator extracted from a quantum circuit or gate. A unitary operator
4.1 Constructing a Unitary
From a quantum circuit:
from pyqpanda3.quantum_info import Unitary
from pyqpanda3.core import QCircuit, X, CNOT, CP
cir = QCircuit()
cir << X(0) << CP(2, 1, 5.28) << CNOT(1, 0)
u = Unitary(cir)
print(u)Unitary(cir) auto-detects the number of qubits from the circuit.
4.2 Accessing Data
The ndarray() method returns the unitary matrix as a numpy array:
cir = QCircuit()
cir << X(0) << CNOT(1, 0)
u = Unitary(cir)
matrix = u.ndarray()
print(matrix)
print("Shape:", matrix.shape) # e.g., (8, 8)Two Unitary objects from the same circuit can be compared:
u1 = Unitary(cir)
u2 = Unitary(cir)
print(u1 == u2) # True5. Matrix
The Matrix class is a general-purpose complex matrix wrapper that supports common linear algebra operations. It serves as the building block for the other classes in the module.
Matrix can represent rectangular and square matrices, row and column vectors, and identity matrices.
5.1 Constructing a Matrix
From a numpy array:
from pyqpanda3.quantum_info import Matrix
import numpy as np
# General rectangular matrix
data = np.array([[1+1j, 2+2j, 3+3j, 4+4j],
[5+5j, 6+6j, 7+7j, 8+8j],
[9+9j, 10+10j, 11+11j, 12+12j]], dtype=complex)
m = Matrix(data)
print(m)From a Python list of lists:
m = Matrix(np.array([[1, 0], [0, 1]], dtype=complex))
print(m.row_total()) # 2
print(m.col_total()) # 25.2 Matrix Operations
The Matrix class provides the following operations:
from pyqpanda3.quantum_info import Matrix
import numpy as np
# Create a Hermitian matrix
data = np.array([[3+0j, 1+2j],
[1-2j, 2+0j]], dtype=complex)
m = Matrix(data)
print("ndarray:", m.ndarray())
print("row_total:", m.row_total()) # 2
print("col_total:", m.col_total()) # 2
print("is_hermitian:", m.is_hermitian()) # True
# Transpose
mt = m.transpose()
print("transpose:", mt)
# Adjoint (conjugate transpose)
ma = m.adjoint()
print("adjoint:", ma)
# L2 norm (largest singular value)
print("L2:", np.linalg.norm(m.ndarray(), 2))Equality check:
m2 = m
print(m == m2) # True6. Quantum Channels
A quantum channel describes the most general physical transformation of a quantum state. Mathematically, it is a completely positive trace-preserving (CPTP) map. pyqpanda3 supports five equivalent representations of quantum channels.
6.1 Kraus Representation
The Kraus operator-sum representation is the most intuitive form:
with the trace-preserving condition
For non-trace-preserving channels (e.g., measurement operators), a two-sided form is available:
Constructing a Kraus channel:
from pyqpanda3.quantum_info import Kraus
import numpy as np
# Single-sided (trace-preserving): E(rho) = sum K_i rho K_i^dagger
K0 = np.array([[1, 0], [0, 0]], dtype=complex)
K1 = np.array([[0, 0], [0, 1]], dtype=complex)
kraus = Kraus([K0, K1])
print(kraus)Two-sided Kraus (for generalized channels):
K0 = np.array([[1+11j, 1+12j], [1+21j, 1+22j]])
K1 = np.array([[2+11j, 2+12j], [2+21j, 2+22j]])
K2 = np.array([[3+11j, 3+12j], [3+21j, 3+22j]])
Ks_left = [K0, K1, K2]
Ks_right = [K2, K1, K0]
kraus_two = Kraus(Ks_left, Ks_right)
print(kraus_two)Accessing operators:
left_ops = kraus_two.left() # list of left operators
right_ops = kraus_two.right() # list of right operators
for op in left_ops:
print(op)Incremental construction:
kraus = Kraus()
kraus.left_push_back(K0)
kraus.left_push_back(K1)
kraus.right_push_back(K0)
print(kraus)Other Kraus methods include append(other_kraus) to compose channels and clear() to remove all operators.
6.2 Choi Representation
The Choi matrix represents a channel as a
A channel is completely positive if and only if the Choi matrix is positive semidefinite. The channel is trace-preserving if and only if the partial trace of the Choi matrix over the output space yields the identity.
from pyqpanda3.quantum_info import Choi
# Construct from Kraus
choi = Choi(kraus)
print(choi)
print("Input dim:", choi.get_input_dim())
print("Output dim:", choi.get_output_dim())6.3 SuperOp Representation
The SuperOp (Superoperator) representation reshapes the channel into a
This is useful for composing channels (matrix multiplication of SuperOp matrices corresponds to sequential channel application).
from pyqpanda3.quantum_info import SuperOp
superop = SuperOp(kraus)
print(superop)
print("Input dim:", superop.get_input_dim())
print("Output dim:", superop.get_output_dim())6.4 Chi Representation
The Chi (
The Chi matrix is useful for process tomography and noise characterization.
from pyqpanda3.quantum_info import Chi
chi = Chi(kraus)
print(chi)
print("Input dim:", chi.get_input_dim())
print("Output dim:", chi.get_output_dim())6.5 PTM Representation
The Pauli Transfer Matrix (PTM) describes how a channel transforms Pauli expectation values:
The PTM is a real-valued matrix (for Hermitian-preserving channels) and is commonly used in randomized benchmarking and gate set tomography.
from pyqpanda3.quantum_info import PTM
ptm = PTM(kraus)
print(ptm)
print("Input dim:", ptm.get_input_dim())
print("Output dim:", ptm.get_output_dim())6.6 Converting Between Representations
All five representations can be constructed from any other representation. pyqpanda3 handles the conversion automatically:
from pyqpanda3.quantum_info import Kraus, Choi, Chi, SuperOp, PTM
K0 = np.array([[1, 0], [0, 0]], dtype=complex)
K1 = np.array([[0, 0], [0, 1]], dtype=complex)
kraus = Kraus([K0, K1])
# Convert to all other representations
choi = Choi(kraus)
superop = SuperOp(kraus)
chi = Chi(kraus)
ptm = PTM(kraus)
# And back -- each can be constructed from any other
kraus_from_choi = Kraus(choi)
kraus_from_chi = Kraus(chi)
kraus_from_superop = Kraus(superop)
kraus_from_ptm = Kraus(ptm)
# Cross-conversion is also supported
choi_from_superop = Choi(superop)
ptm_from_chi = PTM(chi)
chi_from_ptm = Chi(ptm)6.7 Evolving States Through Channels
All channel representations support the evolve method, which takes a StateVector or DensityMatrix and returns a DensityMatrix:
from pyqpanda3.quantum_info import (
StateVector, DensityMatrix, Kraus
)
import numpy as np
# Define an amplitude damping channel
gamma = 0.3
K0 = np.array([[1, 0], [0, np.sqrt(1 - gamma)]], dtype=complex)
K1 = np.array([[0, np.sqrt(gamma)], [0, 0]], dtype=complex)
kraus = Kraus([K0, K1])
# Evolve a density matrix
dm = DensityMatrix(np.eye(2, dtype=complex) / 2)
result = kraus.evolve(dm)
print("Evolved density matrix:", result)
print("Is valid:", result.is_valid())
# Evolve a state vector (returns DensityMatrix)
sv = StateVector([0, 1]) # |1>
result_sv = kraus.evolve(sv)
print("Evolved from |1>:", result_sv)
print("Is valid:", result_sv.is_valid())7. Distance and Similarity Metrics
The quantum_info module provides three statistical distance measures for comparing probability distributions. These are essential for benchmarking quantum hardware, comparing measured and expected outcomes, and analyzing noise.
7.1 Hellinger Distance
The Hellinger distance measures the similarity between two discrete probability distributions. It accepts dictionaries with string or integer keys:
Properties:
- Range:
(identical distributions) when and have disjoint support
from pyqpanda3.quantum_info import hellinger_distance
# String keys
p = {'00': 0.5, '11': 0.5}
q = {'00': 0.6, '11': 0.4}
print(hellinger_distance(p, q))
# Integer keys
p2 = {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25}
q2 = {0: 0.5, 1: 0.3, 2: 0.1, 3: 0.1}
print(hellinger_distance(p2, q2))7.2 Hellinger Fidelity
Derived from the Hellinger distance, the fidelity provides a measure of similarity that equals 1 for identical distributions and 0 for maximally different ones:
from pyqpanda3.quantum_info import hellinger_fidelity
p = {'00': 0.5, '11': 0.5}
q = {'00': 0.48, '11': 0.45, '01': 0.04, '10': 0.03}
hd = hellinger_distance(p, q)
hf = hellinger_fidelity(p, q)
print(f"Hellinger distance: {hd:.6f}")
print(f"Hellinger fidelity: {hf:.6f}")7.3 KL Divergence (Discrete)
The Kullback-Leibler divergence measures how one probability distribution
Properties:
- Always non-negative:
- Not symmetric:
in general if and only if - Undefined if
where
For discrete distributions, KL_divergence accepts two lists or numpy arrays of probabilities:
from pyqpanda3.quantum_info import KL_divergence
p = [0.5, 0.5]
q = [0.6, 0.4]
kl = KL_divergence(p, q)
print(f"KL(p || q) = {kl:.6f}")
# Asymmetry demonstration
kl_reverse = KL_divergence(q, p)
print(f"KL(q || p) = {kl_reverse:.6f}")
# These two values will differ
# Identical distributions
print(KL_divergence([0.25, 0.25, 0.25, 0.25],
[0.25, 0.25, 0.25, 0.25])) # 0.07.4 KL Divergence (Continuous)
For continuous probability distributions, KL_divergence accepts Python callables (functions) along with integration bounds and step size:
The numerical integration uses a Riemann sum with step size dx:
from pyqpanda3.quantum_info import KL_divergence
import math
def normal_pdf(x, mu, sigma):
coefficient = 1 / (sigma * math.sqrt(2 * math.pi))
exponent = math.exp(-0.5 * ((x - mu) / sigma) ** 2)
return coefficient * exponent
def p_pdf(x):
return normal_pdf(x, mu=3, sigma=3.14)
def q_pdf(x):
return 0.000001 # Approximate uniform (near-zero constant)
kl_continuous = KL_divergence(
p_pdf=p_pdf,
q_pdf=q_pdf,
x_start=-5.0,
x_end=6.0,
dx=0.0001
)
print(f"Continuous KL divergence: {kl_continuous:.6f}")8. Complete Example
The following example combines state construction, circuit evolution, channel application, and distance measurement into a single workflow:
import numpy as np
from pyqpanda3 import core
from pyqpanda3.quantum_info import (
StateVector, DensityMatrix, Unitary, Kraus, Choi, Chi, SuperOp, PTM,
hellinger_distance, hellinger_fidelity, KL_divergence,
)
# --- Step 1: Create a Bell state ---
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
print("Bell state purity:", sv.purity()) # (1+0j)
# --- Step 2: Convert to density matrix ---
dm = DensityMatrix(sv)
print("Is valid:", dm.is_valid()) # True
# --- Step 3: Extract unitary from a circuit ---
cir = core.QCircuit()
cir << core.H(0) << core.CNOT(0, 1)
u = Unitary(cir)
print("Unitary matrix shape:", u.ndarray().shape)
# --- Step 4: Apply a depolarizing noise channel ---
p_noise = 0.1
I = np.eye(2, dtype=complex)
X = np.array([[0, 1], [1, 0]], dtype=complex)
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
kraus = Kraus([
np.sqrt(1 - 3*p_noise/4) * I,
np.sqrt(p_noise/4) * X,
np.sqrt(p_noise/4) * Y,
np.sqrt(p_noise/4) * Z,
])
dm_noisy = kraus.evolve(dm)
print("Noisy purity:", dm_noisy.purity())
# --- Step 5: Convert between channel representations ---
choi = Choi(kraus)
chi = Chi(kraus)
superop = SuperOp(kraus)
ptm = PTM(kraus)
# All representations produce equivalent evolution
print("Choi evolve == Chi evolve:",
choi.evolve(dm) == chi.evolve(dm))
# --- Step 6: Compare distributions using distance metrics ---
ideal = {'00': 0.5, '11': 0.5}
noisy = {'00': 0.48, '11': 0.45, '01': 0.04, '10': 0.03}
print("Hellinger distance:", hellinger_distance(ideal, noisy))
print("Hellinger fidelity: ", hellinger_fidelity(ideal, noisy))
print("KL divergence:", KL_divergence(
[0.5, 0.0, 0.0, 0.5],
[0.48, 0.04, 0.03, 0.45]
))9. Mathematical Background
Quantum States
Pure states are described by normalized vectors in Hilbert space:
Mixed states are statistical ensembles of pure states, described by density matrices:
Properties of a valid density matrix:
- Hermitian:
- Positive semidefinite:
- Unit trace:
Purity
The purity quantifies the "mixedness" of a quantum state:
: pure state : mixed state ( ) : maximally mixed state
Kraus Operator-Sum Representation
The most general physical transformation of a quantum state is:
with the trace-preserving condition
Channel Representations
Five equivalent ways to represent the same CPTP map:
| Representation | Matrix Size | Key Property |
|---|---|---|
| Kraus | Set of | Operator-sum form |
| Choi | Positive semidefinite iff CP | |
| SuperOp | Linear map on vectorized | |
| Chi | Expansion in Pauli basis | |
| PTM | Pauli expectation value mapping |
Hellinger Distance and Fidelity
KL Divergence
Discrete form:
Continuous form:
Note that KL divergence is not a true metric -- it is asymmetric and does not satisfy the triangle inequality. It is however a proper divergence measure: always non-negative and zero only when
10. API Reference
StateVector
| Method | Signature | Description |
|---|---|---|
| Constructor | StateVector() | Single qubit in |
| Constructor | StateVector(qbit_total: int) | All-zeros state for n qubits |
| Constructor | StateVector(data: list) | From complex amplitude list |
| Constructor | StateVector(data: dict) | From index-amplitude mapping |
| Constructor | StateVector(data: Eigen) | From Eigen vector |
ndarray() | -> numpy.ndarray | Internal data as numpy array |
dim() | -> int | Dimension ( |
at(idx) | -> complex | Element at index |
purity() | -> complex | |
is_valid() | -> bool | Check normalization |
evolve(circuit) | -> StateVector | Functional evolution |
update_by_evolve(circuit) | -> StateVector | In-place evolution |
get_density_matrix() | -> DensityMatrix | Compute |
DensityMatrix
| Method | Signature | Description |
|---|---|---|
| Constructor | DensityMatrix() | Single qubit |
| Constructor | DensityMatrix(qbit_total: int) | All-zeros for n qubits |
| Constructor | DensityMatrix(data: 2D array) | From matrix data |
| Constructor | DensityMatrix(StateVector) | From pure state |
ndarray() | -> numpy.ndarray | Internal data |
dim() | -> int | Dimension |
at(row, col) | -> complex | Matrix element |
purity() | -> complex | |
is_valid() | -> bool | Hermitian, PSD, trace 1 |
evolve(circuit) | -> DensityMatrix | Functional evolution |
update_by_evolve(circuit) | -> DensityMatrix | In-place evolution |
to_statevector() | -> StateVector | Extract state vector |
Quantum Channels (Kraus, Choi, SuperOp, Chi, PTM)
All channel classes share a common interface:
| Method | Description |
|---|---|
Constructor(other_repr) | Convert from any other representation |
evolve(state) | Apply channel to StateVector or DensityMatrix |
get_input_dim() | Input Hilbert space dimension |
get_output_dim() | Output Hilbert space dimension |
Kraus-specific:
| Method | Description |
|---|---|
left() | List of left operators |
right() | List of right operators |
left_push_back(val) | Append to left operator list |
right_push_back(val) | Append to right operator list |
append(other) | Compose with another Kraus channel |
clear() | Remove all operators |
Distance Functions
| Function | Signature | Description |
|---|---|---|
hellinger_distance | (dict, dict) -> float | Hellinger distance |
hellinger_fidelity | (dict, dict) -> float | Hellinger fidelity |
KL_divergence | (list, list) -> float | Discrete KL divergence |
KL_divergence | (fn, fn, float, float, float) -> float | Continuous KL via numerical integration |
What's Next?
Now that you understand quantum information fundamentals in pyqpanda3, continue with:
- Visualization -- Visualize quantum states on the Bloch sphere and plot state evolution
- Variational Circuits -- Use state evolution in variational quantum algorithms
- Noise Simulation -- Learn how noise channels affect quantum states in practice
Knowledge Check
Test your understanding of the quantum_info module.
Q1: What is the difference between a pure state and a mixed state? How do you distinguish them programmatically?
A1: A pure state can be described by a single state vector dm.purity() and check if the real part equals 1.
Q2: What is the Kraus operator-sum representation, and what condition must Kraus operators satisfy for a trace-preserving channel?
A2: The Kraus representation writes a channel as
Q3: Name all five quantum channel representations in pyqpanda3 and state one advantage of each.
A3: (1) Kraus -- intuitive physical interpretation; (2) Choi -- easy to verify complete positivity (check PSD); (3) SuperOp -- composition is matrix multiplication; (4) Chi -- natural for process tomography in Pauli basis; (5) PTM -- real-valued, used in randomized benchmarking.
Q4: Why is KL divergence not a true metric? What is the consequence for comparing two distributions?
A4: KL divergence is asymmetric:
Q5: If you apply an amplitude damping channel with
A5: The amplitude damping Kraus operators are
Q6: What does is_valid() check for a DensityMatrix?
A6: It verifies three properties: (1) Hermiticity:
Q7: When would you use evolve() versus update_by_evolve() on a StateVector or DensityMatrix?
A7: Use evolve() in a functional programming style where you want to preserve the original state -- it returns a new object. Use update_by_evolve() when you no longer need the original state and want to save memory by modifying in place.
Exercise 1: Depolarizing Channel Purity Analysis
Construct a depolarizing channel with parameter
Solution:
import numpy as np
from pyqpanda3.quantum_info import StateVector, DensityMatrix, Kraus
# Bell state
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
dm = DensityMatrix(sv)
# Pauli matrices
I = np.eye(2, dtype=complex)
X = np.array([[0, 1], [1, 0]], dtype=complex)
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
# 2-qubit identity for tensor product channels
I4 = np.eye(4, dtype=complex)
print("p | purity")
print("------|-------")
for p in [0.0, 0.1, 0.2, 0.3, 0.5, 0.7, 1.0]:
# Single-qubit depolarizing on qubit 0
K0 = np.sqrt(1 - 3*p/4) * I
K1 = np.sqrt(p/4) * X
K2 = np.sqrt(p/4) * Y
K3 = np.sqrt(p/4) * Z
# Extend to 2 qubits: I ⊗ Ki
K0_2 = np.kron(K0, I)
K1_2 = np.kron(K1, I)
K2_2 = np.kron(K2, I)
K3_2 = np.kron(K3, I)
kraus = Kraus([K0_2, K1_2, K2_2, K3_2])
dm_noisy = kraus.evolve(dm)
purity = dm_noisy.purity()
print(f"{p:.1f} | {purity.real:.6f}")Exercise 2: Channel Representation Round-Trip Verification
Create a Kraus channel, convert it to all four other representations, then convert each back to Kraus and verify they produce the same evolution on a test state.
Solution:
import numpy as np
from pyqpanda3.quantum_info import (
StateVector, DensityMatrix, Kraus, Choi, Chi, SuperOp, PTM
)
# Define a bit-flip channel
p = 0.2
K0 = np.array([[np.sqrt(1-p), 0], [0, np.sqrt(1-p)]], dtype=complex)
K1 = np.array([[0, np.sqrt(p)], [np.sqrt(p), 0]], dtype=complex)
kraus = Kraus([K0, K1])
# Convert to all representations and back
choi = Choi(kraus)
kraus_from_choi = Kraus(choi)
chi = Chi(kraus)
kraus_from_chi = Kraus(chi)
superop = SuperOp(kraus)
kraus_from_superop = Kraus(superop)
ptm = PTM(kraus)
kraus_from_ptm = Kraus(ptm)
# Test state |+>
sv = StateVector([1/np.sqrt(2), 1/np.sqrt(2)])
dm = DensityMatrix(sv)
# All should produce the same evolved state
result_original = kraus.evolve(dm)
result_choi = kraus_from_choi.evolve(dm)
result_chi = kraus_from_chi.evolve(dm)
result_superop = kraus_from_superop.evolve(dm)
result_ptm = kraus_from_ptm.evolve(dm)
print("All representations produce same result:")
print(f" Original == Choi round-trip: {result_original == result_choi}")
print(f" Original == Chi round-trip: {result_original == result_chi}")
print(f" Original == SuperOp round-trip: {result_original == result_superop}")
print(f" Original == PTM round-trip: {result_original == result_ptm}")