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 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 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 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 _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 __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 test_monoclinic(): """Test band structure from different variations of hexagonal cells.""" mc1 = Cell([[1, 0, 0], [0, 1, 0], [0, 0.2, 1]]) par = mc1.cellpar() mc2 = Cell.new(par) mc3 = Cell([[1, 0, 0], [0, 1, 0], [-0.2, 0, 1]]) mc4 = Cell([[1, 0, 0], [-0.2, 1, 0], [0, 0, 1]]) path = 'GYHCEM1AXH1' firsttime = True for cell in [mc1, mc2, mc3, mc4]: a = Atoms(cell=cell, pbc=True) a.cell *= 3 a.calc = FreeElectrons(nvalence=1, kpts={'path': path}) lat = a.cell.get_bravais_lattice() assert lat.name == 'MCL' a.get_potential_energy() bs = a.calc.band_structure() coords, labelcoords, labels = bs.get_labels() assert ''.join(labels) == path e_skn = bs.energies if firsttime: coords1 = coords labelcoords1 = labelcoords e_skn1 = e_skn firsttime = False else: for d in [coords - coords1, labelcoords - labelcoords1, e_skn - e_skn1]: print(abs(d).max()) assert abs(d).max() < 1e-13, d
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 test_bravais_orcc_mcl(): import numpy as np from ase.cell import Cell from ase.calculators.emt import EMT from ase import Atoms def get_e(cell): atoms = Atoms('Au', cell=cell, pbc=1) atoms.calc = EMT() return atoms.get_potential_energy() cell = Cell.new([[1, 0, 0], [0, 2, 0], [0.5, 0, 3]]) lat = cell.get_bravais_lattice() assert lat.name == 'ORCC' cell2 = lat.tocell() e1 = get_e(cell) e2 = get_e(cell2) print(e1, e2) assert abs(e2 - e1) < 1e-12 cp1 = cell.niggli_reduce()[0].cellpar() cp2 = lat.tocell().niggli_reduce()[0].cellpar() print('cellpar1', cp1) print('cellpar2', cp2) assert np.abs(cp2 - cp1).max() < 1e-12 mcl_cell = Cell.new([[1, 0, 0], [0, 2, 0], [0.5 - 1e-3, 0, 3]]) mcl_lat = mcl_cell.get_bravais_lattice() assert mcl_lat.name == 'MCL' e1 = get_e(mcl_cell) e2 = get_e(mcl_lat.tocell()) assert abs(e2 - e1) < 1e-11, abs(e2 - e1) # (Error is actually 1e-12) cp1 = mcl_cell.niggli_reduce()[0].cellpar() cp2 = mcl_lat.tocell().niggli_reduce()[0].cellpar() print(cp1) print(cp2) assert np.abs(cp2 - cp1).max() < 1e-12
def test_new(): assert np.array_equal(Cell.new(), np.zeros((3, 3))) assert np.array_equal(Cell.new([1, 2, 3]), np.diag([1, 2, 3])) assert Cell.new(testcellpar).cellpar() == pytest.approx(testcellpar) arr = np.arange(9).reshape(3, 3) assert np.array_equal(Cell.new(arr), arr) with pytest.raises(ValueError): Cell.new([1, 2, 3, 4])
def test_mclc_eps(): a = 6.41 c = 5.87 alpha = 76.7 beta = 103.3 gamma = 152.2 # This lattice maps to something within tolerance of an MCLC lattice, # but the candidate was discarded due to being unconventional (b > c). # This test verifies that the problem was fixed. # # There could be similar problems for other kinds of lattice. It # could perhaps cause the algorithm to find MCL/MCLC/TRI lattices # with higher-than-necessary orthogonality defect if some # candidates are discarded for this reason. cell = Cell.new([a, a, c, alpha, beta, gamma]) lat = cell.get_bravais_lattice(eps=1e-2) print(lat) assert lat.name == 'MCLC'
def __init__(self, cell, kpts=None, special_points=None, path=None): if kpts is None: kpts = np.empty((0, 3)) if special_points is None: special_points = {} else: special_points = dict(special_points) if path is None: path = '' self.cell = cell = Cell.new(cell) assert cell.shape == (3, 3) assert kpts.ndim == 2 and kpts.shape[1] == 3 self.icell = self.cell.reciprocal() self.kpts = kpts self.special_points = special_points assert isinstance(path, str) self.path = path
import pytest import itertools from ase.cell import Cell def all_pbcs(): values = [False, True] yield from itertools.product(values, values, values) @pytest.mark.parametrize('cell', [Cell.new([3, 4, 5]), Cell.new([2, 0, 3])]) def test_uncomplete(cell): for pbc in all_pbcs(): ucell = cell.uncomplete(pbc) assert ucell.rank == sum(pbc & cell.any(1)) assert all(cell.uncomplete(True).any(1) == cell.any(1)), (cell.uncomplete(True), cell) assert all(cell.uncomplete(1).any(1) == cell.any(1)) assert cell.uncomplete(False).rank == 0 assert cell.uncomplete(0).rank == 0
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 __init__(self, cell, atom=None, fractional_position=np.array([0, 0, 0]), dwf=np.nan, occupancy=1, charge=0, site_label='', symbol=''): ''' Create a CifAtom object A CIFAtom is an ASE Atom object with additional fields read from a cif file, such as Debye-Waller factors and site labels. Parameters ---------- cell: ase.cell.Cell The cell in which the atom belongs. kwargs: Keyword arguments passed on to ase.Atom Can also be used to specify additional fields not supported by ASE, such as Debye-Waller factors. Examples -------- To make an atom with a debye-waller factor different form NaN (default), pass on the debye-waller factor using the keyword "debye_waller_factor": ``` my_cif_atom = CIFAtom(symbol='Al', position = [0, 0, 0], debye-waller-factor=1.006) ``` ''' if isinstance(atom, CIFAtom): cell = atom.cell fractional_position = atom.fractional_position dwf = atom.dwf site_label = atom.site_label occupancy = atom.occupancy if isinstance(cell, Cell): self.cell = cell else: cell = np.array(cell, dtype=float) if cell.shape == (3, 3): self.cell = Cell(cell) elif cell.shape == (6, ): dummy_cell = Cell(np.eye(3)) self.cell = dummy_cell.new(cell) else: raise ValueError( 'Cell must be size (3,3) or (6,), got cell {cell!r} of shape {cell.shape}' .format(cell=cell)) self.fractional_position = np.array(fractional_position, dtype=float) self.dwf = float(dwf) try: self.site_label = int(site_label) except ValueError: if isinstance(site_label, str): warn('String site labels may not be supported by MULTEM') self.site_label = site_label else: raise TypeError( 'Type of {site_label} (type {t}) is not supported for site_labels' .format(site_label=site_label, t=type(site_label))) self.occupancy = float(occupancy) position = self.cell.cartesian_positions(self.fractional_position) super().__init__(symbol=symbol, charge=charge, position=position)
def test_bravais_check(): import numpy as np from ase.cell import Cell from ase.lattice import bravais_lattices, UnsupportedLattice from ase.build import bulk, fcc111 from ase.test.testsuite import must_raise bravais = {} for name in bravais_lattices: bravais[name.lower()] = bravais_lattices[name] 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 check(name, cell, pbc=None): if pbc is None: pbc = cell.any(1) pbc = np.asarray(pbc) cell = Cell(cell) # Check all three positive permutations: check_single(name + '@012', cell[[0, 1, 2]], pbc=pbc[[0, 1, 2]]) # 2D lattice determination only supports pbc=(1,1,0) and hence we # check the permutations only for 3D lattices: if cell.rank == 3 and pbc.sum() != 1: check_single(name + '@201', cell[[2, 0, 1]], pbc=pbc[[2, 0, 1]]) check_single(name + '@120', cell[[1, 2, 0]], pbc=pbc[[1, 2, 0]]) check('cub', bravais['cub'](3.3).tocell()) check('fcc', bravais['fcc'](3.4).tocell()) check('fcc', bulk('Au').cell) check('bcc', bravais['bcc'](3.5).tocell()) check('bcc', bulk('Fe').cell) check('tet', bravais['tet'](4., 5.).tocell()) check('tet', np.diag([4., 5., 5.])) check('tet', np.diag([5., 4., 5.])) check('tet', np.diag([5., 5., 4.])) check('bct', bravais['bct'](3., 4.).tocell()) check('orc', bravais['orc'](3., 4., 5.).tocell()) check('orcf', bravais['orcf'](4., 5., 7.).tocell()) check('orci', bravais['orci'](2., 5., 6.).tocell()) check('orcc', bravais['orcc'](3., 4., 5.).tocell()) check('hex', fcc111('Au', size=(1, 1, 3), periodic=True).cell) check('hex', bravais['hex'](5., 6.).tocell()) check('rhl', bravais['rhl'](4., 54.).tocell()) check('mcl', bravais['mcl'](2., 3., 4., 62.).tocell()) check('mclc', bravais['mclc'](3., 4., 5., 75.).tocell()) check('tri', bravais['tri'](7., 6., 5., 65., 70., 80.).tocell()) # For 2D materials we have to check both the tocell() method # but also for realistic cell nonzero nonperiodic axis. check('sqr', bravais['sqr'](3.).tocell()) check('sqr', Cell(np.diag([3., 3., 10.])), pbc=np.array([True, True, False])) check('crect', bravais['crect'](3., 40).tocell()) alpha = 40 / 360 * 2 * np.pi a = 3 x = np.cos(alpha) y = np.sin(alpha) crectcell = np.array([[a, 0, 0], [a * x, a * y, 0], [0, 0, 10]]) check('crect', Cell(crectcell), pbc=[1, 1, 0]) check('rect', bravais['rect'](3., 4.).tocell()) check('rect', Cell.new([3, 4, 10]), pbc=[1, 1, 0]) check('hex2d', bravais['hex2d'](3.).tocell()) x = 0.5 * np.sqrt(3) hexcell = np.array([[a, 0, 0], [-0.5 * a, x * a, 0], [0., 0., 0.]]) check('hex2d', Cell(hexcell)) check('obl', bravais['obl'](3., 4., 40).tocell()) b = 4 x = np.cos(alpha) y = np.sin(alpha) oblcell = np.array([[a, 0, 0], [b * x, b * y, 0], [0, 0, 10]]) check('obl', Cell(oblcell), pbc=np.array([True, True, False])) # 1-d: check('line', Cell(np.diag([a, 0, 0.0]))) check('line', Cell(np.diag([a, 1, 1.0])), pbc=np.array([1, 0, 0])) check('line', Cell(np.diag([0.0, 0, a]))) check('line', Cell(np.diag([1.0, 1, a])), pbc=np.array([0, 0, 1]))
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'
import itertools from ase.cell import Cell def all_pbcs(): values = [False, True] yield from itertools.product(values, values, values) def test(cell): for pbc in all_pbcs(): ucell = cell.uncomplete(pbc) assert ucell.rank == sum(pbc & cell.any(1)) assert all( cell.uncomplete(True).any(1) == cell.any(1)), (cell.uncomplete(True), cell) assert all(cell.uncomplete(1).any(1) == cell.any(1)) assert cell.uncomplete(False).rank == 0 assert cell.uncomplete(0).rank == 0 test(Cell.new([3, 4, 5])) test(Cell.new([2, 0, 3]))
def get_cell(self) -> Cell: cellpar = self.get_cellpar() if cellpar is None: return Cell.new([0, 0, 0]) return Cell.new(cellpar)
def orthogonalize_cell(atoms: Atoms, max_repetitions: int = 5, return_transform: bool = False, transform: Union[bool, str] = True, tolerance=0.01): """ Make the cell of an ASE atoms object orthogonal. This is accomplished by repeating the cell until lattice vectors are close to the three principal Cartesian directions. If the structure is not exactly orthogonal after the structure is repeated by a given maximum the remaining difference will be made up by applying strain. Parameters ---------- atoms : ASE atoms object The non-orthogonal atoms object. max_repetitions : int The maximum number of repetions allowed. Increase this to allow more repetitions and hence less strain. return_transform : bool If true, return the transformations that were applied to make the atoms orthogonal. transform : bool If false no transformation is applied to make the cell orthogonal, hence a non-orthogonal cell may be returned. Returns ------- atoms : ASE atoms object The orthogonal atoms. transform : tuple of arrays The applied transform in the form the euler angles """ eps = 1e-12 zero_vectors = np.linalg.norm(atoms.cell, axis=0) < eps if zero_vectors.sum() > 1: raise RuntimeError( "two or more lattice vectors of the provided Atoms has no length") elif zero_vectors.sum() == 1: atoms.center(axis=np.where(zero_vectors)[0], vacuum=tolerance + eps) k = np.arange(-max_repetitions, max_repetitions + 1) l = np.arange(-max_repetitions, max_repetitions + 1) m = np.arange(-max_repetitions, max_repetitions + 1) a, b, c = atoms.cell vectors = np.abs(((k[:, None] * a[None])[:, None, None] + (l[:, None] * b[None])[None, :, None] + (m[:, None] * c[None])[None, None, :])) norm = np.linalg.norm(vectors, axis=-1) nonzero = norm > eps norm[nonzero == 0] = eps new_vectors = [] for i in range(3): angles = vectors[..., i] / norm optimal = np.abs(angles.max() - angles < eps) optimal = np.where(optimal * nonzero) n = np.linalg.norm(vectors[optimal], axis=1) j = np.argmin(n) new_vector = np.array( [k[optimal[0][j]], l[optimal[1][j]], m[optimal[2][j]]]) new_vector = np.sign(np.dot(new_vector, atoms.cell)[i]) * new_vector new_vectors.append(new_vector) atoms = cut(atoms, *new_vectors, tolerance=tolerance) cell = Cell.new(np.linalg.norm(atoms.cell, axis=0)) A = np.linalg.solve(atoms.cell.complete(), cell.complete()) if transform is True: atoms.positions[:] = np.dot(atoms.positions, A) atoms.cell[:] = cell elif transform == 'raise': if not is_cell_orthogonal(atoms): raise RuntimeError() atoms = shrink_cell(atoms, 2) if return_transform and transform: rotation, zoom, shear = decompose_affine_transform(A) return atoms, (np.array(rotation_matrix_to_euler(rotation)), zoom, shear) else: return atoms
def cell(): return Cell.new(testcellpar)
def from_dict(cls, posinp): r""" Initialize the input positions from a dictionary. Parameters ---------- posinp : dict Posinp as a dictionary coming from an InputParams or Logfile instance. Returns ------- Posinp Posinp initialized from an dictionary. >>> pos_dict = { ... "units": "reduced", ... "cell": [8.07007483423, 'inf', 4.65925987792], ... "positions": [ ... {'C': [0.08333333333, 0.5, 0.25]}, ... {'C': [0.41666666666, 0.5, 0.25]}, ... {'C': [0.58333333333, 0.5, 0.75]}, ... {'C': [0.91666666666, 0.5, 0.75]}, ... ] ... } >>> pos = Posinp.from_dict(pos_dict) >>> pos.boundary_conditions 'surface' If there is no "cell" key, then the boundary conditions are set to "free". Here, given that the units are reduced, this raises a ValueError: >>> del pos_dict["cell"] >>> pos = Posinp.from_dict(pos_dict) Traceback (most recent call last): ... ValueError: Cannot use reduced units with free boundary conditions """ # Read data from the dictionary atoms = [] # atomic positions for atom in posinp["positions"]: atoms.append(Atom.from_dict(atom)) units = posinp.get("units", "atomic") # Units of the coordinates cell = posinp.get("cell") # Simulation cell size angles = posinp.get("angles") # Infer the boundary conditions from the value of cell if cell is None: boundary_conditions = "free" else: if not isinstance(cell, Cell): cell = [ abs(float(size)) if size not in [".inf", "inf"] else 0.0 for size in cell ] cell = Cell.new(cell) lengths_counter = Counter(list(cell.lengths())) if lengths_counter[0] == 1: boundary_conditions = "surface" elif lengths_counter[0] == 3: boundary_conditions = "free" else: boundary_conditions = "periodic" return cls(atoms, units, boundary_conditions, cell=cell, angles=angles)
from ase.dft.band_structure import calculate_band_structure from ase.calculators.test import FreeElectrons from ase.cell import Cell 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)
def test_free_boundary_conditions_has_no_cell(self): assert (self.free_pos.cell == Cell.new()).all()
class TestPosinp: # Posinp with surface boundary conditions surface_filename = os.path.join(tests_fol, "surface.xyz") pos = Posinp.from_file(surface_filename) # Posinp with free boundary conditions free_filename = os.path.join(tests_fol, "free.xyz") free_pos = Posinp.from_file(free_filename) periodic_filename = os.path.join(tests_fol, "periodic.xyz") periodic_pos = Posinp.from_file(periodic_filename) # Posinp read from a string string = """\ 4 atomic free C 0.6661284109 0.000000000 1.153768252 C 3.330642055 0.000000000 1.153768252 C 4.662898877 1.000000000 3.461304757 C 7.327412521 0.000000000 3.461304757""" str_pos = Posinp.from_string(string) value = [len(pos), pos.units, pos.boundary_conditions, pos.cell, pos[0], pos.angles] expected = [ 4, "reduced", "surface", Cell.new(np.array([8.07007483423, 0.0, 4.65925987792])), Atom("C", [0.08333333333, 0.5, 0.25]), np.array([90.0, 90.0, 90.0]), ] def test_from_file(self): for v, e in zip(self.value, self.expected): if isinstance(v, np.ndarray) or isinstance(v, Cell): assert np.allclose(v, e) else: assert v == e def test_from_string(self): assert self.str_pos == self.free_pos def test_repr(self): atoms = [Atom("C", [0, 0, 0]), Atom("N", [0, 0, 1])] new_pos = Posinp(atoms, units="angstroem", boundary_conditions="free") msg = ( "Posinp([Atom('C', [0.0, 0.0, 0.0]), Atom('N', [0.0, 0.0, " "1.0])], 'angstroem', 'free', cell=Cell([0.0, 0.0, 0.0]), angles=[90. 90. 90.])" ) assert repr(new_pos) == msg def test_write(self): fname = os.path.join(tests_fol, "test.xyz") self.pos.write(fname) assert self.pos == Posinp.from_file(fname) os.remove(fname) def test_free_boundary_conditions_has_no_cell(self): assert (self.free_pos.cell == Cell.new()).all() def test_translate_atom(self): new_pos = self.pos.translate_atom(0, [0.5, 0, 0]) assert new_pos != self.pos assert new_pos[0] == Atom("C", [0.58333333333, 0.5, 0.25]) @pytest.mark.parametrize( "fname", ["free_reduced.xyz", "missing_atom.xyz", "additional_atom.xyz"] ) def test_init_raises_ValueError(self, fname): with pytest.raises(ValueError): Posinp.from_file(os.path.join(tests_fol, fname)) @pytest.mark.parametrize( "to_evaluate", [ "Posinp([Atom('C', [0, 0, 0])], 'bohr', 'periodic')", "Posinp([Atom('C', [0, 0, 0])], 'bohr', 'periodic', cell=[1, 1])", "Posinp([Atom('C', [0, 0, 0])], 'bohr', 'periodic', cell=[1,'inf',1])", ], ) def test_init_raises_ValueError2(self, to_evaluate): with pytest.raises(ValueError): eval(to_evaluate) def test_positions(self): expected = [7.327412521, 0.0, 3.461304757] pos1 = Posinp( [Atom("C", expected)], units="angstroem", boundary_conditions="free" ) pos2 = pos1.translate_atom(0, [-7.327412521, 0.0, -3.461304757]) assert np.allclose(pos1.positions, expected) assert np.allclose(pos2.positions, [0, 0, 0]) def test___eq__(self): atom1 = Atom("N", [0.0, 0.0, 0.0]) atom2 = Atom("N", [0.0, 0.0, 1.1]) pos1 = Posinp([atom1, atom2], "angstroem", "free") pos2 = Posinp([atom2, atom1], "angstroem", "free") assert pos1 == pos2 # The order of the atoms in the list do not count assert pos1 != 1 # No error if other object is not a posinp def test_with_surface_boundary_conditions(self): # Two Posinp instances with surface BC are the same even if they # have a different cell size along y-axis pos_with_inf = Posinp( [ Atom( "N", [2.97630782434901e-23, 6.87220595204354e-23, 0.0107161998748779], ), Atom( "N", [-1.10434491945017e-23, -4.87342174483075e-23, 1.10427379608154], ), ], "angstroem", "surface", cell=[40, ".inf", 40], ) with pytest.raises(ValueError): pos_wo_inf = Posinp( [ Atom( "N", [2.97630782434901e-23, 6.87220595204354e-23, 0.0107161998748779], ), Atom( "N", [-1.10434491945017e-23, -4.87342174483075e-23, 1.10427379608154], ), ], "angstroem", "surface", cell=[40, 40, 40], ) # They are obviously different if the cell size along the other # directions are not the same pos2_with_inf = Posinp( [ Atom( "N", [2.97630782434901e-23, 6.87220595204354e-23, 0.0107161998748779], ), Atom( "N", [-1.10434491945017e-23, -4.87342174483075e-23, 1.10427379608154], ), ], "angstroem", "surface", cell=[20, "inf", 40], ) assert pos_with_inf != pos2_with_inf # They still have the same BC assert pos2_with_inf.boundary_conditions == pos_with_inf.boundary_conditions def test_to_centroid(self): atoms = [Atom("N", [0, 0, 0]), Atom("N", [0, 0, 1.1])] pos = Posinp(atoms, units="angstroem", boundary_conditions="free") expected_atoms = [Atom("N", [0, 0, -0.55]), Atom("N", [0, 0, 0.55])] expected_pos = Posinp( expected_atoms, units="angstroem", boundary_conditions="free" ) assert pos.to_centroid() == expected_pos def test_to_barycenter(self): atoms = [Atom("N", [0, 0, 0]), Atom("N", [0, 0, 1.1])] pos = Posinp(atoms, units="angstroem", boundary_conditions="free") expected_atoms = [Atom("N", [0, 0, -0.55]), Atom("N", [0, 0, 0.55])] expected_pos = Posinp( expected_atoms, units="angstroem", boundary_conditions="free" ) assert pos.to_barycenter() == expected_pos def test_distance(self): assert np.isclose(self.free_pos.distance(0, 2), 4.722170992308181) def test_convert(self): pos1 = self.periodic_pos.positions assert self.periodic_pos.units == "angstroem" self.periodic_pos.convert_units("atomic") assert np.isclose( self.periodic_pos.positions, np.array( [ [0.15747717, 0.94486299, 0.4724315], [0.78738583, 0.94486299, 0.4724315], [1.10234016, 0.94486299, 1.41729449], [1.73224882, 0.94486299, 1.41729449], ] ), ).all() assert self.periodic_pos.units == "atomic" self.periodic_pos.convert_units("angstroem") assert np.isclose(self.periodic_pos.positions, pos1).all() assert self.periodic_pos.units == "angstroem" self.periodic_pos.convert_units("angstroem") assert np.isclose(self.periodic_pos.positions, pos1).all() red = Posinp.from_file(tests_fol + "reduced.xyz") red.convert_units("angstroem") print(red.positions) assert np.isclose( red.positions, np.array( [ [0.4233418, 1.32294312, 0.95251905], [3.81007619, 0.26458862, 0.47625952], ] ), ).all() def test_angles(self): h2o = Posinp.from_file(tests_fol + "H2Orelaxed.xyz") a = h2o.angle(1, 0, 2) * 180 / np.pi assert np.isclose(a, 104.1219, atol=10 ** -4) a1, a2 = h2o.angle(0, 1, 2), h2o.angle(2, 1, 0) assert a1 == a2
import numpy as np from ase.cell import Cell from ase.calculators.emt import EMT from ase import Atoms def get_e(cell): atoms = Atoms('Au', cell=cell, pbc=1) atoms.calc = EMT() return atoms.get_potential_energy() cell = Cell.new([[1, 0, 0], [0, 2, 0], [0.5, 0, 3]]) lat = cell.get_bravais_lattice() assert lat.name == 'ORCC' cell2 = lat.tocell() e1 = get_e(cell) e2 = get_e(cell2) print(e1, e2) assert abs(e2 - e1) < 1e-12 cp1 = cell.niggli_reduce()[0].cellpar() cp2 = lat.tocell().niggli_reduce()[0].cellpar() print('cellpar1', cp1) print('cellpar2', cp2) assert np.abs(cp2 - cp1).max() < 1e-12 mcl_cell = Cell.new([[1, 0, 0], [0, 2, 0], [0.5 - 1e-3, 0, 3]]) mcl_lat = mcl_cell.get_bravais_lattice()
# but also for realistic cell nonzero nonperiodic axis. check('sqr', bravais['sqr'](3.).tocell()) check('sqr', Cell(np.diag([3., 3., 10.])), pbc=np.array([True, True, False])) check('crect', bravais['crect'](3., 40).tocell()) alpha = 40 / 360 * 2 * np.pi a = 3 x = np.cos(alpha) y = np.sin(alpha) crectcell = np.array([[a, 0, 0], [a * x, a * y, 0], [0, 0, 10]]) check('crect', Cell(crectcell), pbc=[1, 1, 0]) check('rect', bravais['rect'](3., 4.).tocell()) check('rect', Cell.new([3, 4, 10]), pbc=[1, 1, 0]) check('hex2d', bravais['hex2d'](3.).tocell()) x = 0.5 * np.sqrt(3) hexcell = np.array([[a, 0, 0], [-0.5 * a, x * a, 0], [0., 0., 0.]]) check('hex2d', Cell(hexcell)) check('obl', bravais['obl'](3., 4., 40).tocell()) b = 4 x = np.cos(alpha) y = np.sin(alpha) oblcell = np.array([[a, 0, 0], [b * x, b * y, 0], [0, 0, 10]]) check('obl', Cell(oblcell), pbc=np.array([True, True, False])) # 1-d: