Source code for qtealeaves.convergence_parameters.conv_params_finite_temp

# This code is part of qtealeaves.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
Convergence parameters for systems at finite temperature defining the grid
for imaginary time evolution.
"""

# pylint: disable=too-many-arguments
# pylint: disable=too-many-locals

import numpy as np

from .conv_params import TNConvergenceParameters

__all__ = ["TNConvergenceParametersFiniteT"]


[docs] class TNConvergenceParametersFiniteT(TNConvergenceParameters): """ Convergence parameters for finite temperature. Based on the input temperature grid, the time grid for imaginary time evolution is created. The largest value of time step is limited with the input `dt_max`. Parameters ---------- t_grid : list or np.ndarray Temperature grid, we want to take measurements for each point in the grid. The temperature grid must be sorted in descending order. statics_method : int, optional Only imaginary time evolution methods are enabled, i.e., 3 (two-tensor), 4 (single-tensor link-expansion), or 5 (single-tensor). Default to 4 (single-tensor link-expansion) dt_max : float, optional Maximal time step for imaginary time evolution. Default is 0.1. measure_obs_every_n_iter : int, optional The measurements are done every `measure_obs_every_n_iter` iterations. The target tempertures will fall on multiples of `measure_obs_every_n_iter` Default is 20. k_b : float, optional Value for Boltzmann constant. **kwargs : other :py:class:`TNConvergenceParameters` parameters Attributes ---------- self.sim_params['imag_evo_dt'] : np.ndarray Time step grid. self.measure_obs_every_n_iter : int See Parameters above. self.n_grid : np.ndarray of int The number of iterations/`measure_obs_every_n_iter` needed to reach each of the temperatures from `t_grid`, starting from the infinite temperature. """ def __init__( self, t_grid, statics_method=4, dt_max=0.1, measure_obs_every_n_iter=20, k_b=1, **kwargs, ): if np.any(np.array(t_grid[:-1]) < np.array(t_grid[1:])): raise ValueError( "The input temperature grid must be sorted in descending order." ) if isinstance(statics_method, int): if statics_method not in [3, 4, 5]: raise ValueError( f"Statics method must be imaginary time, but {statics_method}." ) else: if any((method not in [3, 4, 5]) for method in statics_method): raise ValueError( f"Statics method must be imaginary time, but {statics_method}." ) self.k_b = k_b # initialize the array n_grid which represents the # number of iterations/measure_obs_every_n_iter # needed to reach each of the temperatures from t_grid n_grid = np.zeros(len(t_grid), dtype=int) # initialize dt_grid for time steps in imaginary time evolution # measurments fall on every measure_obs_every_n_iter-th step dt_grid = np.zeros(measure_obs_every_n_iter * len(t_grid)) # calculate time step dt_nn such that the next # measurement falls on the next target temperature # the first temperature in a grid is treated outside # the loop dt_nn = 1 / (2 * k_b * t_grid[0] * measure_obs_every_n_iter) num = 1 # if this time step is smaller than the given dt_max, # accept it and store it in dt_grid if dt_nn <= dt_max: dt_grid[:measure_obs_every_n_iter] = dt_nn start = measure_obs_every_n_iter n_step = measure_obs_every_n_iter * dt_nn n_grid[0] = 0 # in case the dt_nn is bigger than dt_max, # we add another measure_obs_every_n_iter iterations # to reach the next target temperature, meaning that # the target temperature will not fall on the next # measurement, but at the one after it # we iterate this procedure until we reach dt_nn < dt_max else: dt_new = dt_nn while dt_new > dt_max: num += 1 dt_new = dt_nn / num dt_grid = np.pad(dt_grid, (0, (num - 1) * measure_obs_every_n_iter)) dt_grid[: measure_obs_every_n_iter * num] = dt_new start = measure_obs_every_n_iter * num # n_step is the cummulated imaginary time passed until # the certain measurement n_step = measure_obs_every_n_iter * dt_new * num n_grid[0] = num - 1 num = 1 # the same procedure as above, looped over the entire # temperature grid for nn in range(1, len(t_grid)): dt_nn = 1 / measure_obs_every_n_iter * (1 / (2 * k_b * t_grid[nn]) - n_step) if dt_nn <= dt_max: dt_grid[start : start + measure_obs_every_n_iter] = dt_nn start += measure_obs_every_n_iter n_step += measure_obs_every_n_iter * dt_nn n_grid[nn] += n_grid[nn - 1] + 1 else: dt_new = dt_nn while dt_new > dt_max: num += 1 dt_new = dt_nn / num dt_grid = np.pad( dt_grid, (0, (num - 1) * measure_obs_every_n_iter), constant_values=0, ) dt_grid[start : start + measure_obs_every_n_iter * num] = dt_new start += measure_obs_every_n_iter * num n_step += measure_obs_every_n_iter * dt_new * num n_grid[nn] += n_grid[nn - 1] + num num = 1 self.n_grid = n_grid max_iter = int((self.n_grid[-1] + 1) * measure_obs_every_n_iter) super().__init__( measure_obs_every_n_iter=measure_obs_every_n_iter, statics_method=statics_method, imag_evo_dt=dt_grid, max_iter=max_iter, **kwargs, ) @property def temperature(self): """ Returns the grid of temperatures at which the measurements are made. To check if the grid corresponds to the input temperature grid, use self.temperature[self.n_grid]. """ dt_grid = self.sim_params["imag_evo_dt"] temp = np.zeros(int(len(dt_grid) / self.measure_obs_every_n_iter)) n_step = 0 # the loop goes through all the measured points and extracts the # temperature at each of them for ii in range(0, int(len(dt_grid) / self.measure_obs_every_n_iter)): temp[ii] = 1 / ( 2 * self.k_b * ( dt_grid[self.measure_obs_every_n_iter * ii] * self.measure_obs_every_n_iter + n_step ) ) n_step += ( self.measure_obs_every_n_iter * dt_grid[self.measure_obs_every_n_iter * ii] ) return temp