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