def traingular_2d_hamiltonian(J1=-0e-21, k1=np.array([-000 * mu_B]), k1dir=np.array([[0.0, 0, 1.0]])): """ Isolated spin in an applied field. field:10T, time step: 1fs. Total time: 100 ps, No Langevin term. """ # make model atoms = Atoms( symbols="H", positions=[[0, 0, 0]], cell=[[1, 0, 0], [-1.0 / 2, np.sqrt(3) / 2, 0], [0, 0, 1]]) spin = np.array([[0, 1, 0]]) ham = SpinHamiltonian( cell=atoms.cell, pos=atoms.get_scaled_positions(), spinat=spin, zion=atoms.get_atomic_numbers()) ham.gilbert_damping = [1.0] J = { (0, 0, (1, 0, 0)): J1, (0, 0, (0, 1, 0)): J1, (0, 0, (1, 1, 0)): J1, (0, 0, (-1, 0, 0)): J1, (0, 0, (0, -1, 0)): J1, (0, 0, (-1, -1, 0)): J1, } ham.set_exchange_ijR(exchange_Jdict=J) ham.set_uniaxial_mca(k1, k1dir) return ham
def square_2d_hamiltonian(Jx=-5e-21, Jy=-5e-21, k1=np.array([-000 * mu_B]), k1dir=np.array([[0.0, 0, 1.0]])): atoms = Atoms( symbols="H", positions=[[0, 0, 0]], cell=[[1, 0, 0], [0, 1, 0], [0, 0, 1]]) spin = np.array([[0, 1, 0]]) ham = SpinHamiltonian( cell=atoms.cell, pos=atoms.get_scaled_positions(), spinat=spin, zion=atoms.get_atomic_numbers()) ham.gilbert_damping = [1.0] J = { (0, 0, (1, 0, 0)): Jx, (0, 0, (-1, 0, 0)): Jx, (0, 0, (0, 1, 0)): Jy, (0, 0, (0, -1, 0)): Jy, } ham.set_exchange_ijR(exchange_Jdict=J) ham.set_uniaxial_mca(k1, k1dir) return ham
def in_cell(atoms: Atoms, index=None, position=None): ''' Checks if `x` and `y` scaled coordinates are between 0 and 1 :param atoms: MUST have scaled_positions property :param index: atom index :param position: :return: bool ''' if index is not None: scaled = atoms.get_scaled_positions()[index][:2] elif position is not None: # scales position to cell scaled = np.linalg.solve(atoms.cell.T, position.T).T[:2] else: raise ValueError('at least one of `position` or `index` ' 'must be provided') return all(0 <= coord < 1 for coord in scaled)
def canting_1d_hamiltonian( J1=3e-21, #J2=0e-21, DMI1=[0, 0, 0e-21], DMI2=[0, 0, -0e-21], k1=np.array([-0 * mu_B]), k1dir=np.array([[0.0, 0.0, 1.0]]), plot_type='2d'): # make model atoms = Atoms(symbols="H", positions=[[0, 0, 0]], cell=[1, 1, 1]) spin = np.array([[0, 1, 0]]) ham = SpinHamiltonian( cell=atoms.cell, pos=atoms.get_scaled_positions(), spinat=spin, zion=atoms.get_atomic_numbers()) ham.gilbert_damping = [0.8] #ham.gyro_ratio=[1.0] J = { (0, 0, (1, 0, 0)): J1, (0, 0, (-1, 0, 0)): J1, #(0, 0, (2, 0, 0)): J2, #(0, 0, (-2, 0, 0)): J2, } ham.set_exchange_ijR(exchange_Jdict=J) k1 = k1 k1dir = k1dir ham.set_uniaxial_mca(k1, k1dir) sc_ham = ham.make_supercell(np.diag([2, 1, 1])) DMI1val = np.array(DMI1) DMI2val = np.array(DMI2) DMI = { (0, 1, (0, 0, 0)): DMI1val, (1, 0, (0, 0, 0)): -DMI1val, (0, 1, (-1, 0, 0)): -DMI2val, (1, 0, (1, 0, 0)): DMI2val, } sc_ham.set_dmi_ijR(dmi_ddict=DMI) return sc_ham
def cubic_3d_2site_hamiltonian(Jx=-0e-21, Jy=-0e-21, Jz=0e-21, DMI=[0, 0, 0e-21], k1=np.array([-0 * mu_B]), k1dir=np.array([[0.0, 0, 1.0]])): atoms = Atoms( symbols="H", positions=[[0, 0, 0]], cell=[[1, 0, 0], [0, 1, 0], [0, 0, 1]]) spin = np.array([[0, 1, 0]]) ham = SpinHamiltonian( cell=atoms.cell, pos=atoms.get_scaled_positions(), spinat=spin, zion=atoms.get_atomic_numbers()) ham.gilbert_damping = [0.8] J = { (0, 0, (1, 0, 0)): Jx, (0, 0, (-1, 0, 0)): Jx, (0, 0, (0, 1, 0)): Jy, (0, 0, (0, -1, 0)): Jy, (0, 0, (0, 0, 1)): Jz, (0, 0, (0, 0, -1)): Jz, } ham.set_exchange_ijR(exchange_Jdict=J) ham.set_uniaxial_mca(k1, k1dir) sc_ham = ham.make_supercell(np.diag([2, 1, 1])) DMIval = np.array(DMI) DMI = { (0, 1, (0, 0, 0)): DMIval, #(1, 0, (0, 0, 0)): -DMIval, (0, 1, (-1, 0, 0)): DMIval, #(1, 0, (1, 0, 0)): -DMIval, #(0, 0, (0, 1, 0)): DMIval, #(0, 0, (0, -1, 0)): -DMIval, #(0, 0, (-1, 0, 0)): -DMIval, #(0, 0, (1, 0, 0)): DMIval, } sc_ham.set_dmi_ijR(dmi_ddict=DMI) return sc_ham
def graphene_nanoribbon(n, m, type='zigzag', saturated=False, C_H=1.09, C_C=1.42, vacuum=None, magnetic=False, initial_mag=1.12, sheet=False, main_element='C', saturate_element='H'): """Create a graphene nanoribbon. Creates a graphene nanoribbon in the x-z plane, with the nanoribbon running along the z axis. Parameters: n: int The width of the nanoribbon. m: int The length of the nanoribbon. type: str The orientation of the ribbon. Must be either 'zigzag' or 'armchair'. saturated: bool If true, hydrogen atoms are placed along the edge. C_H: float Carbon-hydrogen bond length. Default: 1.09 Angstrom. C_C: float Carbon-carbon bond length. Default: 1.42 Angstrom. vacuum: None (default) or float Amount of vacuum added to non-periodic directions, if present. magnetic: bool Make the edges magnetic. initial_mag: float Magnitude of magnetic moment if magnetic. sheet: bool If true, make an infinite sheet instead of a ribbon (default: False) """ b = sqrt(3) * C_C / 4 arm_unit = Atoms(main_element + '4', pbc=(1, 0, 1), cell=[4 * b, 0, 3 * C_C]) arm_unit.positions = [[0, 0, 0], [b * 2, 0, C_C / 2.], [b * 2, 0, 3 * C_C / 2.], [0, 0, 2 * C_C]] zz_unit = Atoms(main_element + '2', pbc=(1, 0, 1), cell=[3 * C_C / 2.0, 0, b * 4]) zz_unit.positions = [[0, 0, 0], [C_C / 2.0, 0, b * 2]] atoms = Atoms() if type == 'zigzag': edge_index0 = np.arange(m) * 2 + 1 edge_index1 = (n - 1) * m * 2 + np.arange(m) * 2 if magnetic: mms = np.zeros(m * n * 2) for i in edge_index0: mms[i] = initial_mag for i in edge_index1: mms[i] = -initial_mag for i in range(n): layer = zz_unit.repeat((1, 1, m)) layer.positions[:, 0] -= 3 * C_C / 2 * i if i % 2 == 1: layer.positions[:, 2] += 2 * b layer[-1].position[2] -= b * 4 * m atoms += layer if magnetic: atoms.set_initial_magnetic_moments(mms) if saturated: H_atoms0 = Atoms(saturate_element + str(m)) H_atoms0.positions = atoms[edge_index0].positions H_atoms0.positions[:, 0] += C_H H_atoms1 = Atoms(saturate_element + str(m)) H_atoms1.positions = atoms[edge_index1].positions H_atoms1.positions[:, 0] -= C_H atoms += H_atoms0 + H_atoms1 atoms.cell = [n * 3 * C_C / 2, 0, m * 4 * b] elif type == 'armchair': for i in range(n): layer = arm_unit.repeat((1, 1, m)) layer.positions[:, 0] -= 4 * b * i atoms += layer if saturated: arm_right_saturation = Atoms(saturate_element + '2', pbc=(1, 0, 1), cell=[4 * b, 0, 3 * C_C]) arm_right_saturation.positions = [[ -sqrt(3) / 2 * C_H, 0, C_H * 0.5 ], [-sqrt(3) / 2 * C_H, 0, 2 * C_C - C_H * 0.5]] arm_left_saturation = Atoms(saturate_element + '2', pbc=(1, 0, 1), cell=[4 * b, 0, 3 * C_C]) arm_left_saturation.positions = [[ b * 2 + sqrt(3) / 2 * C_H, 0, C_C / 2 - C_H * 0.5 ], [b * 2 + sqrt(3) / 2 * C_H, 0, 3 * C_C / 2.0 + C_H * 0.5]] arm_right_saturation.positions[:, 0] -= 4 * b * (n - 1) atoms += arm_right_saturation.repeat((1, 1, m)) atoms += arm_left_saturation.repeat((1, 1, m)) atoms.cell = [b * 4 * n, 0, 3 * C_C * m] atoms.set_pbc([sheet, False, True]) atoms.set_scaled_positions(atoms.get_scaled_positions() % 1.0) if not sheet: atoms.cell[0] = 0.0 if vacuum: atoms.center(vacuum, axis=1) if not sheet: atoms.center(vacuum, axis=0) return atoms
# [ 24.49041667, -4.07833333, -16.32208333], # [ 18.37145833, 14.29020833, -24.48166667], # [ 24.49916667, 12.25541667, -20.39458333], # [ 18.36854167, 16.32791667, -30.60645833], # [ 19.0575 , 0.01166667, 5.45333333], # [ 23.13388889, 6.80888889, 1.36722222], # [ 35.3825 , 5.45333333, -16.31333333]]) # # Test the wrap function. scaled_positions = np.array([[2.0, 3.2, 4.3]]) cell = np.array([[5.43, 5.43, 0.0], [5.43, -5.43, 0.0], [0.00, 0.00, 40.0]]) atoms = Atoms(scaled_positions=scaled_positions, symbols=["Si"], cell=cell, pbc=[True, True, False]) atoms.wrap() correct_pos = np.array([0.0, 0.2, 4.3]) assert np.allclose(correct_pos, atoms.get_scaled_positions(wrap=False)) positions = np.array( [ [4.0725, -4.0725, -1.3575], [1.3575, -1.3575, -1.3575], [2.715, -2.715, 0.0], [4.0725, 1.3575, -1.3575], [0.0, 0.0, 0.0], [2.715, 2.715, 0.0], [6.7875, -1.3575, -1.3575], [5.43, 0.0, 0.0], ] ) cell = np.array([[5.43, 5.43, 0.0], [5.43, -5.43, 0.0], [0.00, 0.00, 40.0]]) atoms = Atoms(positions=positions, symbols=["Si"] * 8, cell=cell, pbc=[True, True, False])
class SislWrapper(): def __init__(self, sisl_hamiltonian, shift_fermi=None, spin=None): self.ham = sisl_hamiltonian self.shift_fermi = shift_fermi self.spin = spin self.orbs = [] self.orb_dict = defaultdict(lambda: []) g = self.ham._geometry _atoms = self.ham._geometry._atoms atomic_numbers = [] atom_positions = g.xyz self.cell = np.array(g.sc.cell) for ia, a in enumerate(_atoms): atomic_numbers.append(a.Z) self.atoms = Atoms(numbers=atomic_numbers, cell=self.cell, positions=atom_positions) xred = self.atoms.get_scaled_positions() sdict = list(symbol_number(self.atoms).keys()) if self.ham.spin.is_colinear: if spin is None: raise ValueError("For colinear spin, spin must be given") else: if spin is not None: raise ValueError( "For non-colinear spin and unpolarized spin, spin should be None" ) self.positions = [] if self.ham.spin.is_colinear: for ia, a in enumerate(_atoms): symnum = sdict[ia] orb_names = [] for x in a.orbitals: name = f"{symnum}|{x.name()}|{spin}" orb_names.append(name) self.positions.append(xred[ia]) self.orbs += orb_names self.orb_dict[ia] += orb_names self.norb = len(self.orbs) self.nbasis = self.norb elif self.ham.spin.is_spinorbit: for spin in ['up', 'down']: for ia, a in enumerate(_atoms): symnum = sdict[ia] orb_names = [] for x in a.orbitals: name = f"{symnum}|{x.name()}|{spin}" orb_names.append(name) self.positions.append(xred[ia]) self.orbs += orb_names self.orb_dict[ia] += orb_names self.norb = len(self.orbs) / 2 self.nbasis = len(self.orbs) else: for ia, a in enumerate(_atoms): symnum = sdict[ia] orb_names = [] for x in a.orbitals: name = f"{symnum}|{x.name()}|None" orb_names.append(name) self.positions.append(xred[ia]) self.orbs += orb_names self.orb_dict[ia] += orb_names self.norb = len(self.orbs) self.nbasis = len(self.orbs) self.positions = np.array(self.positions, dtype=float) def print_orbs(self): print(self.orb_dict) def solve(self, k): if self.spin is None: evals, evecs = self.ham.eigh(k=k, eigvals_only=False) else: evals, evecs = self.ham.eigh(k=k, spin=self.spin, eigvals_only=False) if self.shift_fermi: evals += self.shift_fermi return evals, evecs def Hk(self, k, format='dense'): if self.spin is not None: return self.ham.Hk(k, format=format, spin=self.spin) else: return self.ham.Hk(k, format=format) def solve_all(self, kpts, orth=False): evals = [] evecs = [] for ik, k in enumerate(kpts): if orth and self.ham.orthogonal: S = self.ham.Sk(k, format='dense') Smh = Lowdin(S) H = self.Hk(k, format='dense') Horth = Smh.T.conj() @ H @ Smh evalue, evec = eigh(Horth) else: evalue, evec = self.solve(k) evals.append(evalue) evecs.append(evec) return np.array(evals, dtype=float), np.array(evecs, dtype=complex, order='C') def get_fermi_level(self): if self.shift_fermi: return self.shift_fermi else: return 0.0
class phonon_unfolder: """ phonon unfolding class""" def __init__(self, atoms, supercell_matrix, eigenvectors, qpoints, tol_r=0.03, ndim=3, labels=None, compare=None, phase=True, ghost_atoms=None): """ Params: =================== atoms: The structure of supercell. supercell matrix: The matrix that convert the primitive cell to supercell. eigenvectors: The phonon eigenvectors. format np.array() index=[ikpts, ifreq, 3*iatoms+j]. j=0..2 qpoints: list of q-points, note the q points are in the BZ of the supercell. tol_r: tolerance. If abs(a-b) <r, they are seen as the same atom. ndim: number of dimensions. For 3D phonons, use ndim=3. For electrons(no spin), ndim=1. For spinors, use ndim=2 (TODO: spinor not tested. is it correct?). labels: labels of the basis. for 3D phonons, ndim can be set to 1 alternately, with labels set to ['x','y','z']*natoms. The labels are used to decide if two basis are identical by translation. (Not used for phonon) compare: how to decide the basis are identical (Not used for phonon) ghost_atoms: scaled positions of ghost atoms in case of vacancies. """ self._atoms = atoms self._scmat = supercell_matrix self._evecs = eigenvectors self._qpts = qpoints self._tol_r = tol_r self._ndim = ndim self._labels = labels self._trans_rs = None self._trans_indices = None if ghost_atoms is not None: print("adding ghosts") symbols = atoms.get_chemical_symbols() positions = list(atoms.get_scaled_positions()) cell = atoms.get_cell() for gatom in ghost_atoms: # sorry Rn, you are the chosen one to be the ghost. unlikely to appear in a phonon calculation. symbols.append('C') positions.append(gatom) self._atoms = Atoms(symbols, scaled_positions=positions, cell=cell) # set the eigenvectors to zeros for the ghosts. nkpt, nbranch, neigdim = self._evecs.shape evecs = np.zeros((nkpt, nbranch+ 3 * len(ghost_atoms), neigdim + 3 * len(ghost_atoms)), dtype=self._evecs.dtype) evecs[:,:neigdim, :neigdim]=self._evecs self._evecs=evecs self._make_translate_maps() self._phase = phase def _translate(self, evec, r): """ T(r) psi: r is integer numbers of primitive cell lattice matrix. Params: ================= evec: an eigen vector of supercell r: The translate vector Returns: ================ tevec: translated vector. """ pass def _make_translate_maps(self): """ find the mapping between supercell and translated cell. Returns: =============== A N * (ndim*natoms) array. index[i] is the mapping from supercell to translated supercell so that T(r_i) psi = psi[indices[i]]. TODO: vacancies/add_atoms not supported. How to do it? For vacancies, a ghost atom can be added. For add_atom, maybe we can just ignore them? Will it change the energy spectrum? """ a1 = Atoms(symbols='H', positions=[(0, 0, 0)], cell=[1, 1, 1]) sc = make_supercell(a1, self._scmat) rs = sc.get_scaled_positions() positions = np.array(self._atoms.get_scaled_positions()) indices = np.zeros( [len(rs), len(positions) * self._ndim], dtype='int32') for i, ri in enumerate(rs): inds = [] Tpositions = positions + np.array(ri) close_to_int = lambda x: np.all(np.abs(x - np.round(x)) < self._tol_r) for i_atom, pos in enumerate(positions): jatoms=None d=100000 for j_atom, Tpos in enumerate(Tpositions): dpos = Tpos - pos dj=np.linalg.norm(dpos - np.round(dpos)) if dj<d: d=dj jatom=j_atom #if close_to_int(dpos): #indices[i, j_atom * self._ndim:j_atom * self._ndim + # self._ndim] = np.arange( # i_atom * self._ndim, # i_atom * self._ndim + self._ndim) indices[i, jatom * self._ndim:jatom * self._ndim + self._ndim] = np.arange( i_atom * self._ndim, i_atom * self._ndim + self._ndim) self._trans_rs = rs self._trans_indices = indices print(indices) for ind in indices: print(len(ind), len(set(ind))) def get_weight(self, evec, qpt, G=np.array([0, 0, 0])): """ get the weight of a mode which has the wave vector of qpt and eigenvector of evec. W= sum_1^N < evec| T(r_i)exp(-I (K+G) * r_i| evec>, here G=0. T(r_i)exp(-I K r_i)| evec> = evec[indices[i]] """ weight = 0j N = len(self._trans_rs) for r_i, ind in zip(self._trans_rs, self._trans_indices): if self._phase: #weight += np.vdot(evec, evec[ind]*np.exp(-1j * np.dot(qpt+G,r_i)) ) /N #r_i =np.dot(self._scmat,r_i) weight += np.vdot(evec, evec[ind]) * np.exp( -1j * 2 * np.pi * np.dot(qpt + G, r_i)) / N else: weight += (np.vdot(evec, evec[ind]) ) / N * np.exp( -1j * 2 * np.pi * np.dot(G, r_i)) return weight.real def get_weights(self): """ Get the weight for all the modes. """ nqpts, nfreqs = self._evecs.shape[0], self._evecs.shape[1] weights = np.zeros([nqpts, nfreqs]) for iqpt in range(nqpts): for ifreq in range(nfreqs): weights[iqpt, ifreq] = self.get_weight( self._evecs[iqpt, :, ifreq], self._qpts[iqpt]) self._weights = weights return self._weights