Skip to content

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

The quantum_info module exposes the following components:

python
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:

CategoryClasses / FunctionsPurpose
Quantum StatesStateVector, DensityMatrixRepresent pure and mixed quantum states
OperatorsUnitary, MatrixRepresent quantum operations and general matrices
Quantum ChannelsKraus, Choi, SuperOp, Chi, PTMModel noise and open-system evolution
Distance Metricshellinger_distance, hellinger_fidelity, KL_divergenceCompare probability distributions

2. StateVector

A state vector is the most fundamental representation of a pure quantum state. For an n-qubit system, the state vector is a complex-valued array of length 2n whose entries are probability amplitudes:

|ψ=i=02n1αi|i

where αiC and i|αi|2=1.

2.1 Constructing a StateVector

Default constructor -- creates a single-qubit state |0:

python
from pyqpanda3.quantum_info import StateVector

sv = StateVector()
print(sv)
# Output: [1+0j 0+0j]

From qubit count -- initializes the all-zeros state |0n for an n-qubit system:

python
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:

python
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())  # True

From a dictionary mapping index to amplitude:

python
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:

python
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())      # 4

2.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:

python
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 ρ=|ψψ|. Use get_density_matrix() to compute it:

python
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:

γ=Tr(ρ2)
python
sv = StateVector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
print(sv.purity())  # (1+0j)

Two StateVector objects can be compared using ==:

python
sv1 = StateVector([1, 0, 0, 0])
sv2 = StateVector(sv1)
print(sv1 == sv2)  # True

sv3 = StateVector([0, 1, 0, 0])
print(sv1 == sv3)  # False

3. DensityMatrix

A density matrix (or density operator) provides the most general description of a quantum state, encompassing both pure and mixed states:

ρ=ipi|ψiψi|

where pi0 and ipi=1. The density matrix is a 2n×2n positive semidefinite Hermitian matrix with trace 1.

3.1 Constructing a DensityMatrix

Default constructor -- single qubit in state |00|:

python
from pyqpanda3.quantum_info import DensityMatrix

dm = DensityMatrix()
print(dm)

From qubit count -- initializes the all-zeros state for n qubits:

python
dm = DensityMatrix(3)  # 3 qubits
print(dm.dim())  # 8

From a 2D numpy array:

python
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 |ψψ|:

python
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:

python
dm_copy = DensityMatrix(dm)
print(dm == dm_copy)  # True

3.2 Accessing Data

Use ndarray() to retrieve data as a numpy array, at(i, j) for individual elements, and dim() for the dimension:

python
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())      # 4

3.3 Purity and Validation

Purity measures how "mixed" a state is. For pure states, γ=1; for the maximally mixed state of n qubits, γ=1/2n:

python
# 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:

python
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())  # False

3.4 Evolving a Density Matrix

Like StateVector, DensityMatrix supports both functional and in-place evolution with a QCircuit:

python
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:

python
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 U satisfies UU=I.

4.1 Constructing a Unitary

From a quantum circuit:

python
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:

python
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:

python
u1 = Unitary(cir)
u2 = Unitary(cir)
print(u1 == u2)  # True

5. 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:

python
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:

python
m = Matrix(np.array([[1, 0], [0, 1]], dtype=complex))
print(m.row_total())  # 2
print(m.col_total())  # 2

5.2 Matrix Operations

The Matrix class provides the following operations:

python
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:

python
m2 = m
print(m == m2)  # True

6. 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:

E(ρ)=iKiρKi

with the trace-preserving condition iKiKi=I.

For non-trace-preserving channels (e.g., measurement operators), a two-sided form is available:

E(ρ)=iKi(L)ρ(Ki(R))

Constructing a Kraus channel:

python
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):

python
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:

python
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:

python
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 d2×d2 positive semidefinite matrix. For a channel E acting on d-dimensional states:

J(E)=i,j=0d1|ij|E(|ij|)

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.

python
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 d2×d2 matrix that acts on vectorized density matrices:

vec(E(ρ))=Svec(ρ)

This is useful for composing channels (matrix multiplication of SuperOp matrices corresponds to sequential channel application).

python
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 (χ) matrix expands the channel in the Pauli basis. For an n-qubit channel, the Pauli basis consists of 4n tensor products of {I,X,Y,Z}:

E(ρ)=m,nχmnPmρPn

The Chi matrix is useful for process tomography and noise characterization.

python
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:

Rmn=1dTr(PmE(Pn))

The PTM is a real-valued matrix (for Hermitian-preserving channels) and is commonly used in randomized benchmarking and gate set tomography.

python
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:

python
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:

python
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:

Hd(p,q)=12x(p(x)q(x))2

Properties:

  • Range: [0,1]
  • Hd(p,p)=0 (identical distributions)
  • Hd(p,q)=1 when p and q have disjoint support
python
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:

Hf(p,q)=(1Hd(p,q)2)2
python
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 p diverges from a second distribution q:

KL(p|q)=xp(x)log(p(x)q(x))

Properties:

  • Always non-negative: KL(p|q)0
  • Not symmetric: KL(p|q)KL(q|p) in general
  • KL(p|q)=0 if and only if p=q
  • Undefined if q(x)=0 where p(x)>0

For discrete distributions, KL_divergence accepts two lists or numpy arrays of probabilities:

