Anda di halaman 1dari 23

OpenKet Brief Manual

V ctor S C Canela

Contents
1 Introduction 2 Kets and Bras 2.1 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Basic Algebra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Operators 3.1 Properties . . . . . . . . . 3.2 Creation / Annihilation . 3.3 Examples . . . . . . . . . 3.3.1 Eigenvalue Syntaxis 3.3.2 Quantum Gates . . 3 4 4 5 6 6 6 8 8 9

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

4 Built-in Functions 10 4.1 Hermitian Conjugate: Adj . . . . . . . . . . . . . . . . . . . . . . . 10 4.2 Trace and Partial Trace: Trace, TraceOut . . . . . . . . . . . . . . 11 4.3 Matrix Representation: Qmatrix . . . . . . . . . . . . . . . . . . . . 12 5 Time Evolution Examples 5.1 The Problem . . . . . . . . . . . 5.2 The Translator QeqN . . . . . . . 5.3 Two Level Semiclassical Atoms . 5.4 Two Level Atom Inside a Cavity . 5.4.1 Observables and SubsSol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 13 15 19 21

Introduction

This is an introductory manual to the Python library OpenKet. This library is a tool for manipulating objects from quantum mechanics, such as vectors in the form of kets, operators, etc. It is free software under GNU-GPL v3 license, and uses other free software libraries, such as Sympy, Numpy and Scipy. The project started at the end of 2009 with Vicente Rodr guez under the super1 vision of Pablo Barberis-Blostein , as part of the requirements for his graduation. I started working with Pablo one year later, and continued programming OpenKet. We want OpenKet to be as transparent as possible. This means making the input process close to writing it on paper. Diracs notation is used to represent vectors as kets (having the same visual representation |x ) for example. The basic structure of the library is the denition of object classes with specic rules regarding the basic algebra they should follow. Then there are functions, intended for a specic purpose, for example generate time evolution equations from a Hamiltonian. OpenKet can be downloaded from http://code.google.com/p/openket/. In order to run OpenKet it is needed: Sympy, Pylab and Scipy2 ). OpenKet is in its early stages and has a lot of bugs. Consequently is continuously improving. Before typing the examples presented in the following sections, it is necessary to import OpenKet3 >>> from openket import *

1 2

at IIMAS, UNAM Working in iPython with Pylab is the ideal environment for OpenKet 3 if working in iPython type run -i openket.py

2
2.1

Kets and Bras


Properties

In quantum mechanics, states are represented as vectors of a Hilbert space. Using Diracs notation, these vectors are represented as |x and are called kets. OpenKet understands kets as an object (class) with specic properties or methods. We can dene a ket as follows, >>> x = var("x") >>> v = Ket(x); v |x> In this example x and v are two dierent objects. x is a Sympy variable and v is the object Ket. The argument given for this object is the vector tag. This tag must be either a number class or a Sympy variable, as in the example. There is another argument (optional) which further species the ket. This is the operator tag. The ket is then the eigenvector of the operator (whose name is operator tag), with eigenvalue equal to the vector tag. >>> u = Ket(1,"Op1") >>> w = Ket(1, "Op2") Although u and w have the same vector tag, they are not treated as the same object. For this kind of tags, strings as well as Sympy variables may be used. The object Ket has 2 methods: .op and .eig used to extract all the information from it. While .eig returns the vector tag, .op returns the operator tag as follows, >>> u.eig 1 >>> u.op Op1 Vectors corresponding to the dual space are represented by y | and are called bras. There is also an object Bra in OpenKet, with exactly the same characteristics as de Ket object,

>>> y = var("y") >>> z = Bra(y); z; z.eig; z.op <y| y default Note that when no operator tag is specied .op returns default.

2.2

Basic Algebra

Basic operations may be performed on the Kets and Bras objects, such as: >>> w = Ket(0); >>> (2.1 + 2)*w 4.1|0> + (4.3 + >>> y - 1.7*y + - 0.7<0| + (0.5 x = Ket(1); y = Bra(0); z = Bra(1) + (4.3 + I)*x I)|1> 0.5*z + I*z + I)<1|

