Skip to content

Hamiltonian & Pauli Operators ​

This tutorial introduces the Hamiltonian and Pauli operator framework in pyqpanda3. You will learn how to construct Pauli operators, compose Hamiltonians, and compute expectation values -- the essential building blocks for variational quantum algorithms such as VQE and QAOA.

Prerequisites: Simulation -- you should be familiar with creating quantum programs and running them on simulators.


Table of Contents ​


1. What Is a Hamiltonian? ​

In quantum mechanics, a Hamiltonian H is the operator corresponding to the total energy of a system. It governs how a quantum state evolves in time via the Schrodinger equation:

iℏ∂∂t|ψ(t)⟩=H|ψ(t)⟩

The Energy Eigenvalue Problem ​

The eigenstates of the Hamiltonian satisfy:

H|ψn⟩=En|ψn⟩

where En are the energy eigenvalues. The ground state is the eigenstate with the lowest energy E0.

The Variational Principle ​

The variational principle provides a powerful tool for approximating ground-state energies. For any trial state |ψ⟩:

E0≤⟨ψ|H|ψ⟩

This inequality means that the expectation value of H with respect to any state |ψ⟩ is always greater than or equal to the true ground-state energy. This principle is the foundation of the Variational Quantum Eigensolver (VQE) algorithm:

  1. Prepare a parameterized trial state |ψ(θ)⟩.
  2. Measure the expectation value ⟨H⟩=⟨ψ(θ)|H|ψ(θ)⟩.
  3. Use a classical optimizer to minimize ⟨H⟩ over θ.
  4. The minimum gives an upper bound on E0.

Why Pauli Operators? ​

Quantum hardware can only directly measure in the computational (Z) basis. To measure a general Hamiltonian, we decompose it into a sum of Pauli operators:

H=∑iciPi,Pi∈{I,X,Y,Z}⊗n

where each Pi is a tensor product of single-qubit Pauli matrices (including the identity), and ci is a real or complex coefficient. By the linearity of expectation:

⟨H⟩=∑ici⟨Pi⟩

Each term ⟨Pi⟩ can be measured separately (or grouped with other commuting terms).


2. Pauli Matrices Refresher ​

The four single-qubit Pauli matrices are:

σI=(1001),σX=(0110)σY=(0−ii0),σZ=(100−1)

Key properties:

PropertyExpression
Hermitianσi†=σi
Unitaryσi2=I
Traceless (X, Y, Z)Tr(σi)=0
Anticommutationσiσj=−σjσi for i≠j
Commutation[σi,σj]=2iϵijkσk

Multi-qubit Pauli operators are formed by tensor products. For example:

X⊗Z=X0Z1=(0010000−110000−100)

The subscript indicates the qubit index the Pauli gate acts on.


3. The PauliOperator Class ​

The PauliOperator class in pyqpanda3.hamiltonian represents a linear combination of multi-qubit Pauli terms. It is the primary way to define Hamiltonians and observables in pyqpanda3.

python
from pyqpanda3.hamiltonian import PauliOperator

3.1 Construction ​

PauliOperator provides several constructors to accommodate different input formats. The following diagram summarizes the available construction paths:

mermaid
graph TD
    A[PauliOperator] --> B["Empty: PauliOperator()"]
    A --> C["Copy: PauliOperator(op)"]
    A --> D["String: PauliOperator('X')"]
    A --> E["Dict: PauliOperator({'X0 Z1': 1.0})"]
    A --> F["Lists: PauliOperator(['X0'], [1.0])"]
    A --> G["Lists + AB: PauliOperator(['XZ'], [1.0], True)"]
    A --> H["Tuples: PauliOperator([('XZ', [0,1], 1.0)])"]
    A --> I["Matrix: PauliOperator(numpy_array)"]
    style A fill:#4a90d9,color:#fff

Default and Copy Constructors ​

python
# Create an empty PauliOperator
op_empty = PauliOperator()

# Copy an existing operator
op_copy = PauliOperator(op_empty)

From a String ​

A single Pauli string (acts on qubit 0 with default coefficient 1.0):

python
# Single-qubit Pauli-X on qubit 0
op_x = PauliOperator("X")

# Single-qubit Pauli-Z on qubit 0
op_z = PauliOperator("Z")

The string format uses the pattern <Pauli><qubit_index> separated by spaces. Valid Pauli characters are X, Y, Z, and I.

From a Dictionary ​

The most common construction method -- a dictionary mapping Pauli strings to coefficients:

python
# Multiple terms with real coefficients
op = PauliOperator({
    "X0 Z1": 1.0,
    "Z0 Z1": 0.5,
    "Y0 X1": -0.3
})

# Complex coefficients are also supported
op_complex = PauliOperator({
    "X0 Z1": 1.0 + 0.5j,
    "Z0 Z1": -0.2 + 0.1j
})

This is the recommended way to construct a multi-term operator.

From Lists of Strings and Coefficients ​

Two parallel lists: one for Pauli strings and one for coefficients:

python
paulis = ["X0 Z1", "Y2 I1"]
coefs = [1.1, 2.1]

op = PauliOperator(paulis, coefs)

Both lists must have the same length.

From Lists with the AB Flag ​

When AB_is_A1_B0=True, the Pauli string is interpreted as a compact sequence without explicit qubit indices. The character positions map to qubits: position 0 maps to qubit 0, position 1 to qubit 1, and so on.

python
# "XZI" means: X on qubit 0, Z on qubit 1, I on qubit 2
# "IZX" means: I on qubit 0, Z on qubit 1, X on qubit 2
op = PauliOperator(["XZI", "IZX"], [1.1, 2.1], True)

When AB_is_A1_B0=False, the string positions are interpreted in reverse qubit order (position 0 maps to the highest-index qubit).

From a List of Tuples ​

Each tuple contains (pauli_string, qubit_indices, coefficient):

python
# ("XZ", [0, 4], 1.1) means X on qubit 0, Z on qubit 4, with coefficient 1.1
# ("YX", [1, 2], 2.1) means Y on qubit 1, X on qubit 2, with coefficient 2.1
op = PauliOperator([
    ("XZ", [0, 4], 1.1),
    ("YX", [1, 2], 2.1)
])

This format gives explicit control over which qubits each Pauli character acts on.

From a Numpy Matrix ​

A real-valued square matrix of dimension 2n×2n is automatically decomposed into its Pauli representation:

python
import numpy as np

# A 2x2 matrix (single qubit)
mat = np.array([[1.0, 0.0],
                [0.0, -1.0]])

op = PauliOperator(mat)
# This is equivalent to PauliOperator({"Z0": 1.0})

The matrix must have dimensions that are a power of 2. Internally, pyqpanda3 performs a Pauli decomposition of the input matrix.

3.2 Algebraic Operations ​

PauliOperator supports the standard arithmetic operators. All operations return new objects (immutability is preserved).

Addition and Subtraction ​

python
from pyqpanda3.hamiltonian import PauliOperator

op1 = PauliOperator({"X0 Z1": 1.0, "Z0 Z1": 0.5})
op2 = PauliOperator({"Y0 X1": 0.3})

# Addition of two operators
op_sum = op1 + op2
# Result: {"X0 Z1": 1.0, "Z0 Z1": 0.5, "Y0 X1": 0.3}

# Subtraction
op_diff = op1 - op2
# Result: {"X0 Z1": 1.0, "Z0 Z1": 0.5, "Y0 X1": -0.3}

# Adding a scalar (adds a constant term, equivalent to c * I)
op_shifted = op1 + 1.5

# Scalar subtraction
op_lowered = op1 - 0.5

When two terms share the same Pauli string, their coefficients are combined:

python
a = PauliOperator({"Z0": 1.0})
b = PauliOperator({"Z0": 2.5})
c = a + b
# Result: {"Z0": 3.5}

If the coefficients cancel out, the term is automatically removed:

python
a = PauliOperator({"X0": 1.0})
b = PauliOperator({"X0": 1.0})
c = a - b
# Result: empty operator (no terms)

Scalar Multiplication and Division ​

python
op = PauliOperator({"X0 Z1": 2.0, "Y0": 1.0})

# Multiply by a scalar
op_doubled = op * 2.0
# Result: {"X0 Z1": 4.0, "Y0": 2.0}

# Left-multiplication is also supported
op_tripled = 3.0 * op
# Result: {"X0 Z1": 6.0, "Y0": 3.0}

