Creating and manipulating excitation tree#

First-order excitation#

Create KetBra in ground vibrational state

In [1]: import rotsim2d.pathways as pw

In [2]: import molspecutils.molecule as mol

In [3]: kb1 = pw.KetBra(mol.DiatomState(nu=0, j=1), mol.DiatomState(nu=0, j=1))

In [4]: print(kb1)
|0,1><0,1|

and excite it,

In [5]: pw.excite(kb1, light_name='omg1')
Out[5]: Ketbra(DiatomState(nu=0, j=1), DiatomState(nu=0, j=1))

In [6]: kb1.print_tree()
|0,1><0,1|
└── omg1
    ├── |1,0><0,1|
    └── |1,2><0,1|

kb1 is modified in-place. Use readout() to simulate measurement and see which branches will contribute to macroscopic polarization, i.e. would survive another one-sided application of the dipole operator and taking the trace of the density matrix.

In [7]: pw.readout(kb1)
Out[7]: Ketbra(DiatomState(nu=0, j=1), DiatomState(nu=0, j=1))

In [8]: kb1.print_tree()
|0,1><0,1|
└── omg1
    ├── |1,0><0,1|
    │   └── mu
    │       └── |0,1><0,1|
    └── |1,2><0,1|
        └── mu
            └── |0,1><0,1|

Third-order excitation#

Create KetBra in ground vibrational state and perform three excitations with multi_excite():

In [9]: kb2 = pw.KetBra(mol.DiatomState(nu=0, j=1), mol.DiatomState(nu=0, j=1))

In [10]: kb2 = pw.multi_excite(kb2, light_names=['omg1', 'omg2', 'omg3'],
   ....:                       parts=['ket', 'both', 'both'])
   ....: 

In [11]: kb2.print_tree()
|0,1><0,1|
└── omg1
    ├── |1,0><0,1|
    │   ├── omg2
    │   │   └── |0,1><0,1|
    │   │       ├── omg3
    │   │       │   ├── |1,0><0,1|
    │   │       │   └── |1,2><0,1|
    │   │       └── omg3
    │   │           ├── |0,1><1,0|
    │   │           └── |0,1><1,2|
    │   ├── omg2
    │   │   └── |2,1><0,1|
    │   │       ├── omg3
    │   │       │   ├── |1,0><0,1|
    │   │       │   └── |1,2><0,1|
    │   │       ├── omg3
    │   │       │   ├── |3,0><0,1|
    │   │       │   └── |3,2><0,1|
    │   │       └── omg3
    │   │           ├── |2,1><1,0|
    │   │           └── |2,1><1,2|
    │   └── omg2
    │       ├── |1,0><1,0|
    │       │   ├── omg3
    │       │   │   └── |0,1><1,0|
    │       │   ├── omg3
    │       │   │   └── |2,1><1,0|
    │       │   ├── omg3
    │       │   │   └── |1,0><0,1|
    │       │   └── omg3
    │       │       └── |1,0><2,1|
    │       └── |1,0><1,2|
    │           ├── omg3
    │           │   └── |0,1><1,2|
    │           ├── omg3
    │           │   └── |2,1><1,2|
    │           ├── omg3
    │           │   ├── |1,0><0,1|
    │           │   └── |1,0><0,3|
    │           └── omg3
    │               ├── |1,0><2,1|
    │               └── |1,0><2,3|
    └── |1,2><0,1|
        ├── omg2
        │   ├── |0,1><0,1|
        │   │   ├── omg3
        │   │   │   ├── |1,0><0,1|
        │   │   │   └── |1,2><0,1|
        │   │   └── omg3
        │   │       ├── |0,1><1,0|
        │   │       └── |0,1><1,2|
        │   └── |0,3><0,1|
        │       ├── omg3
        │       │   ├── |1,2><0,1|
        │       │   └── |1,4><0,1|
        │       └── omg3
        │           ├── |0,3><1,0|
        │           └── |0,3><1,2|
        ├── omg2
        │   ├── |2,1><0,1|
        │   │   ├── omg3
        │   │   │   ├── |1,0><0,1|
        │   │   │   └── |1,2><0,1|
        │   │   ├── omg3
        │   │   │   ├── |3,0><0,1|
        │   │   │   └── |3,2><0,1|
        │   │   └── omg3
        │   │       ├── |2,1><1,0|
        │   │       └── |2,1><1,2|
        │   └── |2,3><0,1|
        │       ├── omg3
        │       │   ├── |1,2><0,1|
        │       │   └── |1,4><0,1|
        │       ├── omg3
        │       │   ├── |3,2><0,1|
        │       │   └── |3,4><0,1|
        │       └── omg3
        │           ├── |2,3><1,0|
        │           └── |2,3><1,2|
        └── omg2
            ├── |1,2><1,0|
            │   ├── omg3
            │   │   ├── |0,1><1,0|
            │   │   └── |0,3><1,0|
            │   ├── omg3
            │   │   ├── |2,1><1,0|
            │   │   └── |2,3><1,0|
            │   ├── omg3
            │   │   └── |1,2><0,1|
            │   └── omg3
            │       └── |1,2><2,1|
            └── |1,2><1,2|
                ├── omg3
                │   ├── |0,1><1,2|
                │   └── |0,3><1,2|
                ├── omg3
                │   ├── |2,1><1,2|
                │   └── |2,3><1,2|
                ├── omg3
                │   ├── |1,2><0,1|
                │   └── |1,2><0,3|
                └── omg3
                    ├── |1,2><2,1|
                    └── |1,2><2,3|