Where the I i = 1. Multiplication is also supported for example, if we multiply a bra by a ket we get a number, >>> y*w 1 OpenKet assumes w and y are normalized. However, if we were to dene state vectors with norm not equal to unity, the results would be dierent, >>> psi = 2.1*Ket(0) + I*Ket(1); phi = I*Bra(0) - 4.5*Bra(1) >>> phi*psi -2.4*I Multiplying a ket by a bra results in an outer product, >>> w*y; psi*phi |0><0| 2.1*I|0><0| - 9.45|0><1| - |1><0| - 4.5*I|1><1|

Tensor products can be simulated having two dierent operator tags. For example, >>> f = Ket(0, "Op1"); g = Ket(0, "Op2"); h = Ket(1, "Op3") >>> l = Bra(1, "Op1"); m = Bra(1, "Op2"); n = Bra(1, "Op3") >>> f*g*h |0 Op1>|0 Op2>|1 Op3> >>> l*m*n <1 Op3|<1 Op2|<1 Op1|

3
3.1

Operators
Properties

An operator in OpenKet is an object (class) dened as follows, >>> A = Operator("A"); A A The symbol on the left of the equality does not need to be the same as the argument of Operator, this is just how you are calling it, whereas the argument is how OpenKet tags it. This can be made clear with the only method of Operator, .op. This has the same eect as with kets and bras, it returns the tag of the operator for example, >>> name = Operator("up"); name.op up It is convenient to have the same tags if no confusion is produced. However the option is always available to name objects the way we want.

3.2

Creation / Annihilation

In many physical systems it is convenient to make a canonical transformation from the position - momentum operators to the creation - annihilation operators. The 6

obvious example is the quantum harmonic oscillator. In terms of these particular operators, the Hamiltonian may be written, 1 =h H a a + 2 =a The number operator is dened as N a so the above expression becomes, =h +1 H N 2 are called Fock states and therefore satisfy the equation, The eigenvectors of N a a |n = n|n Where n is the energy level. Individually, the operators a and a operate on |n as follows, a |n = n + 1|n + 1 a |n = n|n 1

OpenKet has two separate object classes for these operators. They are the CreationOperator and the AnnihilationOperator. Both have 2 arguments (optional). The rst is an operator tag type; necessary when having tensor products of vectors with tags that could be confused. The second is a maximum value of the ket, i.e. a number such that if the ket has that eigenvalue, applying a creation operator on it results in zero. This all looks like this, >>> a = AnnihilationOperator(); aa = CreationOperator(); n=var("n") >>> s = Ket(n); t = Bra(n) >>> a*s; aa*s; t*a; t*aa n**(1/2)|-1 + n> (1 + n)**(1/2)|1 + n> (1 + n)**(1/2)<1 + n| n**(1/2)<-1 + n| More complex,

>>> a1 = AnnihilationOperator("mode1") >>> aa1 = CreationOperator("mode1", 4) # maximum value = 4 >>> a2 = AnnihilationOperator("mode2") >>> aa2 = CreationOperator("mode2", 9) # maximum value = 9 >>> p = Ket(2, "mode1")*Ket(3, "mode2"); p |2 mode1>|3 mode2> # bipartite system >>> a1*p; a2*p; a1*a2*p 2**(1/2)|1 mode1>|3 mode2> 3**(1/2)|2 mode1>|2 mode2> 6**(1/2)|1 mode1>|2 mode2> >>> aa1*a1*p; aa2*a2*p 2|2 mode1>|3 mode2> # number operator acting on mode1 3|2 mode1>|3 mode2> # number operator acting on mode2 It is possible to apply the operators as many times as we like. For example (maintaining names), >>> (aa**4)*s (1 + n)**(1/2)*(2 + n)**(1/2)*(3 + n)**(1/2)*(4 + n)**(1/2)|4 + n> >>> (aa1**2)*p; (aa1**3)*p 2*3**(1/2)|4 mode1>|3 mode2> 0 The last zero being a result of having surpassed the eigenvalue 4 (maximum value) for the photon1 system.

3.3
3.3.1

Examples
Eigenvalue Syntaxis

The eigenvalue equation, |a = a|a A Can be computed using OpenKet. It is necessary to match the names of the operator tags of both the Ket and the Operator, >>> a = var("a"); A = Operator("A"); u = Ket(a, "A") >>> A*u a|a A>

3.3.2

Quantum Gates