# Division
op_halved = op * 0.5
# Result: {"X0 Z1": 1.0, "Y0": 0.5}

Operator Multiplication ​

The * operator performs algebraic multiplication. For each pair of terms from the left and right operands, the Pauli strings are multiplied following the Pauli algebra rules:

X⋅Y=iZ,Y⋅Z=iX,Z⋅X=iYX⋅X=I,Y⋅Y=I,Z⋅Z=I
python
a = PauliOperator({"X0": 1.0})
b = PauliOperator({"Y0": 1.0})
c = a * b
# X * Y = iZ, so result: {"Z0": 1j}

The @ operator (matrix multiplication) is an alias for *:

python
d = a @ b
# Same result as a * b

Negation ​

python
op = PauliOperator({"X0 Z1": 1.5})
neg = -op
# Result: {"X0 Z1": -1.5}

In-Place Operations ​

While the binary operators return new objects, the in-place variants (+=, -=, *=) modify the left operand:

python
op = PauliOperator({"X0": 1.0})
op += PauliOperator({"Y0": 0.5})
# op is now {"X0": 1.0, "Y0": 0.5}

op *= 2.0
# op is now {"X0": 2.0, "Y0": 1.0}

3.3 Tensor Products ​

The tensor product (Kronecker product) combines two operators acting on separate sets of qubits. In pyqpanda3, use the .tensor() method:

python
a = PauliOperator({"X0": 1.0})   # Acts on qubit 0
b = PauliOperator({"Z0": 0.5})   # Acts on qubit 0

# Tensor product: a's qubits stay the same, b's qubits are shifted
result = a.tensor(b)
# Result: X on qubit 0, Z on qubit 1, coefficient 0.5
# Equivalent to PauliOperator({"X0 Z1": 0.5})

You can also tensor an operator with itself n times:

python
op = PauliOperator({"X0": 1.0})

# Tensor product of op with itself 3 times
# Result: X on qubit 0, X on qubit 1, X on qubit 2
result = op.tensor(3)

3.4 Inspecting and Converting ​

String Representations ​

python
op = PauliOperator({"X0 Z1": 1.0, "Y2": 0.5, "I0 Z1": 0.3})

# String without identity operators (default __str__)
print(op)
# Output includes only non-trivial Paulis, e.g. "X0 Z1" + 1.0, "Y2" + 0.5, ...

# String with identity operators included
print(op.str_with_I(True))
# Shows full Pauli string for each term including I

# String without identity (alternative method)
print(op.str_no_I())

运行结果:

{ qubit_total = 3, pauli_with_coef_s = { 'Y2 ':0.5 + 0j, 'X0 Z1 ':1 + 0j, 'Z1 ':0.3 + 0j, } }
{ qbit_totql = 3, paulis = [ 'YII', 'IZX', 'IZI', ], coefs = [ 0.5 + 0j, 1 + 0j, 0.3 + 0j, ] }
{ qubit_total = 3, pauli_with_coef_s = { 'Y2 ':0.5 + 0j, 'X0 Z1 ':1 + 0j, 'Z1 ':0.3 + 0j, } }

The AB_is_A1_B0 parameter controls the ordering of qubit indices in the string representation.

Matrix Conversion ​

Convert a PauliOperator to its matrix representation:

python
op = PauliOperator({"Z0": 1.0})
mat = op.matrix()
print(mat)
# Output (numpy complex matrix):
# [[1+0j, 0+0j],
#  [0+0j, -1+0j]]

运行结果:

[[ 1.+0.j  0.+0.j]
 [ 0.+0.j -1.+0.j]]

For multi-qubit operators, the matrix dimension is 2n+1×2n+1 where n is the highest qubit index:

python
op2 = PauliOperator({"X0 Z1": 1.0})
mat2 = op2.matrix()
# 4x4 matrix
print(mat2.shape)  # (4, 4)

运行结果:

(4, 4)

Accessing Internal Data ​

Several methods expose the operator's internal data in different formats:

python
op = PauliOperator({"X0 Z1": 1.5, "Y2": -0.3})

# List of 3-tuples: (pauli_string, qubit_indices, complex_coefficient)
data_complex = op.data_3tuple_list_complex_coeff()
# [("XZ", [0, 1], 1.5+0j), ("Y", [2], -0.3+0j)]

