def find_mic(v, cell, pbc=True): """Finds the minimum-image representation of vector(s) v using either one of two find mic algorithms depending on the given cell, v and pbc.""" cell = Cell(cell) pbc = cell.any(1) & pbc2pbc(pbc) dim = np.sum(pbc) v = np.asarray(v) single = v.ndim == 1 v = np.atleast_2d(v) if dim > 0: naive_find_mic_is_safe = False if dim == 3: vmin, vlen = naive_find_mic(v, cell) # naive find mic is safe only for the following condition if (vlen < 0.5 * min(cell.lengths())).all(): naive_find_mic_is_safe = True # hence skip Minkowski reduction if not naive_find_mic_is_safe: vmin, vlen = general_find_mic(v, cell, pbc=pbc) else: vmin = v.copy() vlen = np.linalg.norm(vmin, axis=1) if single: return vmin[0], vlen[0] else: return vmin, vlen
def ase_decoder_hook(dct): """JSON decoder hook for ase.Atoms de-serialization.""" if "__datetime__" in dct: return datetime.datetime.strptime(dct["__datetime__"], "%Y-%m-%dT%H:%M:%S.%f") if "__complex__" in dct: return complex(*dct["__complex__"]) if "__ndarray__" in dct: return create_ndarray(*dct["__ndarray__"]) # No longer used (only here for backwards compatibility): if "__complex_ndarray__" in dct: r, i = (np.array(x) for x in dct["__complex_ndarray__"]) return r + i * 1j if "__ase_objtype__" in dct: objtype = dct.pop("__ase_objtype__") dct = numpyfy(dct) if objtype == "cell": from ase.cell import Cell pbc = dct.pop("pbc", None) obj = Cell(**dct) if pbc is not None: obj._pbc = pbc else: raise RuntimeError("Do not know how to decode object type {} " "into an actual object".format(objtype)) assert obj.ase_objtype == objtype return obj return dct
def __init__(self, atoms, cellpar, na=1, nb=1, nc=1): """ Create a crystal object. A crystal consists of a unit cell and atoms with fractional coordinates. The atoms cartesian coordinates are calculated based on the unit cell (defined by the cell parameters `a`, `b`, `c`, `alpha`, `beta`, and `gamma`) on the fly. Parameters ---------- atoms: list A list of CIFAtom objects cellpar: array-like A 6-length array na, nb, nc: int Bookkeeping numbers for keeping track of how many times the crystal has been "multiplied" - no real use except for when writing MULTEM files """ self.atoms = atoms self.cellpar = np.array(cellpar, dtype=float) dummy_cell = Cell(np.eye(3)) self.cell = dummy_cell.new(cell=self.cellpar) self.na = na self.nb = nb self.nc = nc site_labels = set([atom.site_label for atom in self.atoms]) site_keys = site_labels self.site_dict = {} for label, key in zip(site_labels, site_keys): self.site_dict[label] = key
def _variant_name(self, a, b, c, alpha, beta, gamma): cell = Cell.new([a, b, c, alpha, beta, gamma]) icellpar = Cell(cell.reciprocal()).cellpar() kangles = kalpha, kbeta, kgamma = icellpar[3:] def raise_unconventional(): raise UnconventionalLattice( tri_angles_explanation.format(*kangles)) eps = self._eps if abs(kgamma - 90) < eps: if kalpha > 90 and kbeta > 90: var = '2a' elif kalpha < 90 and kbeta < 90: var = '2b' else: # Is this possible? Maybe due to epsilon raise_unconventional() elif all(kangles > 90): if kgamma > min(kangles): raise_unconventional() var = '1a' elif all(kangles < 90): # and kgamma > max(kalpha, kbeta): if kgamma < max(kangles): raise_unconventional() var = '1b' else: raise_unconventional() return 'TRI' + var
def find_niggli_ops(latcls, length_grid, angle_grid): niggli_ops = {} for lat in lattice_loop(latcls, length_grid, angle_grid): cell = lat.tocell() try: rcell, op = cell.niggli_reduce() except RuntimeError: print('Niggli reduce did not converge') continue assert op.dtype == int op_key = tuple(op.ravel()) if op_key in niggli_ops: niggli_ops[op_key] += 1 else: niggli_ops[op_key] = 1 rcell_test = Cell(op.T @ cell) rcellpar_test = rcell_test.cellpar() rcellpar = rcell.cellpar() err = np.abs(rcellpar_test - rcellpar).max() assert err < 1e-7, err return niggli_ops
def convert_units(self, new_units): r""" Converts the atomic positions in another units. Parameters ---------- new_units: str The new units in which the positions should be converted. Can either be "angstrom" or "atomic". """ if new_units not in ["angstrom", "atomic"]: raise ValueError("New units are not recognized.") if self.units == new_units: pass elif self.units == "atomic" and new_units == "angstrom": for atom in self: atom.position = atom.position * B_TO_ANG self.cell = Cell.new(self.cell * B_TO_ANG) elif self.units == "angstrom" and new_units == "atomic": for atom in self: atom.position = atom.position * ANG_TO_B self.cell = Cell.new(self.cell * ANG_TO_B) elif self.units == "reduced" and new_units == "atomic": for atom in self: atom.position = np.sum(atom.position * self.cell, axis=0) elif self.units == "reduced" and new_units == "angstrom": for atom in self: atom.position = np.sum(atom.position * self.cell * B_TO_ANG, axis=0) self.cell = Cell.new(self.cell * B_TO_ANG) else: raise NotImplementedError self.units = new_units
def kpath(cell, path=None, npoints=None, supercell_matrix=np.eye(3), eps=1e-3): mycell = Cell(cell) bpath = mycell.bandpath(path=path, npoints=npoints, eps=eps) kpts = bpath.kpts kpts = [np.dot(kpt, supercell_matrix) for kpt in kpts] x, X, knames = bpath.get_linear_kpoint_axis() return kpts, x, X, knames
def cell(self, cell): if len(cell) == 2: cell, angles = cell else: angles = None if isinstance(cell, Cell): self._cell = cell elif cell is None: self._cell = Cell.new() elif isinstance(cell, list) or isinstance(cell, np.ndarray): if isinstance(cell, list): cell = np.array([ abs(float(size)) if size not in [".inf", "inf"] else 0.0 for size in cell ]) if cell.size == 3 and angles is not None: if len(angles) == 3: cell = np.concatenate([cell, angles]) else: raise ValueError("Need three angles to define a cell.") if cell.size in [3, 6, 9]: self._cell = Cell.new(cell) else: raise ValueError( "Cell definition is not valid. See ase.cell.Cell documentation." ) else: raise ValueError( "Cell definition is not valid. See ase.cell.Cell documentation." )
def _path_lengths(path: str, cell: Cell, bands_point_num: int) -> List[int]: # Construct the metric for reciprocal space (based off Wannier90's "utility_metric" subroutine) recip_lat = 2 * math.pi * cell.reciprocal().array recip_metric = recip_lat @ recip_lat.T # Work out the lengths Wannier90 will assign each path (based off Wannier90's "plot_interpolate_bands" subroutine) kpath_pts: List[int] = [] kpath_len: List[float] = [] special_points = cell.bandpath().special_points path_list = path_str_to_list(path, special_points) for i, (start, end) in enumerate(zip(path_list[:-1], path_list[1:])): if start == ',': kpath_pts.append(0) elif end == ',': kpath_pts.append(1) else: vec = special_points[end] - special_points[start] kpath_len.append(math.sqrt(vec.T @ recip_metric @ vec)) if i == 0: kpath_pts.append(bands_point_num) else: kpath_pts.append(int(round(bands_point_num * kpath_len[-1] / kpath_len[0]))) return kpath_pts
def plot_magnon_band( self, kvectors=np.array([[0, 0, 0], [0.5, 0, 0], [0.5, 0.5, 0], [0, 0, 0], [.5, .5, .5]]), knames=['$\Gamma$', 'X', 'M', '$\Gamma$', 'R'], supercell_matrix=None, npoints=50, color='red', kpath_fname=None, Jq=False, ax=None, ): if ax is None: fig, ax = plt.subplots() kptlist = kvectors if knames is None and kvectors is None: # fully automatic k-path bp = Cell(self.cell).bandpath(npoints=npoints) spk = bp.special_points xlist, kptlist, Xs, knames = group_band_path(bp) elif knames is not None and kvectors is None: # user specified kpath by name bp = Cell(self.cell).bandpath(knames, npoints=npoints) spk = bp.special_points kpts = bp.kpts xlist, kptlist, Xs, knames = group_band_path(bp) else: # user spcified kpath and kvector. kpts, x, Xs = bandpath(kvectors, self.cell, npoints) spk = dict(zip(knames, kvectors)) xlist = [x] kptlist = [kpts] if supercell_matrix is not None: kptlist = [np.dot(k, supercell_matrix) for k in kptlist] print("High symmetry k-points:") for name, k in spk.items(): if name == 'G': name = 'Gamma' print(f"{name}: {k}") for kpts, xs in zip(kptlist, xlist): evals, evecs = self.solve_k(kpts, Jq=Jq) # Plot band structure nbands = evals.shape[1] emin = np.min(evals[:, 0]) for i in range(nbands): ax.plot(xs, (evals[:, i]) / 1.6e-22, color=color) ax.set_ylabel('Energy (meV)') ax.set_xlim(xlist[0][0], xlist[-1][-1]) ax.set_xticks(Xs) knames = [x if x != 'G' else '$\Gamma$' for x in knames] ax.set_xticklabels(knames) for x in Xs: ax.axvline(x, linewidth=0.6, color='gray') return ax
def test_line_lattice(): from ase.cell import Cell kx = Cell.new([5, 0, 0]).bandpath(path='GX', npoints=2).kpts kz = Cell.new([0, 0, 5]).bandpath(path='GX', npoints=2).kpts print(kx) print(kz) kx[1, 0] -= 0.5 kz[1, 2] -= 0.5 assert abs(kx).max() == 0.0 assert abs(kz).max() == 0.0
def celldiff(cell1, cell2): """Return a unitless measure of the difference between two cells.""" cell1 = Cell.ascell(cell1).complete() cell2 = Cell.ascell(cell2).complete() v1v2 = cell1.volume * cell2.volume scale = v1v2**(-1. / 3.) # --> 1/Ang^2 x1 = cell1 @ cell1.T x2 = cell2 @ cell2.T dev = scale * np.abs(x2 - x1).max() return dev
def check_single(name, cell, pbc=None): c = Cell(cell, pbc=pbc) try: lattice = c.get_bravais_lattice() except RuntimeError: print('error checking {}'.format(name)) raise name1 = lattice.name.lower() latname = name.split('@')[0] ok = latname == name1 print(name, '-->', name1, 'OK' if ok else 'ERR', c.cellpar()) assert ok, 'Expected {latname} but found {name1}'.format(latname, name1)
def test_bravais_2d_cell_pbc(): """Verify 2D Bravais lattice and band path versus pbc information.""" from ase.cell import Cell cell = Cell([[1.,0.,0.], [.1,1.,0.], [0.,0.,0.]]) lat = cell.get_bravais_lattice() print(cell.cellpar()) print(lat) assert lat.name == 'OBL' cell[2, 2] = 7 lat3d = cell.get_bravais_lattice() print(lat3d) assert lat3d.name == 'MCL' lat2d_pbc = cell.get_bravais_lattice(pbc=[1, 1, 0]) print(lat2d_pbc) assert lat2d_pbc.name == 'OBL' path = cell.bandpath() print(path) path2d = cell.bandpath(pbc=[1, 1, 0]) print(path2d) assert path2d.cell.rank == 2 assert path2d.cell.get_bravais_lattice().name == 'OBL'
def _get_neighbors(self, dx: np.ndarray) -> Iterator[np.ndarray]: pbc = self.atoms.pbc if self.cell is None or not np.all(self.cell == self.atoms.cell): self.cell = self.atoms.cell.array.copy() rcell, self.op = minkowski_reduce(complete_cell(self.cell), pbc=pbc) self.rcell = Cell(rcell) dx_sc = dx @ self.rcell.reciprocal().T offset = np.zeros(3, dtype=np.int32) for _ in range(2): offset += pbc * ((dx_sc - offset) // 1.).astype(np.int32) for ts in product(*[np.arange(-1 * p, p + 1) for p in pbc]): yield (np.array(ts) - offset) @ self.op
def test_o2b2o(): import pickle import sys import unittest from ase.db.core import object_to_bytes, bytes_to_object from ase.cell import Cell import numpy as np if sys.version_info < (3, 6): # pickles are not sorted raise unittest.SkipTest for o1 in [ 1.0, { 'a': np.zeros((2, 2), np.float32), 'b': np.zeros((0, 2), int) }, ['a', 42, True, None, np.nan, np.inf, 1j], Cell(np.eye(3)), { 'a': { 'b': { 'c': np.ones(3) } } } ]: p1 = pickle.dumps(o1) b1 = object_to_bytes(o1) o2 = bytes_to_object(b1) p2 = pickle.dumps(o2) print(o2) print(b1) print() assert p1 == p2, (o1, p1, p2, vars(o1), vars(p1), vars(p2))
def read_cml(fileobj): data = contract(json.load(fileobj)) atoms = Atoms() datoms = data['atoms'] atoms = Atoms(datoms['elements']['number']) if 'unitcell' in data: cell = data['unitcell'] a = cell['a'] b = cell['b'] c = cell['c'] alpha = cell['alpha'] beta = cell['beta'] gamma = cell['gamma'] atoms.cell = Cell.fromcellpar([a, b, c, alpha, beta, gamma]) atoms.pbc = True coords = contract(datoms['coords']) if '3d' in coords: positions = np.array(coords['3d']).reshape(len(atoms), 3) atoms.set_positions(positions) else: positions = np.array(coords['3dfractional']).reshape(len(atoms), 3) atoms.set_scaled_positions(positions) yield atoms
def test_bandstructure_transform_mcl(testdir): # Test that bandpath() correctly transforms the band path from # reference (canonical) cell to actual cell provided by user. def _atoms(cell): atoms = Atoms(cell=cell, pbc=True) atoms.calc = FreeElectrons() return atoms # MCL with beta > 90, which is a common convention -- but ours is # alpha < 90. We want the bandpath returned by that cell to yield the # exact same band structure as our own (alpha < 90) version of the # same cell. cell = Cell.new([3., 5., 4., 90., 110., 90.]) lat = cell.get_bravais_lattice() density = 10.0 cell0 = lat.tocell() path0 = lat.bandpath(density=density) print(cell.cellpar().round(3)) print(cell0.cellpar().round(3)) with workdir('files', mkdir=True): bs = calculate_band_structure(_atoms(cell), cell.bandpath(density=density)) bs.write('bs.json') # bs.plot(emin=0, emax=20, filename='fig.bs.svg') bs0 = calculate_band_structure(_atoms(cell0), path0) bs0.write('bs0.json') # bs0.plot(emin=0, emax=20, filename='fig.bs0.svg') maxerr = np.abs(bs.energies - bs0.energies).max() assert maxerr < 1e-12, maxerr
def create_ase_object(objtype, dct): # We just try each object type one after another and instantiate # them manually, depending on which kind it is. # We can formalize this later if it ever becomes necessary. if objtype == 'cell': from ase.cell import Cell dct.pop('pbc', None) # compatibility; we once had pbc obj = Cell(**dct) elif objtype == 'bandstructure': from ase.spectrum.band_structure import BandStructure obj = BandStructure(**dct) elif objtype == 'bandpath': from ase.dft.kpoints import BandPath obj = BandPath(path=dct.pop('labelseq'), **dct) elif objtype == 'atoms': from ase import Atoms obj = Atoms.fromdict(dct) elif objtype == 'densityofstates': from ase.dft.dos import DOS obj = DOS(**dct) elif objtype == 'griddoscollection': from ase.spectrum.doscollection import GridDOSCollection obj = GridDOSCollection.fromdict(dct) else: raise ValueError('Do not know how to decode object type {} ' 'into an actual object'.format(objtype)) assert obj.ase_objtype == objtype return obj
def test_bravais_eps(): import numpy as np from ase.cell import Cell # This tests a BCT cell which would be mischaracterized as MCLC # depending on comparson's precision (fix: c432fd52ecfdca). # The cell should actually be MCLC for small tolerances, # and BCT with larger ones. But it would always come out MCLC. # # The solution is that the Niggli reduction must run with a more # coarse precision than the lattice recognition algorithm. # # Danger: Since the two mechanisms (Niggli, lattice recognition) # define their precisions differently, it is not certain whether this # problem is entirely gone. cellpar = np.array([3.42864, 3.42864, 3.42864, 125.788, 125.788, 80.236]) cell = Cell.new(cellpar) mclc = cell.get_bravais_lattice(eps=1e-4) bct = cell.get_bravais_lattice(eps=1e-3) print(mclc) print(bct) assert mclc.name == 'MCLC' assert bct.name == 'BCT' # Original cell is not perfect (rounding). perfect_bct_cell = bct.tocell() # perfect_bct_cellpar = bct.cellpar() assert perfect_bct_cell.get_bravais_lattice().name == 'BCT'
def object_hook(dct): if '__datetime__' in dct: return datetime.datetime.strptime(dct['__datetime__'], '%Y-%m-%dT%H:%M:%S.%f') if '__complex_ndarray__' in dct: r, i = (np.array(x) for x in dct['__complex_ndarray__']) return r + i * 1j if '__ase_objtype__' in dct: objtype = dct.pop('__ase_objtype__') dct = numpyfy(dct) # We just try each object type one after another and instantiate # them manually, depending on which kind it is. # We can formalize this later if it ever becomes necessary. if objtype == 'cell': from ase.cell import Cell obj = Cell(**dct) elif objtype == 'bandstructure': from ase.dft.band_structure import BandStructure obj = BandStructure(**dct) elif objtype == 'bandpath': from ase.dft.kpoints import BandPath obj = BandPath(path=dct.pop('labelseq'), **dct) else: raise RuntimeError('Do not know how to decode object type {} ' 'into an actual object'.format(objtype)) assert obj.ase_objtype == objtype return obj return dct
def get_qm_cluster(self, atoms): if self.qm_buffer_mask is None: self.initialize_qm_buffer_mask(atoms) qm_cluster = atoms[self.qm_buffer_mask] del qm_cluster.constraints round_cell = False if self.qm_radius is None: round_cell = True # get all distances between qm atoms. # Treat all X, Y and Z directions independently # only distance between qm atoms is calculated # in order to estimate qm radius in thee directions R_qm, _ = get_distances(atoms.positions[self.qm_selection_mask], cell=atoms.cell, pbc=atoms.pbc) # estimate qm radius in three directions as 1/2 # of max distance between qm atoms self.qm_radius = np.amax(np.amax(R_qm, axis=1), axis=0) * 0.5 if atoms.cell.orthorhombic: cell_size = np.diagonal(atoms.cell) else: raise RuntimeError("NON-orthorhombic cell is not supported!") # check if qm_cluster should be left periodic # in periodic directions of the cell (cell[i] < qm_radius + buffer # otherwise change to non pbc # and make a cluster in a vacuum configuration qm_cluster_pbc = (atoms.pbc & (cell_size < 2.0 * (self.qm_radius + self.buffer_width))) # start with the original orthorhombic cell qm_cluster_cell = cell_size.copy() # create a cluster in a vacuum cell in non periodic directions qm_cluster_cell[~qm_cluster_pbc] = (2.0 * (self.qm_radius[~qm_cluster_pbc] + self.buffer_width + self.vacuum)) if round_cell: # round the qm cell to the required tolerance qm_cluster_cell[~qm_cluster_pbc] = (np.round( (qm_cluster_cell[~qm_cluster_pbc]) / self.qm_cell_round_off) * self.qm_cell_round_off) qm_cluster.set_cell(Cell(np.diag(qm_cluster_cell))) qm_cluster.pbc = qm_cluster_pbc qm_shift = (0.5 * qm_cluster.cell.diagonal() - qm_cluster.positions.mean(axis=0)) if 'cell_origin' in qm_cluster.info: del qm_cluster.info['cell_origin'] # center the cluster only in non pbc directions qm_cluster.positions[:, ~qm_cluster_pbc] += qm_shift[~qm_cluster_pbc] return qm_cluster
def get_special_points(cell, lattice=None, eps=2e-4): """Return dict of special points. The definitions are from a paper by Wahyu Setyawana and Stefano Curtarolo:: http://dx.doi.org/10.1016/j.commatsci.2010.05.010 cell: 3x3 ndarray Unit cell. lattice: str Optionally check that the cell is one of the following: cubic, fcc, bcc, orthorhombic, tetragonal, hexagonal or monoclinic. eps: float Tolerance for cell-check. """ if isinstance(cell, str): warnings.warn('Please call this function with cell as the first ' 'argument') lattice, cell = cell, lattice cell = Cell.ascell(cell) # We create the bandpath because we want to transform the kpoints too, # from the canonical cell to the given one. # # Note that this function is missing a tolerance, epsilon. path = cell.bandpath(npoints=0) return path.special_points
def test_standard_form(): import numpy as np from numpy.testing import assert_allclose from ase.cell import Cell TOL = 1E-10 rng = np.random.RandomState(0) for i in range(20): cell0 = rng.uniform(-1, 1, (3, 3)) for sign in [-1, 1]: cell = Cell(sign * cell0) rcell, Q = cell.standard_form() assert_allclose(rcell @ Q, cell, atol=TOL) assert_allclose(np.linalg.det(rcell), np.linalg.det(cell)) assert_allclose(rcell.ravel()[[1, 2, 5]], 0, atol=TOL)
def get_vectors(self): x, y, z = self.cell_grid cell = np.array( [[x[0].value, x[1].value, x[2].value], [y[0].value, y[1].value, y[2].value], [z[0].value, z[1].value, z[2].value]] ) return Cell(cell)
def format_cell(cell: Cell) -> str: assert cell.rank == 3 lines = [] for name, value in zip(CIFBlock.cell_tags, cell.cellpar()): line = '{:20} {:g}\n'.format(name, value) lines.append(line) assert len(lines) == 6 return ''.join(lines)
def test_1d(self, axis): lcell = self.lcell rcell, op = minkowski_reduce(lcell, pbc=np.roll([1, 0, 0], axis)) assert (rcell == lcell).all() # 1D reduction does nothing zcell = np.zeros((3, 3)) zcell[0] = lcell[0] rcell, _ = Cell(zcell).minkowski_reduce() assert_allclose(rcell, zcell, atol=TOL)
def check_single(name, cell, pbc=None): c = Cell(cell) try: print('TEST', c, pbc) if pbc[:2].all() or sum(pbc) == 1: lattice = c.get_bravais_lattice(pbc=pbc) else: with must_raise(UnsupportedLattice): lattice = c.get_bravais_lattice(pbc=pbc) return except RuntimeError: print('error checking {}'.format(name)) raise name1 = lattice.name.lower() latname = name.split('@')[0] ok = latname == name1 print(name, '-->', name1, 'OK' if ok else 'ERR', c.cellpar()) assert ok, 'Expected {} but found {}'.format(latname, name1)
def update(self, cell, pbc): cell = Cell(cell) mags = cell.lengths() angles = cell.angles() for i in range(3): for j in range(3): if np.isnan(cell[i][j]): cell[i][j] = 0 self.cell_grid[i][j].value = cell[i][j] if np.isnan(mags[i]): mags[i] = 0 self.cell_grid[i][3].value = mags[i] if np.isnan(angles[i]): angles[i] = 0 self.angles[i].value = angles[i] self.pbc[i].var.set(bool(pbc[i]))
def test_2d(self, axis): lcell = self.lcell pbc = np.roll([0, 1, 1], axis) rcell, op = minkowski_reduce(lcell.astype(float), pbc=pbc) assert (rcell[axis] == lcell[axis]).all() zcell = np.copy(lcell) zcell[axis] = 0 rzcell, _ = Cell(zcell).minkowski_reduce() rcell[axis] = 0 assert_allclose(rzcell, rcell, atol=TOL)