In quantum computing the basic unit of information is called a qubit, and is represented as a linear combination of two vectors: {|0 , |1 }. (computational basis). Manipulation of qubits is possible through quantum gates, mathematically described as operators. Take for example the Pauli operators. They can be written in terms of exterior products using the computational basis as follows, 0 = |0 0| + |1 1| x = |0 1| + |1 0| y = i|0 1| + i|1 0| z = |0 0| |1 1| It is possible to write the above in OpenKet 4 , having the advantage that they look visually the same, >>> sigma0 = Ket(0)*Bra(0) + Ket(1)*Bra(1); sigma0 |0><0| + |1><1| >>> sigmaX = Ket(0)*Bra(1) + Ket(1)*Bra(0); sigmaX |0><1| + |1><0| >>> sigmaY = -I*Ket(0)*Bra(1) + I*Ket(1)*Bra(0); sigmaY - I|0><1| + I|1><0| >>> sigmaZ = Ket(0)*Bra(0) - Ket(1)*Bra(1); sigmaZ |0><0| - |1><1| They act on vectors, >>> v = 3*I*Ket(0) + 1.23*Ket(1); v 3*I|0> + 1.23|1> >>> sigmaX*v 1.23|0> + 3*I|1> >>> sigmaY*v
This is not necessary (though it helps a lot to explain how to build operators). The Pauli operators are dened inside OpenKet, as X(tag ), Y(tag ), Z(tag ), (x , y , z , respectively). Where the tag is an optional argument used to specify the operator tag, if handling more than one qubit.
4

- 1.23*I|0> - 3|1> >>> sigmaZ*v 3*I|0> - 1.23|1> We can prove that [x , y ] = 2iz , >>> left = sigmaX*sigmaY; left I|0><0| - I|1><1 >>> right = sigmaY*sigmaX; right - I|0><0| + I|1><1| >>> left - right 2*I|0><0| - 2*I|1><1| And virtually any gate that we can represent as a sum of exterior products can be written fairly easy using OpenKet.

Built-in Functions

This functions are subroutines within OpenKet, so once you run the library all of them will be in your workspace.

4.1

Hermitian Conjugate: Adj

Gives the h.c. of the argument you input, be it a number (complex conjugate), a Ket(Bra) (Bra(Ket)), an Operator (AdjointOperator) or a combination of them. For example, >>> Adj(I) -I >>> Adj(Ket(0)); Adj(Bra(1)) <0| |1> >>> Adj(I*Ket(0) - 4*Ket(1)) - I<0| - 4<1| This is particularly useful for computing the density operator of a state, or its norm,

10

>>> w = I*Ket(0) - 4*Ket(1); w I|0> - 4|1> >>> norm = Adj(w)*w; norm 17 >>> rho = w*Adj(w); rho/norm 1/17|0><0| - 4*I/17|0><1| + 4*I/17|1><0| + 16/17|1><1|

4.2

Trace and Partial Trace: Trace, TraceOut

OpenKet allows us to trace or partially trace states expressed as a sum of exterior products. Lets rst look into TraceOut. This has 2 arguments: the rst one is the total expression and the second one is the operator tag of the space you will be tracing out. Lets rst construct a vector, (2 qubit system, part A and part B) and its density operator, >>> P = Ket(0, "A")*Ket(1, "B") - Ket(1, "A")*Ket(0, "B"); P |0 A>|1 B> - |1 A>|0 B> >>> R = P*Adj(P); R |0 A>|1 B><1 B|<0 A| - |0 A>|1 B><0 B|<1 A| |1 A>|0 B><1 B|<0 A| + |1 A>|0 B><0 B|<1 A| So tracing out looks like this, >>> RB = TraceOut(R, "A"); RB |0 B><0 B| + |1 B><1 B| >>> RA = TraceOut(R, "B"); RA |0 A><0 A| + |1 A><1 A| The total trace is done with the function Trace5 . You only need to input the total expression, Trace rst nds all the operator tags and will then partially trace them all out. >>> Trace(R) 2
5

A second optional argument is the base which you would like to trace with, given as a list

11

