示例#1
0
    def _initialize_dictionary(param, default_params, default_param_types,
                               name):
        """
        This is a utility function for initializing the central
        dictionary of the class. It checks the user inputs against both
        default values and the allowed types. 
        """
        param_new = copy.deepcopy(param)

        # Check that all user keys are suppported
        for key in param_new.keys():
            if not key in default_params.keys():
                raise UnsupportedRequest(key, name)

        # Loop over all possible keys
        for key in default_params.keys():
            if key in param_new.keys():
                # If user specified the wrong type --> raise error
                if not type(param_new[key]) in default_param_types[key]:
                    raise TypeError(
                        str(key) + "-->" + str(param_new[key]) +
                        " is of type " + str(type(param_new[key])) +
                        " not the supported types " +
                        str(default_param_types[key]))

            # If user did not specify, then insert default value
            else:
                param_new[key] = copy.deepcopy(default_params[key])

        return param_new
示例#2
0
    def get_noise(self, taxis_req):
        """
        This function simply returns the noise values for the selected times.

        NOTE: INTERPOLATION SHOULD BE IMPLEMENTED BY DEFAULT. USE FCSPLINE
              FROM RICHARD TO DO IT!

        PARAMETERS
        ----------
        1. taxis_req : list
                       a list of requested time points

        RETURNS
        -------
        1. noise : list
                   a list of list of noise at the requested time points
        """
        # Check that to within 'precision' resolution, all timesteps
        # requested are present on the calculated t-axis.
        it_list = []
        for t in taxis_req:
            test = np.abs(self.__t_axis - t) < precision
            if np.sum(test) == 1:
                it_list.append(np.where(test)[0][0])
            else:
                raise UnsupportedRequest(
                    "Off axis t-samples",
                    "when INTERP = False in the NoiseModel._get_noise()",
                )

        return self.__noise[:, np.array(it_list)]
示例#3
0
    def prepare_noise(self):
        """
        This function is defined for each specific noise model (children classes of
        HopsNoise class) and provides the specific rules for calculating a noise
        trajectory using

        PARAMETERS
        ----------
        None

        RETURNS
        -------
        None
        """
        # Check for locked status
        # -----------------------
        if self.__locked__:
            raise LockedException("NoiseModel.prepare_noise()")

        # Basic Calculation Parameters
        # ----------------------------
        n_lop = self.param["N_L2"]
        nstep_min = int(np.ceil(self.param["TLEN"] / self.param["TAU"])) + 1

        # FFT-FILTER NOISE MODEL
        # ----------------------
        if self.param["DIAGONAL"]:
            t_axis = np.arange(nstep_min) * self.param["TAU"]
            self.param["T_AXIS"] = t_axis

            # Define correlation function for each L-operator
            alpha = self._corr_func_by_lop_taxis(t_axis)

            # Initialize noise
            etas = np.zeros((n_lop, nstep_min), dtype=np.complex128)
            if self.param["SEED"] is not None:
                print("Noise Model initialized with SEED = ",
                      self.param["SEED"])
                rand_state = np.random.RandomState(seed=self.param["SEED"])

            for i in range(n_lop):
                if self.param["SEED"] is not None:
                    seed_i = rand_state.randint(0, 2**30)
                    print(i, "th seed is: ", seed_i)
                else:
                    seed_i = None
                etas[i, :] = self.fft_filter_noise_diagonal(alpha[i, :],
                                                            seed=seed_i)
            self._noise = NumericNoiseTrajectory(etas, t_axis)

            # A noise model has be explicitly calculated. All further modifications
            # to this class should be blocked.
            self.__locked__ = True
        else:
            raise UnsupportedRequest("Non-diagonal FFT Filter in",
                                     type(self).__name__)
示例#4
0
    def apply_filter(self, list_aux, filter_name, params):
        """
        This is a function that implements a variety of different hierarchy filtering
        methods. In all cases, this filtering begins with the current list of auxiliaries
        and then prunes down from that list according to a set of rules.

        PARAMETERS
        ----------
        1. list_aux : list
                      the list of auxiliaries that needs to be filtered
        2. filter_name : str
                         name of filter
        3. params : list
                    the list of parameters for the filter

        RETURNS
        -------
        1. list_aux : list
                      a list of filtered auxiliaries


        ALLOWED LIST OF FILTERS:
        - Triangular: bolean_by_mode, [kmax, kdepth]
             If the mode has a non-zero value, then it is kept only if
             the value is less than kmax and the total auxillary is less
             than kdepth. This essentially truncates some modes at a lower
             order than the other modes.
        - LongEdge: bolean_by_mode, [kmax, kdepth]
             Beyond kdepth, only keep the edge terms upto kmax.
        """
        if filter_name == "Triangular":
            list_aux = filter_aux_triangular(
                list_aux=list_aux,
                list_boolean_by_mode=params[0],
                kmax=params[1][0],
                kdepth=params[1][1],
            )
        elif filter_name == "LongEdge":
            list_aux = filter_aux_longedge(
                list_aux=list_aux,
                list_boolean_by_mode=params[0],
                kmax=params[1][0],
                kdepth=params[1][1],
            )
        elif filter_name == "Markovian":
            list_aux = filter_markovian(list_aux=list_aux, list_boolean=params)
        else:
            raise UnsupportedRequest(filter_name, "hierarchy_static_filter")

        # Update STATIC_FILTERS parameters if needed
        # ------------------------------------------
        if not ([filter_name, params] in self.param["STATIC_FILTERS"] or
                (filter_name, params) in self.param["STATIC_FILTERS"]):
            self.param["STATIC_FILTERS"].append((filter_name, params))

        return list_aux
示例#5
0
    def __init__(self, eom_params):
        """
        A self initializing function that defines the normalization condition and
        checks the adaptive definition.

        PARAMETERS
        ----------
        1. eom_params: a dictionary of user-defined parameters
            a. TIME_DEPENDENCE : boolean          [Allowed: False]
                                 defining time-dependence of system Hamiltonian
            b. EQUATION_OF_MOTION : str           [Allowed: NORMALIZED NONLINEAR, LINEAR]
                                    The hops equation that is being solved
            c. ADAPTIVE_H : boolean               [Allowed: True, False]
                            Boolean that defines if the hierarchy should be
                           adaptively updated
            d. ADAPTIVE_S : boolean               [Allowed: True, False]
                            Boolean that defines if the system should be
                            adaptively updated.
            e. DELTA_H : float                    [Allowed: >0]
                         The threshold value for the adaptive hierarchy
            f. DELTA_S : float                    [Allowed: >0]
                         The threshold value for the adaptive system

        RETURNS
        -------
        None
        """
        self._default_param = EOM_DICT_DEFAULT
        self._param_types = EOM_DICT_TYPES
        self.param = eom_params

        # Define Normalization condition
        # ------------------------------
        if self.param["EQUATION_OF_MOTION"] == "NORMALIZED NONLINEAR":
            self.normalized = True
        elif self.param["EQUATION_OF_MOTION"] == "LINEAR":
            self.normalized = False
        else:
            raise UnsupportedRequest(
                "EQUATION_OF_MOTION =" + self.param["EQUATION_OF_MOTION"],
                type(self).__name__,
            )

        # Check Adaptive Definition
        # -------------------------
        if self.param["ADAPTIVE_H"] or self.param["ADAPTIVE_S"]:
            self.param["ADAPTIVE"] = True
        else:
            self.param["ADAPTIVE"] = False