python
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.0

7.4 KL Divergence (Continuous)

For continuous probability distributions, KL_divergence accepts Python callables (functions) along with integration bounds and step size:

KL(p|q)=xstartxendp(x)log(p(x)q(x))dx

The numerical integration uses a Riemann sum with step size dx:

python
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:

python
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:

|ψ=i=0d1αi|i,i|αi|2=1

Mixed states are statistical ensembles of pure states, described by density matrices:

ρ=ipi|ψiψi|,pi0,ipi=1

Properties of a valid density matrix:

  • Hermitian: ρ=ρ
  • Positive semidefinite: ρ0
  • Unit trace: Tr(ρ)=1

Purity

The purity quantifies the "mixedness" of a quantum state:

γ=Tr(ρ2)
  • γ=1: pure state
  • 1/d<γ<1: mixed state (d=2n)
  • γ=1/d: maximally mixed state

Kraus Operator-Sum Representation

The most general physical transformation of a quantum state is:

E(ρ)=iKiρKi

with the trace-preserving condition iKiKi=I.

Channel Representations

Five equivalent ways to represent the same CPTP map:

RepresentationMatrix SizeKey Property
KrausSet of d×d matricesOperator-sum form
Choidd×ddPositive semidefinite iff CP
SuperOpd2×d2Linear map on vectorized ρ
Chid2×d2Expansion in Pauli basis
PTMd2×d2 (real)Pauli expectation value mapping

Hellinger Distance and Fidelity

Hd(p,q)=12x(p(x)q(x))2Hf(p,q)=(1Hd(p,q)2)2

KL Divergence

Discrete form:

KL(p|q)=xp(x)log(p(x)q(x))

Continuous form:

KL(p|q)=p(x)log(p(x)q(x))dx

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 p=q.


10. API Reference

StateVector

MethodSignatureDescription
ConstructorStateVector()Single qubit in |0
ConstructorStateVector(qbit_total: int)All-zeros state for n qubits
ConstructorStateVector(data: list)From complex amplitude list
ConstructorStateVector(data: dict)From index-amplitude mapping
ConstructorStateVector(data: Eigen)From Eigen vector
ndarray()-> numpy.ndarrayInternal data as numpy array
dim()-> intDimension (2n)
at(idx)-> complexElement at index
purity()-> complexTr(ρ2)
is_valid()-> boolCheck normalization
evolve(circuit)-> StateVectorFunctional evolution
update_by_evolve(circuit)-> StateVectorIn-place evolution
get_density_matrix()-> DensityMatrixCompute |ψψ|

DensityMatrix

MethodSignatureDescription
ConstructorDensityMatrix()Single qubit |00|
ConstructorDensityMatrix(qbit_total: int)All-zeros for n qubits
ConstructorDensityMatrix(data: 2D array)From matrix data
ConstructorDensityMatrix(StateVector)From pure state
ndarray()-> numpy.ndarrayInternal data
dim()-> intDimension
at(row, col)-> complexMatrix element
purity()-> complexTr(ρ2)
is_valid()-> boolHermitian, PSD, trace 1
evolve(circuit)-> DensityMatrixFunctional evolution
update_by_evolve(circuit)-> DensityMatrixIn-place evolution
to_statevector()-> StateVectorExtract state vector

Quantum Channels (Kraus, Choi, SuperOp, Chi, PTM)

All channel classes share a common interface:

MethodDescription
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:

MethodDescription
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

FunctionSignatureDescription
hellinger_distance(dict, dict) -> floatHellinger distance Hd[0,1]
hellinger_fidelity(dict, dict) -> floatHellinger fidelity Hf[0,1]
KL_divergence(list, list) -> floatDiscrete KL divergence
KL_divergence(fn, fn, float, float, float) -> floatContinuous KL via numerical integration

What's Next?

Now that you understand quantum information fundamentals in pyqpanda3, continue with:


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 |ψ and has purity γ=Tr(ρ2)=1. A mixed state is a statistical ensemble and has γ<1. In pyqpanda3, compute 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 E(ρ)=iKiρKi. For trace preservation, the operators must satisfy iKiKi=I. This ensures Tr(E(ρ))=Tr(ρ) for all valid ρ.

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: KL(p|q)KL(q|p) in general, and it does not satisfy the triangle inequality. This means the "direction" matters -- KL(p|q) measures the information lost when using q to approximate p, not a symmetric distance.

Q5: If you apply an amplitude damping channel with γ=0.5 to the state |1, what is the probability of finding the qubit in |0 after evolution?

A5: The amplitude damping Kraus operators are K0=(1001γ) and K1=(0γ00). For |1: K0|1=0.5|1, K1|1=0.5|0. The probability of |0 is |0.5|2=0.5, matching the damping parameter γ.

Q6: What does is_valid() check for a DensityMatrix?

A6: It verifies three properties: (1) Hermiticity: ρ=ρ; (2) Positive semidefiniteness: all eigenvalues 0; (3) Unit trace: Tr(ρ)=1. A matrix satisfying all three is a physically valid density matrix.

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 p and apply it to a Bell state density matrix. Plot or print how the purity changes as p increases from 0 to 1.

Solution:

python
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:

python
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}")

Released under the MIT License.