Skip to content

Hamiltonian simulation

Spin model

Wavefunction evolving with time

For a quantum system with Hamiltonian , its wavefunction is evolving as , where we have assume that . If we further have , then we have , where we have used the spectral decomposition of . This usually needs us to obtain a diagonalization of . Practically, a more efficient way [1] to simulate the time evolution is used if we have an efficient way to implement operators directly. Here we recall its major ideal.

For any Hamiltonian in the form of , we have , where for some large and therefore . Then we have the following approximation:

So the basic element for simulating the whole Hamiltonian is to simulate each term .

Basic gates in spin model

In spin model, the Hamiltonian can be represented as where at each spin site . Correspondingly the basic gate we need to implement in the circuit is in the form of for the pair of any two sites.

gate implementation

We first consider the term on one site. The corresponding implementation is given by

Rz(2a)

Similarly, we can directly implement and by Rx and Ry. These terms represent the effect of the magnetic field in each direction.

gate implementation

This is a two-spin interaction with action on one site and on the other site . We first consider the term . This term represents an Ising type of interaction between two spins. Its implementation is given by:

iaZZ

For this gate, we can calculate the output state of each input state in and see that it is equivalent to . Based on this gate, we can easily implement other types. For implementing , for instance, we can use the transformation , where acting on the first qubit (spin). Its implementation is shown by

iaXZ

By the same spirit, we can implement any terms.

In demoLib, we can use 4 parameters to represent all kinds of spin interactions (including single-spin interaction and any two-spin interaction). We will see that later in an example.

Example: Single spin in the magnetic field

Consider a system with Hamiltonian , for input state , the evolution of the wavefunction is give by

We then simulate this process by quantum circuit. Following is the content in the isQ file spin_singleX.isq.

//spin_singleX.isq
import std;
import physics.spin;

procedure main(int N[], double t[]) {
    qbit Q[1];

    // allocate resources for variables 
    double F[100];
    bool B[100];
    int I[100];

    Initialization(F,B,I); // initialized calculation

    // set interactions       type     para1     para2     value            
    appendInteraction(F,B,I,   0,       0,        0 ,       1.0  );

    TimeEvolutionDeploy (F,B,I, Q,  t[0] ,  N[0] ); 
    M(Q);
}
In this example, there is only one interaction, so we only call appendInteraction once. The list of all possible interactions is given by:
    Interaction types 

    0 : X           i = para1
    1 : Y           i = para1
    2 : Z           i = para1
    3 : XX          i = para1, j = para2
    4 : XY          i = para1, j = para2
    5 : XZ          i = para1, j = para2
    6 : YY          i = para1, j = para2
    7 : YZ          i = para1, j = para2
    8 : ZZ          i = para1, j = para2
As can be seen in the code, type=0 represents the magnetic field in the x-direction. It needs only one position parameter i, so para2 can be anything (here we set 0). We can compile this file by isqc compile spin_singleX.isq, after which we should obtain a .so file spin_singleX.so. The .so file can be sent to the simulator. One should notice that this file needs two additional parameters and .

Here we use a local simulator to test the result. isQ contains a default quantum simulator. More information about it can be found via isqc simulate --help. Here we include some Python scripts to simplify the code. The following Python code runs the circuit many times to collect the data.

import demoLib
import numpy as np
import matplotlib.pyplot as plt

# load local simulator
sim = demoLib.simulator("spin_singleX.so")

N = 1 # N = 1 in Suzuki decomposition
T = np.arange(0,3.0,0.1) # range of t for plot
Y0 = []
Y1 = []
for t in T:
    res = sim.runCircuit(F=[t],I=[N],shots=5000)
    p0 = sim.calculate( res=res, func = lambda l: int(l[0] == 0) )
    p1 = sim.calculate( res=res, func = lambda l: int(l[0] == 1) )
    Y0.append(p0)
    Y1.append(p1)

plt.plot(T,Y0,'-o',label='Prob(0)')
plt.plot(T,Y1,'-o',label='Prob(1)')
plt.xlabel("t")
plt.ylabel("Probability")
plt.legend()
The first line import demoLib is used to load the auxiliary package. The simulator needs an input of the so file. sim.runCircuit will run the circuit to obtain the measurement. The function sim.calculate is used to calculate any observable
, where . The meaning of arguments passed in sim.calculate may be less obvious. One needs to input a definition of a function:
def func(list l) -> float:
    ...
    return 
l contains each bit value 0 or 1. The result is shown as follows:

fig1

Example: Spin chain model

We now study a less trivial model whose Hamiltonian is given by

The first term represents a uniform magnetic field on the system. The second term represents complex XYZ interactions. This model has very important applications in condensed matter physics.

In this demo, we set up a 3-site system. Parameters are set as , , , and . Here we do not consider the connection between the first and third sites. One interested in periodic boundary conditions can set additional interaction. In addition, in the beginning, we set the first spin in the state while others are in . The isQ code file to translate this circuit is spin_chain.isq whose content is shown by