The excitation tree in this case is very long. We can save it’s graphical representation with KetBra.savepng(). This uses anytree.exporter.DotExporter and requires a working installation of GraphViz.

In [12]: kb2.savepng('images/kb2.png')
Out[12]: 'images/kb2.png'
../_images/kb2.png

We can also generate double-sided Feynmann diagrams corresponding to the tree with LaTeX/Tikz:

In [13]: import rotsim2d.visual as vis

In [14]: latex_code = vis.tikz_diagrams(kb2)

#vis.latex_compile('images/kb2_tikz.tex', vis.latex_document(latex_code))

See the result: kb2_tikz.pdf. See rotsim2d.visual.tikz_diagrams() for more details.

Filtering the tree#

kb2 contains all rephasing and non-rephasing pathways, emitted in all directions. The tree can be filtered with one of rotsim2d.pathways functions starting with remove_ or only only_, see Tree-filtering functions. For example, to obtain only pathways emitted in \(\vec{k}_s = \vec{k}_1 - \vec{k_2} + \vec{k}_3\) (non-rephasing pathways) we can use:

In [15]: kb2_SII = pw.only_SII(kb2.copy())

In [16]: kb2_SII = pw.readout(kb2_SII)

In [17]: kb2_SII.savepng('images/kb2_SII.png')
Out[17]: 'images/kb2_SII.png'
../_images/kb2_SII.png

For \(\vec{k}_s = \vec{k}_1 + \vec{k_2} - \vec{k}_3\) direction (rephasing without double-quantum coherences), we can use:

In [18]: kb2_SI = pw.only_SI(kb2.copy())

In [19]: kb2_SI = pw.readout(kb2_SI)

In [20]: kb2_SI.savepng('images/kb2_SI.png')
Out[20]: 'images/kb2_SI.png'
../_images/kb2_SI.png

Multiple excitations tree#

Excitation trees for many initial j values can be generated with gen_pathways():

In [21]: from pprint import pprint

In [22]: js = (0, 1, 2, 3)

In [23]: kbs = pw.gen_pathways(js, meths=[pw.only_SII, pw.only_twocolor],
   ....:                       rotor='symmetric', kiter_func="range(j+1)")
   ....: 

In [24]: pprint(kbs)
[Ketbra(SymTopState(nu=0, j=0, k=0), SymTopState(nu=0, j=0, k=0)),
 Ketbra(SymTopState(nu=0, j=1, k=0), SymTopState(nu=0, j=1, k=0)),
 Ketbra(SymTopState(nu=0, j=1, k=1), SymTopState(nu=0, j=1, k=1)),
 Ketbra(SymTopState(nu=0, j=2, k=0), SymTopState(nu=0, j=2, k=0)),
 Ketbra(SymTopState(nu=0, j=2, k=1), SymTopState(nu=0, j=2, k=1)),
 Ketbra(SymTopState(nu=0, j=2, k=2), SymTopState(nu=0, j=2, k=2)),
 Ketbra(SymTopState(nu=0, j=3, k=0), SymTopState(nu=0, j=3, k=0)),
 Ketbra(SymTopState(nu=0, j=3, k=1), SymTopState(nu=0, j=3, k=1)),
 Ketbra(SymTopState(nu=0, j=3, k=2), SymTopState(nu=0, j=3, k=2)),
 Ketbra(SymTopState(nu=0, j=3, k=3), SymTopState(nu=0, j=3, k=3))]

This call created a list of third-order excitation trees for j values in js. Each tree was filtered by only_SII() and only_twocolor(). meths argument can contain any callable that takes KetBra instance as its sole argument and returns it, presumably after filtering it in some way, see Tree-filtering functions. rotor=’symmetric’ causes the functions to generate tree for different k quantum number values. The range of these values is determined by kiter_func [1]. kiter_func is a Python expression evaluated with ASTEVAL, which should return an iterable over integers. The expression is evaluated for each j value from js argument. The default expression is "range(j+1)", which iterates from 0 to j.

Footnotes