Example 6: Henon-Heiles Hamiltonian (Anharmonic oscillator) by HO-DVR

This sample corresponds to test sample

In dimensionless form, the Henon-Heiles Hamiltonian is given by

\[\hat{H} = \frac{\omega}{2}\sum_{i=1}^{f} \left( - \frac{\partial^2}{\partial Q_i^2} + Q_i^2 \right) + \lambda \left( \sum_{i=1}^{f-1} Q_i^2 Q_{i+1} - \frac{1}{3} Q_{i+1}^3 \right)\]

But PyTDSCF adopts mass-weighted coordinate, thus the Hamiltonian is given by

\[\hat{H} = \frac{1}{2}\sum_{i=1}^{f} \left( - \frac{\partial^2}{\partial q_i^2} + \omega^2 q_i^2 \right) + \lambda \omega^{\frac{3}{2}} \left( \sum_{i=1}^{f-1} q_i^2 q_{i+1} - \frac{1}{3} q_{i+1}^3 \right)\]

where \(Q_i = \sqrt{\omega} q_i\).

run type

wavefunction

backend

Basis

steps

propagation

MPS-SM

Numpy

HO-DVR

100

1. Define parameters

ω (float): frequency in cm-1
λ (float): coupling strength in a.u.
f (int): degree of freedom
N (int): number of grid points for each degree of freedom
m (int): MPS bond dimension
Δt (float): time step size in femtosecond. If spectrum norm of Hamiltonian is large, Δt should be smaller.
backend (str) : JAX or Numpy. (JAX takes 4 seconds, Numpy takes 1 seconds in M2 Macbook Air)
[1]:
ω = 3000
λ = 1.0e-07
f = 3
N = 10
m = 10
Δt = 0.1
backend = "numpy"

2. Import modules

[2]:
from typing import Callable

from discvar import HarmonicOscillator as HO

from pytdscf import BasInfo, Model, Simulator, units, __version__
from pytdscf.dvr_operator_cls import (
    TensorOperator,
    construct_kinetic_operator,
    construct_nMR_recursive,
)
from pytdscf.hamiltonian_cls import TensorHamiltonian
__version__
[2]:
'1.1.0'

3. Define HO-DVR grid basis set

DVR function documentation is here

[3]:
dvr_prims = [HO(N, ω) for _ in range(f)]
basinfo = BasInfo([dvr_prims])

Plot HO-DVR

[4]:
dvr_prims[0].plot_dvr()
dvr_prims[0].plot_fbr()
../_images/notebook_henon_heiles_HO-DVR_9_0.png
../_images/notebook_henon_heiles_HO-DVR_9_1.png

4. Define Anharmonic potential

  • If you use different frequencyies, be careful to “late binding” of Python.

[5]:
ω_au = ω / units.au_in_cm1

# Potential Function of each degree of freedom pair
henon_heiles_func: dict[tuple[int, ...], Callable] = {}
for idof in range(f):
    if idof == 0:
        henon_heiles_func[(0,)] = lambda Q1: pow(ω_au, 2) / 2 * Q1**2
        if f > 1:
            henon_heiles_func[(0, 1)] = (
                lambda Q1, Q2: λ * pow(ω_au, 3 / 2) * (Q1**2 * Q2)
            )
    elif idof == f - 1:
        henon_heiles_func[(f - 1,)] = (
            lambda Qf: pow(ω_au, 2) / 2 * Qf**2
            - λ * pow(ω_au, 3 / 2) / 3 * Qf**3
        )
    else:
        henon_heiles_func[(idof,)] = (
            lambda Qi: pow(ω_au, 2) / 2 * Qi**2
            - λ * pow(ω_au, 3 / 2) / 3 * Qi**3
        )
        henon_heiles_func[(idof, idof + 1)] = (
            lambda Qi, Qi1: λ * pow(ω_au, 3 / 2) * (Qi**2 * Qi1)
        )
[6]:
henon_heiles_func
[6]:
{(0,): <function __main__.<lambda>(Q1)>,
 (0, 1): <function __main__.<lambda>(Q1, Q2)>,
 (1,): <function __main__.<lambda>(Qi)>,
 (1, 2): <function __main__.<lambda>(Qi, Qi1)>,
 (2,): <function __main__.<lambda>(Qf)>}

