An Overview of the Eseries Class

Exponential-series representation of time-dependent quantum objects

The eseries object in QuTiP is a representation of an exponential-series expansion of time-dependent quantum objects (a concept borrowed from the quantum optics toolbox).

An exponential series is parameterized by its amplitude coefficients \(c_i\) and rates \(r_i\), so that the series takes the form \(E(t) = \sum_i c_i e^{r_it}\). The coefficients are typically quantum objects (type Qobj: states, operators, etc.), so that the value of the eseries also is a quantum object, and the rates can be either real or complex numbers (describing decay rates and oscillation frequencies, respectively). Note that all amplitude coefficients in an exponential series must be of the same dimensions and composition.

In QuTiP, an exponential series object is constructed by creating an instance of the class qutip.eseries:

In [1]: es1 = eseries(sigmax(), 1j)

where the first argument is the amplitude coefficient (here, the sigma-X operator), and the second argument is the rate. The eseries in this example represents the time-dependent operator \(\sigma_x e^{i t}\).

To add more terms to an qutip.eseries object we simply add objects using the + operator:

In [2]: omega=1.0

In [3]: es2 = (eseries(0.5 * sigmax(), 1j * omega) +
   ...:       eseries(0.5 * sigmax(), -1j * omega))
   ...: 

The qutip.eseries in this example represents the operator \(0.5 \sigma_x e^{i\omega t} + 0.5 \sigma_x e^{-i\omega t}\), which is the exponential series representation of \(\sigma_x \cos(\omega t)\). Alternatively, we can also specify a list of amplitudes and rates when the qutip.eseries is created:

In [4]: es2 = eseries([0.5 * sigmax(), 0.5 * sigmax()], [1j * omega, -1j * omega])

We can inspect the structure of an qutip.eseries object by printing it to the standard output console:

In [5]: es2
Out[5]: 
ESERIES object: 2 terms
Hilbert space dimensions: [[2], [2]]
Exponent #0 = -1j
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0.   0.5]
 [ 0.5  0. ]]
Exponent #1 = 1j
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0.   0.5]
 [ 0.5  0. ]]

and we can evaluate it at time t by using the qutip.eseries.esval function:

In [6]: esval(es2, 0.0)     # equivalent to es2.value(0.0)
Out[6]: 
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0.  1.]
 [ 1.  0.]]

or for a list of times [0.0, 1.0 * pi, 2.0 * pi]:

In [7]: times = [0.0, 1.0 * pi, 2.0 * pi]

In [8]: esval(es2, times)    # equivalent to es2.value(times)
Out[8]: 
array([ Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0.  1.]
 [ 1.  0.]],
       Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0. -1.]
 [-1.  0.]],
       Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0.  1.]
 [ 1.  0.]]], dtype=object)

To calculate the expectation value of an time-dependent operator represented by an qutip.eseries, we use the qutip.expect function. For example, consider the operator \(\sigma_x \cos(\omega t) + \sigma_z\sin(\omega t)\), and say we would like to know the expectation value of this operator for a spin in its excited state (rho = fock_dm(2,1) produce this state):

In [9]: es3 = (eseries([0.5*sigmaz(), 0.5*sigmaz()], [1j, -1j]) +
   ...:       eseries([-0.5j*sigmax(), 0.5j*sigmax()], [1j, -1j]))
   ...: 

In [10]: rho = fock_dm(2, 1)

In [11]: es3_expect = expect(rho, es3)

In [12]: es3_expect
Out[12]: 
ESERIES object: 2 terms
Hilbert space dimensions: [[1, 1]]
Exponent #0 = (-0-1j)
(-0.5+0j)
Exponent #1 = 1j
(-0.5+0j)

In [13]: es3_expect.value([0.0, pi/2])
Out[13]: array([ -1.00000000e+00,  -6.12323400e-17])

Note the expectation value of the qutip.eseries object, expect(rho, es3), itself is an qutip.eseries, but with amplitude coefficients that are C-numbers instead of quantum operators. To evaluate the C-number qutip.eseries at the times times we use esval(es3_expect, times), or, equivalently, es3_expect.value(times).

Applications of exponential series

The exponential series formalism can be useful for the time-evolution of quantum systems. One approach to calculating the time evolution of a quantum system is to diagonalize its Hamiltonian (or Liouvillian, for dissipative systems) and to express the propagator (e.g., \(\exp(-iHt) \rho \exp(iHt)\)) as an exponential series.

The QuTiP function qutip.essolve.ode2es and qutip.essolve use this method to evolve quantum systems in time. The exponential series approach is particularly suitable for cases when the same system is to be evolved for many different initial states, since the diagonalization only needs to be performed once (as opposed to e.g. the ode solver that would need to be ran independently for each initial state).

As an example, consider a spin-1/2 with a Hamiltonian pointing in the \(\sigma_z\) direction, and that is subject to noise causing relaxation. For a spin originally is in the up state, we can create an qutip.eseries object describing its dynamics by using the qutip.es2ode function:

In [14]: psi0 = basis(2,1)

In [15]: H = sigmaz()

In [16]: L = liouvillian(H, [sqrt(1.0) * destroy(2)])

In [17]: es = ode2es(L, psi0)

The qutip.essolve.ode2es function diagonalizes the Liouvillian \(L\) and creates an exponential series with the correct eigenfrequencies and amplitudes for the initial state \(\psi_0\) (psi0).

We can examine the resulting qutip.eseries object by printing a text representation:

In [18]: es
Out[18]: 
ESERIES object: 2 terms
Hilbert space dimensions: [[2], [2]]
Exponent #0 = (-1+0j)
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[-1.  0.]
 [ 0.  1.]]
Exponent #1 = 0j
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 1.  0.]
 [ 0.  0.]]

or by evaluating it and arbitrary points in time (here at 0.0 and 1.0):

In [19]: es.value([0.0, 1.0])
Out[19]: 
array([ Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0.  0.]
 [ 0.  1.]],
       Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 0.63212056  0.        ]
 [ 0.          0.36787944]]], dtype=object)

and the expectation value of the exponential series can be calculated using the qutip.expect function:

In [20]: es_expect = expect(sigmaz(), es)

The result es_expect is now an exponential series with c-numbers as amplitudes, which easily can be evaluated at arbitrary times:

In [21]: es_expect.value([0.0, 1.0, 2.0, 3.0])
Out[21]: array([-1.        ,  0.26424112,  0.72932943,  0.90042586])
In [22]: times = linspace(0.0, 10.0, 100)

In [23]: sz_expect = es_expect.value(times)

In [24]: from pylab import *

In [25]: plot(times, sz_expect, lw=2);

In [26]: xlabel("Time", fontsize=16)
   ....: ylabel("Expectation value of sigma-z", fontsize=16);
   ....: 

In [28]: title("The expectation value of the $\sigma_{z}$ operator", fontsize=16);
../images/eseries_example.png