Source code for oqupy.pt_tempo

# Copyright 2022 The TEMPO Collaboration
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Module for the process tensor time evolving matrix product operator algorithm
(PT-TEMPO). This module is based on [Strathearn2018], [Pollock2018],
[Jorgensen2019], and [Fux2021].

**[Strathearn2018]**
A. Strathearn, P. Kirton, D. Kilda, J. Keeling and
B. W. Lovett,  *Efficient non-Markovian quantum dynamics using
time-evolving matrix product operators*, Nat. Commun. 9, 3322 (2018).

**[Pollock2018]**
F.  A.  Pollock,  C.  Rodriguez-Rosario,  T.  Frauenheim,
M. Paternostro, and K. Modi, *Non-Markovian quantumprocesses: Complete
framework and efficient characterization*, Phys. Rev. A 97, 012127 (2018).

**[Jorgensen2019]**
M. R. Jørgensen and F. A. Pollock, *Exploiting the causal tensor network
structure of quantum processes to efficiently simulate non-markovian path
integrals*, Phys. Rev. Lett. 123, 240602 (2019)

**[Fux2021]**
G. E. Fux, E. Butler, P. R. Eastham, B. W. Lovett, and
J. Keeling, *Efficient exploration of Hamiltonian parameter space for
optimal control of non-Markovian open quantum systems*, Phys. Rev. Lett. 126,
200401 (2021).
"""

from typing import Dict, Optional, Text, Union
from copy import copy

import numpy as np

from oqupy.base_api import BaseAPIClass
from oqupy.bath import Bath
from oqupy.config import PT_DEFAULT_TOLERANCE
from oqupy.config import PT_TEMPO_BACKEND_CONFIG
from oqupy.process_tensor import BaseProcessTensor
from oqupy.process_tensor import SimpleProcessTensor
from oqupy.process_tensor import FileProcessTensor
from oqupy.backends.pt_tempo_backend import PtTempoBackend
from oqupy.tempo import TempoParameters
from oqupy.tempo import guess_tempo_parameters
from oqupy.tempo import influence_matrix
from oqupy.operators import commutator, acommutator
from oqupy.operators import left_right_super
from oqupy.util import get_progress


PT_CLASS = {"simple": SimpleProcessTensor}



[docs]class PtTempo(BaseAPIClass): """ Class to facilitate a PT-TEMPO computation. Parameters ---------- bath: Bath The Bath (includes the coupling operator to the system). parameters: TempoParameters The parameters for the PT-TEMPO computation. start_time: float The start time. backend_config: dict (default = None) The configuration of the backend. If `backend_config` is ``None`` then the default backend configuration is used. name: str (default = None) An optional name for the tempo object. description: str (default = None) An optional description of the tempo object. """ def __init__( self, bath: Bath, start_time: float, end_time: float, parameters: TempoParameters, process_tensor_file: Optional[Union[Text, bool]] = None, overwrite: Optional[bool] = False, backend_config: Optional[Dict] = None, name: Optional[Text] = None, description: Optional[Text] = None) -> None: """Create a PtTempo object. """ assert isinstance(bath, Bath), \ "Argument 'bath' must be an instance of Bath." self._bath = bath self._dimension = self._bath.dimension self._correlations = self._bath.correlations try: tmp_start_time = float(start_time) except Exception as e: raise AssertionError("Start time must be a float.") from e self._start_time = tmp_start_time try: tmp_end_time = float(end_time) except Exception as e: raise AssertionError("End time must be a float.") from e self._end_time = tmp_end_time assert isinstance(parameters, TempoParameters), \ "Argument 'parameters' must be an instance of TempoParameters." self._parameters = parameters self._process_tensor = None if process_tensor_file or isinstance(process_tensor_file, Text): if isinstance(process_tensor_file, Text): filename = process_tensor_file else: filename = None self._init_file_process_tensor(filename, overwrite) else: self._init_simple_process_tensor() if backend_config is None: self._backend_config = PT_TEMPO_BACKEND_CONFIG else: self._backend_config = backend_config super().__init__(name, description) tmp_coupling_comm = commutator(self._bath._coupling_operator) tmp_coupling_acomm = acommutator(self._bath._coupling_operator) self._coupling_comm = tmp_coupling_comm.diagonal() self._coupling_acomm = tmp_coupling_acomm.diagonal() tmp_num_steps = int((end_time - self._start_time)/self._parameters.dt) assert tmp_num_steps >= 2, \ "Parameter `end_time` must be more than two times steps " \ + "larger than the parameter `start_time`!" self._num_steps = tmp_num_steps self._backend_instance = None self._init_pt_tempo_backend() def _init_simple_process_tensor(self): """ToDo. """ unitary = self._bath.unitary_transform if not np.allclose(unitary, np.identity(self._dimension)): transform_in = left_right_super(unitary.conjugate().T, unitary).T transform_out = left_right_super(unitary, unitary.conjugate().T).T else: transform_in = None transform_out = None self._process_tensor = SimpleProcessTensor( hilbert_space_dimension=self._dimension, dt=self._parameters.dt, transform_in=transform_in, transform_out=transform_out) def _init_file_process_tensor(self, filename, overwrite): """ToDo. """ unitary = self._bath.unitary_transform if not np.allclose(unitary, np.identity(self._dimension)): transform_in = left_right_super(unitary.conjugate().T, unitary).T transform_out = left_right_super(unitary, unitary.conjugate().T).T else: transform_in = None transform_out = None if overwrite: mode = "overwrite" else: mode = "write" self._process_tensor = FileProcessTensor( mode=mode, filename=filename, hilbert_space_dimension=self._dimension, dt=self._parameters.dt, transform_in=transform_in, transform_out=transform_out) def _init_pt_tempo_backend(self): """Create and initialize the pt-tempo backend. """ sum_north = np.array([1.0]*(self._dimension**2)) sum_west = np.array([1.0]*(self._dimension**2)) dkmax = self._parameters.dkmax if dkmax is None: dkmax = self._num_steps self._backend_instance = PtTempoBackend( dimension=self._dimension, influence=self._influence, process_tensor=self._process_tensor, sum_north=sum_north, sum_west=sum_west, num_steps=self._num_steps, dkmax=dkmax, epsrel=self._parameters.epsrel, config=self._backend_config) def _influence(self, dk: int): """Create the influence functional matrix for a time step distance of dk. """ return influence_matrix( dk, parameters=self._parameters, correlations=self._correlations, coupling_acomm=self._coupling_acomm, coupling_comm=self._coupling_comm) @property def dimension(self) -> np.ndarray: """Hilbert space dimension. """ return copy(self._dimension)
[docs] def compute(self, progress_type: Optional[Text] = None) -> None: """ Propagate (or continue to propagate) the TEMPO tensor network to time `end_time`. Parameters ---------- progress_type: str (default = None) The progress report type during the computation. Types are: {``silent``, ``simple``, ``bar``}. If `None` then the default progress type is used. """ if self._backend_instance.step is None: self._backend_instance.initialize() progress = get_progress(progress_type) title = "--> PT-TEMPO computation:" with progress(self._backend_instance.num_steps, title) as prog_bar: while self._backend_instance.compute_step(): prog_bar.update(self._backend_instance.step) prog_bar.update(self._backend_instance.step)
[docs] def get_process_tensor( self, progress_type: Optional[Text] = None) -> BaseProcessTensor: """ Returns a the computed process tensor. It performs the computation if it hasn't been already done. Parameters ---------- progress_type: str (default = None) The progress report type during the computation. Types are: {``silent``, ``simple``, ``bar``}. If `None` then the default progress type is used. Returns ------- process_tensor: SimpleProcessTensor The computed process tensor. """ if self._backend_instance.step is None or \ self._backend_instance.step < self._backend_instance.num_steps: self.compute(progress_type=progress_type) if len(self._process_tensor) < self._backend_instance.num_steps: self._backend_instance.update_process_tensor() return self._process_tensor
[docs]def pt_tempo_compute( bath: Bath, start_time: float, end_time: float, parameters: Optional[TempoParameters] = None, tolerance: Optional[float] = PT_DEFAULT_TOLERANCE, process_tensor_file: Optional[Union[Text, bool]] = None, overwrite: Optional[bool] = False, backend_config: Optional[Dict] = None, progress_type: Optional[Text] = None, name: Optional[Text] = None, description: Optional[Text] = None) -> BaseProcessTensor: """ Shortcut for creating a process tensor by performing a PT-TEMPO computation. Parameters ---------- bath: Bath The Bath (includes the coupling operator to the system). start_time: float The start time. end_time: float The time to which the PT-TEMPO should be computed. parameters: TempoParameters The parameters for the PT-TEMPO computation. tolerance: float Tolerance for the parameter estimation (only applicable if `parameters` is None). backend_config: dict (default = None) The configuration of the backend. If `backend_config` is ``None`` then the default backend configuration is used. progress_type: str (default = None) The progress report type during the computation. Types are: {``'silent'``, ``'simple'``, ``'bar'``}. If `None` then the default progress type is used. name: str (default = None) An optional name for the tempo object. description: str (default = None) An optional description of the tempo object. """ if parameters is None: assert tolerance is not None, \ "If 'parameters' is 'None' then 'tolerance' must be " \ + "a positive float." parameters = guess_tempo_parameters(bath=bath, start_time=start_time, end_time=end_time, tolerance=tolerance) ptt = PtTempo(bath, start_time, end_time, parameters, process_tensor_file, overwrite, backend_config, name, description) ptt.compute(progress_type=progress_type) return ptt.get_process_tensor()