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?
- 2. Pauli Matrices Refresher
- 3. The PauliOperator Class
- 4. The Hamiltonian Class
- 5. Expectation Value Computation
- 6. PauliTerm and PauliWithQbit Helper Classes
- 7. Practical Example: VQE for the H2 Molecule
- 8. Summary
1. What Is a Hamiltonian?
In quantum mechanics, a Hamiltonian
The Energy Eigenvalue Problem
The eigenstates of the Hamiltonian satisfy:
where
The Variational Principle
The variational principle provides a powerful tool for approximating ground-state energies. For any trial state
This inequality means that the expectation value of
- Prepare a parameterized trial state
. - Measure the expectation value
. - Use a classical optimizer to minimize
over . - The minimum gives an upper bound on
.
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:
where each
Each term
2. Pauli Matrices Refresher
The four single-qubit Pauli matrices are:
Key properties:
| Property | Expression |
|---|---|
| Hermitian | |
| Unitary | |
| Traceless (X, Y, Z) | |
| Anticommutation | |
| Commutation |
Multi-qubit Pauli operators are formed by tensor products. For example:
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.
from pyqpanda3.hamiltonian import PauliOperator3.1 Construction
PauliOperator provides several constructors to accommodate different input formats. The following diagram summarizes the available construction paths:
Default and Copy Constructors
# Create an empty PauliOperator
op_empty = PauliOperator()
# Copy an existing operator
op_copy = PauliOperator(op_existing)From a String
A single Pauli term without an explicit coefficient (defaults to coefficient 1.0):
# Single-qubit Pauli-X on qubit 0
op_x = PauliOperator("X0")
# Multi-qubit term: X on qubit 0, Z on qubit 1
op_xz = PauliOperator("X0 Z1")
# Three-qubit term
op_xyz = PauliOperator("X0 Y1 Z2")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:
# Multiple terms with real coefficients
op = PauliOperator({
"X0 Z1": 1.0,
"Z0 Z1": 0.5,
"Y0 X1": -0.3
})
# Complex coefficients are also supported
import numpy as np
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:
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.
# "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):
# ("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
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
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.5When two terms share the same Pauli string, their coefficients are combined:
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:
a = PauliOperator({"X0": 1.0})
b = PauliOperator({"X0": 1.0})
c = a - b
# Result: empty operator (no terms)Scalar Multiplication and Division
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:
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 *:
d = a @ b
# Same result as a * bNegation
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:
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:
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
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
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())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:
op = PauliOperator({"Z0": 1.0})
mat = op.matrix()
print(mat)
# Output (numpy complex matrix):
# [[1+0j, 0+0j],
# [0+0j, -1+0j]]For multi-qubit operators, the matrix dimension is
op2 = PauliOperator({"X0 Z1": 1.0})
mat2 = op2.matrix()
# 4x4 matrix
print(mat2.shape) # (4, 4)Accessing Internal Data
Several methods expose the operator's internal data in different formats:
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
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 ZMaximum Qubit Index
op = PauliOperator({"X0 Z3": 1.0})
print(op.max_qbit_idx()) # 3Terms Access
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()}")Serialization
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:
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}")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.
from pyqpanda3.hamiltonian import Hamiltonian, PauliOperatorConstruction
Hamiltonian supports the same construction patterns as PauliOperator:
# 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:
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 = -haExtracting the Underlying PauliOperator
H = Hamiltonian({"Z0": 1.0, "X0": 0.5})
# Get the underlying PauliOperator
pauli = H.pauli_operator()
# Convert to matrix
mat = H.matrix()String Representation
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 IWhen to Use Hamiltonian vs PauliOperator
- Use
PauliOperatorwhen you need fine-grained access to individual terms, Pauli decomposition data, commuting group analysis, or when building observables for general purposes. - Use
Hamiltonianwhen interfacing with functions that specifically expect aHamiltoniantype, such asexpval_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
5.1 Core API
Both functions are exported by pyqpanda3.core:
from pyqpanda3.core import expval_hamiltonian, expval_pauli_operator, QProg, CPUQVM
from pyqpanda3.hamiltonian import Hamiltonian, PauliOperatorexpval_hamiltonian
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"
) -> floatexpval_pauli_operator
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"
) -> floatThe following diagram illustrates the expectation value computation pipeline:
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
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.0Multi-term Operator
# 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}")The expectation value is computed as:
For the Bell state
So the expected result is
Using the Hamiltonian API
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}")5.3 Using Noise Models
You can include a noise model when computing expectation values to simulate realistic hardware conditions:
from pyqpanda3.core import NoiseModel, pauli_x_error, depolarizing_error, GateType
# 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}")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
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()PauliWithQbit
A PauliWithQbit pairs a single Pauli operator with a specific qubit:
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}")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:
For a bond distance of approximately 0.74 Angstroms, the coefficients are:
| Coefficient | Value |
|---|---|
| -1.0523792 | |
| -0.3979374 | |
| -0.3979374 | |
| -0.0112801 | |
| 0.1809313 |
The exact ground-state energy is approximately
Step 1: Define the Hamiltonian
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)Step 2: Build the Ansatz Circuit
We use a hardware-efficient ansatz with a single parameter:
from pyqpanda3 import core
import numpy as np
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 progStep 3: Define the Objective Function
def objective(theta):
"""Compute <H2> for a given parameter theta."""
prog = build_ansatz(theta)
expectation = core.expval_pauli_operator(prog, h2_hamiltonian)
return expectationStep 4: Optimize
Using a simple grid search for illustration (in practice, you would use scipy.optimize.minimize):
# 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")Step 5: Using scipy.optimize
For a more robust optimization:
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}")Complete Script
"""
VQE for H2 molecule using pyqpanda3
Estimates the ground-state energy of the H2 Hamiltonian.
"""
from pyqpanda3 import core
from pyqpanda3.hamiltonian import PauliOperator
import numpy as np
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}")The output should be close to
8. Summary
This tutorial covered the essential components for working with Hamiltonians and Pauli operators in pyqpanda3:
| Component | Description |
|---|---|
PauliOperator | Represents a linear combination of multi-qubit Pauli terms |
Hamiltonian | A wrapper around PauliOperator for physical Hamiltonians |
PauliTerm | A single term in a Pauli decomposition |
PauliWithQbit | A Pauli operator acting on a specific qubit |
expval_pauli_operator() | Computes |
expval_hamiltonian() | Computes |
Key Takeaways
- Construction flexibility:
PauliOperatorandHamiltoniancan be constructed from strings, dictionaries, lists, tuples, or numpy matrices. - Algebraic operations: The standard Python operators (
+,-,*,@) and.tensor()method enable expressive operator algebra. - Expectation values: Use
expval_pauli_operator()orexpval_hamiltonian()with aQProgto compute expectation values, optionally with noise models and multiple shots. - VQE workflow: Define the Hamiltonian as a Pauli decomposition, build a parameterized ansatz circuit, compute the expectation value, and optimize classically.
Next Steps
- Variational Circuits -- Learn about
VQCircuitfor automatic gradient computation. - Quantum Information -- Explore state vectors, density matrices, and distance metrics.
- Noise Simulation -- Build realistic noise models for hardware-aware simulation.