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))
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)