示例#6
0
    def __init__(
        self,
        system_param,
        eom_param={},
        noise_param={},
        hierarchy_param={},
        integration_param=dict(INTEGRATOR="RUNGE_KUTTA",
                               INCHWORM=False,
                               INCHWORM_MIN=5),
    ):
        """
        This class contains four classes:
        1. Class: HopsNoise1 (Hierarchy Noise)
          * Class: NoiseTrajectory
        2. Class: HopsNoise2 (Optional Unitary Noise )
          * Class: NoiseTrajectory
        3. Class: HopsBasis
          * Class: HopsHierarchy
          * Class: HopsSystem
          * Class: HopsEOM
        4. Class: HopsStorage

        INPUTS:
        -------
        1. system_param: dictionary of user-defined system parameters
            [see hops_system.py]
            * HAMILTONIAN
            * GW_SYSBATH
            * CORRELATION_FUNCTION_TYPE
            * LOPERATORS
            * CORRELATION_FUNCTION
        2. eom_parameters: dictionary of user-defined eom parameters
            [see hops_eom.py]
            * EQUATION_OF_MOTION
            * ADAPTIVE_H
            * ADAPTIVE_S
            * DELTA_H
            * DELTA_S
        3. noise_parameters: dictionary of user-defined noise parameters
            [see hops_noise.py]
            * SEED
            * MODEL
            * TLEN
            * TAU
            * INTERP
        4. hierarchy_parameters: dictionary of user-defined hierarchy parameters
            [see hops_hierarchy.py]
            * MAXHIER
            * STATIC_FILTERS
        5. integration_parameters: dictionary of user-defined integration parameters
            [see integrator_rk.py]
            * INTEGRATOR
            * INCHWORM
            * INCHWORM_MIN

        FUNCTIONS:
        ----------
        1. initialize(psi_0): this function prepares the class for actually running a
                              a calculation by instantiating a specific hierarchy, pre-
                              paring the EOM matrices, and defining the initial
                              condition.
        2. propagate(t,dt): this function calculates the time evolution of psi(t)
                            according to the EOM from the current time to the time
                            "t" in steps of "dt."

        PROPERTIES:
        -----------
        1. t         - the current time of the simulation
        2. psi       - the current system wave function
        3. psi_traj  - the full trajectory of the system wave function
        4. phi       - the current full state of the hierarchy
        5. t_axis    - the time points at which psi_traj has been stored
        """
        # Instantiate sub-classes
        # -----------------------
        eom = HopsEOM(eom_param)
        system = HopsSystem(system_param)
        hierarchy = HopsHierarchy(hierarchy_param, system.param)
        self.basis = HopsBasis(system, hierarchy, eom)
        self.noise1 = prepare_noise(noise_param, self.basis.system.param)
        if "L_NOISE2" in system.param.keys():
            self.noise2 = prepare_noise(noise_param,
                                        self.basis.system.param,
                                        flag=2)
        else:
            noise_param2 = {
                "TLEN": noise_param["TLEN"],
                "TAU": noise_param["TAU"],
                "MODEL": "ZERO",
            }
            self.noise2 = prepare_noise(noise_param2,
                                        self.basis.system.param,
                                        flag=1)

        # Define integration method
        # -------------------------
        self._inchworm = integration_param["INCHWORM"]
        self._inchworm_count = 0
        self._inchworm_min = integration_param["INCHWORM_MIN"]

        if integration_param["INTEGRATOR"] == "RUNGE_KUTTA":
            from mesohops.dynamics.integrator_rk import (
                runge_kutta_step,
                runge_kutta_variables,
            )

            self.step = runge_kutta_step
            self.integration_var = runge_kutta_variables
            self.integrator_step = 0.5

        else:
            raise UnsupportedRequest(
                ("Integrator of type " + str(integration_param["INTEGRATOR"])),
                type(self).__name__,
            )

        # LOCKING VARIABLE
        self.__initialized__ = False
