/
mpo_evolution.py
147 lines (124 loc) · 5.63 KB
/
mpo_evolution.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""Time evolution using the WI or WII approximation of the time evolution operator."""
# Copyright 2020-2021 TeNPy Developers, GNU GPLv3
import numpy as np
import time
from scipy.linalg import expm
import logging
logger = logging.getLogger(__name__)
from .algorithm import TimeEvolutionAlgorithm
from ..linalg import np_conserved as npc
from .truncation import TruncationError
from ..tools.params import asConfig
__all__ = ['ExpMPOEvolution']
class ExpMPOEvolution(TimeEvolutionAlgorithm):
"""Time evolution of an MPS using the W_I or W_II approximation for ``exp(H dt)``.
:cite:`zaletel2015` described a method to obtain MPO approximations :math:`W_I` and
:math:`W_{II}` for the exponential ``U = exp(i H dt)`` of an MPO `H`, implemented in
:meth:`~tenpy.networks.mpo.MPO.make_U_I` and :meth:`~tenpy.networks.mpo.MPO.make_U_II`.
This class uses it for real-time evolution.
Parameters
----------
psi : :class:`~tenpy.networks.mps.MPS`
Initial state to be time evolved. Modified in place.
model : :class:`~tenpy.models.model.MPOModel`
The model representing the Hamiltonian which we want to
time evolve psi with.
options : dict
Further optional parameters are described in :cfg:config:`ExpMPOEvolution`.
Options
-------
.. cfg:config :: ExpMPOEvolution
:include: ApplyMPO, TimeEvolutionAlgorithm
start_trunc_err : :class:`~tenpy.algorithms.truncation.TruncationError`
Initial truncation error for :attr:`trunc_err`
approximation : 'I' | 'II'
Specifies which approximation is applied. The default 'II' is more precise.
See :cite:`zaletel2015` and :meth:`~tenpy.networks.mpo.MPO.make_U`
for more details.
order : int
Order of the algorithm. The total error up to time `t` scales as ``O(t*dt^order)``.
Implemented are order = 1 and order = 2.
Attributes
----------
options : :class:`~tenpy.tools.params.Config`
Optional parameters, see :meth:`run` for more details
evolved_time : float
Indicating how long `psi` has been evolved, ``psi = exp(-i * evolved_time * H) psi(t=0)``.
trunc_err : :class:`~tenpy.algorithms.truncation.TruncationError`
The error of the represented state which is introduced due to the truncation during
the sequence of update steps
psi : :class:`~tenpy.networks.mps.MPS`
The MPS, time evolved in-place.
model : :class:`~tenpy.models.model.MPOModel`
The model defining the Hamiltonian.
_U : list of :class:`~tenpy.networks.mps.MPO`
Exponentiated `H_MPO`;
_U_param : dict
A dictionary containing the information of the latest created `_U`.
We won't recalculate `_U` if those parameters didn't change.
"""
def __init__(self, psi, model, options):
super().__init__(psi, model, options)
options = self.options
self.evolved_time = options.get('start_time', 0.)
self.trunc_err = options.get('start_trunc_err', TruncationError())
self._U_MPO = None
self._U_param = {}
options.setdefault('start_env_sites', model.H_MPO.max_range)
def run(self):
"""Run the real-time evolution with the W_I/W_II approximation. """
dt = self.options.get('dt', 0.01)
N_steps = self.options.get('N_steps', 1)
approximation = self.options.get('approximation', 'II')
order = self.options.get('order', 2)
self.calc_U(dt, order, approximation)
self.update(N_steps)
return self.psi
def calc_U(self, dt, order=2, approximation='II'):
"""Calculate ``self._U_MPO``.
This function calculates the approximation ``U ~= exp(-i dt_ H)`` with
``dt_ = dt` for ``order=1``, or
``dt_ = (1 - 1j)/2 dt`` and ``dt_ = (1 + 1j)/2 dt`` for ``order=2``.
Parameters
----------
dt : float
Size of the time-step used in calculating `_U`
order : int
The order of the algorithm. Only 1 and 2 are allowed.
approximation : 'I' or 'II'
Type of approximation for the time evolution operator.
"""
U_param = dict(dt=dt, order=order, approximation=approximation)
if self._U_param == U_param:
return # nothing to do: _U is cached
self._U_param = U_param
logger.info("Calculate U for %s", U_param)
H_MPO = self.model.H_MPO
if order == 1:
U_MPO = H_MPO.make_U(dt * -1j, approximation=approximation)
self._U_MPO = [U_MPO]
elif order == 2:
U1 = H_MPO.make_U(-(1. + 1j) / 2. * dt * 1j, approximation=approximation)
U2 = H_MPO.make_U(-(1. - 1j) / 2. * dt * 1j, approximation=approximation)
self._U_MPO = [U1, U2]
else:
raise ValueError("order {0:d} not implemented".format(order=order))
def update(self, N_steps):
"""Time evolve by `N_steps` steps.
Parameters
----------
N_steps: int
The number of time steps psi is evolved by.
Returns
-------
trunc_err: :class:`~tenpy.algorithms.truncation.TruncationError`
Truncation error induced during the update.
"""
trunc_err = TruncationError()
for _ in np.arange(N_steps):
for U_MPO in self._U_MPO:
trunc_err += U_MPO.apply(self.psi, self.options)
self.evolved_time = self.evolved_time + N_steps * self._U_param['dt']
self.trunc_err = self.trunc_err + trunc_err # not += : make a copy!
# (this is done to avoid problems of users storing self.trunc_err after each `update`)
return trunc_err