[1]:
import quanguru as qg

# QuanGuru uses sparse matrices by default,
# but sparse matrices are not easily readable when we print them
# so, in below examples, we use .A to print them as arrays

3 - Coupling quantum systems#

In this tutorial, we show how to create a coupling between the sub-system of a composite quantum system.

Two qubits exchange interaction#

A simple example is the exchange interaction between two qubits

H = \frac{1}{2}f_{z,1}\sigma_{z,1} + \frac{1}{2}f_{z,2}\sigma_{z,2} + f_{pm}\sigma_{+,1}\sigma_{-,2} + f_{mp}\sigma_{-,1}\sigma_{+,2}

where the first two terms (\frac{1}{2}f_{z,1}\sigma_{z,1} + \frac{1}{2}f_{z,2}\sigma_{z,2}) are the free evolution terms of each qubit, and the other two terms (f_{pm}\sigma_{+,1}\sigma_{-,2} + f_{mp}\sigma_{-,1}\sigma_{+,2}) are the exchange coupling between them.

The Qubit objects already implement their free evolution terms, and we will create each coupling term below.

[2]:
qub1 = qg.Qubit(frequency=1)
qub2 = qg.Qubit(frequency=1)

twoQub = qub1 + qub2

In order to create a coupling, we call the createSysCoupling method on our composite quantum system twoQub. createSysCoupling method cover various different usages, but here we will demonstrate its basic usage, where we pass 3 essential information regarding the coupling:

  • A list of systems to be coupled with (in our case [qub1, qub2])

  • A list of coupling operators (in our case [qg.sigmap, qg.sigmam] and [qg.sigmam, qg.sigmap])

  • coupling frequency

createSysCoupling method returns a coupling object that can later be used to modify coupling parameters, such as frequency.

Note that the order of systems in the given list does not need to match the Hilbert space structure, but you need to make sure that the list of coupling operators are consistent with the systems. For example, below two couplings create f_{pm}\sigma_{+,1}\sigma_{-,2} and f_{mp}\sigma_{-,1}\sigma_{+,2}, respectively, and we can understand this from the order of operators.

[3]:
couplingPM12 = twoQub.createTerm(operator=[qg.sigmap, qg.sigmam],
                                 frequency=1,
                                 qSystem=[qub1, qub2])
couplingMP12 = twoQub.createTerm(operator=[qg.sigmam, qg.sigmap],
                                 frequency=1,
                                 qSystem=[qub1, qub2])

Let’s print the total Hamiltonian (i.e. incl. coupling) of the coupled twoQub system and verify.

[4]:
twoQub.totalHamiltonian.A
[4]:
array([[ 1.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.],
       [ 0.,  1.,  0.,  0.],
       [ 0.,  0.,  0., -1.]])

Three qubits nearest-neighbor exchange interaction#

We can extend the above Hamiltonian to 3 qubits as

H = \frac{1}{2}f_{z,1}\sigma_{z,1} + \frac{1}{2}f_{z,2}\sigma_{z,2} + \frac{1}{2}f_{z,3}\sigma_{z,3} + f_{pm,1,2}\sigma_{+,1}\sigma_{-,2} + f_{mp,1,2}\sigma_{-,1}\sigma_{+,2} + f_{pm,2,3}\sigma_{+,2}\sigma_{-,3} + f_{mp,2,3}\sigma_{-,2}\sigma_{+,3}

which in a more compact form and for n sub-systems written as

H = \frac{1}{2}\sum_{i=1}^{N}f_{z,i}\sigma_{z,i} + \sum_{i=1}^{N-1}f_{pm,i,i+1}\sigma_{+,i}\sigma_{-,i+1} + \sum_{i=1}^{N-1}f_{mp,i,i+1}\sigma_{-,i}\sigma_{+,i+1}

In order to extend our system, we first need to add a new qubits to twoQub system then create a coupling between this new qubit and qub2.

[5]:
qub3 = qg.Qubit(frequency=1)

threeQub = twoQub + qub3

couplingPM23 = threeQub.createTerm(operator=[qg.sigmap, qg.sigmam],
                                   frequency=1,
                                   qSystem=[qub2, qub3])
couplingMP23 = threeQub.createTerm(operator=[qg.sigmam, qg.sigmap],
                                   frequency=1,
                                   qSystem=[qub2, qub3])

Let’s print the total Hamiltonian (i.e. incl. coupling) of the coupled twoQub system and verify.

[6]:
threeQub.totalHamiltonian.A
[6]:
array([[ 1.5,  0. ,  0. ,  0. ,  0. ,  0. ,  0. ,  0. ],
       [ 0. ,  0.5,  1. ,  0. ,  0. ,  0. ,  0. ,  0. ],
       [ 0. ,  1. ,  0.5,  0. ,  1. ,  0. ,  0. ,  0. ],
       [ 0. ,  0. ,  0. , -0.5,  0. ,  1. ,  0. ,  0. ],
       [ 0. ,  0. ,  1. ,  0. ,  0.5,  0. ,  0. ,  0. ],
       [ 0. ,  0. ,  0. ,  1. ,  0. , -0.5,  1. ,  0. ],
       [ 0. ,  0. ,  0. ,  0. ,  0. ,  1. , -0.5,  0. ],
       [ 0. ,  0. ,  0. ,  0. ,  0. ,  0. ,  0. , -1.5]])

Many-body coupling#

In above cases, the coupling terms contain operators only for two of the systems. In more general cases, we might need terms with more systems. As an example consider the below arbitrary/artificial coupling term

f_{fb}\sigma_{x,1}\sigma_{y,2}\sigma_{z,3}

QuanGuru supports such many body terms, and we can create the above term as

[7]:
couplingXYZ = threeQub.createTerm(operator=[qg.sigmax, qg.sigmay, qg.sigmaz],
                                  frequency=1,
                                  qSystem=[qub1, qub2, qub3])

# we can also print the Hamiltonian term corresponding to a coupling as
print(couplingXYZ.totalHamiltonian.A)
[[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.-1.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+1.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+1.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.-1.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.-1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.-1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
[8]:
print(qg.tensorProd(qg.sigmax(), qg.sigmay(), qg.sigmaz()).A)
[[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.-1.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+1.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+1.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.-1.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.-1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.-1.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
[9]:
threeQub.totalHamiltonian.A
[9]:
array([[ 1.5+0.j,  0. +0.j,  0. +0.j,  0. +0.j,  0. +0.j,  0. +0.j,
         0. -1.j,  0. +0.j],
       [ 0. +0.j,  0.5+0.j,  1. +0.j,  0. +0.j,  0. +0.j,  0. +0.j,
         0. +0.j,  0. +1.j],
       [ 0. +0.j,  1. +0.j,  0.5+0.j,  0. +0.j,  1. +1.j,  0. +0.j,
         0. +0.j,  0. +0.j],
       [ 0. +0.j,  0. +0.j,  0. +0.j, -0.5+0.j,  0. +0.j,  1. -1.j,
         0. +0.j,  0. +0.j],
       [ 0. +0.j,  0. +0.j,  1. -1.j,  0. +0.j,  0.5+0.j,  0. +0.j,
         0. +0.j,  0. +0.j],
       [ 0. +0.j,  0. +0.j,  0. +0.j,  1. +1.j,  0. +0.j, -0.5+0.j,
         1. +0.j,  0. +0.j],
       [ 0. +1.j,  0. +0.j,  0. +0.j,  0. +0.j,  0. +0.j,  1. +0.j,
        -0.5+0.j,  0. +0.j],
       [ 0. +0.j,  0. -1.j,  0. +0.j,  0. +0.j,  0. +0.j,  0. +0.j,
         0. +0.j, -1.5+0.j]])