// spin_chain.isq
import std;
import physics.spin;

procedure main( int N[], double t[] ) {
    qbit Q[3]; //spin chain contains 3 sites

    // allocate resource for variables 
    double F[100];
    bool B[100];
    int I[100];

    // initialized calculation
    Initialization(F,B,I);

    // first qubit in |1>, others are |0>
    X(Q[0]);

    // set interactions       type     para1     para2     value              
    appendInteraction(F,B,I,   2,       0,        0 ,      -1.0  ); //hz0 
    appendInteraction(F,B,I,   2,       1,        0 ,      -1.0  ); //hz1 
    appendInteraction(F,B,I,   2,       2,        0 ,      -1.0  ); //hz2

    appendInteraction(F,B,I,   3,       0,        1 ,      -0.2  ); //jx01
    appendInteraction(F,B,I,   3,       1,        2 ,      -0.2  ); //jx12

    appendInteraction(F,B,I,   6,       0,        1 ,      -0.3  ); //jy01
    appendInteraction(F,B,I,   6,       1,        2 ,      -0.3  ); //jy12 

    appendInteraction(F,B,I,   8,       0,        1 ,      -0.1  ); //jz01
    appendInteraction(F,B,I,   8,       1,        2 ,      -0.1  ); //jz12

    TimeEvolutionDeploy (F,B,I, Q,  t[0] ,  N[0] ); 
    M (Q);
}
Correspondingly, we calculate at the end. The corresponding Python script is
import demoLib
import numpy as np
import matplotlib.pyplot as plt

# load local simulator
sim = demoLib.simulator("spin_chain.so")


N = 20 # N = 1 in Suzuki decomposition
T = np.arange(0,10.0,0.1) # range of t for plot
Z0,Z1,Z2 = [],[],[]

for t in T:
    res = sim.runCircuit(F=[t],I=[N],shots=5000)
    z0 = sim.calculate(res=res, func = lambda l: 1-l[0]*2)
    z1 = sim.calculate(res=res, func = lambda l: 1-l[1]*2)
    z2 = sim.calculate(res=res, func = lambda l: 1-l[2]*2)
    Z0.append(z0)
    Z1.append(z1)
    Z2.append(z2)

plt.plot(T,Z0,'-o',label='$\\langle \sigma_0 \\rangle$')
plt.plot(T,Z1,'-o',label='$\\langle \sigma_1 \\rangle$')
plt.plot(T,Z2,'-o',label='$\\langle \sigma_2 \\rangle$')

plt.legend()

plt.xlabel(r'$t$',size=15)
plt.ylabel(r'$\langle\sigma_z\rangle$',size=15)
plt.title(f'Dynamics of a Heisenberg spin chain (N={N})');
The result is shown as follows

As a comparison, an exact calculation by QuTip is shown in the figure. The precision can further increase by increasing . To be more clear, we check more carefully by the following figure.

As shown in the figure, for case, in a small time regime the result of the calculation matches the exact result very well. However, at large t regime, to obtain a better result we need a larger .

Any other spin mode

One can easily extend the code to make a circuit to simulate an arbitrary spin model with more complicated interaction types. For any (2-local) spin system, we can represent it as . For each , we can use at most 4 parameters (Type , , , ) to specify the term.

Fermion-Hubbard model

The Hubbard model is one of the most important systems in condensed matter physics. Studying this model will reveal much exotic physics in strongly correlated electron systems such as superconductivity and quantum Hall effect. In this tutorial, we study the time evolution of a wave function in the system with such a Hamiltonian.

Hamiltonian

We start from a most general Hamiltonian given by where and
For a system with spins, the indices and should also be implicit. represents the single particle terms, such as onsite energy and hopping terms. contains many-body terms. For example, if we set and , we obtain the Coulomb interaction In the most general case, can consider other interactions, such as Hund's interaction, and is written as In the standard Hubbard, we consider and . Nevertheless, the following discussion is also suited for other two-particle interactions.

Our task is to find an encoding method to transform this Hamiltonian into one with Pauli tensors only. By Jordan–Wigner transformation , the diagonal term of is given by the hopping term (off-diagonal term) is given by


where , , and and , and . For real case, we have . Finally the Coulomb interaction is given by

Example

In the following, we will give an example step by step showing coding with isQ. This example needs one to download the package demoLib first. Let us start!

Let study a 4-site system with Hamiltonian given by

example

where we have defined means . Therefore, the hopping terms are acting like a ring. Since there is the spin degree of freedom, we need to use 2 qubits to represent a single site in the Hubbard model. Therefore, we need to define 8 qubits by

qbit Q[8]; 
We set the parameters as , , . The tedious progress for deploying is packaged inside demoLib. For instance, setting the hopping term between site 0 and site 1 is done by one-line code:
appendHubbardInteraction_Hopping(F, B, I, 0, 1, t); 
where F, B, and I are source spaces, and the user need not care about it. The 4-th and 5-th variables represent the hopping sites. The last variable pass in the value of t. As we can see, by our lib it is very easy to set a term in Hamiltonian. We set initial state as full filling the first site, as shown in Figure. The corresponding code is
X(Q[0]);
X(Q[1]);
Then we would like to study the wavefunction at any time . A full code that is runnable is given by
// hubbard_main.isq
import std;
import physics.hubbard;

procedure main(int intPara[] , double douPara[]) {
    int N;
    double T;
    double t, U, m; // parameters in Hubbard model
    qbit Q[8]; // use 8 qubit to encode 4 sites in Hubbard model

    // allocate resource for storing Hamiltonian
    double F[100]; 
    bool B[1];
    int I[200];
    Initialization(F,B,I); // start calculation

    t = -1.0; // hopping term
    U =  2.0; // Coulomb U term
    m = -1.0; // onsite energy

    N = intPara[0]; // time slides  
    T = douPara[0]; // evolution time

    // set there is a spin up particle on each site as the initial state
    X(Q[0]);
    X(Q[1]);

    // set interactions                 
    appendHubbardInteraction_Hopping(F,B,I,   0, 1,  t ); //hopping between (0,1)
    appendHubbardInteraction_Hopping(F,B,I,   1, 2,  t ); //hopping between (1,2)
    appendHubbardInteraction_Hopping(F,B,I,   2, 3,  t ); //hopping between (2,3)
    appendHubbardInteraction_Hopping(F,B,I,   0, 3,  t ); //hopping between (0,3)

    appendHubbardInteraction_OnSite(F,B,I, 0, m); // onsite energy on site 0
    appendHubbardInteraction_OnSite(F,B,I, 1, m); // onsite energy on site 1
    appendHubbardInteraction_OnSite(F,B,I, 2, m); // onsite energy on site 2
    appendHubbardInteraction_OnSite(F,B,I, 3, m); // onsite energy on site 3 

    appendHubbardInteraction_CoulombU(F,B,I,  0, U); // U on site 0
    appendHubbardInteraction_CoulombU(F,B,I,  1, U); // U on site 1
    appendHubbardInteraction_CoulombU(F,B,I,  2, U); // U on site 2
    appendHubbardInteraction_CoulombU(F,B,I,  3, U); // U on site 3

    TimeEvolutionDeploy(F,B,I,Q,T,N); // deploy gates on circuit

    M(Q);
}
This code needs 2 input parameters and . is the number of time slides and is the evolving time. We can compile this code by:
isqc compile hubbard_main.isq
And we can run it with the built-in simulator by (set T=0.5 and N=2)
isqc simulate hubbard_main.so -i 2 -d 0.5 --shots 500
To visualize the result, we calculate the particle number of spin up on each site at any time. To plot the result, we use a more efficient simulator by the following Python script
import demoLib
import numpy as np
import matplotlib.pyplot as plt
import os 

# load local simulator
sim = demoLib.simulator("hubbard_main.so")

N = 50 # Suzuki decomposition
T = np.arange(0,5.0,0.1) # range of t for plot
Y0,Y1,Y2,Y3 = [],[],[],[],

for t in T:
    res = sim.runCircuit(F=[t],I=[N],shots=5000)
    p0 = sim.calculate( res=res, func = lambda l: int(l[0] == 1) );Y0.append(p0)
    p1 = sim.calculate( res=res, func = lambda l: int(l[2] == 1) );Y1.append(p1)
    p2 = sim.calculate( res=res, func = lambda l: int(l[4] == 1) );Y2.append(p2)
    p3 = sim.calculate( res=res, func = lambda l: int(l[6] == 1) );Y3.append(p3)


plt.plot(T,Y0,'-o',label='$N_{0\\uparrow}$')
plt.plot(T,Y1,'o' ,label='$N_{1\\uparrow}$')
plt.plot(T,Y2,'-o',label='$N_{2\\uparrow}$')
plt.plot(T,Y3,'--',label='$N_{3\\uparrow}$')
plt.xlabel("t")
plt.ylabel("particle number")
plt.legend()
plt.title(f'simulation with N = {N}')
The results with and are shown below. We also calculate the result by exact method (classical simulation). As we can see, for the case, the result at a small time region agrees with the exact results very well. At the large time region, the time slide becomes large, therefore the error increase. As one can see from the geometry of the system, sites 1 and 3 are symmetric, therefore we know that . To obtain a better result, we can set as shown in the figure.

example

Reference

  1. S. Lloyd. “Universal Quantum Simulators.” Science 273 (5278), 1996:1073–78.
  2. J. Hubbard. "Electron Correlations in Narrow Energy Bands." Proc. R. Soc.Lond. A 276 (1365), 1963: 238–57.
  3. P. Jordan and E. Wigner. "Ber Das Paulische Quivalenzverbot." Z. Physik 47 (9-10), 1928: 631–51.