Skip to content

Quantum State Preparation ​

Learn how to encode classical data into quantum states using the Encode class in pyqpanda3. This tutorial covers 14 encoding methods -- from simple basis encoding to advanced sparse and approximate MPS techniques -- with mathematical foundations, code examples, and practical guidance for choosing the right method.


Table of Contents ​


Why Quantum State Preparation Matters ​

Quantum state preparation -- also called data encoding or data embedding -- is the process of mapping classical data into quantum states. It is a fundamental step in virtually every quantum algorithm:

  • Quantum machine learning -- kernel methods, variational classifiers, and quantum neural networks all require classical data loaded into quantum registers.
  • Quantum simulation -- requires preparing the initial state of the physical system under study before simulating its evolution.
  • Quantum algorithms -- Grover's search, quantum phase estimation, and Hamiltonian simulation assume specific input states.

The choice of encoding method directly impacts circuit depth, qubit count, expressiveness, and ultimately algorithm performance. pyqpanda3 provides 14 encoding methods through the Encode class, each optimized for different data characteristics.


Setting an Initial State Directly with init_state ​

In 0.4.1, CPUQVM gained init_state: if you already know the complex amplitudes of the target state, you can set it directly as the simulator's initial state, bypassing the encoding circuit entirely. The simulator resets to this initial state before every run(); calling init_state() with no argument restores the default |0...0>.

python
import numpy as np
from pyqpanda3.core import CPUQVM, QProg, X, measure

qvm = CPUQVM()

# Set the initial state directly to the Bell state (|00>+|11>)/sqrt(2) — no encoding circuit needed
qvm.init_state(np.array([1 / np.sqrt(2), 0, 0, 1 / np.sqrt(2)], dtype=complex))

prog = QProg() << measure([0, 1], [0, 1])
qvm.run(prog, 1000)
print("bell:", qvm.result().get_counts())

# init_state takes effect before every run: X(1) flips |11> in the Bell state to |10>
qvm.run(QProg() << X(1) << measure([0, 1], [0, 1]), 1000)
print("after X(1):", qvm.result().get_counts())

qvm.init_state()  # restore the default |0...0>
qvm.run(prog, 1000)
print("default:", qvm.result().get_counts())

Output (counts fluctuate with the random seed):

bell: {'00': 492, '11': 508}
after X(1): {'01': 499, '10': 501}
default: {'00': 1000}

Note: MPSQVM's C++ init_state is not exposed in the Python bindings (0.4.1); use CPUQVM.init_state on the Python side.


The Encode Class ​

The Encode class in pyqpanda3.core is the central interface for all quantum state preparation methods. You create an instance, call an encoding method, and then extract results.

python
from pyqpanda3 import core

enc = core.Encode()

Core Methods ​

get_circuit() -- Returns the QCircuit that implements the encoding:

python
enc = core.Encode()
enc.amplitude_encode([0, 1], [0.5, 0.5, 0.5, 0.5])
circuit = enc.get_circuit()
prog = core.QProg()
prog << circuit

get_out_qubits() -- Returns the output qubit indices. Some methods use auxiliary qubits internally, so output qubits may differ from input qubits:

python
enc = core.Encode()
norm = sum(x**2 for x in [0.5, 0.3, 0.2, 0.4, 0.1, 0.6, 0.3, 0.5]) ** 0.5
enc.dc_amplitude_encode(list(range(7)), [x/norm for x in [0.5, 0.3, 0.2, 0.4, 0.1, 0.6, 0.3, 0.5]])
out_qubits = enc.get_out_qubits()

get_fidelity(data) -- Computes fidelity between the encoded state and the target state. Accepts List[float] or List[complex]. Fidelity ranges from 0 (orthogonal) to 1 (identical):

F=‖⟨ψencoded∥ψtarget⟩‖2
python
enc = core.Encode()
enc.amplitude_encode([0, 1], [0.5, 0.5, 0.5, 0.5])
fidelity = enc.get_fidelity([0.5, 0.5, 0.5, 0.5])  # 1.0 for exact methods

Workflow ​

mermaid
graph LR
    A["core.Encode()"] --> B["enc.method(qubits, data)"]
    B --> C["enc.get_circuit()"]
    B --> D["enc.get_out_qubits()"]
    B --> E["enc.get_fidelity(data)"]
    C --> F["QProg() << circuit"]
    F --> G["CPUQVM().run(prog, shots)"]

    style A fill:#4a90d9,color:#fff
    style B fill:#50b86c,color:#fff
    style G fill:#c0392b,color:#fff

Method Selection Guide ​

mermaid
graph TD
    START["Classical Data"] --> Q1{"Data type?"}
    Q1 -->|"Binary string"| BASIC["basic_encode"]
    Q1 -->|"Real/complex vector"| Q2{"Is data sparse?"}

    Q2 -->|"Yes"| Q3{"How sparse?"}
    Q3 -->|"Few non-zero entries"| SPARSE["sparse_isometry"]
    Q3 -->|"Moderately sparse"| EFF["efficient_sparse"]
    Q3 -->|"Known basis states"| DS["ds_quantum_state_preparation"]

    Q2 -->|"No (dense)"| Q4{"Precision?"}
    Q4 -->|"Exact"| Q5{"Data size?"}
    Q4 -->|"Approximate OK"| MPS["approx_mps_encode"]

    Q5 -->|"Small"| AMP["amplitude_encode"]
    Q5 -->|"Medium"| Q6{"Depth constraint?"}
    Q6 -->|"Minimize depth"| RECUR["amplitude_encode_recursive"]
    Q6 -->|"Balanced"| SCHMIDT["schmidt_encode"]

    START -->|"Feature vector"| Q7{"Density?"}
    Q7 -->|"1 angle/qubit"| ANGLE["angle_encode"]
    Q7 -->|"2 angles/qubit"| DENSE["dense_angle_encode"]
    Q7 -->|"With entanglement"| IQP["iqp_encode"]

    START -->|"Block data"| BID["bid_amplitude_encode"]

    style BASIC fill:#3498db,color:#fff
    style AMP fill:#2ecc71,color:#fff
    style ANGLE fill:#e67e22,color:#fff
    style MPS fill:#d35400,color:#fff
    style SPARSE fill:#1abc9c,color:#fff

1. Basic Encoding ​

Method: basic_encode(qubits, data: str)

Maps a binary string directly onto the computational basis state. Each character in the string corresponds to one qubit: '1' applies an X gate (bit flip), '0' leaves the qubit in the ground state |0⟩. This is the simplest possible encoding -- it uses at most n single-qubit gates with no entanglement.

Given binary string b=bn−1…b0 of length n, the encoding prepares:

∥b⟩=∥bn−1⟩⊗⋯⊗∥b0⟩,gatei={Xbi=1Ibi=0
PropertyValue
Qubitsn (string length)
Depth1
ExactYes
python
from pyqpanda3 import core

enc = core.Encode()
enc.basic_encode([0, 1, 2], "101")

prog = core.QProg()
prog << enc.get_circuit() << core.measure([0, 1, 2], [0, 1, 2])
machine = core.CPUQVM()
machine.run(prog, 1000)
print(machine.result().get_counts())  # {'101': 1000}

运行结果:

{'101': 1000}

Use for: encoding classical bit strings, preparing computational basis states, testing.


2. Angle Encoding ​

Method: angle_encode(qubits, data, gate_type=GateType.RY)

Maps each data value to a rotation angle on the corresponding qubit. This is one of the most commonly used encodings in quantum machine learning because it produces shallow circuits (depth 1) with tunable expressiveness through the choice of rotation axis.

For data vector x=(x0,…,xn−1):

∥ψ⟩=⨂i=0n−1Rg(xi)∥0⟩

With the default RY gate: RY(θ)|0⟩=cos⁡(θ/2)|0⟩+sin⁡(θ/2)|1⟩. Other supported types include GateType.RX and GateType.RZ.

PropertyValue
Qubitsn (data length)
Depth1
ExactNo (information compression)
python
from pyqpanda3 import core

# Default RY gates
enc = core.Encode()
enc.angle_encode([0, 1, 2], [0.5, 1.0, 1.5])

# Using RX gates
enc2 = core.Encode()
enc2.angle_encode([0, 1, 2], [0.5, 1.0, 1.5], core.GateType.RX)

Use for: QML feature encoding, variational circuit first layers, shallow circuits.


3. Dense Angle Encoding ​

Method: dense_angle_encode(qubits, data)

Packs two data values into each qubit using both RY and RZ rotations, doubling the amount of information stored per qubit compared to standard angle encoding. This makes it attractive when qubit count is limited.

For a data vector of even length 2n, the encoding uses n qubits with two rotations each:

∥ψ⟩=⨂i=0n−1RZ(x2i+1)⋅RY(x2i)∥0⟩

The resulting single-qubit state for qubit i is:

RZ(β)⋅RY(α)∥0⟩=cos(α2)e−iβ/2∥0⟩+sin(α2)eiβ/2∥1⟩
PropertyValue
Qubitsm/2 (half the data length)
Depth2
ExactNo
python
enc = core.Encode()
# 6 values encoded into 3 qubits
enc.dense_angle_encode([0, 1, 2], [3.14, 3.14, 0.5, 1.0, 0.3, 0.7])

Use for: limited qubit count, higher-dimensional feature vectors.


4. Amplitude Encoding ​

Method: amplitude_encode(qubits, data)

Maps a normalized classical vector directly onto the amplitudes of a multi-qubit quantum state. This is the most information-dense exact encoding available: n qubits can represent 2n amplitudes. The circuit uses controlled rotations and uniformly controlled gates to set each amplitude precisely.

Given normalized x=(x0,…,x2n−1) with |x|=1:

∥ψ⟩=∑i=02n−1xi∥i⟩

Accepts List[float] (real) or List[complex] (complex amplitudes). Data is automatically normalized. For real input, the statevector matches the input exactly. For complex input x∈C2n, both magnitude and phase of each element are encoded.

PropertyValue
Qubitslog2⁡(N)
Gate countO(N) real, O(Nlog⁡N) complex
ExactYes
python
import numpy as np
from pyqpanda3 import core

# Real-valued: equal superposition
enc = core.Encode()
enc.amplitude_encode([0, 1], [0.5, 0.5, 0.5, 0.5])
print(f"Fidelity: {enc.get_fidelity([0.5, 0.5, 0.5, 0.5])}")

# Complex-valued
enc2 = core.Encode()
data = [0.5+0j, 0.5j, -0.5, 0.5-0.5j]
norm = np.linalg.norm(data)
enc2.amplitude_encode([0, 1], [complex(x/norm) for x in data])

# Run
prog = core.QProg()
prog << enc.get_circuit() << core.measure([0, 1], [0, 1])
machine = core.CPUQVM()
machine.run(prog, 1000)
print(machine.result().get_counts())

运行结果:

Fidelity: 1.0
{'11': 237, '10': 245, '01': 252, '00': 266}

Use for: exact data representation, moderate data sizes (up to ~16 qubits), loading classical data for quantum algorithms.


5. Recursive Amplitude Encoding ​

Method: amplitude_encode_recursive(qubits, data)

Same result as standard amplitude encoding but uses a top-down recursive decomposition strategy. The circuit structure differs from the standard approach, which can lead to better parallelizability on hardware with specific connectivity patterns. Accepts both List[float] and List[complex].

Given normalized x, define:

α=∑i=02n−1−1∥xi∥2,β=∑i=2n−12n−1∥xi∥2

The first rotation sets RY(θ)|0⟩=α|0⟩+β|1⟩, then recurses on each half:

∥ψ⟩=α∥0⟩⊗∥ψupper⟩+β∥1⟩⊗∥ψlower⟩

Accepts List[float] and List[complex].

PropertyValue
Qubitslog2⁡(N)
DepthO(N), structured recursively
ExactYes
python
import numpy as np

enc = core.Encode()
data = [0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25]
norm = np.linalg.norm(data)
enc.amplitude_encode_recursive([0, 1, 2], [x/norm for x in data])
print(f"Fidelity: {enc.get_fidelity([x/norm for x in data])}")

运行结果:

Fidelity: 1.0000000000000004

Use for: same as amplitude encoding, but with hardware-favorable circuit structure.


6. IQP Encoding ​

Method: iqp_encode(qubits, data, control_list=[], bool_inverse=False, repeats=1)

Applies a structured pattern of Hadamard gates, diagonal rotations, and entangling phase gates. IQP circuits are believed hard to simulate classically.

The circuit is U=H⊗n⋅UD⋅H⊗n where:

UD=∏(i,j)∈ERZZij(ϕij)⋅∏k=0n−1RZk(xk)

E is the edge set from control_list. Parameters: control_list (qubit pairs for RZZ gates), bool_inverse (apply inverse), repeats (circuit repetitions).

PropertyValue
Qubitsn (data length)
DepthO(1) per repetition (commuting gates)
ExactNo (probabilistic)
python
# Basic IQP
enc = core.Encode()
enc.iqp_encode([0, 1, 2], [0.5, 1.0, 1.5])

# With entanglement and repetition
enc2 = core.Encode()
enc2.iqp_encode([0, 1, 2], [0.5, 1.0, 1.5],
                control_list=[(0,1), (1,2)], repeats=2)

prog = core.QProg()
prog << enc.get_circuit() << core.measure([0,1,2], [0,1,2])
machine = core.CPUQVM()
machine.run(prog, 1000)
print(machine.result().get_counts())

运行结果:

{'110': 122, '101': 121, '100': 127, '011': 100, '001': 105, '111': 150, '010': 137, '000': 138}

Use for: QML kernel methods, entanglement-based feature maps, classically hard circuits.


7. Schmidt Encoding ​

Method: schmidt_encode(qubits, data, cutoff=0)

Uses the Schmidt decomposition to break the target state into bipartite components recursively. The cutoff parameter truncates small singular values for approximate encoding with fewer gates.

For an n-qubit state split into subsystems A and B:

∥ψ⟩=∑k=1rσk∥uk⟩A⊗∥vk⟩B

Terms with σk<cutoff are discarded, reducing depth at the cost of accuracy.

PropertyValue
Qubitslog2⁡(N)
DepthDepends on Schmidt rank
ExactYes when cutoff=0
python
enc = core.Encode()
data = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
norm = np.linalg.norm(data)
data = [x/norm for x in data]

# Exact
enc.schmidt_encode([0, 1, 2], data, cutoff=0)
print(f"Fidelity (exact): {enc.get_fidelity(data):.6f}")

# Approximate
enc2 = core.Encode()
enc2.schmidt_encode([0, 1, 2], data, cutoff=1e-6)
print(f"Fidelity (cutoff): {enc2.get_fidelity(data):.6f}")

运行结果:

Fidelity (exact): 1.000000
Fidelity (cutoff): 1.000000

Use for: low Schmidt-rank states, trading accuracy for depth, hierarchical data.


8. Divide-and-Conquer Amplitude Encoding ​

Method: dc_amplitude_encode(qubits, data)

Splits the data vector into halves and recursively encodes each half. Uses auxiliary qubits for the decomposition, producing shallower circuits than standard amplitude encoding at the cost of more qubits. Always use get_out_qubits() to find which qubits hold the final encoded state.

The algorithm computes norms α=|x(0)| and β=|x(1)| for each split, applies a rotation to encode (α,β) on a control qubit, and recurses on both halves. Total qubits required: 2n−1 for data length 2n.

PropertyValue
Qubits2n−1 for data length 2n
DepthO(log⁡N)
ExactYes
python
import numpy as np

enc = core.Encode()
data = [0.5, 0.3, 0.2, 0.4, 0.1, 0.6, 0.3, 0.5]
norm = np.linalg.norm(data)
enc.dc_amplitude_encode(list(range(7)), [x/norm for x in data])

out_qubits = enc.get_out_qubits()
prog = core.QProg()
prog << enc.get_circuit()
prog << core.measure(out_qubits, list(range(len(out_qubits))))
machine = core.CPUQVM()
machine.run(prog, 1000)
print(machine.result().get_counts())

运行结果:

{'110': 80, '101': 302, '100': 3, '011': 131, '001': 66, '111': 182, '010': 38, '000': 198}

Use for: when circuit depth matters more than qubit count, NISQ hardware.


9. BID Amplitude Encoding ​

Method: bid_amplitude_encode(qubits, data, split=-1)

Block-Inverse Decomposition: partitions the data vector into 2s blocks, encodes each independently, then combines with multiplexed rotations. The split parameter controls block size; -1 selects automatically. Like dc_amplitude_encode, the output qubits may differ from input qubits, so always check with get_out_qubits().

For a normalized vector x∈C2n partitioned into 2s blocks:

∥ψ⟩=∑k=02s−1∥xk∥⋅∥k⟩⊗∥x~k⟩

where |x~k⟩=xk/|xk| is the normalized block state.

PropertyValue
Qubitslog2⁡(N)
DepthO(N/2s) per block
ExactYes
python
import numpy as np

enc = core.Encode()
data = [0.1, 0.3, 0.2, 0.4, 0.15, 0.25, 0.35, 0.45]
norm = np.linalg.norm(data)
enc.bid_amplitude_encode([0, 1, 2], [x/norm for x in data], split=3)

Use for: structured data with natural block decomposition, tunable encoding complexity.


10. Double Sparse State Preparation ​

Method: ds_quantum_state_preparation(qubits, data)

Handles sparse data where most entries are zero. Complexity depends on the number of non-zero entries s rather than 2n.

Given sparse state {(bk,αk)}: |ψ⟩=∑kαk|bk⟩

Accepts four data types:

TypeDescription
Dict[str, float]Sparse real-valued state
Dict[str, complex]Sparse complex-valued state
List[float]Dense vector (sparsity auto-detected)
List[complex]Dense vector (sparsity auto-detected)
PropertyValue
Qubitsn + potential auxiliaries
DepthO(s⋅n)
ExactYes
python
# Sparse dictionary format
enc = core.Encode()
data_dict = {"000": 0.40, "001": 0.91, "111": 0.08}
norm = sum(v**2 for v in data_dict.values()) ** 0.5
enc.ds_quantum_state_preparation([0,1,2,3,4,5],
    {k: v/norm for k, v in data_dict.items()})
out_qubits = enc.get_out_qubits()

# Dense vector format (auto-detect sparsity)
enc2 = core.Encode()
norm = sum(x**2 for x in [0.0, 0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5]) ** 0.5
enc2.ds_quantum_state_preparation(list(range(6)),
    [x/norm for x in [0.0, 0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5]])

Use for: data with many zeros, states specified in ket notation.


11. Sparse Isometry Encoding ​

Method: sparse_isometry(qubits, data)

Constructs an isometry mapping |0⟩⊗n to the target sparse state using a direct unitary decomposition with Givens rotations and controlled operations. Unlike ds_quantum_state_preparation, this method uses a direct isometry approach rather than a double-sparse decomposition.

V∥0⟩⊗n=∑kαk∥bk⟩

The isometry is decomposed into a sequence of Givens rotations and controlled operations. The complexity scales with the number of non-zero amplitudes s rather than the Hilbert space dimension 2n.

Accepts same four data types as ds_quantum_state_preparation.

PropertyValue
Qubitsn
DepthO(s⋅n)
ExactYes
python
enc = core.Encode()
state = {
    "000": complex(0.37, 0.44),
    "001": complex(0.20, 0.34),
    "010": complex(0.53, 0.25),
    "100": complex(0.20, 0.35)
}
norm = sum(abs(v)**2 for v in state.values()) ** 0.5
state = {k: v/norm for k, v in state.items()}
enc.sparse_isometry([0, 1, 2], state)

prog = core.QProg()
prog << enc.get_circuit() << core.measure([0,1,2], [0,1,2])
machine = core.CPUQVM()
machine.run(prog, 1000)
print(machine.result().get_counts())

运行结果:

{'100': 172, '001': 170, '010': 324, '000': 334}

Use for: exact encoding of sparse states, states with known basis labels.


12. Efficient Sparse Encoding ​

Method: efficient_sparse(qubits, data)

Optimizes sparse preparation by finding the minimal set of distinguishing bits D that uniquely identify all non-zero basis states, then encoding only the reduced 2|D|-dimensional space. Achieves better gate counts than sparse_isometry for clustered sparsity patterns.

The circuit first encodes the amplitude distribution on the distinguishing qubits, then uses conditional operations to set the remaining qubits. This avoids encoding the full 2n-dimensional space.

Accepts same data types as sparse_isometry.

PropertyValue
Qubitsn
DepthO(s+n) best case
ExactYes
python
enc = core.Encode()
data_dict = {"000": 0.40, "001": 0.91, "111": 0.08}
norm = sum(v**2 for v in data_dict.values()) ** 0.5
enc.efficient_sparse([0, 1, 2], {k: v/norm for k, v in data_dict.items()})
# Or with a dense vector
enc2 = core.Encode()
data = [0.5, 0.3, 0.2, 0.4, 0.1, 0.6, 0.3, 0.5]
norm = sum(x**2 for x in data) ** 0.5
enc2.efficient_sparse([0,1,2], [x/norm for x in data])

Use for: sparse states where gate count is critical, patterns with shared bit prefixes.


13. Approximate MPS Encoding ​

Method: approx_mps_encode(qubits, data, layers=3, sweeps=100, double2float=False)

Represents the target state as a matrix product state and iteratively optimizes the circuit to approximate it. Sacrifices exact accuracy for dramatically reduced circuit depth.

∥ψ⟩=∑i1,…,inTr(A1i1A2i2⋯Anin)∥i1…in⟩

Each MPS tensor is mapped to single-qubit and two-qubit gates, optimized over sweeps iterations. More layers yield higher fidelity but deeper circuits. Accepts List[float] and List[complex].

ParameterDefaultDescription
layers3MPS layers (expressiveness)
sweeps100Optimization iterations
double2floatFalseConvert to float32 for speed
PropertyValue
Qubitslog2⁡(N)
DepthO(n⋅layers)
ExactNo (approximate)
python
import numpy as np
from pyqpanda3 import core

np.random.seed(42)
data = np.random.randn(8)
data = (data / np.linalg.norm(data)).tolist()

# Default parameters
enc = core.Encode()
enc.approx_mps_encode([0,1,2], data, layers=3, sweeps=100)
print(f"Fidelity (3 layers): {enc.get_fidelity(data):.6f}")

# Higher fidelity
enc2 = core.Encode()
enc2.approx_mps_encode([0,1,2], data, layers=5, sweeps=200)
print(f"Fidelity (5 layers): {enc2.get_fidelity(data):.6f}")

运行结果:

Fidelity (3 layers): 1.000000
Fidelity (5 layers): 1.000000

Use for: large data vectors, low-entanglement states, NISQ devices, when approximate encoding is acceptable.


14. Topology-Aware MPS Encoding ​

topology_aware_mps_encode() is the hardware-aware companion to approximate MPS encoding. It still prepares an approximate amplitude-encoded state, but it restricts every two-qubit operation to the connected edges you supply. That makes it a good fit when you already know the device connectivity and want to avoid adding SWAPs inside the state-preparation circuit itself.

python
from pyqpanda3.core import Encode

data = [0.25] * 16
topology = [[0, 1], [1, 2], [2, 3]]

encoder = Encode()
encoder.topology_aware_mps_encode(
    [0, 1, 2, 3],
    data,
    topology,
    depth=3,
)

print(type(encoder.get_circuit()).__name__)
print(round(encoder.get_fidelity(data), 6))

Expected output:

text
QCircuit
1.0

Use a larger depth when fidelity matters more than circuit-generation time. The input amplitudes must already be normalized, and the topology graph must stay connected across the qubits used for encoding.


Comparing Encoding Methods ​

MethodQubitsDepthExactData TypesBest For
basic_encoden1YesstrBasis states
angle_encoden1Nofloat[]QML features
dense_angle_encoden/22Nofloat[]Compact features
amplitude_encodelog2⁡NO(N)Yesfloat[], complex[]Exact loading
amplitude_encode_recursivelog2⁡NO(N)Yesfloat[], complex[]Structured states
iqp_encodenO(1)Nofloat[]QML kernels
schmidt_encodelog2⁡NVariesYes/Approxfloat[]Low-rank states
dc_amplitude_encode2n−1O(log⁡N)Yesfloat[]Shallow circuits
bid_amplitude_encodelog2⁡NVariesYesfloat[]Block data
ds_quantum_state_preparationn+auxO(sn)YesDict, float[], complex[]Sparse states
sparse_isometrynO(sn)YesDict, float[], complex[]Sparse exact
efficient_sparsenO(s+n)YesDict, float[], complex[]Efficient sparse
approx_mps_encodelog2⁡NO(nL)Nofloat[]Large states
topology_aware_mps_encodelog2⁡NO(nL)Nofloat[], complex[]Hardware-aware

Decision Flowchart ​

The following flowchart guides you to the most appropriate encoding method based on your data characteristics and constraints:

mermaid
flowchart TD
    A[Start: Choose Encoding] --> B{Data type?}
    B -->|Binary string| C[basic_encode]
    B -->|Real/Complex vector| D{Sparsity?}
    B -->|Feature vector for QML| E{Qubit budget?}

    D -->|Dense, all non-zero| F{Need exact?}
    D -->|Sparse, few non-zero| G{s < n?}
    D -->|Low-rank structure| H{Rank known?}

    F -->|Yes| I{Circuit depth budget?}
    F -->|No, approximate OK| J[approx_mps_encode]

    I -->|Generous| K[amplitude_encode]
    I -->|Tight| L[dc_amplitude_encode]

    G -->|Yes| M[efficient_sparse]
    G -->|No| N[sparse_isometry]

    H -->|Yes| O[schmidt_encode]
    H -->|No| P[bid_amplitude_encode]

    E -->|Many qubits available| Q[angle_encode]
    E -->|Few qubits| R[dense_angle_encode]
    E -->|Need entanglement| S[iqp_encode]

    style C fill:#c8e6c9
    style K fill:#bbdefb
    style J fill:#fff9c4
    style M fill:#ffe0b2
    style Q fill:#e1bee7

Qubit and Gate Cost Summary ​

For a data vector of length N=2n:

MethodQubits RequiredGate CountCircuit Depth
basic_encodenO(n)O(1)
angle_encodenO(n)O(1)
dense_angle_encode⌈n/2⌉O(n)O(1)
amplitude_encodenO(2n)O(2n)
amplitude_encode_recursivenO(2n)O(n)
iqp_encodenO(n2)O(1)
schmidt_encodenO(2nr)O(2nr)
dc_amplitude_encodenO(n2n)O(n)
bid_amplitude_encodenO(n2n/2)O(n2n/2)
ds_quantum_state_preparationn+O(1)O(sn)O(sn)
sparse_isometrynO(sn)O(sn)
efficient_sparsenO(s+n)O(s+n)
approx_mps_encodenO(nL)O(nL)
topology_aware_mps_encodenO(nL)O(nL)

where s is the number of non-zero amplitudes, r is the Schmidt rank, and L is the number of MPS layers.


Complete Example ​

This example encodes real-world classification data using multiple methods and compares circuit statistics and fidelity:

python
from pyqpanda3 import core
import numpy as np

# Sample feature vectors (normalized)
samples = [
    [0.50, 0.35, 0.60, 0.50],
    [0.80, 0.20, 0.30, 0.45],
    [0.15, 0.70, 0.55, 0.40],
]

for i, raw in enumerate(samples):
    norm = np.linalg.norm(raw)
    data = [x / norm for x in raw]
    print(f"\n--- Sample {i+1} ---")

    # Angle encoding (4 qubits)
    enc1 = core.Encode()
    enc1.angle_encode([0,1,2,3], raw)
    c1 = enc1.get_circuit()

    # Amplitude encoding (2 qubits)
    enc2 = core.Encode()
    enc2.amplitude_encode([0,1], data)
    c2 = enc2.get_circuit()

    # IQP encoding (4 qubits, with entanglement)
    enc3 = core.Encode()
    enc3.iqp_encode([0,1,2,3], raw, control_list=[(0,1),(1,2),(2,3)])
    c3 = enc3.get_circuit()

    # MPS approximation (2 qubits)
    enc4 = core.Encode()
    enc4.approx_mps_encode([0,1], data, layers=3, sweeps=100)
    c4 = enc4.get_circuit()

    # Schmidt encoding (2 qubits)
    enc5 = core.Encode()
    enc5.schmidt_encode([0,1], data, cutoff=0)
    c5 = enc5.get_circuit()

    print(f"  Angle:     {c1.size()} gates, depth {c1.depth()}")
    print(f"  Amplitude: {c2.size()} gates, depth {c2.depth()}, "
          f"fidelity {enc2.get_fidelity(data):.4f}")
    print(f"  IQP:       {c3.size()} gates, depth {c3.depth()}")
    print(f"  MPS:       {c4.size()} gates, depth {c4.depth()}, "
          f"fidelity {enc4.get_fidelity(data):.4f}")
    print(f"  Schmidt:   {c5.size()} gates, depth {c5.depth()}, "
          f"fidelity {enc5.get_fidelity(data):.4f}")

    # Run amplitude-encoded circuit
    prog = core.QProg()
    prog << c2 << core.measure([0,1], [0,1])
    machine = core.CPUQVM()
    machine.run(prog, 1000)
    print(f"  Counts: {machine.result().get_counts()}")

运行结果:

--- Sample 1 ---
  Angle:     4 gates, depth 1
  Amplitude: 5 gates, depth 5, fidelity 1.0000
  IQP:       1 gates, depth 11
  MPS:       3 gates, depth 19, fidelity 1.0000
  Schmidt:   5 gates, depth 3, fidelity 1.0000
  Counts: {'11': 236, '10': 395, '01': 136, '00': 233}

--- Sample 2 ---
  Angle:     4 gates, depth 1
  Amplitude: 5 gates, depth 5, fidelity 1.0000
  IQP:       1 gates, depth 11
  MPS:       3 gates, depth 21, fidelity 1.0000
  Schmidt:   5 gates, depth 3, fidelity 1.0000
  Counts: {'11': 201, '10': 105, '01': 36, '00': 658}

--- Sample 3 ---
  Angle:     4 gates, depth 1
  Amplitude: 5 gates, depth 5, fidelity 1.0000
  IQP:       1 gates, depth 11
  MPS:       3 gates, depth 19, fidelity 1.0000
  Schmidt:   5 gates, depth 3, fidelity 1.0000
  Counts: {'11': 184, '10': 299, '01': 495, '00': 22}

Fidelity Benchmarking ​

For approximate methods, you can sweep parameters to find the best accuracy-depth tradeoff:

python
from pyqpanda3 import core
import numpy as np

np.random.seed(42)
data = np.random.randn(8)
data = (data / np.linalg.norm(data)).tolist()
qubits = [0, 1, 2]

# Exact methods (fidelity = 1.0)
for name, fn in [
    ("amplitude_encode", lambda e: e.amplitude_encode(qubits, data)),
    ("amplitude_encode_recursive", lambda e: e.amplitude_encode_recursive(qubits, data)),
    ("schmidt_encode", lambda e: e.schmidt_encode(qubits, data)),
]:
    enc = core.Encode()
    fn(enc)
    print(f"{name}: fidelity = {enc.get_fidelity(data):.6f}, "
          f"gates = {enc.get_circuit().size()}")

# Approximate MPS with varying parameters
for layers in [1, 2, 3, 5, 10]:
    enc = core.Encode()
    enc.approx_mps_encode(qubits, data, layers=layers, sweeps=200)
    print(f"approx_mps_encode (layers={layers}): "
          f"fidelity = {enc.get_fidelity(data):.6f}, "
          f"gates = {enc.get_circuit().size()}")

运行结果:

amplitude_encode: fidelity = 1.000000, gates = 15
amplitude_encode_recursive: fidelity = 1.000000, gates = 5
schmidt_encode: fidelity = 1.000000, gates = 5
approx_mps_encode (layers=1): fidelity = 1.000000, gates = 2
approx_mps_encode (layers=2): fidelity = 1.000000, gates = 4
approx_mps_encode (layers=3): fidelity = 1.000000, gates = 6
approx_mps_encode (layers=5): fidelity = 1.000000, gates = 10
approx_mps_encode (layers=10): fidelity = 1.000000, gates = 20

API Quick Reference ​

Encode Class Methods ​

MethodSignatureDescription
basic_encode(qubits, data: str)Binary string to basis state
angle_encode(qubits, data, gate_type=RY)Rotation-based per-qubit
dense_angle_encode(qubits, data)Two angles per qubit
amplitude_encode(qubits, data)Exact amplitude (real/complex)
amplitude_encode_recursive(qubits, data)Recursive decomposition
iqp_encode(qubits, data, control_list=[], bool_inverse=False, repeats=1)IQP circuit
schmidt_encode(qubits, data, cutoff=0)Schmidt decomposition
dc_amplitude_encode(qubits, data)Divide-and-conquer
bid_amplitude_encode(qubits, data, split=-1)Block-based
ds_quantum_state_preparation(qubits, data)Double sparse (dict/vector)
sparse_isometry(qubits, data)Sparse isometry (dict/vector)
efficient_sparse(qubits, data)Efficient sparse (dict/vector)
approx_mps_encode(qubits, data, layers=3, sweeps=100, double2float=False)Approximate MPS
topology_aware_mps_encode(qubits, data, topology, depth=3)Topology-aware MPS
get_circuit()Returns QCircuit
get_out_qubits()Returns output qubit indices
get_fidelity(data)Encoding fidelity

Data Type Support ​

Methodstrfloat[]complex[]Dict[str,float]Dict[str,complex]
basic_encodeYes--------
angle_encode--Yes------
dense_angle_encode--Yes------
amplitude_encode--YesYes----
amplitude_encode_recursive--YesYes----
iqp_encode--Yes------
schmidt_encode--Yes------
dc_amplitude_encode--Yes------
bid_amplitude_encode--Yes------
ds_quantum_state_preparation--YesYesYesYes
sparse_isometry--YesYesYesYes
efficient_sparse--YesYesYesYes
approx_mps_encode--Yes------
topology_aware_mps_encode--YesYes----

Summary ​

In this tutorial you learned:

  1. Quantum state preparation maps classical data into quantum states. The encoding method you choose directly affects circuit resources and algorithm performance.

  2. The Encode class (core.Encode()) is the unified interface. Call an encoding method, then use get_circuit(), get_out_qubits(), and get_fidelity(data) to extract results.

  3. 14 encoding methods span a wide design space:

  4. Fidelity assessment with get_fidelity() quantifies encoding accuracy, essential for approximate methods.

  5. Method selection depends on data type, accuracy requirements, and hardware constraints.

The next tutorial covers Hamiltonian and Pauli Operators, where you will learn to construct and manipulate Hamiltonians for variational quantum algorithms.


Knowledge Check ​

Test your understanding of quantum state preparation in pyqpanda3.

Q1: What is the difference between amplitude encoding and angle encoding? When would you prefer one over the other?

A1: Amplitude encoding maps data values directly to the probability amplitudes of a quantum state: |ψ⟩=∑ixi|i⟩ (after normalization). It uses ⌈log2⁡(n)⌉ qubits for n data values but produces deep circuits. Angle encoding maps each data value to a rotation angle on a separate qubit: Ry(xi), requiring n qubits for n values but producing shallow circuits. Prefer amplitude encoding when qubits are scarce and angle encoding when circuit depth must be minimized (e.g., on NISQ hardware).

Q2: What normalization condition must the input data satisfy for amplitude encoding? What happens if it is not satisfied?

A2: The data must satisfy ∑i|xi|2=1 (unit norm). The amplitude_encode method normalizes the input data internally, dividing by the total norm. This means you can pass unnormalized data and the method will handle it, but you should be aware of the implicit normalization.

Q3: Explain what the cutoff parameter does in schmidt_encode. Why might you want a non-zero cutoff?

A3: The cutoff parameter in schmidt_encode truncates singular values below the threshold in the Schmidt decomposition. With cutoff=0 (default), no truncation occurs and the encoding is exact. A non-zero cutoff removes small singular values, reducing circuit depth at the cost of encoding fidelity. This trade-off is useful for large-scale problems where exact encoding would require too many gates.

Q4: What is the difference between ds_quantum_state_preparation and sparse_isometry? Both handle sparse data.

A4: Both methods target sparse quantum states, but they use different algorithms. ds_quantum_state_preparation uses a double-sparse approach optimized for states with few non-zero amplitudes in the computational basis. sparse_isometry uses an isometry-based method that preserves inner product relations. The choice depends on the sparsity pattern: ds_quantum_state_preparation is generally more efficient for uniformly sparse states, while sparse_isometry handles structured sparsity better.

Q5: What does the layers parameter control in approx_mps_encode? What happens as you increase it?

A5: The layers parameter controls the number of variational layers in the Matrix Product State (MPS) ansatz used for encoding. More layers increase the expressiveness of the ansatz, allowing higher-fidelity encoding of complex data distributions. However, more layers also increase circuit depth and the number of parameters to optimize, requiring more sweeps and potentially longer computation time. The default of 3 layers provides a good balance for most use cases.

Q6: After calling an encoding method on an Encode object, how do you use the result in a quantum program?

A6: After calling an encoding method (e.g., enc.amplitude_encode(qubits, data)), you extract the circuit with enc.get_circuit() and the output qubits with enc.get_out_qubits(). You then append the circuit to a QProg using the << operator: prog << enc.get_circuit(). The output qubits can be used for subsequent operations or measurement.

Q7: Why does approx_mps_encode have a double2float parameter? What is the trade-off?

A7: The double2float parameter converts double-precision (64-bit) input data to single-precision (32-bit float) before encoding. This reduces the numerical precision of the optimization, which can lead to slightly lower fidelity but faster computation. The trade-off is accuracy vs. speed. For most quantum computing applications where quantum hardware has limited precision anyway, single-precision may be sufficient.


Exercise 1: Encoding Comparison

Encode the same 4-element data vector using three different methods (angle, amplitude, and Schmidt), then compare their fidelity and circuit depth.

Solution:

python
from pyqpanda3.core import Encode, CPUQVM, QProg, measure

data = [0.5, 0.5, 0.5, 0.5]
qubits = [0, 1]

# --- Angle Encoding ---
enc_angle = Encode()
enc_angle.angle_encode(qubits, data[:2])  # Only 2 values for 2 qubits
angle_circuit = enc_angle.get_circuit()
print("Angle encoding circuit:", angle_circuit)

# --- Amplitude Encoding ---
enc_amp = Encode()
enc_amp.amplitude_encode(qubits, data)
amp_circuit = enc_amp.get_circuit()
amp_fidelity = enc_amp.get_fidelity(data)
print(f"Amplitude encoding fidelity: {amp_fidelity:.6f}")

# --- Schmidt Encoding ---
enc_schmidt = Encode()
enc_schmidt.schmidt_encode(qubits, data, cutoff=0)
schmidt_circuit = enc_schmidt.get_circuit()
schmidt_fidelity = enc_schmidt.get_fidelity(data)
print(f"Schmidt encoding fidelity: {schmidt_fidelity:.6f}")

# --- Run and compare ---
qvm = CPUQVM()

for name, circuit in [("Amplitude", amp_circuit), ("Schmidt", schmidt_circuit)]:
    prog = QProg()
    prog << circuit << measure(qubits, [0, 1])
    qvm.run(prog, shots=1000)
    counts = qvm.result().get_counts()
    print(f"{name} encoding results: {counts}")

运行结果:

Angle encoding circuit: 
          ┌──────────────┐ 
q_0:  |0>─┤RY(0.50000000)├ 
          ├──────────────┤ 
q_1:  |0>─┤RY(0.50000000)├ 
          └──────────────┘ 
 c :   / ═════════════════
                          


Amplitude encoding fidelity: 1.000000
Schmidt encoding fidelity: 1.000000
Amplitude encoding results: {'11': 236, '10': 275, '01': 226, '00': 263}
Schmidt encoding results: {'11': 235, '10': 255, '01': 271, '00': 239}

Exercise 2: Sparse State Preparation

Prepare a 3-qubit state with only 2 non-zero amplitudes using both ds_quantum_state_preparation and sparse_isometry. Compare the results.

Solution:

python
from pyqpanda3.core import Encode, CPUQVM, QProg, measure
import numpy as np

# Sparse state: |000> and |111> only
sparse_data = {"000": 0.6, "111": 0.8}
qubits = [0, 1, 2, 3, 4, 5]

# Normalize
norm = np.sqrt(sum(v**2 for v in sparse_data.values()))
sparse_data_norm = {k: v/norm for k, v in sparse_data.items()}

# Method 1: ds_quantum_state_preparation
enc1 = Encode()
enc1.ds_quantum_state_preparation(qubits, sparse_data_norm)
fidelity1 = enc1.get_fidelity(list(sparse_data_norm.values()))
print(f"ds_quantum_state_preparation fidelity: {fidelity1:.6f}")

# Method 2: sparse_isometry
enc2 = Encode()
enc2.sparse_isometry(qubits, sparse_data_norm)
fidelity2 = enc2.get_fidelity(list(sparse_data_norm.values()))
print(f"sparse_isometry fidelity: {fidelity2:.6f}")

# Run both on a simulator
qvm = CPUQVM()

for name, enc in [("DS Preparation", enc1), ("Sparse Isometry", enc2)]:
    prog = QProg()
    prog << enc.get_circuit() << measure(qubits, list(range(len(qubits))))
    qvm.run(prog, shots=1000)
    counts = qvm.result().get_counts()
    print(f"{name}: {counts}")

运行结果:

ds_quantum_state_preparation fidelity: 0.129600
sparse_isometry fidelity: 0.129600
DS Preparation: {'111000': 637, '000000': 363}
Sparse Isometry: {'000111': 654, '000000': 346}

Exercise 3: Approximate MPS with Parameter Sweep

Encode an 8-element data vector using approx_mps_encode with varying layers (1, 3, 5) and sweeps (50, 100, 200). Report the fidelity for each combination.

Solution:

python
import numpy as np
from pyqpanda3.core import Encode

# 8-element data (requires 3 qubits)
data = [0.1, 0.3, 0.5, 0.2, 0.4, 0.6, 0.15, 0.25]
norm = np.linalg.norm(data)
data_norm = [x / norm for x in data]
qubits = [0, 1, 2]

print(f"{'Layers':<8} {'Sweeps':<8} {'Fidelity':<12}")
print("-" * 28)

for layers in [1, 3, 5]:
    for sweeps in [50, 100, 200]:
        enc = Encode()
        enc.approx_mps_encode(qubits, data_norm, layers=layers, sweeps=sweeps)
        fidelity = enc.get_fidelity(data_norm)
        print(f"{layers:<8} {sweeps:<8} {fidelity:.6f}")

运行结果:

Layers   Sweeps   Fidelity    
----------------------------
1        50       1.000000
1        100      1.000000
1        200      1.000000
3        50       1.000000
3        100      1.000000
3        200      1.000000
5        50       1.000000
5        100      1.000000
5        200      1.000000

Released under the MIT License.