예제 #1
0
def _get_spacegroup(atoms, symprec=1e-5, center=None):
    """ASE implementation of get_spacegroup.
    """

    # we try all available spacegroups from 1 to 230, backwards
    # a Space group is the collection of all symmetry operations which lets the
    # unit cell invariant.
    found = None
    positions = atoms.get_scaled_positions(wrap=True)  # in the lattice frame

    # make sure we are insensitive to translation. this choice is arbitrary and
    # could lead to a 'slightly' wrong guess for the Space group, e.g. do not
    # guess centro-symmetry.
    if center:
        try:
            positions -= positions[center]
        except IndexError:
            pass

    # search space groups from the highest symmetry to the lowest
    # retain the first match
    for nb in range(230, 0, -1):
        sg = Spacegroup(nb)
        #
        # now we scan all atoms in the cell and look for equivalent sites
        try:
            sites, kinds = sg.equivalent_sites(positions,
                                               onduplicates='keep',
                                               symprec=symprec)
        except TypeError:
            # ASE <= 3.9
            sites, kinds = sg.equivalent_sites(positions,
                                               ondublicates='keep',
                                               symprec=symprec)
        #
        # the equivalent sites should match all other atom locations in the cell
        # as the spacegroup transforms the unit cell in itself
        if len(sites) == len(positions):
            # store the space group into the list
            found = sg
            break

    return found
예제 #2
0
    def read(self):
        """
        Read data from the CIF file
        """
        with open(self.filename, 'r') as f:
            ciffile = ReadCif(f)
            for block in ciffile:
                spacegroup = Spacegroup(int(block['_space_group_IT_number']))

                cellpar = np.array([
                    block['_cell_length_a'],
                    block['_cell_length_b'],
                    block['_cell_length_c'],
                    block['_cell_angle_alpha'],
                    block['_cell_angle_beta'],
                    block['_cell_angle_gamma'],
                ],
                                   dtype=float)

                try:
                    site_labels = block['_atom_site_label']
                except KeyError:
                    print('Could not get site labels from cif file')
                try:
                    occupancies = np.array(block['_atom_site_occupancy'],
                                           dtype=float)
                except KeyError:
                    print('Could not get occupancies from cif file')
                try:
                    fract_x = np.array(block['_atom_site_fract_x'],
                                       dtype=float)
                    fract_y = np.array(block['_atom_site_fract_y'],
                                       dtype=float)
                    fract_z = np.array(block['_atom_site_fract_z'],
                                       dtype=float)
                except KeyError:
                    warn(
                        'Could not get fractional coordinates from cif file, getting absolute coordinates instead.'
                    )
                    try:
                        x = np.array(block['_atom_site_cartn_x'], dtype=float)
                        y = np.array(block['_atom_site_cartn_y'], dtype=float)
                        z = np.array(block['_atom_site_cartn_z'], dtype=float)
                    except KeyError:
                        warn(
                            'Could not get absolute coordinates from cif file')
                        x = [np.nan] * len(block)
                        y = [np.nan] * len(block)
                        z = [np.nan] * len(block)
                    finally:
                        fract_x = x / cellpar[0]
                        fract_y = y / cellpar[1]
                        fract_z = z / cellpar[2]
                else:
                    x = fract_x * cellpar[0]
                    y = fract_y * cellpar[1]
                    z = fract_y * cellpar[2]
                finally:
                    x = x.T
                    y = y.T
                    z = z.T
                    fract_x = fract_x.T
                    fract_y = fract_y.T
                    fract_z = fract_z.T

                    positions = np.array([x, y, z])
                    fractional_positions = np.array(
                        [fract_x, fract_y, fract_z])
                try:
                    symbols = block['_atom_site_type_symbol']
                except KeyError:
                    print(
                        'Could not get atom site chemical symbols from cif file'
                    )
                try:
                    dwf = block['_atom_site_B_iso_or_equiv']
                except KeyError:
                    print('Could not get Debye-Waller factors from cif file')

                basis_atoms = []
                for label, occ, fx, fy, fz, symbol, B in zip(
                        site_labels, occupancies, fractional_positions[0],
                        fractional_positions[1], fractional_positions[2],
                        symbols, dwf):
                    atom = CIFAtom(cellpar,
                                   symbol=symbol,
                                   occupancy=occ,
                                   fractional_position=(fx, fy, fz),
                                   dwf=B,
                                   site_label=label)
                    basis_atoms.append(atom)

                atoms = []
                for atom in basis_atoms:
                    equivalent_sites, kinds = spacegroup.equivalent_sites(
                        atom.fractional_position,
                        onduplicates='warn',
                        occupancies=atom.occupancy)
                    for site in equivalent_sites:
                        position = site * cellpar[:3]
                        equivalent_atom = CIFAtom(cellpar,
                                                  fractional_position=site,
                                                  site_label=atom.site_label,
                                                  symbol=atom.symbol,
                                                  dwf=atom.dwf,
                                                  occupancy=atom.occupancy)
                        atoms.append(equivalent_atom)
                self.atoms = atoms
                self.crystal = Crystal(atoms, cellpar)
