def test_dynamics():
    dyn_A = Dynamics()
    str(dyn_A)
    assert len(dyn_A) == 0
    dyn_B = Dynamics(times, states)
    str(dyn_B)
    assert len(dyn_B) == len(times)
    np.testing.assert_almost_equal(times, dyn_B.times)
    np.testing.assert_almost_equal(states, dyn_B.states)
    np.testing.assert_almost_equal(states[0].shape, dyn_B.shape)
    dyn_B.add(other_time, other_state)
def test_dynamics_bad_input():
    with pytest.raises(AssertionError):
        Dynamics(0.1, states)
    with pytest.raises(AssertionError):
        Dynamics(times, 0.1)
    with pytest.raises(AssertionError):
        Dynamics([other_time], states)
    dyn_A = Dynamics(times, states)
    with pytest.raises(AssertionError):
        dyn_A.add("bla", other_state)
    with pytest.raises(AssertionError):
        dyn_A.add(other_time, "bla")
    with pytest.raises(AssertionError):
        dyn_A.add(other_time, np.random.rand(3, 3))
    dyn_B = Dynamics()
    with pytest.raises(AssertionError):
        dyn_A.add(other_time, np.random.rand(2, 2, 2))
    with pytest.raises(AssertionError):
        dyn_A.add(other_time, np.random.rand(3, 2))
Exemple #3
0
class Tempo(BaseAPIClass):
    """
    Class representing the entire TEMPO tensornetwork as introduced in
    [Strathearn2018].

    Parameters
    ----------
    system: BaseSystem
        The system.
    bath: Bath
        The Bath (includes the coupling operator to the sytem).
    parameters: TempoParameters
        The parameters for the TEMPO computation.
    initial_state: ndarray
        The initial density matrix of the sytem.
    start_time: float
        The start time.
    backend: str (default = None)
        The name of the backend to use for the computation. If
        `backend` is ``None`` then the default backend is used.
    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.
    description_dict: dict (default = None)
        An optional dictionary with descriptive data.
    """
    def __init__(self,
                 system: BaseSystem,
                 bath: Bath,
                 parameters: TempoParameters,
                 initial_state: ndarray,
                 start_time: float,
                 backend: Optional[Text] = None,
                 backend_config: Optional[Dict] = None,
                 name: Optional[Text] = None,
                 description: Optional[Text] = None,
                 description_dict: Optional[Dict] = None) -> None:
        """Create a Tempo object. """
        self._backend_class, self._backend_config = \
            get_tempo_backend(backend, backend_config)

        assert isinstance(system, BaseSystem), \
            "Argument 'system' must be an instance of BaseSystem."
        self._system = system

        assert isinstance(bath, Bath), \
            "Argument 'bath' must be an instance of Bath."
        self._bath = bath

        assert isinstance(parameters, TempoParameters), \
            "Argument 'parameters' must be an instance of TempoParameters."
        self._parameters = parameters

        try:
            __initial_state = np.array(initial_state, dtype=NpDtype)
            __initial_state.setflags(write=False)
        except Exception as e:
            raise AssertionError("Initial state must be numpy array.") from e
        assert len(__initial_state.shape) == 2, \
            "Initial state is not a matrix."
        assert __initial_state.shape[0] == \
            __initial_state.shape[1], \
            "Initial state is not a square matrix."
        self._initial_state = __initial_state
        self._dimension = self._initial_state.shape[0]

        try:
            __start_time = float(start_time)
        except Exception as e:
            raise AssertionError("Start time must be a float.") from e
        self._start_time = __start_time

        assert self._bath.dimension == self._dimension and \
            self._system.dimension == self._dimension, \
            "Hilbertspace dimensions are unequal: " \
            + "system ({}), ".format(self._system.dimension) \
            + "initial state ({}), ".format(self._dimension) \
            + "and bath coupling ({}), ".format(self._bath.dimension)

        super().__init__(name, description, description_dict)

        __coupling_comm = commutator(self._bath._coupling_operator)
        __coupling_acomm = acommutator(self._bath._coupling_operator)
        self._coupling_comm = __coupling_comm.diagonal()
        self._coupling_acomm = __coupling_acomm.diagonal()

        self._dynamics = None
        self._backend_instance = None

        self._init_tempo_backend()

    def _init_tempo_backend(self):
        """Create and initialize the tempo backend. """
        dim = self._dimension
        initial_state = self._initial_state.reshape(dim**2)
        influence = self._influence
        unitary_transform = self._bath.unitary_transform
        propagators = self._propagators
        sum_north = np.array([1.0] * (dim**2))
        sum_west = np.array([1.0] * (dim**2))
        dkmax = self._parameters.dkmax
        epsrel = self._parameters.epsrel
        self._backend_instance = self._backend_class(
            initial_state,
            influence,
            unitary_transform,
            propagators,
            sum_north,
            sum_west,
            dkmax,
            epsrel,
            config=self._backend_config)

    def _init_dynamics(self):
        """Create a Dynamics object with metadata from the Tempo object. """
        name = None
        description = "computed from '{}' tempo".format(self.name)
        description_dict = {
            "tempo_type":str(type(self)),
            "tempo_name":self.name,
            "tempo_description":self.description,
            "tempo_description_dict":self.description_dict,
            "parameters_type":str(type(self._parameters)),
            "parameters_name":self._parameters.name,
            "parameters_description":self._parameters.description,
            "parameters_description_dict":self._parameters.description_dict,
            "system_type":str(type(self._system)),
            "system_name":self._system.name,
            "system_description":self._system.description,
            "system_description_dict":self._system.description_dict,
            "bath_type":str(type(self._bath)),
            "bath_name":self._bath.name,
            "bath_description":self._bath.description,
            "bath_description_dict":self._bath.description_dict,
            "correlations_type":str(type(self._bath.correlations)),
            "correlations_name": \
                self._bath.correlations.name,
            "correlations_description": \
                self._bath.correlations.description,
            "correlations_description_dict": \
                self._bath.correlations.description_dict,
            "backend_class":str(self._backend_class),
            "initial_state":self._initial_state,
            "dt":self._parameters.dt,
            "dkmax":self._parameters.dkmax,
            "epsrel":self._parameters.epsrel,
            }
        self._dynamics = Dynamics(name=name,
                                  description=description,
                                  description_dict=description_dict)

    def _influence(self, dk: int):
        """Create the influence functional matrix for a time step distance
        of dk. """

        if dk == 0:
            shape = "upper-triangle"
        else:
            shape = "square"

        dt = self._parameters.dt
        eta_dk = self._bath.correlations.correlation_2d_integral( \
            time_1=float(dk)*dt,
            delta=dt,
            shape=shape,
            epsrel=self._parameters.epsrel)
        op_p = self._coupling_acomm
        op_m = self._coupling_comm

        if dk == 0:
            infl = np.diag(np.exp(-op_m*(eta_dk.real*op_m \
                                          + 1j*eta_dk.imag*op_p)))
        else:
            infl = np.exp(-np.outer(eta_dk.real*op_m \
                                  + 1j*eta_dk.imag*op_p, op_m))

        return infl

    def _propagators(self, step: int):
        """Create the system propagators (first and second half) for the time
        step `step`. """
        dt = self._parameters.dt
        t = self._time(step)
        first_step = expm(self._system.liouvillian(t + dt / 4.0) * dt / 2.0).T
        second_step = expm(
            self._system.liouvillian(t + dt * 3.0 / 4.0) * dt / 2.0).T
        return first_step, second_step

    def _time(self, step: int):
        """Return the time that corresponds to the time step `step`. """
        return self._start_time + float(step) * self._parameters.dt

    @property
    def dimension(self) -> ndarray:
        """Hilbert space dimension. """
        return copy(self._dimension)

    def compute(self, end_time: float, progress_type: Text = None) -> None:
        """
        Propagate (or continue to propagete) the TEMPO tensor network to
        time `end_time`.

        Parameters
        ----------
        end_time: float
            The time to which the TEMPO should be computed.
        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.
        """
        try:
            __end_time = float(end_time)
        except Exception as e:
            raise AssertionError("End time must be a float.") from e

        dim = self._dimension
        if self._backend_instance.step is None:
            step, state = self._backend_instance.initialize()
            self._init_dynamics()
            self._dynamics.add(self._time(step), state.reshape(dim, dim))

        start_step = self._backend_instance.step
        end_step = int((end_time - self._start_time) / self._parameters.dt)
        num_step = max(0, end_step - start_step)

        progress = get_progress(progress_type)
        with progress(num_step) as prog_bar:
            while self._time(self._backend_instance.step) < __end_time:
                step, state = self._backend_instance.compute_step()
                self._dynamics.add(self._time(step), state.reshape(dim, dim))
                prog_bar.update(self._backend_instance.step - start_step)
            prog_bar.update(self._backend_instance.step - start_step)

    def get_dynamics(self) -> Dynamics:
        """Returns a copy of the computed dynamics. """
        return copy(self._dynamics)