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'
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'
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'
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