>>> Trace(Ket(0)*Bra(1) + Ket(0)*Bra(0) + 2*I*Ket(1)*Bra(1)) 1 + 2*I One application of this is to be able to nd if we are dealing with a mixed or a pure state, by computing the square of the density operator and calculating its total trace. Suppose we have the pure state R1= | | with, | = (|0 |1 )/ 2 and we have the state R2= 0.25|0 0| + 0.75|1 1|. First we input this states, >>> psi = (Ket(0) - Ket(1))/sqrt(2) >>> R1 = psi*Adj(psi); R1 0.5|0><0| - 0.5|0><1| - 0.5|1><0| + 0.5|1><1| >>> R2 = 0.25*Ket(0)*Bra(0) + 0.75*Ket(1)*Bra(1); R2 0.25|0><0| + 0.75|1><1| Both of them have total traces equal to one, but if we square them and calculate their traces, >>> Trace(R1*R1) 1.0 >>> Trace(R2*R2) 0.625 Thus concluding that R2 is not a pure state.

4.3

Matrix Representation: Qmatrix

We are able nonetheless to work with the matrix representation of operators. This is done via Qmatrix. The function has 2 arguments, the rst is the expression (sum of exterior products) and the second is the base in list form. For example, recall the Pauli operators of section 3.3.2; lets obtain the matrix representation of x in the computational basis {|0 , |1 }, >>> >>> [0, [1, basecomp = [Ket(0), Ket(1)] Qmatrix(sigmaX, base) 1] 0]

12

Yet if we change base, for example {|+ , | }, where | = (|0 |1 ) / 2, the matrix representation changes accordingly, >>> basepm = [(Ket(0)+Ket(1))/sqrt(2), (Ket(0)-Ket(1))/sqrt(2)] >>> Qmatrix(sigmaX, basepm) [1.0, 0] [ 0, -1.0] This is useful when calculating for example eigenvalues, making things easier when computing more complex properties such as Von Neumann entropy.

5
5.1

Time Evolution Examples


The Problem

One important aspect of understanding a certain system is to understand its time evolution. In quantum mechanics there are many ways of representing how a system evolves. In this section we will demonstrate the use of OpenKet subroutines to solve numerically this problem by considering rst the Von Neumann equation, i ] = [H, h Numerically this is a set of coupled ODEs (ordinary dierential equations). The thing is, one has to nd a way to write and name the variables to produce a script readable by an ODEs solver. The problems presented in this manual are discrete level atoms in special sit as a sum of exterior uations. We face this type of problems by rst writing H products of elements of a base; then verifying the time evolution of the matrix elements of , namely i||j , considering them as variables. It is convenient to end up with real coecients equations, due to the fact that not all ODEs solvers work with complex numbers, and the point is to make this as transparent as possible.

5.2

The Translator QeqN

The rst thing is to write the Hamiltonian and every extra term in the right hand side of the time evolution equation in terms that OpenKet understands. Then comes in the function QeqN. Basically it translates terms of the form i||j to 13

symbolic expressions (variables) which are easier to handle. Then it writes the equations into a text le as well as the dictionary relating the i||j terms with the variables. QeqN has the following arguments QeqN(density operator, time derivative, base, le name, dictionary name). The st argument is just an object class Operator used to designate the density operator. The second is the right hand side of the time evolution equation. The third argument is a list, containing the elements of the base, expressed as kets. The fourth and fth arguments are strings naming the text le and the dictionary written in the le. QeqN uses 2 other functions: Dictionary(base, density operator) 6 and Qch(expression, dictionary). Dictionary does just that, create a dictionary (with name D) with words as i||j objects and denitions as variables. It is worth noting that in order to have j ||i j ||i and i||j 2 ; always real coecients, the variables must be in fact i||j + 2 i 7 because i||j is in general complex . Lets illustrate this by building the dictionary of the base {|0 , |1 }, >>> base=[Ket(0), Ket(1)]; R=Operator("R") >>> Dictionary(base, R) { <0|R|0>: Re0, <0|R|1>: Re1 + I*Im1, <1|R|0>: Re1 - I*Im1, <1|R|1>: Re2} The symbols Re0, Re1, Im1, Re2 are declared as real variables. The function Qch uses the dictionary created above to substitute bracket terms, for the symbols declared in Dictionary. For example, >>> D = Dictionary(base, R) >>> expr = Bra(0)*R*Ket(0)+2*I*Bra(0)*R*Ket(1)-3.4*Bra(1)*R*Ket(1); expr <0|R|0> + 2*I<0|R|1> - 3.4<1|R|1> >>> EXPR = Qch(expr, D); EXPR Re0 - 3.4*Re2 + 2*I*(Re1 + I*Im1) So QeqN rst creates a dictionary, then substitutes bracket terms, and then writes to a le. Before writing to the le, it replaces the real variables with strings
6 7

Not to be confused with the dictionary written in the le Although i||i is always real in physical situations

14

of the form y[n] where n is some number. The cost of introducing new notation is justied because strings are objects which consume a lot less memory than variables, in this case.8 The written le looks something like this, def f(y, t): return [-2*y[2],0,y[0] - 1.0*y[3],2*y[2]] dict={<1|R|0>: y1 - I*y2, <1|R|1>: y3, <0|R|0>: y0, <0|R|1>: y1 + I*y2} The text is the usual way to dene a function in Python. The name f is always used as well as its arguments. dict is a dictionary object, having its name specied in the last argument of QeqN. It serves only the purpose of aiding the user in determining which bracket term is which string9 . After the return command, there is a list which contains the temporal evolution of the matrix elements. The position of a certain item determines the time derivative of the matrix element corresponding to y[item]. For example, in the text presented before, we have [-2*y[2],0,y[0] - 1.0*y[3],2*y[2]], so looking at the rst entry,
d y[0] dt

= 2y[2]

Equivalent to (using the dict), d 0||1 1||0 0||0 = 2 dt 2i The sintaxis of the le is such that the ODE solver odeint from the package Scipy understands it. So all we need to do, is run the le from our workspace, and specify the time step and the initial condition. To illustrate this, we show two interesting physical problems.

5.3

Two Level Semiclassical Atoms

In some physical situations when an atom interacts with an electromagnetic eld, we can describe the atom using a 2 element base; let this base be {|0 , |1 }. Furthermore let the energy of the levels be h 0 and h 1 respectively. Now, the total
Why then declare them rst as real variables? Because we need to be able to operate and simplify expressions, and this is done on symbolic variables, not strings. The package Sympy takes care of all the algebra, but again, in order to do so, the objects must be symbolic variables. 9 Helping in the study of observables, as will be evident in the last section
8

15

Hamiltonian may be divided into two parts, the Hamiltonian of the unperturbed 0 , and the interaction part V . These can be found to have the following atom H form, 0 = h H 0 |0 0| + h 1 |1 1| = gE (t)|0 1| + g E (t)|1 0| V Where we have put g = e 0|r|1 and E (t) = |E (t)| E (t) with | | = 1. We assume the eld has the form, E (t) = A cos(t) It is convenient to change to the interaction picture, and following the rotating and wave approximation (RWA) we end up with the Hamiltonian (setting = gA 2 h having the frequency of the eld equal to the dierence of the two level frequencies), =h H (|0 1| + |0 1|) Following is the script which numerically solves the time evolution of the system described10 , run -i openket.py from scipy.integrate import odeint # import Scipys ODE solver R = Operator("R") b = [Ket(0), Ket(1)] H = Ket(0)*Bra(1) + Ket(1)*Bra(0) Rdot = -I*Commutator(H, R) QeqN(R, Rdot, b, "func", "dic") run -i func initialc = [1,0,0,0] # initial condition t = linspace(0,10,1000) # time-step sol = odeint(f, initialc, t) es = sol[:,3] plot(t, es) The graph we obtain is the following,
10

assuming = 1 for simplicity

16

1.0 0.8 0.6

1||1

0.4 0.2 0.00

10

Figure 1: Time evolution for 1||1 with the initial condition (t=0) = |0 0| Note that the initial condition is given as a list, where the position in the list indicates the bracket term that one wishes to give as the initial condition (the dic tells you which is which). Also the time-step is made with the command linspace having 3 arguments: the rst two specify the interval of solution while the third one is the total number of points in the interval. In the script, sol is the actual solution, and has the form,
t=0 t=0.01 t=10

sol = [[y[0], y[1], y[2], y[3]], [y[0], y[1], y[2], y[3]], . . . , [y[0], y[1], y[2], y[3]]] So the command es = sol[:,3] partitions the total solution sol and only keeps the third entry (y[3] or 1||1 ) in all the lists within sol. The last line is just the plotting of the probability of nding the atom in the excited state versus time. Suppose we dont want to make the RWA, or treat the problem from the =H 0 + V , interaction picture; that is, work with the total Hamiltonian11 H =h H 0 |0 0| + h 1 |1 1| + 2 h cos(t)|0 1| + 2 h cos(t)|1 0| A possible script, (notice that the structure is the same as the last script)12 :

11 12

Note that it is time-dependent explicitly Setting = 0.5, again for simplicity

17

run -i openket.py from scipy.integrate import odeint from sympy import * # the whole package # to be able to treat cos(n*t) as a variable R = Operator("R") b = [Ket(0), Ket(1)] w1=10; w0=1; v=9 # define parameters (resonant condition: v = w1-w0) t = var("t") H = w0*Ket(0)*Bra(0) + w1*Ket(1)*Bra(1) H = H + cos(v*t)*(Ket(0)*Bra(1) + Ket(1)*Bra(0)) Rdot = -I*Commutator(H, R) QeqN(R, Rdot, b, "func", "dic") run -i func initialc = [1,0,0,0] t = linspace(0,10,1000) # has to have the same tag as the variable t sol = odeint(f, initialc, t) es = sol[:,3] plot(t, es) We obtain,
1.0 0.8 0.6

1||1

0.4 0.2 0.00

10

Figure 2: Time evolution for 1||1 with the initial condition (t=0) = |0 0| and without the RWA. Values (0 = 1; 1 = 10; = 9) Changing the parameters a bit,

18

1.0 0.8 0.6

0.12 0.10 0.08

1||1

0.4 0.2 0.00

1||1
6 8 10

0.06

0.04 0.02

0.00

5.5

6.0

6.5

7.0

(a) Full size

(b) Detail

Figure 3: Time evolution for 1||1 with the initial condition (t=0) = |0 0| and without the RWA. Values (0 = 1; 1 = 100; = 99)

5.4

Two Level Atom Inside a Cavity

The last scenario tests basically every aspect of OpenKet. Let a two level atom inside a cavity interact with an electromagnetic eld. There is laser pumping and losses, and there is spontaneous emission from the atom. This can be modeled by having a Hamiltonian of the form, =g a H + a + + a + a

Where = |0 1|; + = |1 0|. The Von Neumann equation has extra dissipative terms, = i H, + kL( a) + L( ) h 2

Having L( o) = 2 oo o o o o . To solve this we have to use a nite base (truncating the Fock states), such as, {|i
atom |j photon

: i = 0, 1 ;

j = 0, 1, . . . , n}

Deciding which n has to be carefully thought, (both because of physical issues, and computer speed issues). Let us work with a base having this structure and with n = 4. Writing the script is no problem, its just copying the Hamiltonian and time evolution equations from before 13
13

the base will have 10 elements {|0

at |0 ph , |0 at |1 ph , . . . , |1 at |0 ph , |1 at |1 ph , . . . , |1 at |4 ph }

19

run -i openket.py from scipy.integrate import odeint a = AnnihilationOperator("photon",4) # maximum value 4 aa = CreationOperator("photon",4) # maximum value 4 splus = Ket(1,"atom")*Bra(0,"atom") sminus = Adj(splus) g = 0.6; epsilon = 0.1; k = 0.5; gamma = 1.5 # define parameters H = g*(aa*sminus + a*splus) + epsilon*(a + aa) # Hamiltonian def L(x): # Define the function L R=Operator("R") return 2*x*R*Adj(x) - R*Adj(x)*x - Adj(x)*x*R R = Operator("R") Rdot = - I*Commutator(H, R) + \ k*(2*a*R*aa - R*aa*a - aa*a*R) + (gamma/2.0)*L(sminus) l=[]; m=[]; b=[] # start the base construction for i in range(2): l.append(Ket(i, "atom")) # atom part for j in range(5): m.append(Ket(j,"photon")) # photon part for i in l: for j in m: b.append(i*j) # put them together y0 = [] # initial condition for i in range(100): y0.append(0) # fill it with zeroes y0[0] = 1 # at time t=0 we have atom at base state and no photons t = linspace(0,10,100) QeqN(R, Rdot, b, "func", "dic") run -i func sol = odeint(f, y0, t) This completely solves the problem numerically. sol has all the time evolution information of . The problem is, how can we manage this information in order to say, plot the time evolution of a certain observable?
producing a 100 element density matrix. When we input the initial condition, it must be in the form of a list of 100 entries. This is not dicult at all, as may be evident from the script. The challenge is to obtain a certain observable.

20

5.4.1

Observables and SubsSol

Suppose now we want to plot the mean number of photons against time. The observable is tr(a a ). To be able to plot this, we must rst compute the trace and have something of the type, i,j cij i||j This can be done directly with the function Trace using the second optional argument, (keeping the names we dened in the previous script) >>> obs = Trace(R*aa*a, b); obs <1 photon|<0 atom|R|0 atom>|1 photon> + <1 photon|<1 atom|R|1 atom>|1 photon> + 2<2 photon|<0 atom|R|0 atom>|2 photon> + 2<2 photon|<1 atom|R|1 atom>|2 photon> + 3<3 photon|<0 atom|R|0 atom>|3 photon> + 3<3 photon|<1 atom|R|1 atom>|3 photon> + 4<4 photon|<0 atom|R|0 atom>|4 photon> + 4<4 photon|<1 atom|R|1 atom>|4 photon> The point is to transform the above expression into a symbolic variable type. Remember the dictionary object dic, (obtained from QeqN) >>> dic { <0 photon|<0 <0 photon|<0 <0 photon|<0 ... <0 photon|<1 <0 photon|<1 <0 photon|<1 <0 photon|<1 <0 photon|<1 ...}

atom|R|0 atom>|0 photon>: atom|R|0 atom>|1 photon>: atom|R|0 atom>|2 photon>: atom|R|0 atom|R|0 atom|R|0 atom|R|1 atom|R|1 atom>|2 atom>|3 atom>|4 atom>|0 atom>|1 photon>: photon>: photon>: photon>: photon>:

y0, y1 + I*y2, y3 + I*y4, y41 y54 y65 y75, y76 + I*y42, I*y55, I*y66, I*y77,

So using this dictionary and the function Qch, we are able to transform the expression obs, >>> obs = Qch(obs, dic); obs y19 + y84 + 2*y36 + 2*y91 + 3*y51 + 3*y96 + 4*y64 + 4*y99 Now comes SubsSol. At this point, we have the instructions of what we want to plot (the above expression) and the whole time evolution (the object sol); to 21

plot this manually would be very unpractical (you would have to type sol[:,19] + sol[:,84] + 2*sol[:,36] + . . . ). SubsSol does this for you14 . It has two arguments SubsSol(obs, sol ). The rst argument is the instructions of what to plot, (the expression with the yis variables); and the second argument is the object sol which returns odeint, i.e. the whole time evolution. It looks like this, >>> OBS = SubsSol(obs, sol) Then OBS is an array of elements which has the time evolution of the mean number of photons, in this case. Plotting OBS results in,
0.014 0.012 0.010

) tr(N

0.008 0.006 0.004 0.002 0.0000 2 4

10

Figure 4: Evolution of the mean number of photons, having the atom at the base state and no photons as an initial condition Lets try now plotting the time evolution of the probability of nding the atom at the base state. This can be calculated with tr(|0 0|atom 1photon ). Typing this in OpenKet is easy: >>> bs=Trace(R*Ket(0, atom)*Bra(0, atom), b); bs <0 photon|<0 atom|R|0 atom>|0 photon> + <1 photon|<0 atom|R|0 atom>|1 photon> + <2 photon|<0 atom|R|0 atom>|2 photon> + <3 photon|<0 atom|R|0 atom>|3 photon> + <4 photon|<0 atom|R|0 atom>|4 photon>
it also supports expressions with functions in them e.g. sin, log, cos, etc. (actually all functions recognized by Sympy)
14

22

Note that the tensor product with the identity on the photon part is implicit. Next, we change it to sympy variables using the dic, >>> bs = Qch(bs, dic); bs y0 + y19 + y36 + y51 + y64 Obtain the numerical information with SubsSol, >>> BS = SubsSol(bs, sol) >>> plot(t, BS) The plot15 ,
1.000 0.999
photon)

0.998 0.997

tr(|0 0|atom

0.996 0.995

0.994 0.993 0.9920 2 4

10

Figure 5: Time-evolution of the Probability of nding the atom in the base state; having the atom at the base state and no photons as an initial condition

This can be done in one line, due to the fact that Python allows for nesting of functions: >>> plot(t, SubsSol(Qch(Trace(R*Ket(0,atom)*Bra(0,atom), b), dic), sol, b))

15

23

Anda mungkin juga menyukai