def __init__(self, n_dims, sys_hamiltonian, sys_op, corr): """ Parameters ---------- n_dims : np.ndarray a vector representing the possible n sys_hamiltionian : np.ndarray H_s sys_op : X_s in in H_sb X_s (x) X_b corr : Correlation Correlation caused by X_b """ self.n_dims = n_dims self.k_max = len(n_dims) assert isinstance(corr, Correlation) assert self.k_max == corr.k_max self._i = len(n_dims) self._j = len(n_dims) + 1 self.corr = corr assert sys_op.ndim == 2 assert sys_op.shape == sys_hamiltonian.shape self.n_states = sys_op.shape[0] self.op = np.array(sys_op, dtype=DTYPE) self.h = np.array(sys_hamiltonian, dtype=DTYPE)
def normalize(self, forced=False): """Normalize the array of self. Only work when self.normalized. Set `forced` to `True` to normalize any way. """ array = self.array if array is None or (not self.normalized and not forced): return axis = self.axis if axis is None: norm = np.array(self.local_norm()) self.set_array(array / norm) ans = norm else: norm = linalg.norm shape = self.shape dim = shape.pop(axis) array = np.reshape(np.moveaxis(array, axis, 0), (dim, -1)) vecs = [] norm_list = [] for vec_i in array: for vec_j in vecs: vec_i -= vec_j * np.dot(np.conj(vec_j), vec_i) norm_ = norm(vec_i) vecs.append(vec_i / norm_) norm_list.append(norm_) array = np.array(vecs) array = np.moveaxis(np.reshape(array, [-1] + shape), 0, axis) self.set_array(array) ans = norm_list return ans
def mat_element(mps1, mpo, mps2): r"""Calculate the matrix element :math:`\langle 1 | O | 2 \rangle` by contract_from_left, where :math:`| 1 \rangle`, :math:`| 2 \rangle` are MPS, :math:`O` is MPO. Parameters mps1 : [(_, _, _) ndarray] a list of MPS matrixes mpo : [(_, _, _, _) ndarray] a list of MPO matrixes or None mps2 : [(_, _, _) ndarray] a list of MPS matrixes Returns ------- Matrix element :math:`\langle 1 | O | 2 \rangle` or :math:`\langle 1 | 2 \rangle` if mpo is None """ if mpo is not None: L = np.array([[[1.0]]]) for i in range(0, len(mpo)): L = contract_from_left(L, mps1[i], mpo[i], mps2[i]) return L[0, 0, 0] else: L = np.array([[1.0]]) assert len(mps1) == len(mps2) for i in range(0, len(mps1)): L = contract_from_left(L, mps1[i], None, mps2[i]) return L[0, 0]
def inner_hamiltonian(self, prefix='I'): omega_list, lambda_list, dim_list = [ getattr(self, name) for name in self.INNER ] coupling_list = list( np.sqrt(np.array(lambda_list) / 2) * np.array(omega_list)) self.inner_prefix = prefix return self.vibration_hamiltonian(omega_list, coupling_list, dim_list, prefix=prefix)
def solve(self, n_state=None): r"""Solve the TISE with the potential energy given. Parameters ---------- n_state : int, optional Number of states to be calculated, sorted by energy from low to high. Returns ------- energy : [float] eigenstates : np.ndarray See Also ________ DVR : Definition of all attributes. """ if n_state is None: n_state = self.n - 1 self._h_mat = self.h_mat() self.energy, v = eigsh(self._h_mat, k=n_state, which='SA') # self.energy, v = scipy.linalg.eigh( # self._h_mat, eigvals=(0, n_state - 1)) tmp = np.transpose(v) es = [] for i in tmp: vi = i if i[0] >= 0.0 else -i es.append(vi) self.eigenstates = np.array(es) return self.energy, self.eigenstates
def _contract_Rs(mpo, mps): R_list = [np.array([[[1.0]]])] for i in range(len(mpo) - 1, 0, -1): R_list.append( contract_from_right(R_list[-1], mps[i], mpo[i], mps[i])) return R_list
def _solve_ode(self, diff, y0, ode_inter, reformer, updater): OdeSolver = getattr(integrate, self.ode_method) in_real = self.ode_in_real t0 = self.time if t0 is None: t0 = 0.0 t1 = t0 + ode_inter if in_real: y0 = np.array((y0.real, y0.imag), dtype='float64') complex_diff = diff def diff(t, x): xc = np.array(x[0] + 1.0j * x[1], dtype='complex128') yc = complex_diff(t, xc) y = np.array((yc.real, yc.imag), dtype='float64') return y ode_solver = OdeSolver(diff, t0, y0, t1, vectorized=False) cmf_steps = self.cmf_steps for n in count(1): if ode_solver.status != 'running': logging.debug( __('* Propagation done. Average CMF steps: {}', n // cmf_steps)) break if n % cmf_steps == 0: if n >= self.max_ode_steps: msg = __('Reach ODE limit {}', n) logging.warning(msg) raise RuntimeWarning(msg) if reformer is not None: reformer() ode_solver.step() updater(ode_solver.y) return
def vibration_hamiltonian(self, omega_list, coupling_list, dim_list, prefix='V'): """ Returns ------- h_list: [[(str, ndarray)]] """ elec_leaf = self.elec_leaf projector = np.array([[0., 0.], [0., 1.]]) h_list = [] zipped = zip(dim_list, omega_list, coupling_list) for n, (dim, omega, c) in enumerate(zipped): ph = Phonon(dim, omega) leaf = prefix + str(n) self.leaves.append(leaf) self.dimensions[leaf] = dim # ph part h_list.append([[leaf, ph.hamiltonian]]) # e-ph part h_list.append([[leaf, -omega * ph.coordinate_operator], [elec_leaf, 2. * (c / omega) * projector]]) # e part h_list.append([[elec_leaf, 2. * (c / omega)**2 * projector]]) return h_list
def get_ext_wfns(n_states, wfns, op, search_method='krylov'): wfns = np.array(wfns) n_states = np.shape(wfns)[1] space = np.transpose(np.array(wfns)) vecs = np.transpose(np.array(wfns)) assert np.shape(space)[1] <= n_states if search_method == 'krylov': while True: space = linalg.orth(vecs) if np.shape(space)[0] >= n_states: break vecs = list(op @ vecs) np.concatenate((space, vecs), axis=1) psi = space[:, :n_states] return np.transpose(psi) else: raise NotImplementedError
def _calculate_dvr(self): # calculate grid points step_length = self.length / (self.n + 1) self.grid_points = np.array([self.a + step_length * i for i in range(1, self.n + 1)]) # calculate U matrix j = np.arange(1, self.n + 1)[:, None] a = np.arange(1, self.n + 1)[None, :] self._u_mat = (np.sqrt(2 / (self.n + 1)) * np.sin(j * a * np.pi / (self.n + 1))) return self.grid_points, self._u_mat
def _calc_diag(self, func): v = [] for i in range(self.dim): x = [] sub = self.subindex(i) for j, n in enumerate(sub): x.append(self.grid_points_list[j][n]) v.append(func(x)) return np.array(v)
def field(t): mu, tau, t_d, omega = [getattr(self, name) for name in self.FIELD] h = [[0., mu], [mu, 0.]] delta = t - t_d coeff = (np.exp(-4. * np.log(2.) * (delta / tau)**2) * np.cos(omega * delta)) ans = coeff * np.array(h) if ti_array is not None: ans += ti_array return ans
def __init__(self, gamma, lambda_, k_max=1, beta=None): if k_max != 1: raise NotImplementedError if beta is None: s = 0.0 else: # Naive high temperature s = 2 * lambda_ / beta a = -gamma * lambda_ super().__init__( k_max=k_max, beta=beta, coeff=np.array([s + 1.0j * a]), conj_coeff=np.array([s - 1.0j * a]), derivative=np.array([-gamma]), ) self.gamma = gamma self.lambda_ = lambda_ return
def asymm_coeff(self): def _a(k): if k == 0: a = -self.omega_0 * self.lambda_ else: a = 0 return a return np.array([_a(k) for k in range(self.k_max)])
def exp_coeff(self): """Masturaba Frequencies""" def _gamma(k): if k == 0: gamma = self.omega_0 else: gamma = 2.0 * np.pi * k / (self.beta * self.hbar) return gamma return np.array([_gamma(k) for k in range(self.k_max)])
def symm_coeff(self): v, l, bh = self.omega_0, self.lambda_, self.beta * self.hbar def _s(k): if k == 0: s = v * l / np.tan(bh * v / 2.0) else: s = 2.0 * self.spectrum(self.exp_coeff[k]) / bh return s return np.array([_s(k) for k in range(self.k_max)])
def __init__(self, k_max=None, beta=None, coeff=None, conj_coeff=None, derivative=None): """ k_max : int number of the terms in the basis functions beta : float Inverse temperature; 1 / (k_B T) """ self.spectrum = None self.k_max = k_max self.beta = beta self.coeff = np.array(coeff, dtype=DTYPE) if coeff is not None else None self.conj_coeff = np.array( conj_coeff, dtype=DTYPE) if conj_coeff is not None else None self.derivative = np.array( derivative, dtype=DTYPE) if derivative is not None else None return
def electron_hamiltonian(self, name='ELEC'): """ Returns ------- [(str, ndarray)] """ e1, e2, v = [getattr(self, n) for n in self.ELEC] leaf = str(name) h = [[e1, v], [v, e2]] self.elec_leaf = leaf self.leaves.append(leaf) self.dimensions[leaf] = 2 return [[[leaf, np.array(h)]]]
def _matvec(self, vec): v = np.reshape(vec, self.io_sizes) ans = np.zeros_like(v) for i, h_i in enumerate(self.h_list): v_i = np.swapaxes(v, -1, i) size_i = (self.io_sizes[:i] + self.io_sizes[i + 1:] + [self.io_sizes[i]]) v_i = np.reshape(v_i, (-1, self.io_sizes[i])) tmp = np.array(list(map(h_i.dot, v_i))) tmp = np.reshape(tmp, size_i) ans += np.swapaxes(tmp, -1, i) ans = np.reshape(ans, -1) if self.v_rst is not None: ans = ans + self.v_rst * vec return ans
def __init__(self, root, h_list, f_list=None, use_str_name=False): """ Parameters ---------- root : Tensor h_list : [[(Leaf, array)]] h_list is a list of `term`, where `term` is a list of tuple like `(Leaf, array)`. This is time independent part of Hamiltonian. f_list : [[(Leaf, float -> array)]] h_list is a list of `term`, where `term` is a list of tuple like `(Leaf, array)`. This is time dependent part of Hamiltonian. use_str_name : bool whether to use str to replace Leaf above. Default is False. """ self.root = root self.h_list = h_list self.f_list = f_list # For propogation purpose self.time = None self.overall_norm = 1 self.init_energy = None # Type check for term in h_list: for pair in term: if not isinstance(pair[0], Leaf) and not use_str_name: raise TypeError('0-th ary in tuple must be of type Leaf!') if np.array(pair[1]).ndim != 2: raise TypeError('1-th ary in tuple must be 2-D ndarray!') if f_list is not None: for term in f_list: for pair in term: if not isinstance(pair[0], Leaf) and not use_str_name: raise TypeError('0-th ary in tuple must be of type ' 'Leaf!') if not callable(pair[1]): raise TypeError('1-th ary in tuple must be callable!') if use_str_name: all_terms = h_list if f_list is None else h_list + f_list leaves_dict = {leaf.name: leaf for leaf in root.leaves()} for term in all_terms: for _ in range(len(term)): fst, snd = term.pop(0) term.append((leaves_dict[str(fst)], snd)) # Some cached data self.inv_density = {} # {Tensor: ndarray} self.env_ = {} # {(int, Tensor, int): ndarray} return
def _orthonormalize(self, use_svd=True): if use_svd and self._trial_vecs: trial_mat = np.transpose(np.array(self._trial_vecs)) trial_mat = orth(trial_mat) self._trial_vecs = list(np.transpose(trial_mat)) elif self._trial_vecs: vecs = [] for vec_i in self._trial_vecs: for vec_j in vecs: vec_i -= vec_j * np.dot(vec_j.conj(), vec_i) norm_ = norm(vec_i) if norm_ > 1.e-7: vecs.append(vec_i / norm_) self._trial_vecs = vecs return self._trial_vecs
def gen_extended_rho(self, rho): """Get rho_n from rho with the conversion: rho[n_0, ..., n_(k-1), i, j] Parameters ---------- rho : np.ndarray """ shape = list(rho.shape) assert len(shape) == 2 and shape[0] == shape[1] # Let: rho_n[0, i, j] = rho and rho_n[n, i, j] = 0 ext = np.zeros((np.prod(self.n_dims), )) ext[0] = 1 rho_n = np.reshape(np.tensordot(ext, rho, axes=0), list(self.n_dims) + shape) return np.array(rho_n, dtype=DTYPE)
def relaxation_hamiltonian(self, coupling_list, prefix='R'): """ Returns ------- h_list: [[(str, ndarray)]] """ omega_list, _, dim_list = [getattr(self, name) for name in self.INNER] elec_leaf = self.elec_leaf projector = np.array([[0., 1.], [1., 0.]]) h_list = [] zipped = zip(dim_list, omega_list, coupling_list) for n, (dim, omega, c) in enumerate(zipped): ph = Phonon(dim, omega) leaf = prefix + str(n) # e-ph part h_list.append([[leaf, omega * ph.coordinate_operator], [elec_leaf, (c / omega) * projector]]) return h_list
def _single_eom(self, tensor, n, cache=False): """C.f. `Multi-Configuration Time Dependent Hartree Theory: a Tensor Network Perspective`, p38. This method does not contain the `i hbar` coefficient. Parameters ---------- tensor : Tensor Must in a graph with all nodes' array set, including the leaves. n : int No. of Hamiltonian term. Return: ------- array : ndarray With the same shape with tensor.shape. """ partial_product = Tensor.partial_product partial_trace = Tensor.partial_trace partial_env = tensor.partial_env # Env Hamiltonians tmp = tensor.array for i in range(tensor.order): try: env_ = self.env_[(n, tensor, i)] except KeyError: env_ = partial_env(i, proper=True) if cache: self.env_[(n, tensor, i)] = env_ tmp = partial_product(tmp, i, env_) # For non-root nodes... if tensor.axis is not None: # Inversion axis, inv = tensor.axis, self.inv_density[tensor] tmp = partial_product(tmp, axis, inv) # Projection tmp_1 = np.array(tmp) array = tensor.array conj_array = np.conj(array) tmp = partial_trace(tmp, axis, conj_array, axis) tmp = partial_product(array, axis, tmp, j=1) tmp = (tmp_1 - tmp) return tmp
def _partial_transform(i, tensor, mat): r""" Parameters ---------- i : int tensor : (..., m_i, ...) ndarray mat : (n_i, m_i) ndarray Returns ------- tensor : (..., n_i, ...) ndarray """ tensor_shape = list(tensor.shape) shape = tensor_shape[:i] + tensor_shape[i + 1:] + [mat.shape[0]] v_i = np.swapaxes(tensor, -1, i) v_i = np.reshape(v_i, (-1, mat.shape[1])) v_i = np.array(list(map(mat.dot, v_i))) v_i = np.reshape(v_i, shape) v_i = np.swapaxes(v_i, -1, i) return v_i
def heisenberg(N, J=1.0, Jz=1.0, h=0): r"""Generate a MPO for Heisenberg Model. .. math:: H = \sum^{N-2}_{i=0} \frac{J}{2} (S^+_i S^-_{i+1} + S^-_i S^+_{i+1}) + J_z S^z_i S^z_{i+1} - \sum^{N-1}_{i=0} h S^z_i For 1-D antiferromagnetic, :math:`J = J_z = 1`. Parameters ---------- N : int number of sites. J : float coupling constant. Jz : float coupling constant in z-direction. h : float external magnetic field. Returns ------- mpo : [(5, 5, 2, 2) ndarray] A list of MPO matrixes. """ # Local operators I = np.eye(2) Z = np.zeros((2, 2)) Sz = np.array([[0.5, 0.0], [0.0, -0.5]]) Sp = np.array([[0., 0.], [1., 0.]]) Sm = np.array([[0., 1.], [0., 0.]]) # left-hand edge: 1*5 Wfirst = np.array( [[-h * Sz, (J / 2.) * Sm, (J / 2.) * Sp, (Jz / 2.) * Sz, I]]) # mid: 5*5 W = np.array([[I, Z, Z, Z, Z], [Sp, Z, Z, Z, Z], [Sm, Z, Z, Z, Z], [Sz, Z, Z, Z, Z], [-h * Sz, (J / 2.) * Sm, (J / 2.) * Sp, (Jz / 2.) * Sz, I]]) # right-hand edge: 5*1 Wlast = np.array([[I], [Sp], [Sm], [Sz], [-h * Sz]]) mpo = [Wfirst] + ([W] * (N - 2)) + [Wlast] return mpo
def lowering(self, vec): self.check_vec(vec) ans = np.zeros_like(vec) tmp = np.array([np.sqrt(i) for i in range(self.dim)]) * vec ans[:-1] = tmp[1:] return ans
def raising(self, vec): self.check_vec(vec) ans = np.zeros_like(vec) ans[1:] = vec[:-1] ans *= np.array([np.sqrt(i) for i in range(self.dim)]) return ans
def test_mctdh(fname=None): sys_leaf = Leaf(name='sys0') ph_leaves = [] for n, (omega, g) in enumerate(ph_parameters, 1): ph_leaf = Leaf(name='ph{}'.format(n)) ph_leaves.append(ph_leaf) def ph_spf(): t = Tensor(axis=0) t.name = 'spf' + str(hex(id(t)))[-4:] return t graph, root = huffman_tree(ph_leaves, obj_new=ph_spf, n_branch=2) try: graph[root].insert(0, sys_leaf) except KeyError: ph_leaf = root root = Tensor() graph[root] = [sys_leaf, ph_leaf] finally: root.name = 'wfn' root.axis = None stack = [root] while stack: parent = stack.pop() for child in graph[parent]: parent.link_to(parent.order, child, 0) if child in graph: stack.append(child) # Define the detailed parameters for the ML-MCTDH tree h_list = model.wfn_h_list(sys_leaf, ph_leaves) solver = MultiLayer(root, h_list) bond_dict = {} # Leaves for s, i, t, j in root.linkage_visitor(): if t.name.startswith('sys'): bond_dict[(s, i, t, j)] = 2 else: if isinstance(t, Leaf): bond_dict[(s, i, t, j)] = max_tier else: bond_dict[(s, i, t, j)] = rank_wfn solver.autocomplete(bond_dict) # set initial root array init_proj = np.array([[A, 0.0], [B, 0.0]]) / np.sqrt(A**2 + B**2) root_array = Tensor.partial_product(root.array, 0, init_proj, 1) root.set_array(root_array) solver = MultiLayer(root, h_list) solver.ode_method = 'RK45' solver.cmf_steps = solver.max_ode_steps # constant mean-field solver.ps_method = 'split' solver.svd_err = 1.0e-14 # Define the obersevable of interest logger = Logger(filename=prefix + fname, level='info').logger logger2 = Logger(filename=prefix + 'en_' + fname, level='info').logger for n, (time, r) in enumerate( solver.propagator( steps=count, ode_inter=dt_unit, split=True, )): if n % callback_interval == 0: t = Quantity(time).convert_to(unit='fs').value rho = r.partial_env(0, proper=False) logger.info("{} {} {} {} {}".format(t, rho[0, 0], rho[0, 1], rho[1, 0], rho[1, 1])) en = np.trace(rho @ model.h) logger2.info('{} {}'.format(t, en))
ph_parameters = [ #(Quantity(400, 'cm-1').value_in_au, Quantity(500, 'cm-1').value_in_au), #(Quantity(800, 'cm-1').value_in_au, Quantity(500, 'cm-1').value_in_au), #(Quantity(1200, 'cm-1').value_in_au, Quantity(500, 'cm-1').value_in_au), (Quantity(1600, 'cm-1').value_in_au, Quantity(500, 'cm-1').value_in_au), ] dof = len(ph_parameters) drude = Drude( gamma=Quantity(20, 'cm-1').value_in_au, lambda_=Quantity(400, 'cm-1').value_in_au, beta=beta, ) model = SBM( sys_ham=np.array([[-0.5 * e, v], [v, 0.5 * e]], dtype=DTYPE), sys_op=np.array([[-0.5, 0.0], [0.0, 0.5]], dtype=DTYPE), ph_parameters=ph_parameters, ph_dims=(dof * [max_tier]), bath_corr=drude, bath_dims=[max_tier], ) # init state A, B = 1.0, 1.0 wfn_0 = np.array([A, B]) / np.sqrt(A**2 + B**2) rho_0 = np.tensordot(wfn_0, wfn_0, axes=0) # Propagation dt_unit = Quantity(0.001, 'fs').value_in_au callback_interval = 100