def __call__(self, parser, ns, value, option_string=None): E = ns._E Emap = strmap(float, value, E.min(), E.max()) def Eindex(e): return np.abs(E - e).argmin() # Convert to actual indices E = [] for begin, end in Emap: if begin is None and end is None: ns._Erng = None return elif begin is None: E.append(range(Eindex(end)+1)) elif end is None: E.append(range(Eindex(begin), len(E))) else: E.append(range(Eindex(begin), Eindex(end)+1)) # Issuing unique also sorts the entries ns._Erng = np.unique(arrayi(E).flatten())
def __call__(self, func, k, *args, **kwargs): """ Return a functions return values as the Bloch unfolded equivalent according to this object Calling the `Bloch` object is a shorthand for the manual use of the `Bloch.unfold_points` and `Bloch.unfold` methods. This call structure is a shorthand for: >>> bloch = Bloch([2, 1, 2]) >>> k_unfold = bloch.unfold_points([0] * 3) >>> M = [func(*args, k=k) for k in k_unfold] >>> bloch.unfold(M, k_unfold) Notes ----- The function passed *must* have a keyword argument ``k``. Parameters ---------- func : callable method called which returns a matrix. k : (3, ) of float k-point to be unfolded *args : list arguments passed directly to `func` **kwargs: dict keyword arguments passed directly to `func` Returns ------- M : unfolded Bloch matrix """ K_unfold = self.unfold_points(k) M0 = func(*args, k=K_unfold[0, :], **kwargs) shape = (K_unfold.shape[0], M0.shape[0], M0.shape[1]) M = empty(shape, dtype=dtype_real_to_complex(M0.dtype)) M[0] = M0 del M0 for i in range(1, K_unfold.shape[0]): M[i] = func(*args, k=K_unfold[i, :], **kwargs) return bloch_unfold(_a.arrayi(self._bloch), K_unfold, M)
def set_bc(self, boundary=None, a=None, b=None, c=None): """ Set the boundary conditions on the grid Parameters ---------- boundary: (3, 2) or (3, ) or int, optional boundary condition for all boundaries (or the same for all) a: int or list of int, optional boundary condition for the first unit-cell vector direction b: int or list of int, optional boundary condition for the second unit-cell vector direction c: int or list of int, optional boundary condition for the third unit-cell vector direction Raises ------ ValueError : if specifying periodic one one boundary, so must the opposite side. """ if not boundary is None: if isinstance(boundary, Integral): self.bc = _a.arrayi([[boundary] * 2] * 3) else: self.bc = _a.asarrayi(boundary) if not a is None: self.bc[0, :] = a if not b is None: self.bc[1, :] = b if not c is None: self.bc[2, :] = c # shorthand for bc bc = self.bc[:, :] for i in range(3): if (bc[i, 0] == self.PERIODIC and bc[i, 1] != self.PERIODIC) or \ (bc[i, 0] != self.PERIODIC and bc[i, 1] == self.PERIODIC): raise ValueError(self.__class__.__name__ + '.set_bc has a one non-periodic and ' 'one periodic direction. If one direction is periodic, both instances ' 'must have that BC.')
def read_supercell_nsc(self): """ Reads the supercell number of supercell information """ # First line contains no no_s line = self.readline().split() no_s = int(line[1]) self.readline() self.readline() nsc = [0] * 3 def int_abs(i): return abs(int(i)) for _ in range(no_s): line = self.readline().split() isc = list(map(int_abs, line[12:15])) if isc[0] > nsc[0]: nsc[0] = isc[0] if isc[1] > nsc[1]: nsc[1] = isc[1] if isc[2] > nsc[2]: nsc[2] = isc[2] return arrayi([n * 2 + 1 for n in nsc])
def _read_class(self, cls, **kwargs): """ Reads a class model from a file """ # Ensure that the geometry is written geom = self.read_geometry() # Determine the type of delta we are storing... E = kwargs.get('E', None) ilvl, ik, iE = self._get_lvl_k_E(**kwargs) # Get the level lvl = self._get_lvl(ilvl) if iE < 0 and ilvl in [3, 4]: raise ValueError(f"Energy {E} eV does not exist in the file.") if ik < 0 and ilvl in [2, 4]: raise ValueError("k-point requested does not exist in the file.") if ilvl == 1: sl = [slice(None)] * 2 elif ilvl == 2: sl = [slice(None)] * 3 sl[0] = ik elif ilvl == 3: sl = [slice(None)] * 3 sl[0] = iE elif ilvl == 4: sl = [slice(None)] * 4 sl[0] = ik sl[1] = iE # Now figure out what data-type the delta is. if 'Redelta' in lvl.variables: # It *must* be a complex valued Hamiltonian is_complex = True dtype = np.complex128 elif 'delta' in lvl.variables: is_complex = False dtype = np.float64 # Get number of spins nspin = len(self.dimensions['spin']) # Now create the sparse matrix stuff (we re-create the # array, hence just allocate the smallest amount possible) C = cls(geom, nspin, nnzpr=1, dtype=dtype, orthogonal=True) C._csr.ncol = _a.arrayi(lvl.variables['n_col'][:]) # Update maximum number of connections (in case future stuff happens) C._csr.ptr = np.insert(_a.cumsumi(C._csr.ncol), 0, 0) C._csr.col = _a.arrayi(lvl.variables['list_col'][:]) - 1 # Copy information over C._csr._nnz = len(C._csr.col) C._csr._D = np.empty([C._csr.ptr[-1], nspin], dtype) if is_complex: for ispin in range(nspin): sl[-2] = ispin C._csr._D[:, ispin].real = lvl.variables['Redelta'][sl] * Ry2eV C._csr._D[:, ispin].imag = lvl.variables['Imdelta'][sl] * Ry2eV else: for ispin in range(nspin): sl[-2] = ispin C._csr._D[:, ispin] = lvl.variables['delta'][sl] * Ry2eV _mat_spin_convert(C) return C
def __init__(self, bloch): """ Create `Bloch` object """ self._bloch = _a.arrayi(bloch) self._bloch = np.where(self._bloch < 1, 1, self._bloch)
def initialize(self): """ Initialize the internal data-arrays used for efficient calculation of the real-space quantities This method should first be called *after* all options has been specified. If the user hasn't specified the ``bz`` value as an option this method will update the internal integration Brillouin zone based on the ``dk`` option. """ # Try and guess the directions unfold = self._unfold nsc = self.parent.nsc.copy() axes = self._options['axes'] if axes is None: if nsc[2] == 1: axes = _a.arrayi([0, 1]) elif nsc[1] == 1: axes = _a.arrayi([0, 2]) elif nsc[0] == 1: axes = _a.arrayi([1, 2]) else: raise ValueError(self.__class__.__name__ + '.initialize currently only supports a 2D real-space self-energy, hence the MonkhorstPack grid should reflect only 2 periodic directions.') self._options['axes'] = axes # Check that we have periodicity along the chosen axes nsc_sum = nsc[axes].sum() if nsc_sum == 1: raise ValueError(self.__class__.__name__ + '.initialize found no periodic directions for the chosen integration axes: {} and {}.'.format(*nsc[axes])) elif nsc_sum < 6: raise ValueError((self.__class__.__name__ + '.initialize found one periodic direction ' 'out of two for the chosen integration axes: {} and {}. ' 'For 1D systems the regular surface self-energy method is appropriate.').format(*nsc[axes])) if self._options['semi_axis'] is None and self._options['k_axis'] is None: # None of the axis has been described if nsc[axes[0]] > 3: k_ax = axes[0] s_ax = axes[1] elif nsc[axes[1]] > 3: k_ax = axes[1] s_ax = axes[0] else: # Choose the direction of k to be the smallest shortest sc = self.parent.sc.tile(unfold[axes[0]], axes[0]).tile(unfold[axes[1]], axes[1]) rcell = fnorm(sc.rcell)[axes] k_ax = axes[np.argmax(rcell)] if k_ax == axes[0]: s_ax = axes[1] else: s_ax = axes[0] self._options['semi_axis'] = s_ax self._options['k_axis'] = k_ax s_ax = 'ABC'[s_ax] k_ax = 'ABC'[k_ax] info(self.__class__.__name__ + '.initialize determined the semi-inf- and k-directions to be: {} and {}'.format(s_ax, k_ax)) elif self._options['k_axis'] is None: s_ax = self._options['semi_axis'] if s_ax == axes[0]: k_ax = axes[1] else: k_ax = axes[0] self._options['k_axis'] = k_ax k_ax = 'ABC'[k_ax] info(self.__class__.__name__ + '.initialize determined the k direction to be: {}'.format(k_ax)) elif self._options['semi_axis'] is None: k_ax = self._options['k_axis'] if k_ax == axes[0]: s_ax = axes[1] else: s_ax = axes[0] self._options['semi_axis'] = s_ax s_ax = 'ABC'[s_ax] info(self.__class__.__name__ + '.initialize determined the semi-infinite direction to be: {}'.format(s_ax)) k_ax = self._options['k_axis'] s_ax = self._options['semi_axis'] if nsc[s_ax] != 3: raise ValueError(self.__class__.__name__ + '.initialize found the self-energy direction to be ' 'incompatible with the parent object. It *must* have 3 supercells along the ' 'semi-infinite direction.') P0 = self.real_space_parent() V_atoms = self.real_space_coupling(True)[1] self._calc = { # The below algorithm requires the direction to be negative # if changed, B, C should be reversed below 'SE': RecursiveSI(self.parent, '-' + 'ABC'[s_ax], eta=self._options['eta']), # Used to calculate the real-space self-energy 'P0': P0.Pk(), # in sparse format 'S0': P0.Sk(), # in sparse format # Orbitals in the coupling atoms 'orbs': P0.a2o(V_atoms, True).reshape(-1, 1), } # Update the BrillouinZone integration grid in case it isn't specified if self._options['bz'] is None: # Update the integration grid # Note this integration grid is based on the big system. sc = self.parent.sc.tile(unfold[axes[0]], axes[0]).tile(unfold[axes[1]], axes[1]) rcell = fnorm(sc.rcell)[k_ax] nk = [1] * 3 nk[k_ax] = int(self._options['dk'] * rcell) self._options['bz'] = MonkhorstPack(sc, nk, trs=self._options['trs']) info(self.__class__.__name__ + '.initialize determined the number of k-points: {}'.format(nk[k_ax]))
def get_constraints(self, factor=0.95): """ Return contraints for the zeta channels """ # Now we define the constraints of the orbitals. def unpack(name): try: # split at name symbol, name, zeta = name.split(".") n = int(name[1]) l = int(name[3]) zeta = int(zeta[1:]) return symbol, n, l, zeta except: return None, None, None, None orb_R = {} # (n,l) = {1: idx-nlzeta=1, 2: idx-nlzeta=2} for i, v in enumerate(self.variables): symbol, n, l, zeta = unpack(v.name) if symbol is None: continue (orb_R.setdefault(symbol, {}).setdefault((n, l), {}).update({zeta: i})) def assert_bounds(i1, i2): v1 = self.variables[i1] v2 = self.variables[i2] b1 = v1.bounds b2 = v2.bounds if not np.allclose(b1, b2): raise ValueError( "Bounds for zeta must be the same due to normalization") # get two lists of neighbouring zeta's # Our constraint is that zeta cutoffs should be descending. zeta1, zeta2 = [], [] # v now contains a dictionary with indices for the zeta orbitals for atom in orb_R.values(): for z_idx in atom.values(): for i in range(2, max(z_idx.keys()) + 1): # this will request zeta-indices in order (zeta1, zeta2, ...) zeta1.append(z_idx[i - 1]) zeta2.append(z_idx[i]) assert_bounds(zeta1[-1], zeta2[-1]) zeta1 = arrayi(zeta1) zeta2 = arrayi(zeta2) # now create constraint def fun_factory(factor, zeta1, zeta2): def fun(v): # an inequality constraint must return a non-negative # zeta1.R * `factor` - zeta2.R >= 0. return v[zeta1] * factor - v[zeta2] return fun def jac_factory(factor, zeta1, zeta2): def jac(v): out = zerosd([len(zeta1), len(v)]) idx = arangei(len(zeta1)) # derivative of # zeta1.R * `factor` - zeta2.R >= 0. out[idx, zeta1] = factor out[idx, zeta2] = -1 return out return jac constr = [] constr.append({ "type": "ineq", "fun": fun_factory(factor, zeta1, zeta2), "jac": jac_factory(factor, zeta1, zeta2), }) return constr
def _r_geometry_multiple(self, steps, ret_data=False, squeeze=False): asteps = steps steps = dict((step, i) for i, step in enumerate(steps)) # initialize all things cell = [None] * len(steps) cell_set = [False] * len(steps) xyz_set = [False] * len(steps) atom = [None for _ in steps] xyz = [None for _ in steps] data = [None for _ in steps] data_set = [not ret_data for _ in steps] line = " " all_loaded = False while line != '' and not all_loaded: line = self.readline() if line.isspace(): continue kw = line.split()[0] if kw not in ("CONVVEC", "PRIMVEC", "PRIMCOORD"): continue step = _get_kw_index(line) if step != -1 and step not in steps: continue if step not in steps and step == -1: step = idstep = istep = None else: idstep = steps[step] istep = idstep if kw == "CONVVEC": if step is None: if not any(cell_set): cell_set = [True] * len(cell_set) else: continue elif cell_set[istep]: continue else: cell_set[istep] = True icell = _a.zerosd([3, 3]) for i in range(3): line = self.readline() icell[i] = line.split() if step is None: cell = [icell] * len(cell) else: cell[istep] = icell elif kw == "PRIMVEC": if step is None: cell_set = [True] * len(cell_set) else: cell_set[istep] = True icell = _a.zerosd([3, 3]) for i in range(3): line = self.readline() icell[i] = line.split() if step is None: cell = [icell] * len(cell) else: cell[istep] = icell elif kw == "PRIMCOORD": if step is None: raise ValueError(f"{self.__class__.__name__}" " contains an unindexed (or somehow malformed) 'PRIMCOORD'" " section but you've asked for a particular index. This" f" shouldn't happen. line:\n {line}" ) iatom = [] ixyz = [] idata = [] line = self.readline().split() for _ in range(int(line[0])): line = self.readline().split() if not xyz_set[istep]: iatom.append(int(line[0])) ixyz.append([float(x) for x in line[1:4]]) if ret_data and len(line) > 4: idata.append([float(x) for x in line[4:]]) if not xyz_set[istep]: atom[istep] = iatom xyz[istep] = ixyz xyz_set[istep] = True data[idstep] = idata data_set[idstep] = True all_loaded = all(xyz_set) and all(cell_set) and all(data_set) if not all(xyz_set): which = [asteps[i] for i in np.flatnonzero(xyz_set)] raise ValueError(f"{self.__class__.__name__} file did not contain atom coordinates for the following requested index: {which}") if ret_data: data = _a.arrayd(data) if data.size == 0: data.shape = (len(steps), len(xyz[0]), 0) xyz = _a.arrayd(xyz) cell = _a.arrayd(cell) atom = _a.arrayi(atom) geoms = [] for istep in range(len(steps)): if len(atom) == 0: geoms.append(si.Geometry(xyz[istep], sc=SuperCell(cell[istep]))) elif len(atom[0]) == 1 and atom[0][0] == -999: # should we perhaps do AtomUnknown? geoms.append(None) else: geoms.append(Geometry(xyz[istep], atoms=atom[istep], sc=SuperCell(cell[istep]))) if squeeze and len(steps) == 1: geoms = geoms[0] if ret_data: data = data[0] if ret_data: return geoms, data return geoms
def sc_off(self, sc_off): """ Set the supercell offset """ self._sc_off[:, :] = _a.arrayi(sc_off, order='C') self._update_isc_off()