# List of 3-tuples with float coefficients
data_float = op.data_3tuple_list_float_coeff()
# [("XZ", [0, 1], 1.5), ("Y", [2], -0.3)]

# Dictionary with string keys and float values
data_dict = op.data_dict_float_coeff()
# {"X0 Z1": 1.5, "Y2": -0.3}

# Dictionary with string keys and complex values
data_dict_c = op.data_dict_complex_coeff()
# {"X0 Z1": (1.5+0j), "Y2": (-0.3+0j)}

Getting the Qubit List ​

python
op = PauliOperator({"X0 I1 Z2": 1.0})
i_qubits, xyz_qubits = op.qubits()
# i_qubits: [1]          -- qubits only acted on by I
# xyz_qubits: [0, 2]     -- qubits acted on by X, Y, or Z

Maximum Qubit Index ​

python
op = PauliOperator({"X0 Z3": 1.0})
print(op.max_qbit_idx())  # 3

运行结果:

3

Terms Access ​

python
op = PauliOperator({"X0 Z1": 1.0, "Y2": 0.5})
terms = op.terms()
for term in terms:
    print(f"Coefficient: {term.coef()}")
    paulis = term.paulis()
    for pwq in paulis:
        print(f"  Qubit {pwq.qbit()}: {pwq.pauli_char()}")

运行结果:

Coefficient: (0.5+0j)
  Qubit 2: Y
Coefficient: (1+0j)
  Qubit 0: X
  Qubit 1: Z

Serialization ​

python
op = PauliOperator({"Z0": 0.5, "X1": -0.3})

# Serialize to string
s = op.data_dict_str()
# e.g., '"Z0": 0.5, "X1": -0.3'

# Deserialize from string
op_restored = PauliOperator.from_dict_str(s)

3.5 Commuting Groups ​

For efficient measurement, you can group commuting Pauli terms together:

python
op = PauliOperator({
    "X0 X1": 1.0,
    "Z0 Z1": 0.5,
    "Y0 Y1": -0.3,
    "Z0": 0.2
})

# Group by full commutation
groups = op.group_commuting(qubit_wise=False)

# Group by qubit-wise commutation (stricter grouping)
groups_qw = op.group_commuting(qubit_wise=True)

for i, group in enumerate(groups):
    print(f"Group {i}: {group}")

Output (the number of groups and their membership may vary by implementation version):

Group 0: { qubit_total = 2, pauli_with_coef_s = { 'X0 X1 ':1 + 0j, } }
Group 1: { qubit_total = 2, pauli_with_coef_s = { 'Z0 Z1 ':0.5 + 0j, 'Z0 ':0.2 + 0j, } }
Group 2: { qubit_total = 2, pauli_with_coef_s = { 'Y0 Y1 ':-0.3 + 0j, } }

Qubit-wise commuting groups can be measured simultaneously using a single basis rotation per group, which is crucial for reducing the number of circuit executions in variational algorithms.


4. The Hamiltonian Class ​

The Hamiltonian class in pyqpanda3.hamiltonian is a thin wrapper around PauliOperator that represents a quantum Hamiltonian. It supports the same constructors and algebraic operations as PauliOperator but is used specifically in contexts where the operator represents a physical Hamiltonian.

python
from pyqpanda3.hamiltonian import Hamiltonian, PauliOperator

Construction ​

Hamiltonian supports the same construction patterns as PauliOperator:

python
# Default (empty)
H1 = Hamiltonian()

# From a dictionary of Pauli strings
H2 = Hamiltonian({"X0 Z1": 1.0, "Z0 Z1": 0.5})

# From a PauliOperator
pauli_op = PauliOperator({"X0 Z1": 1.0, "Z0 Z1": 0.5})
H3 = Hamiltonian(pauli_op)

# From a string (single Pauli on qubit 0)
H4 = Hamiltonian("Z")

# From lists
H5 = Hamiltonian(["X0 Z1", "Y2 I1"], [1.1, 2.1])

# From lists with AB flag
H6 = Hamiltonian(["XZI", "IZX"], [1.1, 2.1], True)

# From tuples
H7 = Hamiltonian([("XZ", [0, 4], 1.1), ("YX", [1, 2], 2.1)])

# From a numpy matrix
import numpy as np
H8 = Hamiltonian(np.array([[1, 0], [0, -1]]))

Algebraic Operations ​

The arithmetic operators work identically to PauliOperator:

python
ha = Hamiltonian({"X0 Z1": 1.0})
hb = Hamiltonian({"Y0 X1": 0.5})

# Addition
h_sum = ha + hb

# Subtraction
h_diff = ha - hb

# Scalar multiplication
h_scaled = ha * 2.0

# Operator multiplication
h_prod = ha * hb

# Tensor product
h_tensor = ha.tensor(hb)

# Negation
h_neg = -ha

Extracting the Underlying PauliOperator ​

python
H = Hamiltonian({"Z0": 1.0, "X0": 0.5})

# Get the underlying PauliOperator
pauli = H.pauli_operator()

# Convert to matrix
mat = H.matrix()

String Representation ​

python
H = Hamiltonian({"X0 Z1": 1.0, "Y2": 0.5})

print(H)              # Default string (without I)
print(H.str_with_I()) # Full string with identity operators
print(H.str_no_I())   # Compact string without I

运行结果:

{ qubit_total = 3, pauli_with_coef_s = { 'Y2 ':0.5 + 0j, 'X0 Z1 ':1 + 0j, } }
{ qbit_totql = 3, paulis = [ 'YII', 'IZX', ], coefs = [ 0.5 + 0j, 1 + 0j, ] }
{ qubit_total = 3, pauli_with_coef_s = { 'Y2 ':0.5 + 0j, 'X0 Z1 ':1 + 0j, } }

When to Use Hamiltonian vs PauliOperator ​

  • Use PauliOperator when you need fine-grained access to individual terms, Pauli decomposition data, commuting group analysis, or when building observables for general purposes.
  • Use Hamiltonian when interfacing with functions that specifically expect a Hamiltonian type, such as expval_hamiltonian().

Both types can be used interchangeably in most expectation value functions, as pyqpanda3 provides separate APIs for each.


5. Expectation Value Computation ​

Computing the expectation value ⟨ψ|H|ψ⟩ is the central operation in variational quantum algorithms. pyqpanda3 provides two functions for this purpose.

5.1 Core API ​

Both functions are exported by pyqpanda3.core:

python
from pyqpanda3.core import expval_hamiltonian, expval_pauli_operator, QProg, CPUQVM
from pyqpanda3.hamiltonian import Hamiltonian, PauliOperator

expval_hamiltonian ​

text
expval_hamiltonian(
    prog,             # QProg -- the quantum program
    hamiltonian,      # Hamiltonian -- the observable
    shots=1,          # int -- number of measurement shots (1 = state vector)
    model=NoiseModel(),  # optional noise model
    used_threads=4,   # int -- number of parallel threads
    backend="CPU"     # str -- "CPU" or "GPU"
) -> float

expval_pauli_operator ​

text
expval_pauli_operator(
    prog,             # QProg -- the quantum program
    pauli_operator,   # PauliOperator -- the observable
    shots=1,          # int -- number of measurement shots (1 = state vector)
    model=NoiseModel(),  # optional noise model
    used_threads=4,   # int -- number of parallel threads
    backend="CPU"     # str -- "CPU" or "GPU"
) -> float

The following diagram illustrates the expectation value computation pipeline:

mermaid
flowchart LR
    A["QProg<br/>(quantum circuit)"] --> C
    B["PauliOperator /<br/>Hamiltonian"] --> C
    C["expval_*<br/>function"] --> D{"shots == 1?"}
    D -- Yes --> E["State vector<br/>simulation"]
    D -- No --> F["Measurement<br/>sampling"]
    E --> G["Exact<br/>expectation value"]
    F --> H["Statistical<br/>estimate"]
    style C fill:#4a90d9,color:#fff
    style G fill:#2ecc71,color:#fff
    style H fill:#f39c12,color:#fff

When shots=1, the function computes the exact expectation value using state vector simulation. When shots > 1, it performs measurement-based estimation with the specified number of shots.

5.2 Example: Bell State ​

Let us compute the expectation value of a Pauli operator with respect to a Bell state |Φ+⟩=12(|00⟩+|11⟩):

