Simulating the Schrödinger Equation with Pauli Decomposition and Trotter-Suzuki Decomposition

Introduction

Simulating the dynamics of the Schrödinger equation on quantum computers is a foundational task for exploring quantum systems. This requires efficient representations and approximations of the Hamiltonian \( H \) and its associated time evolution operator. Two key techniques are Pauli decomposition and Trotter-Suzuki decomposition, which together enable the practical implementation of quantum simulations.


The Schrödinger Equation and Time Evolution

The time evolution of a quantum state \( |\psi(t)\rangle \) is governed by the time-dependent Schrödinger equation:

\[
i \hbar \frac{\partial}{\partial t} |\psi(t)\rangle = \hat{H} |\psi(t)\rangle,
\]

where:

  • \( |\psi(t)\rangle \) is the quantum state at time \( t \),
  • \( \hat{H} \) is the Hamiltonian operator,
  • \( \hbar \) is the reduced Planck constant.

The formal solution to this equation is:

\[
|\psi(t)\rangle = e^{-\frac{i}{\hbar} \hat{H} t} |\psi(0)\rangle,
\]

where \( e^{-\frac{i}{\hbar} \hat{H} t} \) is the time-evolution operator. This operator encapsulates the dynamics of the quantum system over time and is the central focus of quantum simulations.


Pauli Decomposition

In quantum computing, the Hamiltonian \( \hat{H} \) is represented in terms of tensor products of Pauli operators. This is achieved by expressing \( \hat{H} \) as:

\[
\hat{H} = \sum_{i,j} h_{i,j} (\sigma_i \otimes \sigma_j),
\]

where \( \sigma_i, \sigma_j \in {I, X, Y, Z} \) are Pauli matrices (including the identity \( I \)), and \( h_{i,j} \) are scalar coefficients. This decomposition leverages the completeness of the Pauli basis, which spans the space of Hermitian operators.

The coefficients \( h_{i,j} \) are determined using the trace operation as:

\[
h_{i,j} = \frac{1}{4} \text{Tr}\left[(\sigma_i \otimes \sigma_j) \hat{H}\right].
\]

This formula extracts the contribution of each Pauli term \( (\sigma_i \otimes \sigma_j) \) to the Hamiltonian. The factor \( \frac{1}{4} \) arises from the normalization of the Pauli matrices and ensures correct weighting of the terms in the decomposition.


Time Evolution and Trotter-Suzuki Decomposition

The dynamics of a quantum state involve applying the unitary time evolution operator \( U(t) = e^{-\frac{i}{\hbar} \hat{H} t} \). When \( \hat{H} \) is expressed as a sum of Pauli terms, it is typically decomposed into smaller Hamiltonian components:

\[
\hat{H} = \sum_j \hat{H}_j.
\]

For non-commuting \( \hat{H}_j \), the exact evolution operator cannot be directly implemented. The Trotter-Suzuki decomposition approximates \( U(t) \) as:

\[
e^{-\frac{i}{\hbar} \hat{H} t} \approx \left( \prod_j e^{-\frac{i}{\hbar} \hat{H}_j t / k} \right)^k,
\]

where \( k \) is the number of Trotter steps. Increasing \( k \) improves the accuracy of the approximation. For higher fidelity, higher-order Trotter-Suzuki formulas add correction terms that further reduce errors.

Each term \( e^{-\frac{i}{\hbar} \hat{H}_j t} \), when expressed in the Pauli basis, corresponds to rotations or other simple operations that can be efficiently implemented on quantum hardware. For example, \( e^{-i h_{i,j} (\sigma_i \otimes \sigma_j) t} \) is a unitary operator that acts on specific qubits, and its implementation is straightforward using quantum gates.


An Example of Simulating Quantum Evolution with Pauli Decomposition and Trotter-Suzuki Approximation

This example demonstrates the simulation of quantum evolution using Pauli decomposition and the Trotter-Suzuki approximation. Starting with a Hermitian operator representing the system’s Hamiltonian, the Pauli decomposition extracts coefficients to encode the Hamiltonian into quantum circuits.

The Hermitian operator is constructed using a similarity transformation, and the Pauli matrices of the Hamiltonian are derived via Pauli decomposition:

import numpy as np
from logicqubit.synthesis import *

# Define diagonal matrix D and transformation matrix P
D = np.matrix('0.9 0 0 0; 0 2.4 0 0; 0 0 0.7 0; 0 0 0 0.45')
P = np.matrix('1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1')

# Construct the Hamiltonian via similarity transformation
H = P*D*P.I

# Perform Pauli decomposition
dec = PauliDecomposition(H)
h = dec.get_a() # Extract coefficients for Pauli matrices

print("h:", h)
h: [[1.1125, 0.0, 0j, -0.3125],
[0.0, 0.0, 0j, 0.0],
[0j, 0j, 0j, 0j],
[0.5374999999999999, 0.0, 0j, -0.43750000000000006]]

Using classical computation, we evolve the initial state under the Hamiltonian:

import numpy as np
from scipy.linalg import expm
from logicqubit.logic import *

# Define gates
gates = Gates()

ID = gates.ID()
X = gates.X()
Y = gates.Y()
Z = gates.Z()

# Define tensor products of Pauli matrices
II = ID.kron(ID)
IZ = ID.kron(Z)
ZI = Z.kron(ID)
ZZ = Z.kron(Z)

# Time evolution parameter
t = 1.0

# Reconstruct the Hamiltonian using the Pauli decomposition coefficients
H = h[0][0]*II.get() + h[0][3]*IZ.get() + h[3][0]*ZI.get() + h[3][3]*ZZ.get()

# Compute the time evolution operator
U = expm(-1j * H * t)

# Initial state |00>
initial_state = np.array([1, 0, 0, 0])  # |00>

# Compute the final state
final_state = np.dot(U, initial_state)

print("Final state:", final_state)
Final state: [0.62160997-0.78332691j 0.        +0.j         0.        +0.j
 0.        +0.j        ]

Simulate the quantum evolution using the Trotter-Suzuki approximation:

from logicqubit.logic import *

# Time evolution parameters
t = 1.0
n_steps = 10
delta_t = t / n_steps

# Define a single Trotter step
def trotter_step(q1, q2, delta_t): # H = h_00*II + h_03*IZ + h_30*ZI + h_33*ZZ
    # h_00 * II is a global phase (does not affect dynamics)
    
    angle = h[0][3] * delta_t  # h_03*IZ
    q2.RZ(2 * angle)

    angle = h[3][0] * delta_t  # h_30*ZI
    q1.RZ(2 * angle)

    angle = h[3][3] * delta_t  # h_33*ZZ
    q2.CNOT(q1)
    q2.RZ(2 * angle)
    q2.CNOT(q1)

# Initialize the quantum system
logicQuBit  = LogicQuBit(2)
q1 = Qubit()
q2 = Qubit()

# Apply Trotter steps iteratively
for _ in range(n_steps):
    trotter_step(q1, q2, delta_t)

# Retrieve the resulting state
result = logicQuBit.getDictPsi()

# Apply global phase correction (from h_00 * II)
keys = result.keys()
values = map(lambda x: x * exp(-1j*h[0][0]), result.values()) # multiply by global phase
result = dict(zip(keys, values))

quantum_state = np.array([result.get(state, 0) for state in ['00', '01', '10', '11']])

print("Quantum state:", quantum_state)
Quantum state: [0.62160997-0.78332691j 0.        +0.j         0.        +0.j
 0.        +0.j        ]

Finally, we calculate the fidelity to compare the classical and quantum results:

# Fidelity between classical and quantum results
fidelity = np.abs(np.dot(np.conjugate(quantum_state), final_state))**2

print("Fidelity:", fidelity)
Fidelity: 1.0

Check out the Colab Notebook.

Conclusion

This example illustrates the techniques of quantum algorithms for simulating quantum evolution. By leveraging Pauli decomposition, the Hamiltonian is efficiently encoded into a quantum circuit, and the Trotter-Suzuki approximation is employed to simulate time evolution on a quantum system. The comparison of classical and quantum results, validated through fidelity calculations, demonstrates the accuracy and potential of these methods for quantum simulation. This approach highlights the power of quantum computing in solving complex problems in physics and beyond, paving the way for more advanced simulations in quantum systems.

Leave a Comment