Source code for oqupy.control

# 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 system 'control operations' as discussed in [Pollock2018].

**[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).
"""

from typing import Callable, List, Optional, Text, Tuple, Union
from copy import deepcopy

import numpy as np
from numpy import ndarray

from oqupy.base_api import BaseAPIClass
from oqupy.config import NpDtype

[docs]class Control(BaseAPIClass): """ Represents a set of system control operations. A control operation is a superoperator that acts on the system instantaneously at a particular time, as described in [Pollock2018]. Parameters ---------- dimension: int The Hilbert space dimension of the system. name: str An optional name for the set of control operations. description: str An optional description of the set of control operations. """ def __init__( self, dimension: int, name: Optional[Text] = None, description: Optional[Text] = None) -> None: """Creates a Control object. """ self._dimension = dimension self._step_controls = {'pre':{}, 'post':{}} self._time_controls = {'pre':{}, 'post':{}} self._control_times = {'pre':np.array([]), 'post':np.array([])} super().__init__(name, description) @property def dimension(self): """Hilbert space dimension of the controlled system. """ return self._dimension
[docs] def add_single( self, time: Union[int, float], control_operation: ndarray, post: Optional[bool] = False) -> None: r""" Adds a single control operation at time `time`. Parameters ---------- time: Union[int, float] The time at which the operation should be applied. If `type(time)` is `int` then `time` is understood as the *timestep* to which it shall be applied. control_operation: ndarray The control operation super operator of shape :math:`d^2 \times d^2`, where :math:`d` is the system Hilbert space dimension. post: bool If `True` (`False`) the operator is applied at the corresponding time step *after* (*before*) a possible measurement of the state. """ if post: pre_post = 'post' else: pre_post = 'pre' if isinstance(time, int): steps = self._step_controls[pre_post].keys() if time in steps: self._step_controls[pre_post][time] = \ control_operation @ self._step_controls[pre_post][time] else: self._step_controls[pre_post][time] = control_operation elif isinstance(time, float): if time in self._control_times[pre_post]: self._time_controls[pre_post][time] = \ control_operation @ self._time_controls[pre_post][time] else: self._time_controls[pre_post][time] = control_operation times = np.append(self._control_times[pre_post], time) times.sort() self._control_times[pre_post] = times else: raise TypeError("Parameter `time` must be either int or float.")
[docs] def add_continuous( self, control_fct: Callable[[ndarray, float], ndarray], post: Optional[bool] = False) -> None: """ ToDo """ raise NotImplementedError()
[docs] def get_controls( self, step: int, dt: Optional[float] = None, start_time: Optional[float] = 0.0, ) -> Tuple[ndarray, ndarray]: """ Get the pre and post measurement control operation for a specific time step. Parameters ---------- step: int The time step. dt: float The time step length. start_time: float The initial time step off-set. Returns ------- pre: ndarray The control superoperator that should be applied before a state measurement. post: ndarray The control superoperator that should be applied after a state measurement. """ pre_control_bool = False post_control_bool = False pre_control = np.identity(self.dimension**2) post_control = np.identity(self.dimension**2) # -- pre time-stamp controls -- a = np.round((self._control_times['pre'] - start_time) / dt) times = np.array(self._control_times['pre'])[np.nonzero(a==step)] if len(times) > 0: print(times) pre_control_bool = True pre_control = self._time_controls['pre'][times[0]] @ pre_control for t in times[1:]: pre_control = self._time_controls['pre'][t] @ pre_control # -- pre step controls -- steps = self._step_controls['pre'].keys() if step in steps: pre_control_bool = True pre_control = self._step_controls['pre'][step] @ pre_control # -- post step controls -- steps = self._step_controls['post'].keys() if step in steps: post_control_bool = True post_control = self._step_controls['post'][step] @ post_control # -- post time-stamp controls -- a = np.round((self._control_times['post'] - start_time) / dt) times = np.array(self._control_times['post'])[np.nonzero(a==step)] if len(times) > 0: post_control_bool = True post_control = self._time_controls['post'][times[0]] @ post_control for t in times[1:]: post_control = self._time_controls['post'][t] @ post_control if not pre_control_bool: pre_control = None if not post_control_bool: post_control = None return pre_control, post_control
[docs]class ChainControl(BaseAPIClass): """ Control operations on a linear system chain. Parameters ---------- hilbert_space_dimensions: List[int] Hilbert space dimension for each chain site. name: str An optional name for the chain controls. description: str An optional description of the chain controls. """ def __init__( self, hilbert_space_dimensions: List[int], name: Optional[Text] = None, description: Optional[Text] = None) -> None: """Create a ChainControl object. """ tmp_hs_dims = np.array(hilbert_space_dimensions, int) assert len(tmp_hs_dims.shape) == 1 assert len(tmp_hs_dims) >= 1 assert np.all(tmp_hs_dims > 0) self._hs_dims = tmp_hs_dims self._single_site_controls_pre = [] self._single_site_controls_post = [] super().__init__(name, description) def __len__(self): """Length of the chain. """ return len(self._hs_dims) @property def hs_dims(self): """Hilbert space dimensions. """ return self._hs_dims
[docs] def add_single_site_control( self, control: ndarray, site: int, step: int, post: Optional[bool] = False, name: Optional[Text] = None) -> None: """ Add a control operation at site `site` and time step `step`. Parameters ---------- control: ndarray Control operation in Liouville space. site: int Site index. step: int Timestep to which the control should be applied. post: bool True if the control should be applied *after* the measurement of this time step. name: Text An optional name to recognize a control operation. """ assert isinstance(site, int) assert site < len(self) assert isinstance(step, int) contr = np.array(control, dtype=NpDtype) assert contr.shape == (self._hs_dims[site]**2, self._hs_dims[site]**2) if not post: self._single_site_controls_pre.append({ "contr":contr, "site":site, "step":step, "name":name}) else: self._single_site_controls_post.append({ "contr":contr, "site":site, "step":step, "name":name})
[docs] def get_single_site_controls( self, step: int, post: bool) -> List[ndarray]: """ Get a list of single site controls for the time step `step`. Parameters ---------- step: int The time step. post: bool If `True` (`False`) the set of control superoperators that should be applied *after* (*before*) the measurement is returned. Returns ------- superoperators_list: list[ndarray] List of single site control superoperators. """ empty = True controls = [None] * len(self) if not post: ss_controls = self._single_site_controls_pre else: ss_controls = self._single_site_controls_post for ssc in ss_controls: if ssc["step"] == step: empty = False if controls[ssc["site"]] is None: controls[ssc["site"]] = ssc["contr"] else: controls[ssc["site"]] = \ controls[ssc["site"]] @ ssc["contr"] if empty: return None return deepcopy(controls)