Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lindblad Simulation

The Jupyter notebook corresponding to this section can be found here.

In this guide you will learn how to create a simulation of the Lindblad equation, which is given by where is the Lindbladian with Here, is a Hamiltonian of a spin system. For this guide, we choose the Hamiltonian of the Heisenberg spin chain, i.e.,

Imports and Parameters

Throughout this guide, we will make use of the following imports.

import matplotlib.pyplot as plt
import numpy as np
import numpy.random
from matplotlib import animation
from scipy.linalg import toeplitz

from hqs_quantum_solver.spins import (
    VectorSpace, Operator, SpinState, isotropic_interaction)
from hqs_quantum_solver.util import unit_vector
from hqs_quantum_solver.lindblad import Lindbladian
from hqs_quantum_solver.evolution import evolve_krylov, observers

Furthermore, we make use of the parameters as shown below.

n_spins = 6  # The number of spins.
t_end = 10   # The duration of the simulation.
J = 1        # The interaction strength.

Hamiltonian and Lindblad

To construct the Lindbladian, we first have to define the vector space that we are using, then construct the Hamiltonian, and then combine the two to obtain the Lindblad operator. We start with the vector space.

The vector space is defined by creating a VectorSpace object from the spins module. The vector spaces we use in quantum mechanics are defined by the states they represent. Thus, to construct the VectorSpace object, we have to describe the states of the system. First, we give the sites argument to specify the number of spins in the system. Second, we give the total_spin_z argument, which can be used to only include the states with a given total spin polarization. Since we do not want to restrict the total spin, we provide "all" as an argument.

v = VectorSpace(sites=n_spins, total_spin_z="all")

To construct a spin operator in HQS Quantum Solver, we need to pass an expression that describes the operator to the constructor of the Operator class. We use the isotropic_interaction function to obtain the desired expression. This function takes a matrix, where the entry at index determines the interaction strength of site to site and vice versa. By providing a matrix where the first upper off-diagonal is set to a constant value and the remaining entries set to zero, we obtain the desired spin chain model.

coef = J * toeplitz(np.zeros(v.sites), unit_vector(v.sites, 1))
H = Operator(isotropic_interaction(coef), domain=v, dtype=complex)

Next, we need to set up the coefficient matrices , , and . For this example, we choose these matrices as random, positive definite matrices. Once the matrices are constructed, we pass them together with the Hamiltonian to the constructor of the Lindbladian class, giving us the desired Lindblad operator.

rng = numpy.random.default_rng(1234)

def random_positive_definite(low, high, shape):
    r = rng.uniform(low, high, shape)
    return r.T.conj() @ r

gamma_z = random_positive_definite(0, 0.3, (v.sites, v.sites))
gamma_p = random_positive_definite(0, 0.2, (v.sites, v.sites))
gamma_m = random_positive_definite(0, 0.1, (v.sites, v.sites))

L = Lindbladian(H, gamma_z, gamma_p, gamma_m)

Initial State

Before we can simulate the time evolution, we need to define the initial state of the simulation. We choose where . The vector is constructed by calling the fock_state function with a list containing SpinState.UP followed by multiple SpinState.DOWN entries.

state = v.fock_state([SpinState.UP] + [SpinState.DOWN] * (n_spins-1))
rho = state[:,np.newaxis] @ state[:,np.newaxis].T.conj()

Time Evolution

We can finally perform the actual time evolution of the system. The form of the Lindblad equation listed at the beginning is compatible with the regular time-stepping functions provided by Quantum Solver. Here, we want to use the evolve_krylov function. We provide the operator, the initial state, and a list of time points at which to evaluate the state of the system. Note, that internally a step-size is chosen that guarantees the desired accuracy. The time points are only used for the evaluation. Furthermore, by passing observer=observers.state() we instruct the method to record the full state at each time point. Finally, we provide a progress callback that displays the current state of the simulation while the simulation is in progress.

timesteps = np.linspace(0, t_end)

def progress(t):
    print(f"t = {t:7.2f}", end="\r", flush=True)

result = evolve_krylov(
    L, rho, timesteps, observer=observers.state(), progress=progress)

print()

The state of the system for each time point in timesteps is now stored in result.observations as a list. We can use Matplotlib to create an animation.

fig, ax = plt.subplots()
image = ax.imshow(np.abs(result.observations[0]), cmap='magma_r', vmin=0, vmax=1)
plt.colorbar(image)

def update(frame):
    ax.set_title(f'State density at $t = {timesteps[frame]:.2f}$')
    image.set_data(np.abs(result.observations[frame]))
    return [image]

ani = animation.FuncAnimation(fig, update, frames=len(result.observations))
plt.show()

Running the script should produce the following animation.

Complete Code

# Title    : HQS Quantum Solver Linblad Solver
# Filename : lindblad.py

# ===== Imports =====

import matplotlib.pyplot as plt
import numpy as np
import numpy.random
from matplotlib import animation
from scipy.linalg import toeplitz

from hqs_quantum_solver.spins import (
    VectorSpace, Operator, SpinState, isotropic_interaction)
from hqs_quantum_solver.util import unit_vector
from hqs_quantum_solver.lindblad import Lindbladian
from hqs_quantum_solver.evolution import evolve_krylov, observers

# ===== Parameters =====

n_spins = 6  # The number of spins.
t_end = 10   # The duration of the simulation.
J = 1        # The interaction strength.

# ===== Constructing the Lindbladian =====

v = VectorSpace(sites=n_spins, total_spin_z="all")

coef = J * toeplitz(np.zeros(v.sites), unit_vector(v.sites, 1))
H = Operator(isotropic_interaction(coef), domain=v, dtype=complex)

rng = numpy.random.default_rng(1234)

def random_positive_definite(low, high, shape):
    r = rng.uniform(low, high, shape)
    return r.T.conj() @ r

gamma_z = random_positive_definite(0, 0.3, (v.sites, v.sites))
gamma_p = random_positive_definite(0, 0.2, (v.sites, v.sites))
gamma_m = random_positive_definite(0, 0.1, (v.sites, v.sites))

L = Lindbladian(H, gamma_z, gamma_p, gamma_m)

# ===== Initial State =====

state = v.fock_state([SpinState.UP] + [SpinState.DOWN] * (n_spins-1))
rho = state[:,np.newaxis] @ state[:,np.newaxis].T.conj()

# ===== Time Evolution =====

timesteps = np.linspace(0, t_end)

def progress(t):
    print(f"t = {t:7.2f}", end="\r", flush=True)

result = evolve_krylov(
    L, rho, timesteps, observer=observers.state(), progress=progress)

print()

# ===== Visualization =====

fig, ax = plt.subplots()
image = ax.imshow(np.abs(result.observations[0]), cmap='magma_r', vmin=0, vmax=1)
plt.colorbar(image)

def update(frame):
    ax.set_title(f'State density at $t = {timesteps[frame]:.2f}$')
    image.set_data(np.abs(result.observations[frame]))
    return [image]

ani = animation.FuncAnimation(fig, update, frames=len(result.observations))
plt.show()