python
from pyqpanda3 import core
from pyqpanda3.hamiltonian import PauliOperator

# Create a Bell state: |00> + |11>
prog = core.QProg()
prog << core.H(0)
prog << core.CNOT(0, 1)
prog << core.BARRIER([0, 1])

# Define the observable: Z0 * Z1
# For |00> + |11>, the expectation of ZZ is 1.0
# because ZZ|00> = |00> and ZZ|11> = |11>
op_zz = PauliOperator({"Z0 Z1": 1.0})

# Compute exact expectation value (shots=1)
exp_val = core.expval_pauli_operator(prog, op_zz)
print(f"<ZZ> = {exp_val}")  # Expected: 1.0

# Compute with finite shots (statistical estimate)
exp_val_shots = core.expval_pauli_operator(prog, op_zz, shots=1000)
print(f"<ZZ> (1000 shots) = {exp_val_shots}")  # Close to 1.0

运行结果:

<ZZ> = 1.0000000000000002
<ZZ> (1000 shots) = 1.0000000000000002

Multi-term Operator ​

python
# Define a multi-term operator
op = PauliOperator({
    "Z0 Z1": 0.5,   # ZZ interaction
    "X0 X1": 0.3,   # XX interaction
    "Y0 Y1": 0.2    # YY interaction
})

exp_val = core.expval_pauli_operator(prog, op)
print(f"<H> = {exp_val}")

运行结果:

<H> = 0.6000000000000001

The expectation value is computed as:

⟨H⟩=0.5⟨Z0Z1⟩+0.3⟨X0X1⟩+0.2⟨Y0Y1⟩

For the Bell state |Φ+⟩:

⟨Z0Z1⟩=1,⟨X0X1⟩=1,⟨Y0Y1⟩=−1

So the expected result is 0.5(1)+0.3(1)+0.2(−1)=0.6.

Using the Hamiltonian API ​

python
from pyqpanda3 import core
from pyqpanda3.hamiltonian import Hamiltonian

H = Hamiltonian({"Z0 Z1": 0.5, "X0 X1": 0.3})

exp_val = core.expval_hamiltonian(prog, H)
print(f"<H> = {exp_val}")

运行结果:

<H> = 0.8000000000000002

5.3 Using Noise Models ​

You can include a noise model when computing expectation values to simulate realistic hardware conditions:

python
from pyqpanda3 import core
from pyqpanda3.core import NoiseModel, pauli_x_error, depolarizing_error, GateType
from pyqpanda3.hamiltonian import PauliOperator

# Build a noise model
model = NoiseModel()
model.add_all_qubit_quantum_error(pauli_x_error(0.01), GateType.H)
model.add_all_qubit_quantum_error(depolarizing_error(0.02), GateType.CNOT)

# Create circuit
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1) << core.BARRIER([0, 1])

# Define observable
op = PauliOperator({"Z0 Z1": 1.0})

# Compute noisy expectation value
exp_val_noisy = core.expval_pauli_operator(
    prog, op,
    shots=1000,
    model=model
)
print(f"<ZZ> (noisy) = {exp_val_noisy}")

运行结果:

<ZZ> (noisy) = 0.962

With noise, the expectation value will deviate from the ideal value of 1.0 due to gate errors.


6. PauliTerm and PauliWithQbit Helper Classes ​

Internally, a PauliOperator is composed of PauliTerm objects, each of which contains PauliWithQbit entries. These classes provide a structured way to inspect individual terms.

PauliTerm ​

A PauliTerm represents a single term c⋅P0⊗P1⊗⋯ in the Pauli decomposition.

python
op = PauliOperator({"X0 Z1": 1.5, "Y2": -0.3})
terms = op.terms()

for term in terms:
    # Get the coefficient
    c = term.coef()
    print(f"Coefficient: {c}")

    # Get all Pauli-qubit pairs
    paulis = term.paulis()
    for pwq in paulis:
        print(f"  Qubit {pwq.qbit()}: Pauli {pwq.pauli_char()}")

    # Get qubit classification
    i_qubits, xyz_qubits = term.qubits()
    print(f"  I-only qubits: {i_qubits}")
    print(f"  XYZ qubits: {xyz_qubits}")

    # Export data in various formats
    data_3c = term.data_3tuple_complex_coeff()
    data_2c = term.data_2tuple_complex_coeff()
    data_3f = term.data_3tuple_float_coeff()
    data_2f = term.data_2tuple_float_coeff()

    # Convert to a quantum circuit
    circuit = term.to_qcircuit()