예제 #3
0
 def _analyze(self):
     """Analyze the topology to cut the fragments out."""
     # separate the dummies from the rest
     logger.debug("Analyzing fragments of topology {0}.".format(self.name))
     numbers = numpy.asarray(self.atoms.get_atomic_numbers())
     Xis = numpy.where(numbers == 0)[0]
     Ais = numpy.where(numbers > 0)[0]
     # setup the tags
     tags = numpy.zeros(len(self.atoms))
     tags[Xis] = Xis + 1
     self.atoms.set_tags(tags)
     tags = self.atoms.get_tags()
     # analyze
     # first build the neighborlist
     cutoffs = self._get_cutoffs(Xis=Xis, Ais=Ais)
     neighborlist = NeighborList(cutoffs=cutoffs,
                                 bothways=True,
                                 self_interaction=False,
                                 skin=0.0)
     neighborlist.update(self.atoms)
     # iterate over non-dummies to find dummy neighbors
     for ai in Ais:
         # get indices and offsets of dummies only!
         ni, no = neighborlist.get_neighbors(ai)
         ni, no = zip(*[(idx, off) for idx, off in list(zip(ni, no))
                        if idx in Xis])
         ni = numpy.asarray(ni)
         no = numpy.asarray(no)
         # get absolute positions, no offsets
         positions = self.atoms.positions[ni] + no.dot(self.atoms.cell)
         # create the Atoms object
         fragment = Atoms("X" * len(ni), positions, tags=tags[ni])
         # calculate the point group properties
         max_order = min(8, len(ni))
         shape = symmetry.get_symmetry_elements(mol=fragment.copy(),
                                                max_order=max_order)
         pg = symmetry.PointGroup(mol=fragment.copy(), tol=0.1)
         # save that info
         self.fragments[ai] = fragment
         self.shapes[ai] = shape
         self.pointgroups[ai] = pg.schoenflies
     # now getting the equivalent sites using the Spacegroup object
     sg = self.atoms.info["spacegroup"]
     if not isinstance(sg, Spacegroup):
         sg = Spacegroup(sg)
     scaled_positions = self.atoms.get_scaled_positions()
     seen_indices = []
     symbols = numpy.array(self.atoms.get_chemical_symbols())
     for ai in Ais:
         if ai in seen_indices:
             continue
         sites, _ = sg.equivalent_sites(scaled_positions[ai])
         these_indices = []
         for site in sites:
             norms = numpy.linalg.norm(scaled_positions - site, axis=1)
             if norms.min() < 1e-6:
                 these_indices.append(norms.argmin())
             # take pbc into account
             norms = numpy.abs(norms - 1.0)
             if norms.min() < 1e-6:
                 these_indices.append(norms.argmin())
         these_indices = [idx for idx in these_indices if idx in Ais]
         seen_indices += these_indices
         self.equivalent_sites.append(these_indices)
     logger.info("{es} equivalent sites kinds.".format(
         es=len(self.equivalent_sites)))
     return None
예제 #4
0
파일: xtal.py 프로젝트: puckvg/ase-copy
def crystal(symbols=None,
            basis=None,
            occupancies=None,
            spacegroup=1,
            setting=1,
            cell=None,
            cellpar=None,
            ab_normal=(0, 0, 1),
            a_direction=None,
            size=(1, 1, 1),
            onduplicates='warn',
            symprec=0.001,
            pbc=True,
            primitive_cell=False,
            **kwargs):
    """Create an Atoms instance for a conventional unit cell of a
    space group.

    Parameters:

    symbols : str | sequence of str | sequence of Atom | Atoms
        Element symbols of the unique sites.  Can either be a string
        formula or a sequence of element symbols. E.g. ('Na', 'Cl')
        and 'NaCl' are equivalent.  Can also be given as a sequence of
        Atom objects or an Atoms object.
    basis : list of scaled coordinates
        Positions of the unique sites corresponding to symbols given
        either as scaled positions or through an atoms instance.  Not
        needed if *symbols* is a sequence of Atom objects or an Atoms
        object.
    occupancies : list of site occupancies
        Occupancies of the unique sites. Defaults to 1.0 and thus no mixed
        occupancies are considered if not explicitly asked for. If occupancies
        are given, the most dominant species will yield the atomic number.
    spacegroup : int | string | Spacegroup instance
        Space group given either as its number in International Tables
        or as its Hermann-Mauguin symbol.
    setting : 1 | 2
        Space group setting.
    cell : 3x3 matrix
        Unit cell vectors.
    cellpar : [a, b, c, alpha, beta, gamma]
        Cell parameters with angles in degree. Is not used when `cell`
        is given.
    ab_normal : vector
        Is used to define the orientation of the unit cell relative
        to the Cartesian system when `cell` is not given. It is the
        normal vector of the plane spanned by a and b.
    a_direction : vector
        Defines the orientation of the unit cell a vector. a will be
        parallel to the projection of `a_direction` onto the a-b plane.
    size : 3 positive integers
        How many times the conventional unit cell should be repeated
        in each direction.
    onduplicates : 'keep' | 'replace' | 'warn' | 'error'
        Action if `basis` contain symmetry-equivalent positions:
            'keep'    - ignore additional symmetry-equivalent positions
            'replace' - replace
            'warn'    - like 'keep', but issue an UserWarning
            'error'   - raises a SpacegroupValueError
    symprec : float
        Minimum "distance" betweed two sites in scaled coordinates
        before they are counted as the same site.
    pbc : one or three bools
        Periodic boundary conditions flags.  Examples: True,
        False, 0, 1, (1, 1, 0), (True, False, False).  Default
        is True.
    primitive_cell : bool
        Wheter to return the primitive instead of the conventional
        unit cell.

    Keyword arguments:

    All additional keyword arguments are passed on to the Atoms
    constructor.  Currently, probably the most useful additional
    keyword arguments are `info`, `constraint` and `calculator`.

    Examples:

    Two diamond unit cells (space group number 227)

    >>> diamond = crystal('C', [(0,0,0)], spacegroup=227,
    ...     cellpar=[3.57, 3.57, 3.57, 90, 90, 90], size=(2,1,1))
    >>> ase.view(diamond)  # doctest: +SKIP

    A CoSb3 skutterudite unit cell containing 32 atoms

    >>> skutterudite = crystal(('Co', 'Sb'),
    ...     basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)],
    ...     spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90])
    >>> len(skutterudite)
    32
    """
    sg = Spacegroup(spacegroup, setting)
    if (not isinstance(symbols, basestring)
            and hasattr(symbols, '__getitem__') and len(symbols) > 0
            and isinstance(symbols[0], ase.Atom)):
        symbols = ase.Atoms(symbols)
    if isinstance(symbols, ase.Atoms):
        basis = symbols
        symbols = basis.get_chemical_symbols()
    if isinstance(basis, ase.Atoms):
        basis_coords = basis.get_scaled_positions()
        if cell is None and cellpar is None:
            cell = basis.cell
        if symbols is None:
            symbols = basis.get_chemical_symbols()
    else:
        basis_coords = np.array(basis, dtype=float, copy=False, ndmin=2)

    if occupancies is not None:
        occupancies_dict = {}

        for index, coord in enumerate(basis_coords):
            # Compute all distances and get indices of nearest atoms
            dist = spatial.distance.cdist(coord.reshape(1, 3), basis_coords)
            indices_dist = np.flatnonzero(dist < symprec)

            occ = {symbols[index]: occupancies[index]}

            # Check nearest and update occupancy
            for index_dist in indices_dist:
                if index == index_dist:
                    continue
                else:
                    occ.update({symbols[index_dist]: occupancies[index_dist]})

            occupancies_dict[index] = occ.copy()

    sites, kinds = sg.equivalent_sites(basis_coords,
                                       onduplicates=onduplicates,
                                       symprec=symprec)

    # this is needed to handle deuterium masses
    masses = None
    if 'masses' in kwargs:
        masses = kwargs['masses'][kinds]
        del kwargs['masses']

    symbols = parse_symbols(symbols)

    if occupancies is None:
        symbols = [symbols[i] for i in kinds]
    else:
        # make sure that we put the dominant species there
        symbols = [
            sorted(occupancies_dict[i].items(), key=lambda x: x[1])[-1][0]
            for i in kinds
        ]

    if cell is None:
        cell = cellpar_to_cell(cellpar, ab_normal, a_direction)

    info = dict(spacegroup=sg)
    if primitive_cell:
        info['unit_cell'] = 'primitive'
    else:
        info['unit_cell'] = 'conventional'

    if 'info' in kwargs:
        info.update(kwargs['info'])

    if occupancies is not None:
        info['occupancy'] = occupancies_dict

    kwargs['info'] = info

    atoms = ase.Atoms(symbols,
                      scaled_positions=sites,
                      cell=cell,
                      pbc=pbc,
                      masses=masses,
                      **kwargs)

    #  if all occupancies are 1, no partial occupancy present
    if occupancies:
        if not all([occ == 1 for occ in occupancies]):
            # use tags to identify sites, and in particular the occupancy
            atoms.set_tags(kinds)

    if isinstance(basis, ase.Atoms):
        for name in basis.arrays:
            if not atoms.has(name):
                array = basis.get_array(name)
                atoms.new_array(name, [array[i] for i in kinds],
                                dtype=array.dtype,
                                shape=array.shape[1:])

    if primitive_cell:
        from ase.build import cut
        prim_cell = sg.scaled_primitive_cell

        # Preserve calculator if present:
        calc = atoms.calc
        atoms = cut(atoms, a=prim_cell[0], b=prim_cell[1], c=prim_cell[2])
        atoms.calc = calc

    if size != (1, 1, 1):
        atoms = atoms.repeat(size)
    return atoms