示例#7
0
    def error_flux_down(self, Φ, type):
        """
        A function that returns the error associated with neglecting flux from members of
        H_t to auxiliaries in H_t^C that arise due to flux from higher auxiliaries to
        lower auxiliaries.

        .. math::
            \sum_{n \in \mathcal{S}_{t}} \\left \\vert F[\\vec{k}-\\vec{e}_n] \\frac{g_n}{\gamma_n} N^{(\\vec{k})}_t \psi_{t,n}^{(\\vec{k})}\\right \\vert^2

        .. math::
            \sum_{\\vec{k} \in \mathcal{H}_{t}} \\left \\vert F[\\vec{k}-\\vec{e}_n] \\frac{g_n}{\gamma_n} N^{(\\vec{k})}_t \psi_{t,n}^{(\\vec{k})}\\right \\vert^2

        PARAMETERS
        ----------
        1. Φ : np.array
               The current state of the hierarchy
        2. type: string
                 'H' - Hierarchy type calculation
                 'S' - State type calculation

        RETURNS
        -------
        1. E2_flux_down_error : np.array
                                the error induced by neglecting flux from H_t (or H_S)
                                to auxiliaries with higher summed index in H_t^C.
        """
        # Constants
        # ---------
        list_new_states = [
            self.system.list_state_indices_by_hmode[:, 0] + i * self.n_state
            for i in range(self.n_hier)
        ]
        list_modes_from_site_index = [
            item for sublist in list_new_states for item in sublist
        ]

        # Reshape hierarchy (to matrix)
        # ------------------------------
        P2_pop_site = (
            np.abs(np.asarray(Φ).reshape([self.n_state, self.n_hier], order="F")) ** 2
        )
        P1_aux_norm = np.sqrt(np.sum(P2_pop_site, axis=0))
        P2_modes_from0 = np.asarray(Φ)[
            np.tile(self.system.list_state_indices_by_hmode[:, 0], self.n_hier)
        ]
        P2_pop_modes_down_1 = (np.abs(P2_modes_from0) ** 2).reshape(
            [self.n_hmodes, self.n_hier], order="F"
        )
        P1_modes = np.asarray(Φ)[list_modes_from_site_index]
        P2_pop_modes = np.abs(P1_modes).reshape([self.n_hmodes, self.n_hier], order="F")

        # Get flux factors
        # ----------------
        G2_bymode = np.array(self.system.g)[
            np.tile(list(range(self.n_hmodes)), self.n_hier)
        ].reshape([self.n_hmodes, self.n_hier], order="F")
        W2_bymode = np.array(self.system.w)[
            np.tile(list(range(self.n_hmodes)), self.n_hier)
        ].reshape([self.n_hmodes, self.n_hier], order="F")

        F2_filter_aux = np.array(
            [
                [
                    1 if aux[self.system.list_absindex_mode[i_mode_rel]] - 1 >= 0 else 0
                    for aux in self.hierarchy.auxiliary_list
                ]
                for i_mode_rel in range(self.n_hmodes)
            ]
        )

        if type == "H":
            # Hierarchy Type Downward Flux
            # ============================
            E2_flux_down_error = (
                np.real(
                    F2_filter_aux
                    * np.abs(G2_bymode / W2_bymode)
                    * (P2_pop_modes_down_1 * P1_aux_norm[None, :] + P2_pop_modes)
                )
                / hbar
            )
        elif type == "S":
            # State Type Downward Flux
            # ========================
            # Construct <L_m> term
            # --------------------
            E2_lm = np.tile(
                np.sum(
                    F2_filter_aux * np.abs(G2_bymode / W2_bymode) * P2_pop_modes_down_1,
                    axis=0,
                ),
                [self.n_state, 1],
            )

            # Map Error to States
            # -------------------
            M2_state_from_mode = np.zeros([self.n_state, self.system.n_hmodes])
            M2_state_from_mode[
                self.system.list_state_indices_by_hmode[:, 0],
                np.arange(np.shape(self.system.list_state_indices_by_hmode)[0]),
            ] = 1
            E2_flux_down_error = (
                M2_state_from_mode
                @ np.real(F2_filter_aux * np.abs(G2_bymode / W2_bymode) * P2_pop_modes)
                / hbar
            )
            E2_flux_down_error += E2_lm * P2_pop_site / hbar
        else:
            E2_flux_down_error = 0
            raise UnsupportedRequest(type, "error_flux_down")

        return E2_flux_down_error
示例#8
0
    def _prepare_derivative(
        self,
        system,
        hierarchy,
        list_stable_aux=None,
        list_add_aux=None,
        list_stable_state=None,
        list_old_absindex_L2=None,
        n_old=None,
        permute_index=None,
        update=False,
    ):
        """
        This function will prepare a new derivative function that performs an update
        on previous super-operators.

        PARAMETERS
        ----------
        1. system : HopsSystems object
                    an instance of HopsSystem
        2. hierarchy : HopsHierarchy object
                       an instance of HopsHierarchy
        3. list_stable_aux : list
                             list of current auxiliaries that were also present in
                             the previous basis
        4. list_add_aux : list
                          list of additional auxiliaries needed for the current basis
        5. list_stable_state : list
                               list of current states that were also present in the
                               previous basis
        6. list_old_absindex_L2 : list
                                  list of the absolute indices of L operators in
                                  the last basis
        7. n_old : int
                   multiplication of the previous hierarchy and system dimensions
        8. permute_index : list
                           list of rows and columns of non zero entries that define
                           a permutation matrix
        9. update : boolean
                    True = updating adaptive calculation,
                    False = non-adaptive calculation

        RETURNS
        -------
        1. dsystem_dt : function
                        a function that returns the derivative of phi and z_mem based
                        on if the calculation is linear or nonlinear
        """

        # Prepare Super-Operators
        # -----------------------
        if not update:
            self.K2_k, self.Z2_k, self.K2_kp1, self.Z2_kp1, self.K2_km1 = calculate_ksuper(
                system, hierarchy)
        else:
            self.K2_k, self.Z2_k, self.K2_kp1, self.Z2_kp1, self.K2_km1 = update_ksuper(
                self.K2_k,
                self.Z2_k,
                self.K2_kp1,
                self.Z2_kp1,
                self.K2_km1,
                list_stable_aux,
                list_add_aux,
                list_stable_state,
                list_old_absindex_L2,
                system,
                hierarchy,
                n_old,
                permute_index,
            )

        # Combine Sparse Matrices
        # -----------------------
        K2_stable = self.K2_k + self.K2_kp1 + self.K2_km1
        list_L2 = system.list_L2_coo  # list_L2
        if self.param["EQUATION_OF_MOTION"] == "NORMALIZED NONLINEAR":
            nmode = len(hierarchy.auxiliary_list[0])
            list_tuple_index_phi1_L2_mode = [
                (
                    hierarchy._aux_index(
                        hierarchy._const_aux_edge(system.list_absindex_mode[i],
                                                  1, nmode)),
                    index_L2,
                    i,
                ) for (i, index_L2) in enumerate(system.list_index_L2_by_hmode)
                if (hierarchy._const_aux_edge(system.list_absindex_mode[i], 1,
                                              nmode) in
                    hierarchy.auxiliary_list)
            ]

            def dsystem_dt(
                Φ,
                z_mem1_tmp,
                z_rnd1_tmp,
                z_rnd2_tmp,
                K2_stable=K2_stable,
                Z2_k=self.Z2_k,
                Z2_kp1=self.Z2_kp1,
                list_L2=list_L2,
                list_index_L2_by_hmode=system.list_index_L2_by_hmode,
                list_mode_absindex_L2=system.param["LIST_INDEX_L2_BY_HMODE"],
                nsys=system.size,
                list_absindex_L2=system.list_absindex_L2,
                list_absindex_mode=system.list_absindex_mode,
                list_g=system.param["G"],
                list_w=system.param["W"],
                list_tuple_index_phi1_L2_mode=list_tuple_index_phi1_L2_mode,
            ):
                """
                This is the core function for calculating the time-evolution of the
                wave function. The logic here becomes slightly complicated because
                we need use both the relative and absolute indices at different points.

                z_hat1_tmp : relative
                z_rnd1_tmp : absolute
                z_rnd2_tmp: absolute
                z_mem1_tmp : absolute
                list_avg_L2 : relative
                Z2_k, Z2_kp1 : relative
                Φ_deriv : relative
                z_mem1_deriv : absolute

                The nonlinear evolution equation used to perform this calculation
                takes the following form:
                ~
                Ψ̇_t^(k)=(-iH-kw+(z~_t)L)ψ_t^(k) + κα(0)Lψ_t^(k-1) - (L†-〈L†〉_t)ψ_t^(k+1)
                with z~ = z^* + ∫ds(a^*)(t-s)〈L†〉
                A super operator notation is implemented in this code.

                PARAMETERS
                ----------
                1. Φ : np.array
                       current full hierarchy
                2. z_mem1_tmp : np.array
                                array of memory values for each mode
                3. z_rnd1_tmp : np.array
                                array of random noise corresponding to NOISE1 for the
                                set of time points required in the integration
                4. z_rnd2_tmp : np.array
                                array of random noise corresponding to NOISE2 for the
                                set of time points required in the integration
                5. K2_stable : np.array
                               the component of the super operator that does not depend
                               on noise
                6. Z2_k : np.array
                          the component of the super operator that is multiplied by
                          noise z and maps the Kth hierarchy to the Kth hierarchy
                7. Z2_kp1 : np.array
                            the component of the super operator that is multiplied by
                            noise z and maps the (K+1) hierarchy to the kth hierarchy
                8. list_L2 : list
                             list of L operators
                9. list_index_L2_by_hmode : list
                                             list of length equal to the number of modes
                                             in the current hierarchy basis and each
                                             entry is an index for the relative list_L2.
                10. list_mode_absindex_L2 : list
                                            list of length equal to the number of
                                            'modes' in the current hierarchy basis and
                                            each entry is an index for the absolute
                                            list_L2.
                11. nsys : int
                          t he current dimension (size) of the system basis
                12. list_absindex_L2 : list
                                       list of length equal to the number of L-operators
                                       in the current system basis where each element
                                       is the index for the absolute list_L2
                13. list_absindex_mode : list
                                         list of length equal to the number of modes in
                                         the current system basis that corresponds to
                                         the absolute index of the modes
                14. list_g : list
                             list of pre exponential factors for bath correlation
                             functions
                15. list_w : list
                             list of exponents for bath correlation functions (w = γ+iΩ)
                16. list_tuple_index_phi1_index_L2 : list
                                                     list of tuples with each tuple
                                                     containing the index of the first
                                                     auxiliary mode (phi1) in the
                                                     hierarchy and the index of the
                                                     corresponding L operator

                RETURNS
                -------
                1. Φ_deriv : np.array
                             the derivative of phi with respect to time
                2. z_mem1_deriv : np.array
                                  the derivative of z_mem with respect to time
                """

                # Construct Noise Terms
                # ---------------------
                z_hat1_tmp = np.conj(
                    z_rnd1_tmp[list_absindex_L2]) + compress_zmem(
                        z_mem1_tmp, list_index_L2_by_hmode, list_absindex_mode)
                z_tmp2 = z_rnd2_tmp[list_absindex_L2]

                # Construct other fluctuating terms
                # ---------------------------------
                list_avg_L2 = [
                    operator_expectation(L, Φ[:nsys]) for L in list_L2
                ]  # <L>
                norm_corr = calc_norm_corr(
                    Φ,
                    z_hat1_tmp,
                    list_avg_L2,
                    list_L2,
                    nsys,
                    list_tuple_index_phi1_L2_mode,
                    np.array(list_g)[np.array(list_absindex_mode)],
                    np.array(list_w)[np.array(list_absindex_mode)],
                )

                # calculate dphi/dt
                # -----------------
                Φ_deriv = K2_stable @ Φ
                Φ_deriv -= norm_corr * Φ
                for j in range(len(list_avg_L2)):
                    # ASSUMING: L = L^*
                    Φ_deriv += (z_hat1_tmp[j] +
                                2 * np.real(z_tmp2[j])) * (Z2_k[j] @ Φ)
                    Φ_deriv += np.conj(list_avg_L2[j]) * (Z2_kp1[j] @ Φ)

                # calculate dz/dt
                # ---------------
                z_mem1_deriv = calc_delta_zmem(
                    z_mem1_tmp,
                    list_avg_L2,
                    list_g,
                    list_w,
                    list_mode_absindex_L2,
                    list_absindex_mode,
                )

                return Φ_deriv, z_mem1_deriv

        elif self.param["EQUATION_OF_MOTION"] == "LINEAR":

            def dsystem_dt(
                Φ,
                z_mem1_tmp,
                z_rnd1_tmp,
                z_rnd2_tmp,
                K2_stable=K2_stable,
                Z2_k=self.Z2_k,
                Z2_kp1=self.Z2_kp1,
            ):
                """
                NOTE: unlike in the non-linear case, this equation of motion does
                      NOT support an adaptive solution at the moment. Implementing
                      adaptive integration would require updating the adaptive
                      routine to handle non-normalized wave-functions.

                      A USER CALLING LINEAR AND ADAPTIVE SHOULD GET AN INFORMATIVE
                      ERROR MESSAGE, BUT THAT SHOULD BE HANDLED IN HopsTrajectory
                      CLASS.

                The linear evolution equation used to perform this calculation
                takes the following form:

                Ψ_̇t^(k)=(-iH-kw+((z^*)_t)L)ψ_t^(k) + κα(0)Lψ_t^(k-1) - (L^†)ψ_t^(k+1)
                A super operator notation is implemented in this code.

                PARAMETERS
                ----------
                1. Φ : np.array
                       current full hierarchy
                2. z_mem1_tmp : np.array
                                array of memory values for each mode
                3. z_rnd1_tmp : np.array
                                array of random noise corresponding to NOISE1 for the
                                set of time points required in the integration
                4. z_rnd2_tmp : np.array
                                array of random noise corresponding to NOISE2 for the
                                set of time points required in the integration
                5. K2_stable : np.array
                               the component of the super operator that does not depend
                               on noise
                6. Z2_k : np.array
                          the component of the super operator that is multiplied by
                          noise z and maps the Kth hierarchy to the Kth hierarchy
                7. Z2_kp1 : np.array
                            the component of the super operator that is multiplied by
                            noise z and maps the (K+1) hierarchy to the kth hierarchy

                RETURNS
                -------
                1. Φ_deriv : np.array
                             the derivative of phi with respect to time
                2. z_mem1_deriv : np.array
                                  the derivative of z_men with respect to time (=0)
                """

                # prepare noise
                # -------------
                z_hat1_tmp = np.conj(z_rnd1_tmp)

                # calculate dphi/dt
                # -----------------
                Φ_deriv = K2_stable @ Φ
                for j in range(len(Z2_k)):
                    Φ_deriv += (z_hat1_tmp[j] +
                                2 * np.real(z_rnd2_tmp[j])) * (Z2_k[j] @ Φ)

                # calculate dz/dt
                # ---------------
                z_mem1_deriv = 0 * z_hat1_tmp

                return Φ_deriv, z_mem1_deriv

        else:
            raise UnsupportedRequest(
                "EQUATION_OF_MOTION =" + self.param["EQUATION_OF_MOTION"],
                type(self).__name__,
            )

        self.dsystem_dt = dsystem_dt

        return dsystem_dt