运行结果:

Coefficient: (-0.3+0j)
  Qubit 2: Pauli Y
  I-only qubits: [0, 1]
  XYZ qubits: [2]
Coefficient: (1.5+0j)
  Qubit 0: Pauli X
  Qubit 1: Pauli Z
  I-only qubits: [2]
  XYZ qubits: [0, 1]

PauliWithQbit ​

A PauliWithQbit pairs a single Pauli operator with a specific qubit:

python
op = PauliOperator({"X0 Y1 Z2": 1.0})
terms = op.terms()

for term in terms:
    for pwq in term.paulis():
        qubit = pwq.qbit()          # Qubit index
        is_x = pwq.is_X()           # True if this is Pauli-X
        is_y = pwq.is_Y()           # True if this is Pauli-Y
        is_z = pwq.is_Z()           # True if this is Pauli-Z
        is_i = pwq.is_I()           # True if this is identity
        char = pwq.pauli_char()     # 'X', 'Y', 'Z', or 'I'
        gate = pwq.to_qgate()       # Corresponding QGate object

        print(f"Qubit {qubit}: {char}")

运行结果:

Qubit 0: X
Qubit 1: Y
Qubit 2: Z

7. Practical Example: VQE for the H2 Molecule ​

This section demonstrates a complete workflow using the Variational Quantum Eigensolver (VQE) to estimate the ground-state energy of the H2 molecule.

Background ​

The electronic Hamiltonian of H2 at equilibrium bond length, after Jordan-Wigner transformation and freezing core orbitals, can be expressed as a 2-qubit Pauli operator:

HH2=c0I+c1Z0+c2Z1+c3Z0Z1+c4X0X1

For a bond distance of approximately 0.74 Angstroms, the coefficients are:

CoefficientValue
c0-1.0523792
c1-0.3979374
c2-0.3979374
c3-0.0112801
c40.1809313

The exact ground-state energy is approximately −1.137 Hartree.

Step 1: Define the Hamiltonian ​

python
from pyqpanda3.hamiltonian import PauliOperator

# H2 Hamiltonian coefficients (Hartree units)
h2_coefficients = {
    "": -1.0523792,       # Constant (identity) term
    "Z0": -0.3979374,     # Single-qubit Z on qubit 0
    "Z1": -0.3979374,     # Single-qubit Z on qubit 1
    "Z0 Z1": -0.0112801,  # ZZ interaction
    "X0 X1": 0.1809313    # XX interaction
}

# Note: the identity term is added as a scalar offset
h2_hamiltonian = PauliOperator({
    "Z0": -0.3979374,
    "Z1": -0.3979374,
    "Z0 Z1": -0.0112801,
    "X0 X1": 0.1809313
})

# Add the constant (identity) term
h2_hamiltonian += -1.0523792

print("H2 Hamiltonian:")
print(h2_hamiltonian)

运行结果:

H2 Hamiltonian:
{ qubit_total = 2, pauli_with_coef_s = { 'Z0 ':-0.397937 + 0j, 'X0 X1 ':0.180931 + 0j, 'Z0 Z1 ':-0.0112801 + 0j, 'Z1 ':-0.397937 + 0j, '':-1.05238 + 0j, } }

Step 2: Build the Ansatz Circuit ​

We use a hardware-efficient ansatz with a single parameter:

python
from pyqpanda3 import core

def build_ansatz(theta):
    """Build a parameterized ansatz circuit for H2.

    The ansatz prepares:
    |psi(theta)> = RY(theta)_0 |01>

    This creates a state that explores the X0X1 subspace
    relevant to the H2 Hamiltonian.
    """
    prog = core.QProg()
    # Prepare a reference state
    prog << core.X(1)
    # Apply entangling and rotation gates
    prog << core.RY(0, theta)
    prog << core.CNOT(0, 1)
    prog << core.BARRIER([0, 1])
    return prog

Step 3: Define the Objective Function ​

python
def objective(params):
    """Compute <H2> for a given parameter theta."""
    theta = params[0]
    prog = build_ansatz(theta)
    expectation = core.expval_pauli_operator(prog, h2_hamiltonian)
    return expectation

Step 4: Optimize ​

Using a simple grid search for illustration (in practice, you would use scipy.optimize.minimize):

python
import numpy as np

# Grid search over theta
thetas = np.linspace(0, 2 * np.pi, 100)
energies = [objective([t]) for t in thetas]

# Find minimum
min_idx = np.argmin(energies)
optimal_theta = thetas[min_idx]
min_energy = energies[min_idx]

print(f"Optimal theta: {optimal_theta:.4f}")
print(f"Minimum energy: {min_energy:.6f} Hartree")
print(f"Exact ground state: -1.137274 Hartree")
print(f"Error: {abs(min_energy - (-1.137274)):.6f} Hartree")

运行结果:

Optimal theta: 4.6965
Minimum energy: -1.222008 Hartree
Exact ground state: -1.137274 Hartree
Error: 0.084734 Hartree

Step 5: Using scipy.optimize ​

For a more robust optimization:

python
from scipy.optimize import minimize

result = minimize(
    objective,
    x0=[0.0],           # Initial guess
    method='COBYLA',     # Gradient-free optimizer
    options={'maxiter': 200}
)

print(f"Optimization result:")
print(f"  Theta: {result.x[0]:.6f}")
print(f"  Energy: {result.fun:.6f} Hartree")
print(f"  Converged: {result.success}")

运行结果:

Optimization result:
  Theta: -1.570750
  Energy: -1.222030 Hartree
  Converged: True

Complete Script ​

python
"""
VQE for H2 molecule using pyqpanda3
Estimates the ground-state energy of the H2 Hamiltonian.
"""
from pyqpanda3 import core
from pyqpanda3.hamiltonian import PauliOperator
from scipy.optimize import minimize

# --- Define the H2 Hamiltonian ---
h2 = PauliOperator({
    "Z0": -0.3979374,
    "Z1": -0.3979374,
    "Z0 Z1": -0.0112801,
    "X0 X1": 0.1809313
})
h2 += -1.0523792  # Identity offset

# --- Ansatz ---
def build_ansatz(theta):
    prog = core.QProg()
    prog << core.X(1)
    prog << core.RY(0, theta)
    prog << core.CNOT(0, 1)
    prog << core.BARRIER([0, 1])
    return prog

# --- Objective ---
def objective(params):
    theta = params[0]
    prog = build_ansatz(theta)
    return core.expval_pauli_operator(prog, h2)

# --- Optimize ---
result = minimize(objective, x0=[0.0], method='COBYLA')
print(f"Ground state energy estimate: {result.fun:.6f} Hartree")
print(f"Optimal parameter: {result.x[0]:.6f}")

运行结果:

Ground state energy estimate: -1.222030 Hartree
Optimal parameter: -1.570750

The output should be close to −1.137 Hartree, which is the exact ground-state energy of the H2 molecule at equilibrium.


8. Summary ​

This tutorial covered the essential components for working with Hamiltonians and Pauli operators in pyqpanda3:

ComponentDescription
PauliOperatorRepresents a linear combination of multi-qubit Pauli terms
HamiltonianA wrapper around PauliOperator for physical Hamiltonians
PauliTermA single term in a Pauli decomposition
PauliWithQbitA Pauli operator acting on a specific qubit
expval_pauli_operator()Computes ⟨ψ|P|ψ⟩ for a PauliOperator
expval_hamiltonian()Computes ⟨ψ|H|ψ⟩ for a Hamiltonian

Key Takeaways ​

  1. Construction flexibility: PauliOperator and Hamiltonian can be constructed from strings, dictionaries, lists, tuples, or numpy matrices.
  2. Algebraic operations: The standard Python operators (+, -, *, @) and .tensor() method enable expressive operator algebra.
  3. Expectation values: Use expval_pauli_operator() or expval_hamiltonian() with a QProg to compute expectation values, optionally with noise models and multiple shots.
  4. VQE workflow: Define the Hamiltonian as a Pauli decomposition, build a parameterized ansatz circuit, compute the expectation value, and optimize classically.

Next Steps ​

Released under the MIT License.