예제 #5
0
파일: xtal.py 프로젝트: rosswhitfield/ase
def crystal(symbols=None, basis=None, spacegroup=1, setting=1,
            cell=None, cellpar=None,
            ab_normal=(0, 0, 1), a_direction=None, size=(1, 1, 1),
            onduplicates='warn', symprec=0.001,
            pbc=True, primitive_cell=False, **kwargs):
    """Create an Atoms instance for a conventional unit cell of a
    space group.

    Parameters:

    symbols : str | sequence of str | sequence of Atom | Atoms
        Element symbols of the unique sites.  Can either be a string
        formula or a sequence of element symbols. E.g. ('Na', 'Cl')
        and 'NaCl' are equivalent.  Can also be given as a sequence of
        Atom objects or an Atoms object.
    basis : list of scaled coordinates
        Positions of the unique sites corresponding to symbols given
        either as scaled positions or through an atoms instance.  Not
        needed if *symbols* is a sequence of Atom objects or an Atoms
        object.
    spacegroup : int | string | Spacegroup instance
        Space group given either as its number in International Tables
        or as its Hermann-Mauguin symbol.
    setting : 1 | 2
        Space group setting.
    cell : 3x3 matrix
        Unit cell vectors.
    cellpar : [a, b, c, alpha, beta, gamma]
        Cell parameters with angles in degree. Is not used when `cell`
        is given.
    ab_normal : vector
        Is used to define the orientation of the unit cell relative
        to the Cartesian system when `cell` is not given. It is the
        normal vector of the plane spanned by a and b.
    a_direction : vector
        Defines the orientation of the unit cell a vector. a will be
        parallel to the projection of `a_direction` onto the a-b plane.
    size : 3 positive integers
        How many times the conventional unit cell should be repeated
        in each direction.
    onduplicates : 'keep' | 'replace' | 'warn' | 'error'
        Action if `basis` contain symmetry-equivalent positions:
            'keep'    - ignore additional symmetry-equivalent positions
            'replace' - replace
            'warn'    - like 'keep', but issue an UserWarning
            'error'   - raises a SpacegroupValueError
    symprec : float
        Minimum "distance" betweed two sites in scaled coordinates
        before they are counted as the same site.
    pbc : one or three bools
        Periodic boundary conditions flags.  Examples: True,
        False, 0, 1, (1, 1, 0), (True, False, False).  Default
        is True.
    primitive_cell : bool
        Wheter to return the primitive instead of the conventional
        unit cell.

    Keyword arguments:

    All additional keyword arguments are passed on to the Atoms
    constructor.  Currently, probably the most useful additional
    keyword arguments are `info`, `constraint` and `calculator`.

    Examples:

    Two diamond unit cells (space group number 227)

    >>> diamond = crystal('C', [(0,0,0)], spacegroup=227,
    ...     cellpar=[3.57, 3.57, 3.57, 90, 90, 90], size=(2,1,1))
    >>> ase.view(diamond)  # doctest: +SKIP

    A CoSb3 skutterudite unit cell containing 32 atoms

    >>> skutterudite = crystal(('Co', 'Sb'),
    ...     basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)],
    ...     spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90])
    >>> len(skutterudite)
    32
    """
    sg = Spacegroup(spacegroup, setting)
    if (not isinstance(symbols, str) and
        hasattr(symbols, '__getitem__') and
        len(symbols) > 0 and
        isinstance(symbols[0], ase.Atom)):
        symbols = ase.Atoms(symbols)
    if isinstance(symbols, ase.Atoms):
        basis = symbols
        symbols = basis.get_chemical_symbols()
    if isinstance(basis, ase.Atoms):
        basis_coords = basis.get_scaled_positions()
        if cell is None and cellpar is None:
            cell = basis.cell
        if symbols is None:
            symbols = basis.get_chemical_symbols()
    else:
        basis_coords = np.array(basis, dtype=float, copy=False, ndmin=2)
    sites, kinds = sg.equivalent_sites(basis_coords,
                                       onduplicates=onduplicates,
                                       symprec=symprec)
    symbols = parse_symbols(symbols)
    symbols = [symbols[i] for i in kinds]
    if cell is None:
        cell = cellpar_to_cell(cellpar, ab_normal, a_direction)

    info = dict(spacegroup=sg)
    if primitive_cell:
        info['unit_cell'] = 'primitive'
    else:
        info['unit_cell'] = 'conventional'

    if 'info' in kwargs:
        info.update(kwargs['info'])
    kwargs['info'] = info

    atoms = ase.Atoms(symbols,
                      scaled_positions=sites,
                      cell=cell,
                      pbc=pbc,
                      **kwargs)

    if isinstance(basis, ase.Atoms):
        for name in basis.arrays:
            if not atoms.has(name):
                array = basis.get_array(name)
                atoms.new_array(name, [array[i] for i in kinds],
                                dtype=array.dtype, shape=array.shape[1:])

    if primitive_cell:
        from ase.build import cut
        prim_cell = sg.scaled_primitive_cell
        atoms = cut(atoms, a=prim_cell[0], b=prim_cell[1], c=prim_cell[2])

    if size != (1, 1, 1):
        atoms = atoms.repeat(size)
    return atoms