5. Construct MPO

Refer to this document for more details of construct_nMR_recursive function.

[7]:
mpo = construct_nMR_recursive(
    dvr_prims, nMR=2, func=henon_heiles_func, rate=0.99999999999
)
17:10:54 | INFO | scalar term 0.0 is excluded from MPO.
17:10:54 | INFO | DONE: construct 2 quasi full-dimensional MPOs
17:10:54 | INFO | 0-2: part of full-dimensional MPOs optimization
17:10:54 | INFO | Execute (10, 110) matrix SVD in 0-1 sites MPO optimization
17:10:54 | INFO | Execute (20, 10) matrix SVD in 1-2 sites MPO optimization
17:10:54 | INFO | 0-1: full-dimensional MPOs optimization
17:10:54 | INFO | Execute (10, 30) matrix SVD in 0-1 sites MPO optimization
17:10:54 | INFO | Execute (20, 10) matrix SVD in 1-2 sites MPO optimization
17:10:54 | INFO | 0-sweep: full-dimensional MPOs optimization
17:10:54 | INFO | 1-sweep: full-dimensional MPOs optimization
17:10:54 | INFO | 2-sweep: full-dimensional MPOs optimization
17:10:54 | INFO | 3-sweep: full-dimensional MPOs optimization
17:10:54 | INFO | final MPO bond-dimension [2, 3]
[8]:
for core in mpo:
    print(core.shape)
(1, 10, 2)
(2, 10, 3)
(3, 10, 1)

6. Define Hamiltonian

[9]:
# MPO has legs on (0,1,2, ... ,f-1) sites. This legs are given by tuple key
V = {tuple([idof for idof in range(f)]): TensorOperator(mpo=mpo)}
# V₀₀ is given by
potential = [[V]]

# Kinetic energy operator is given by
K = construct_kinetic_operator(dvr_prims)
# K₀₀ is given by
kinetic = [[K]]

H = TensorHamiltonian(
    ndof=f, potential=potential, kinetic=kinetic, backend=backend
)
operators = {"hamiltonian": H}

7. Define model of Wavefunction and Operators

[10]:
model = Model(basinfo=basinfo, operators=operators)
model.m_aux_max = m
vib_gs = [1.0] + [0.0] * (N - 1)
vib_es = [0.0] + [1.0] + [0.0] * (N - 2)
print([[vib_es] + [vib_gs] * (f - 1)])
model.init_weight_VIBSTATE = [
    [vib_es] + [vib_gs] * (f - 1)
]  # only first mode is excited
[[[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]]

8. Execute simulation

[11]:
jobname = "henon_heiles"
simulator = Simulator(jobname=jobname, model=model, backend=backend)
simulator.propagate(maxstep=100, stepsize=Δt)
17:10:54 | INFO | Log file is ./henon_heiles_prop/main.log
17:10:54 | INFO | Wave function is saved in wf_henon_heiles.pkl
17:10:54 | INFO | Start initial step    0.000 [fs]
17:10:54 | INFO | End     0 step; propagated    0.100 [fs]; AVG Krylov iteration: 3.00
17:10:55 | INFO | End    99 step; propagated    9.900 [fs]; AVG Krylov iteration: 3.00
17:10:55 | INFO | End simulation and save wavefunction
17:10:55 | INFO | Wave function is saved in wf_henon_heiles.pkl
[11]:
(0.03417251439684914, <pytdscf.wavefunction.WFunc at 0x7ff696286420>)

9. Check result

[12]:
!ls {jobname}*
henon_heiles_HO-DVR.ipynb  henon_heiles_Sine-DVR.ipynb

henon_heiles_prop:
autocorr.dat  expectations.dat  main.log  populations.dat
[13]:
import matplotlib.pyplot as plt

from pytdscf import spectra

time, autocorr = spectra.load_autocorr(jobname + "_prop" + "/autocorr.dat")
plt.plot(time, autocorr.real, label="real")
plt.plot(time, autocorr.imag, label="imag")
plt.title("Autocorrelation")
plt.legend()
plt.xlabel("time [fs]")
plt.show()
../_images/notebook_henon_heiles_HO-DVR_24_0.png
[ ]: