Beispiel #1
0
    def test_electrostatics(self):
        """Tests that the results are consistent with the electrostatic
        interpretation. Each matrix [i, j] element should correspond to the
        Coulomb energy of a system consisting of the pair of atoms i, j.
        """
        system = H2O
        n_atoms = len(system)
        a = 0.5
        desc = EwaldSumMatrix(n_atoms_max=3, permutation="none", flatten=False)

        # The Ewald matrix contains the electrostatic interaction between atoms
        # i and j. Here we construct the total electrostatic energy for a
        # system consisting of atoms i and j.
        matrix = desc.create(system, a=a, rcut=rcut, gcut=gcut)
        energy_matrix = np.zeros(matrix.shape)
        for i in range(n_atoms):
            for j in range(n_atoms):
                if i == j:
                    energy_matrix[i, j] = matrix[i, j]
                else:
                    energy_matrix[i, j] = matrix[i, j] + matrix[i, i] + matrix[j, j]

        # Converts unit of q*q/r into eV
        conversion = 1e10 * scipy.constants.e / (4 * math.pi * scipy.constants.epsilon_0)
        energy_matrix *= conversion

        # The value in each matrix element should correspond to the Coulomb
        # energy of a system with with only those atoms. Here the energies from
        # the Ewald matrix are compared against the Ewald energy calculated
        # with pymatgen.
        positions = system.get_positions()
        atomic_num = system.get_atomic_numbers()
        for i in range(n_atoms):
            for j in range(n_atoms):
                if i == j:
                    pos = [positions[i]]
                    sym = [atomic_num[i]]
                else:
                    pos = [positions[i], positions[j]]
                    sym = [atomic_num[i], atomic_num[j]]

                i_sys = Atoms(
                    cell=system.get_cell(),
                    positions=pos,
                    symbols=sym,
                    pbc=True,
                )

                structure = Structure(
                    lattice=i_sys.get_cell(),
                    species=i_sys.get_atomic_numbers(),
                    coords=i_sys.get_scaled_positions(),
                )
                structure.add_oxidation_state_by_site(i_sys.get_atomic_numbers())
                ewald = EwaldSummation(structure, eta=a, real_space_cut=rcut, recip_space_cut=gcut)
                energy = ewald.total_energy

                # Check that the energy given by the pymatgen implementation is
                # the same as given by the descriptor
                self.assertTrue(np.allclose(energy_matrix[i, j], energy, atol=0.00001, rtol=0))
    def __init__(self,
                 blocks,
                 blmin,
                 volume,
                 cellbounds=None,
                 cell=None,
                 splits={(1, ): 1}):

        self.volume = volume
        self.cellbounds = cellbounds
        self.cell = cell

        # normalize splitting probabilities:
        tot = sum([v for v in splits.values()])
        self.splits = {k: v * 1. / tot for k, v in splits.items()}

        # pre-process blocks and blmin:
        self.blocks = []
        numbers = []
        for block, count in blocks:
            if isinstance(block, Atoms):
                pass
            elif block in atomic_numbers:
                block = Atoms(block)
            else:
                block = molecule(block)
            self.blocks.append((block, count))
            numbers.extend(block.get_atomic_numbers())
        numbers = np.unique(numbers)

        if type(blmin) == dict:
            self.blmin = blmin
        else:
            self.blmin = closest_distances_generator(numbers, blmin)
def getSpaceGroup(elements, coords, la, lb, lc):

    print(elements)

    #coords = getFracCoords()

    crystal = Atoms([elements[0]] * 20, coords, pbc=[1, 1, 1])

    lattice = [[la, 0, 0], [0, lb, 0], [0, 0, lc]]

    cell = (lattice, coords, crystal.get_atomic_numbers())

    spacegroup = spglib.get_spacegroup(cell, symprec=1e-3)
    print(spacegroup, crystal.get_atomic_numbers())

    return (spacegroup)
Beispiel #4
0
def ase_atoms_to_spglib_cell(
        structure: Atoms) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Returns a tuple of three components: cell metric, atomic positions, and
    atomic species of the input ASE Atoms object.
    """
    return (structure.cell, structure.get_scaled_positions(),
            structure.get_atomic_numbers())
Beispiel #5
0
def draw_CPKsurf(interfc, ascale=1.0, outName=None, zLim=None, title=''):
    plt.rcParams["font.family"] = "Dejavu Serif"
    print(' |- Drawing CPK pic of \t%s to\t%s'%\
        (interfc.tags, outName+'-cpk.png'))
    atoms = interfc.get_allAtoms()
    if zLim is None:
        zLim = [(interfc.get_subPos()[:, 2].min() +
                 interfc.get_subPos()[:, 2].max()) / 2,
                interfc.get_pos()[:, 2].max()]
    atoms = atoms[[a.index for a in atoms \
        if zLim[0] < interfc.get_pos()[a.index][2] <= zLim[1]]]
    # atoms = atoms[[a.index for a in atoms \
    #     if zLim[0] < allpos[a.index][2] <= zLim[1]]]
    ori_cell = convert_cell(atoms.get_cell())
    atoms_old = atoms.copy()
    atoms = Atoms(sorted(atoms, key=lambda atm: atm.position[2]))
    allnum = atoms.get_atomic_numbers()
    allpos = atoms.get_positions()
    adsList = interfc.get_adsList()
    bufList = interfc.get_bufList()
    adsList = [i for i in range(len(atoms)) \
        if atoms.get_positions()[i] in interfc.get_pos()[adsList]]
    bufList = [i for i in range(len(atoms)) \
        if atoms.get_positions()[i] in interfc.get_pos()[bufList]]
    allc = [
        0 if i in adsList  # Full color
        else 0.5 if i in bufList  # Shallow color
        else 1.0 for i in range(len(atoms))
    ]
    plotCell(ori_cell[0] * 0 + ori_cell[1] * 0,
             ori_cell[0] * 1 + ori_cell[1] * 0,
             ori_cell[0] * 1 + ori_cell[1] * 1,
             ori_cell[0] * 0 + ori_cell[1] * 1,
             linwt=5)
    for i in range(len(atoms)):
        anum = allnum[i]
        apos = allpos[i]
        plotAtom(anum, apos, scale=ascale,\
            colorparam=allc[i], linwt=1.5-allc[i]/2)
        plotElliShine(anum, apos, atomscale=ascale, shift=0.45, scale=0.667)
    plt.axis('scaled')
    plt.xlim(min(ori_cell[0][0], ori_cell[1][0]),
             max(ori_cell[0][0], ori_cell[1][0]))
    plt.ylim(min(ori_cell[0][1], ori_cell[1][1]),
             max(ori_cell[0][1], ori_cell[1][1]))
    plt.tight_layout()
    plt.axis('off')
    plt.gca().xaxis.set_major_locator(plt.NullLocator())
    plt.gca().yaxis.set_major_locator(plt.NullLocator())
    plt.margins(0, 0)
    plt.title(title, fontsize='xx-large', fontweight='bold')
    if outName is not None:
        plt.savefig(outName+'-cpk.png', dpi=100, \
            bbox_inches = "tight", transparent=True, pad_inches = 0)
    else:
        plt.show()
    plt.close()
Beispiel #6
0
    def get_density(self, atom_indicees=None, gridrefinement=2):
        """Get sum of atomic densities from the given atom list.

        All atoms are taken if the list is not given."""
 
        all_atoms = self.calculator.get_atoms()
        if atom_indicees is None:
            atom_indicees = range(len(all_atoms))

        density = self.calculator.density
        spos_ac = all_atoms.get_scaled_positions()
        rank_a = self.finegd.get_ranks_from_positions(spos_ac)
        density.set_positions(all_atoms.get_scaled_positions(),
                              rank_a
                              )

        # select atoms
        atoms = []
        D_asp = {}
        rank_a = []
        all_D_asp = self.calculator.density.D_asp
        all_rank_a = self.calculator.density.rank_a
        for a in atom_indicees:
            if a in all_D_asp:
                D_asp[len(atoms)] = all_D_asp.get(a)
            atoms.append(all_atoms[a])
            rank_a.append(all_rank_a[a])
        atoms = Atoms(atoms, 
                      cell=all_atoms.get_cell(), pbc=all_atoms.get_pbc())
        spos_ac = atoms.get_scaled_positions()
        Z_a = atoms.get_atomic_numbers()

        par = self.calculator.input_parameters
        setups = Setups(Z_a, par.setups, par.basis, par.lmax, 
                        XC(par.xc), 
                        self.calculator.wfs.world)
        self.D_asp = D_asp

        # initialize 
        self.initialize(setups, 
                        self.calculator.timer,
                        np.zeros((len(atoms), 3)), False)
        self.set_mixer(None)
        # FIXME nparray causes partitionong.py test to fail
        self.set_positions(spos_ac, np.array(rank_a))
        basis_functions = BasisFunctions(self.gd,
                                         [setup.phit_j
                                          for setup in self.setups],
                                         cut=True)
        basis_functions.set_positions(spos_ac)
        self.initialize_from_atomic_densities(basis_functions)

        aed_sg, gd = self.get_all_electron_density(atoms, 
                                                   gridrefinement)
        return aed_sg[0], gd
Beispiel #7
0
    def get_density(self, atom_indices=None, gridrefinement=2):
        """Get sum of atomic densities from the given atom list.

        All atoms are taken if the list is not given."""

        all_atoms = self.calculator.get_atoms()
        if atom_indices is None:
            atom_indices = range(len(all_atoms))

        density = self.calculator.density
        spos_ac = all_atoms.get_scaled_positions()
        rank_a = self.finegd.get_ranks_from_positions(spos_ac)

        density.set_positions(all_atoms.get_scaled_positions(),
                              AtomPartition(self.finegd.comm, rank_a))

        # select atoms
        atoms = []
        D_asp = {}
        rank_a = []
        all_D_asp = self.calculator.density.D_asp
        all_rank_a = self.calculator.density.atom_partition.rank_a
        for a in atom_indices:
            if a in all_D_asp:
                D_asp[len(atoms)] = all_D_asp.get(a)
            atoms.append(all_atoms[a])
            rank_a.append(all_rank_a[a])
        atoms = Atoms(atoms,
                      cell=all_atoms.get_cell(),
                      pbc=all_atoms.get_pbc())
        spos_ac = atoms.get_scaled_positions()
        Z_a = atoms.get_atomic_numbers()

        par = self.calculator.parameters
        setups = Setups(Z_a, par.setups, par.basis, XC(par.xc),
                        self.calculator.wfs.world)

        # initialize
        self.initialize(setups, self.calculator.timer, np.zeros(len(atoms)),
                        False)
        self.set_mixer(None)

        # FIXME nparray causes partitionong.py test to fail
        self.set_positions(spos_ac, AtomPartition(self.gd.comm, rank_a))
        self.D_asp = D_asp
        basis_functions = BasisFunctions(
            self.gd, [setup.phit_j for setup in self.setups], cut=True)
        basis_functions.set_positions(spos_ac)
        self.initialize_from_atomic_densities(basis_functions)

        aed_sg, gd = self.get_all_electron_density(atoms, gridrefinement)
        return aed_sg.sum(axis=0), gd
Beispiel #8
0
def make_anti_bcc_struc(alat):
    """
    Creates the crystal structure using ASE.
    :param alat: Lattice parameter in angstrom
    :return: structure object converted from ase
    """
    fecell = Atoms('Fe2', [(0, 0, 0), (alat/2, alat/2, alat/2)], cell=[alat, alat, alat, 90, 90, 90], pbc=True)
    # check how your cell looks like
    #write('s.cif', gecell)
    print(fecell, fecell.get_atomic_numbers())
    #fecell.set_atomic_numbers([26, 27])
    structure = Struc(ase2struc(fecell))
    print(structure.species)
    return structure
Beispiel #9
0
 def _preprocess_atoms(self, atoms: Atoms) -> Dict[str, Optional[Tensor]]:
     pos = torch.tensor(
         atoms.get_positions(), device=self.device, dtype=self.dtype, requires_grad=True
     )
     Z = torch.tensor(atoms.get_atomic_numbers(), device=self.device)
     if any(atoms.pbc):
         cell = torch.tensor(
             atoms.get_cell(), device=self.device, dtype=self.dtype, requires_grad=True
         )
     else:
         cell = None
     pbc = torch.tensor(atoms.pbc, device=self.device)
     edge_index, S = self._calc_edge_index(pos, cell, pbc)
     input_dicts = dict(pos=pos, Z=Z, cell=cell, pbc=pbc, edge_index=edge_index, shift=S)
     return input_dicts
Beispiel #10
0
    def get_density(self, atom_indicees=None):
        """Get sum of atomic densities from the given atom list.

        All atoms are taken if the list is not given."""

        all_atoms = self.calculator.get_atoms()
        if atom_indicees is None:
            atom_indicees = range(len(all_atoms))

        density = self.calculator.density
        density.set_positions(all_atoms.get_scaled_positions() % 1.0,
                              self.calculator.wfs.rank_a)

        # select atoms
        atoms = []
        D_asp = {}
        rank_a = []
        all_D_asp = self.calculator.density.D_asp
        all_rank_a = self.calculator.density.rank_a
        for a in atom_indicees:
            if a in all_D_asp:
                D_asp[len(atoms)] = all_D_asp.get(a)
            atoms.append(all_atoms[a])
            rank_a.append(all_rank_a[a])
        atoms = Atoms(atoms, cell=all_atoms.get_cell())
        spos_ac = atoms.get_scaled_positions() % 1.0
        Z_a = atoms.get_atomic_numbers()

        par = self.calculator.input_parameters
        setups = Setups(Z_a, par.setups, par.basis, par.lmax, XC(par.xc),
                        self.calculator.wfs.world)
        self.D_asp = D_asp

        # initialize
        self.initialize(setups, par.stencils[1], self.calculator.timer,
                        [0] * len(atoms), False)
        self.set_mixer(None)
        self.set_positions(spos_ac, rank_a)
        basis_functions = BasisFunctions(
            self.gd, [setup.phit_j for setup in self.setups], cut=True)
        basis_functions.set_positions(spos_ac)
        self.initialize_from_atomic_densities(basis_functions)

        aed_sg, gd = Density.get_all_electron_density(self,
                                                      atoms,
                                                      gridrefinement=2)

        return aed_sg[0], gd
def read_ref_coords(system_name, filename="coords"):

    skip = True
    list_atoms = []
    with open(filename) as fp:
        for line in fp:
            if skip:
                skip = False
                continue
            temp = line.strip().split()
            list_atoms.append(
                Atom(temp[0],
                     (float(temp[1]), float(temp[2]), float(temp[3]))))

    system = Atoms(list_atoms)

    return system.get_atomic_numbers(), system.get_positions()
Beispiel #12
0
def _crd2frag(symbols, crds, pbc=False, cell=None, return_bonds=False):
    atomnumber = len(symbols)
    if pbc:
        all_atoms = Atoms(symbols=symbols, positions=crds, pbc=True, cell=cell)
        repeated_atoms = all_atoms.repeat(2)[atomnumber:]
        tree = cKDTree(crds)
        d = tree.query(repeated_atoms.get_positions(), k=1)[0]
        nearest = d < 5
        ghost_atoms = repeated_atoms[nearest]
        realnumber = np.where(nearest)[0] % atomnumber
        all_atoms += ghost_atoms
    # Use openbabel to connect atoms
    mol = openbabel.OBMol()
    mol.BeginModify()
    for idx, (num, position) in enumerate(
            zip(all_atoms.get_atomic_numbers(), all_atoms.positions)):
        atom = mol.NewAtom(idx)
        atom.SetAtomicNum(int(num))
        atom.SetVector(*position)
    mol.ConnectTheDots()
    mol.PerceiveBondOrders()
    mol.EndModify()
    bonds = []
    for ii in range(mol.NumBonds()):
        bond = mol.GetBond(ii)
        a = bond.GetBeginAtom().GetId()
        b = bond.GetEndAtom().GetId()
        bo = bond.GetBO()
        if a >= atomnumber and b >= atomnumber:
            # duplicated
            continue
        elif a >= atomnumber:
            a = realnumber[a - atomnumber]
        elif b >= atomnumber:
            b = realnumber[b - atomnumber]
        bonds.extend([[a, b, bo], [b, a, bo]])
    bonds = np.array(bonds, ndmin=2).reshape((-1, 3))
    graph = csr_matrix((bonds[:, 2], (bonds[:, 0], bonds[:, 1])),
                       shape=(atomnumber, atomnumber))
    frag_numb, frag_index = connected_components(graph, 0)
    if return_bonds:
        return frag_numb, frag_index, graph
    return frag_numb, frag_index
Beispiel #13
0
def test():
    """Simple test function to test atoms2json and json2atoms function"""

    from ase import Atoms
    teststructure = Atoms('N2', [(0, 0, 0), (0, 0, 1.1)])

    teststring = atoms2json(teststructure,
                            additional_information={"abc": "def"
                                                    })  #convert to the string

    newstructure = json2atoms(teststring)  #and convert back

    assert (newstructure.get_atomic_numbers() ==
            teststructure.get_atomic_numbers()).all()
    assert (
        newstructure.get_positions() == teststructure.get_positions()).all()
    assert (newstructure.get_cell() == teststructure.get_cell()).all()
    assert (newstructure.get_pbc() == teststructure.get_pbc()).all()
    assert newstructure.info["key_value_pairs"]["abc"] == "def"

    print("JSON Conversion in both directions: apparently no issue")
Beispiel #14
0
def make_struc(alat, atom, clat):
    """
    Creates the crystal structure using ASE.
    :param alat: Lattice parameter in angstrom
    :return: structure object converted from ase
    """
    if atom == 'Cu' or atom == 'Au':
        fcccell = bulk(atom, 'fcc', a=alat)
        write('fcc.cif', fcccell)
        print(fcccell, fcccell.get_atomic_numbers())
        structure = Struc(ase2struc(fcccell))
    elif atom == 'CuAu':
        lattice = alat * numpy.identity(3)
        lattice[2][2] = clat
        symbols = ['Cu', 'Au']
        sc_pos = [[0, 0, 0], [0.5, 0.5, 0.5]]
        bctcell = Atoms(symbols=symbols, scaled_positions=sc_pos, cell=lattice)
        write('bct.cif', bctcell)
        print(bctcell, bctcell.get_atomic_numbers())
        structure = Struc(ase2struc(bctcell))
    # check how your cell looks like
    print(structure.species)
    return structure
Beispiel #15
0
    def test_features(self):
        """Tests that the correct features are present in the desciptor.
        """
        desc = SineMatrix(n_atoms_max=2, permutation="none", flatten=False)

        # Test that without cell the matrix cannot be calculated
        system = Atoms(
            positions=[[0, 0, 0], [1.0, 1.0, 1.0]],
            symbols=["H", "H"],
        )
        with self.assertRaises(ValueError):
            desc.create(system)

        # Test that periodic boundaries are considered by seeing that an atom
        # in the origin is replicated to the  corners
        system = Atoms(
            cell=[
                [10, 10, 0],
                [0, 10, 0],
                [0, 0, 10],
            ],
            scaled_positions=[[0, 0, 0], [1.0, 1.0, 1.0]],
            symbols=["H", "H"],
            pbc=True,
        )
        # from ase.visualize import view
        # view(system)
        matrix = desc.create(system)

        # The interaction between atoms 1 and 2 should be infinite due to
        # periodic boundaries.
        self.assertEqual(matrix[0, 1], float("Inf"))

        # The interaction of an atom with itself is always 0.5*Z**2.4
        atomic_numbers = system.get_atomic_numbers()
        for i, i_diag in enumerate(np.diag(matrix)):
            self.assertEqual(i_diag, 0.5 * atomic_numbers[i]**2.4)
Beispiel #16
0
    def __init__(self,
                 atoms: Atoms,
                 num_configs,
                 sigmas: Mapping[Union[str, int], float],
                 seed=None):
        """
        Generates atomic configurations for thermal diffuse scattering.

        Randomly displaces the atomic positions of an ASE Atoms object to emulate thermal vibrations.

        Parameters
        ----------
        atoms : ase.Atoms
            ASE atoms object with the average atomic configuration.
        sigmas : Mapping[Union[str, int], float]
            Mapping from atomic species to the variance of the displacements of that atomic species. The atomic species
            can be specified as atomic number or symbol.
        """
        new_sigmas = {}
        for key, sigma in sigmas.items():
            try:
                new_sigmas[atomic_numbers[key]] = sigma
            except KeyError:
                pass

        unique_atomic_numbers = np.unique(atoms.get_atomic_numbers())

        if len(
                set(unique_atomic_numbers).intersection(set(
                    new_sigmas.keys()))) != len(unique_atomic_numbers):
            raise RuntimeError(
                'Mapping sigma not provided for all atomic species')

        self._sigmas = new_sigmas
        self._atoms = atoms
        self._num_configs = num_configs
        self._seed = seed
Beispiel #17
0
def WriteSlabFilm(output,
                  atoms,
                  precision=0.06,
                  smear=0.6,
                  slab_identifers=(78, 79),
                  excluded_from_density=(8, 1),
                  formula_prefix=("Pt", "Au"),
                  formula_suffix=("O", "H")):
    '''
    Write the slab and film layer/type information to the output file,
    returns an array of slab layer tags. After the script runs, 
    the atoms will be tagged to appropriate layers.
    '''

    #First generate an array of the density of atoms on each step of the Z axis.
    #This step is influenced by precision and smear.
    density = []
    for step in DRange(0, atoms.get_cell()[2][2], precision):
        #Keep track of how many atoms are in this density step
        dcount = 0
        for atom in atoms:
            #Exclude some atoms from the density count.  Hydrogen and oxygen are ignored
            #by default as they typically arn't represented in layers very well.
            if not (atom.number in excluded_from_density):
                if ((atom.position[2] - smear) < step) and (
                    (atom.position[2] + smear) > step):
                    dcount += 1
        density.append(dcount)
    #At this point density is populated with the density of atoms on the Z axis
    #Next is identifying the layer ranges.
    startx = -1
    endx = -1
    density_max = -1

    #Ranges are defined
    layer_ranges = []

    #Iterate over all the density values
    for d_index in xrange(0, len(density), 1):
        #Move the value to a local variable
        value = density[d_index]
        #if there is no know endx
        if endx == -1:
            #check if the value is higher than 1/6th the last known highest density for this section.
            if value > (density_max / 6):
                #set all the relevant values
                startx = d_index
                endx = d_index
                density_max = value
        #if you have found a starting index and the value has increased,  then restart the range
        elif value > density_max:
            startx = d_index
            endx = d_index
            density_max = value
        #if the value is the same,  extend the range
        elif value == density_max:
            endx = d_index
        #if the value ever drops under 1/6th of the highest known density for this section,  then the
        #layer can be defined with known values and the startx/endx can be reset.  Don't reset the density
        #max as there is no good reason to.  All layers should have densities that are at least that similar
        #or the definition of a layer is hard to define.  Also might cause errors as moving down away from
        #the density peak.
        elif value <= density_max / 6:
            layer_ranges.append((startx - (smear / precision), endx - startx,
                                 endx + (smear / precision)))
            startx = -1
            endx = -1

    #Find which atoms belong in which layers and tag them.
    for index, layer_range in enumerate(layer_ranges):
        atoms_in_layer = [
            atom for atom in atoms
            if (atom.position[2] / precision > layer_range[0]) and (
                atom.position[2] / precision < layer_range[2])
        ]
        for atom in atoms_in_layer:
            atom.tag = index + 1

    #Define a dictionary of atom tag keys and if they are part of the slab
    slab_layers = {}

    #Try to indentify which layers belong in the slab
    for atom in atoms:
        if atom.tag == 0:
            continue
        if atom.number in slab_identifers:
            slab_layers[atom.tag] = True
        else:
            #This part is designed to not overwrite a value if it already exists.  You never want
            #to overwrite a True value.
            try:
                slab_layers[atom.tag] = (slab_layers[atom.tag])
            except:
                slab_layers[atom.tag] = False

    for atom in atoms:
        #if atom is not assigned
        if atom.tag == 0:
            new_layer = 0
            distance = 1000
            #loop over all layer ranges
            for index, layer_range in enumerate(layer_ranges):
                #the atoms excluded from the density are not part of the slab
                if (atom.number
                        in excluded_from_density) and (slab_layers[index + 1]):
                    continue
                start = abs((atom.position[2] / smear * precision) -
                            layer_range[0])
                end = abs((atom.position[2] / smear * precision) -
                          layer_range[2])
                if (distance >= start):
                    new_layer = index
                    distance = start
                if (distance > end):
                    new_layer = index
                    distance = start
            atom.tag = new_layer + 1

    slab_layer_count = 0
    film_layer_count = 0
    for slab_layer in slab_layers:
        if slab_layers[slab_layer]:
            slab_layer_count += 1
        else:
            film_layer_count += 1

    output['SlabLayers'] = slab_layer_count
    output['FilmLayers'] = film_layer_count

    if film_layer_count == 0:
        return Reports.NoFilmLayers, None
    if slab_layer_count == 0:
        return Reports.NoSlabLayers, None

    slab = Atoms([atom for atom in atoms if (slab_layers[atom.tag])])
    slab.set_cell(atoms.get_cell())
    film = Atoms([atom for atom in atoms if not (slab_layers[atom.tag])])
    film.set_cell(atoms.get_cell())

    slabform = slab.get_chemical_formula()
    filmform = film.get_chemical_formula()

    #Obtain the layers of the slab as individual layers for additional slab analysis
    layers = []
    known_layers = set()
    for atom in slab:
        known_layers.add(atom.tag)
    for layer in known_layers:
        this_layer = []
        for atom in atoms:
            if atom.tag == layer:
                this_layer.append(atom)
        layers.append(Atoms(this_layer))

    layers.sort(key=lambda layer: layer[0].position[2])

    layer_formulas = []
    for layer in layers:
        layer_formulas.append(layer.get_chemical_formula())

    slab_numbers = slab.get_atomic_numbers()

    slabtype = ""  #H**o,  Hetero,  Skin
    skintype = ""  #Only defined if skinned | Plate,  Anneal
    skincomp = ""  #Only defined if skinned
    annealcomp = ""  #Reduced composition of annealed layer
    slabcomp = layer_formulas[0]
    if (CountList(slab_numbers, slab_numbers[0]) == len(slab_numbers)):
        slabtype = "H**o"
    elif CountList(layer_formulas, layer_formulas[0]) == len(layer_formulas):
        slabtype = "Hetero"
    elif ((CountList(layer_formulas, layer_formulas[0])
           == len(layer_formulas) - 1)
          or (CountList(layer_formulas, layer_formulas[1])
              == len(layer_formulas) - 1)):  #Plated Skin
        slabtype = "Skin"
        skintype = "Plate"
        skincomp = layer_formulas[len(layer_formulas) - 1]
    elif ((CountList(layer_formulas, layer_formulas[0])
           == len(layer_formulas) - 2) or (CountList(
               layer_formulas, layer_formulas[1]) == len(layer_formulas) - 2)
          or (CountList(layer_formulas, layer_formulas[2])
              == len(layer_formulas) - 2)):  #Annealed Skin
        slabtype = "Skin"
        skintype = "Anneal"
        skincomp = layer_formulas[len(layer_formulas) - 1]
        annealcomp = layer_formulas[len(layer_formulas) - 2]
    else:  #Failed
        return Reports.SlabTypeIDFail, None

    output['SystemFormula'] = FormatFormula(atoms.get_chemical_formula(),
                                            formula_prefix,
                                            formula_suffix,
                                            red=False)
    output['SlabFormula'] = FormatFormula(slabform,
                                          formula_prefix,
                                          formula_suffix,
                                          red=False)
    output['SlabType'] = slabtype
    output['SkinType'] = skintype
    output['SkinComp'] = FormatFormula(skincomp, formula_prefix,
                                       formula_suffix)
    output['AnnealComp'] = FormatFormula(annealcomp, formula_prefix,
                                         formula_suffix)
    output['SlabComp'] = FormatFormula(slabcomp, formula_prefix,
                                       formula_suffix)
    output['FilmFormula'] = FormatFormula(filmform,
                                          formula_prefix,
                                          formula_suffix,
                                          red=False)
    output['FilmComp'] = FormatFormula(filmform, formula_prefix,
                                       formula_suffix)

    return False, slab_layers
Beispiel #18
0
def WriteSlabFilm(output, atoms, precision = 0.06, smear = 0.6,
                 slab_identifers = (78, 79),
                 excluded_from_density = (8, 1),
                 formula_prefix = ("Pt","Au"),
                 formula_suffix = ("O", "H")):
    #First generate an array of the density of atoms on each step of the Z axis.
    #This step is influenced by precision and smear.
    density = []
    for step in DRange(0,atoms.get_cell()[2][2],precision):
        #Keep track of how many atoms are in this density step
        dcount=0
        for atom in atoms:
            #Exclude some atoms from the density count.  Hydrogen and oxygen are ignored
            #by default as they typically arn't represented in layers very well.
            if not (atom.number in excluded_from_density):
                if ((atom.position[2]-smear) < step) and ((atom.position[2]+smear) > step):
                    dcount+=1
        density.append(dcount)
    #At this point density is populated with the density of atoms on the Z axis
    #Next is identifying the layer ranges.
    startx = -1
    endx = -1
    density_max = -1

    #Ranges are defined
    layer_ranges = []
    
    #Iterate over all the density values
    for d_index in xrange(0,len(density),1):
        #Move the value to a local variable
        value = density[d_index]
        #if there is no know endx
        if endx == -1:
            #check if the value is higher than 1/6th the last known highest density for this section.
            if value > (density_max/6):
                #set all the relevant values
                startx = d_index
                endx = d_index
                density_max = value
        #if you have found a starting index and the value has increased,  then restart the range
        elif value > density_max:
            startx = d_index
            endx = d_index
            density_max = value
        #if the value is the same,  extend the range
        elif value == density_max:
            endx = d_index
        #if the value ever drops under 1/6th of the highest known density for this section,  then the
        #layer can be defined with known values and the startx/endx can be reset.  Don't reset the density
        #max as there is no good reason to.  All layers should have densities that are at least that similar
        #or the definition of a layer is hard to define.  Also might cause errors as moving down away from
        #the density peak.
        elif value <= density_max/6:
            layer_ranges.append((startx-(smear/precision),endx-startx,endx+(smear/precision)))
            startx = -1
            endx = -1 
    
    #Find which atoms belong in which layers and tag them.
    for index, layer_range in enumerate(layer_ranges):
        atoms_in_layer = [atom for atom in atoms if (atom.position[2]/precision > layer_range[0])
                           and (atom.position[2]/precision < layer_range[2])]
        for atom in atoms_in_layer:
            atom.tag=index+1

    #Define a dictionary of atom tag keys and if they are part of the slab
    slab_layers = {}
    
    #Try to indentify which layers belong in the slab
    for atom in atoms:
        if atom.tag == 0:
            continue
        if atom.number in slab_identifers:
            slab_layers[atom.tag] = True
        else:
            #This part is designed to not overwrite a value if it already exists.  You never want
            #to overwrite a True value.
            try:
                slab_layers[atom.tag]=(slab_layers[atom.tag])
            except:
                slab_layers[atom.tag]=False

    for atom in atoms:
        #if atom is not assigned
        if atom.tag == 0:
            new_layer = 0
            distance = 1000
            #loop over all layer ranges
            for index, layer_range in enumerate(layer_ranges):
                #the atoms excluded from the density are not part of the slab
                if (atom.number in excluded_from_density) and (slab_layers[index+1]):
                    continue
                start = abs((atom.position[2]/smear*precision)-layer_range[0])
                end = abs((atom.position[2]/smear*precision)-layer_range[2])
                if (distance>=start):
                    new_layer = index
                    distance = start
                if (distance>end):
                    new_layer = index
                    distance = start
            atom.tag=new_layer+1
    
    slab_layer_count=0
    film_layer_count=0
    for slab_layer in slab_layers:
        if slab_layers[slab_layer]:
            slab_layer_count+=1
        else:
            film_layer_count+=1

    output[K._VSF_SlabLayers_]=slab_layer_count
    output[K._VSF_FilmLayers_]=film_layer_count

    if film_layer_count == 0:
        return E._NoFilmLayers_, None
    if slab_layer_count == 0:
        return E._NoSlabLayers_, None
        
    slab = Atoms([atom for atom in atoms if (slab_layers[atom.tag])])
    slab.set_cell(atoms.get_cell())
    film = Atoms([atom for atom in atoms if not (slab_layers[atom.tag])])
    film.set_cell(atoms.get_cell())

    slabform = slab.get_chemical_formula()
    filmform = film.get_chemical_formula()

    #Obtain the layers of the slab as individual layers for additional slab analysis
    layers=[]
    known_layers=set()
    for atom in slab:
        known_layers.add(atom.tag)
    for layer in known_layers:
        this_layer=[]
        for atom in atoms:
            if atom.tag == layer:
                this_layer.append(atom)
        layers.append(Atoms(this_layer))

    layers.sort(key=lambda layer: layer[0].position[2])
    
    layer_formulas = []
    for layer in layers:
        layer_formulas.append(layer.get_chemical_formula())
        
    slab_numbers = slab.get_atomic_numbers()
    
    slabtype = "" #H**o,  Hetero,  Skin
    skintype = "" #Only defined if skinned | Plate,  Anneal
    skincomp = "" #Only defined if skinned
    annealcomp = "" #Reduced composition of annealed layer
    slabcomp = layer_formulas[0]
    if (Count(slab_numbers, slab_numbers[0])==len(slab_numbers)):
        slabtype = "H**o"
    elif Count(layer_formulas, layer_formulas[0])==len(layer_formulas):
        slabtype = "Hetero"
    elif ((Count(layer_formulas, layer_formulas[0])==len(layer_formulas)-1) or
          (Count(layer_formulas, layer_formulas[1])==len(layer_formulas)-1)): #Plated Skin
        slabtype = "Skin"
        skintype = "Plate"
        skincomp = layer_formulas[len(layer_formulas)-1]
    elif ((Count(layer_formulas, layer_formulas[0])==len(layer_formulas)-2) or
          (Count(layer_formulas, layer_formulas[1])==len(layer_formulas)-2) or 
          (Count(layer_formulas, layer_formulas[2])==len(layer_formulas)-2)): #Annealed Skin
        slabtype = "Skin"
        skintype = "Anneal"
        skincomp = layer_formulas[len(layer_formulas)-1]
        annealcomp = layer_formulas[len(layer_formulas)-2]
    else: #Failed
        return E._SlabTypeIDFail_, None

    output[K._SystemFormula_] = FormatForm(atoms.get_chemical_formula(), formula_prefix, formula_suffix, red = False)
    output[K._VSF_SlabFormula_] = FormatForm(slabform, formula_prefix, formula_suffix, red = False)
    output[K._VSF_SlabType_] = slabtype
    output[K._VSF_SkinType_] = skintype
    output[K._VSF_SkinComp_] = FormatForm(skincomp, formula_prefix, formula_suffix)
    output[K._VSF_AnnealComp_] = FormatForm(annealcomp, formula_prefix, formula_suffix)
    output[K._VSF_SlabComp_] = FormatForm(slabcomp, formula_prefix, formula_suffix)
    output[K._VSF_FilmFormula_] = FormatForm(filmform, formula_prefix, formula_suffix, red = False)
    output[K._VSF_FilmComp_] = FormatForm(filmform, formula_prefix, formula_suffix)

    return False, slab_layers
Beispiel #19
0
    def atoms_to_graph_const_cutoff(
        atoms: ase.Atoms,
        cutoff,
        atom_to_node_fn,
        self_interaction=False,
        cutoff_covalent=False,
    ):

        atoms.wrap()
        atom_numbers = atoms.get_atomic_numbers()

        if cutoff_covalent:
            radii = ase.data.covalent_radii[atom_numbers] * cutoff
        else:
            radii = [cutoff] * len(atoms)
        neighborhood = NeighborList(radii,
                                    skin=0.0,
                                    self_interaction=self_interaction,
                                    bothways=True)
        neighborhood.update(atoms)

        nodes = []
        connections = []
        connections_offset = []
        edges = []
        if np.any(atoms.get_pbc()):
            atom_positions = atoms.get_positions(wrap=True)
        else:
            atom_positions = atoms.get_positions(wrap=False)
        unitcell = atoms.get_cell()

        for ii in range(len(atoms)):
            nodes.append(atom_to_node_fn(atom_numbers[ii]))

        for ii in range(len(atoms)):
            neighbor_indices, offset = neighborhood.get_neighbors(ii)
            for jj, offs in zip(neighbor_indices, offset):
                ii_pos = atom_positions[ii]
                jj_pos = atom_positions[jj] + np.dot(offs, unitcell)
                dist_vec = ii_pos - jj_pos
                dist = np.sqrt(np.dot(dist_vec, dist_vec))

                connections.append([jj, ii])
                connections_offset.append(np.vstack((offs, np.zeros(3,
                                                                    float))))
                edges.append([dist])

        if len(edges) == 0:
            warnings.warn("Generated graph has zero edges")
            edges = np.zeros((0, 1))
            connections = np.zeros((0, 2))
            connections_offset = np.zeros((0, 2, 3))
        else:
            connections_offset = np.stack(connections_offset, axis=0)

        return (
            np.array(nodes),
            atom_positions,
            np.array(edges),
            np.array(connections),
            connections_offset,
            unitcell,
        )
Beispiel #20
0
    def atoms_to_graph_knearest(atoms: ase.Atoms,
                                num_neighbors,
                                atom_to_node_fn,
                                initial_radius=3.0):

        atoms.wrap()
        atom_numbers = atoms.get_atomic_numbers()
        unitcell = atoms.get_cell()

        for multiplier in range(1, 11):
            if multiplier == 10:
                raise RuntimeError("Reached maximum radius")
            radii = [initial_radius * multiplier] * len(atoms)
            neighborhood = NeighborList(radii,
                                        skin=0.0,
                                        self_interaction=False,
                                        bothways=True)
            neighborhood.update(atoms)

            nodes = []
            connections = []
            connections_offset = []
            edges = []
            if np.any(atoms.get_pbc()):
                atom_positions = atoms.get_positions(wrap=True)
            else:
                atom_positions = atoms.get_positions(wrap=False)
            keep_connections = []
            keep_connections_offset = []
            keep_edges = []

            for ii in range(len(atoms)):
                nodes.append(atom_to_node_fn(atom_numbers[ii]))

            early_exit = False
            for ii in range(len(atoms)):
                this_edges = []
                this_connections = []
                this_connections_offset = []
                neighbor_indices, offset = neighborhood.get_neighbors(ii)
                if len(neighbor_indices) < num_neighbors:
                    # Not enough neigbors, so exit and increase radius
                    early_exit = True
                    break
                for jj, offs in zip(neighbor_indices, offset):
                    ii_pos = atom_positions[ii]
                    jj_pos = atom_positions[jj] + np.dot(offs, unitcell)
                    dist_vec = ii_pos - jj_pos
                    dist = np.sqrt(np.dot(dist_vec, dist_vec))

                    this_connections.append([jj, ii])  # from, to
                    this_connections_offset.append(
                        np.vstack((offs, np.zeros(3, float))))
                    this_edges.append([dist])
                edges.append(np.array(this_edges))
                connections.append(np.array(this_connections))
                connections_offset.append(
                    np.stack(this_connections_offset, axis=0))
            if early_exit:
                continue
            else:
                for e, c, o in zip(edges, connections, connections_offset):
                    # Keep only num_neighbors closest indices
                    keep_ind = np.argsort(e[:, 0])[0:num_neighbors]
                    keep_edges.append(e[keep_ind])
                    keep_connections.append(c[keep_ind])
                    keep_connections_offset.append(o[keep_ind])
            break
        return (
            np.array(nodes),
            atom_positions,
            np.concatenate(keep_edges),
            np.concatenate(keep_connections),
            np.concatenate(keep_connections_offset),
            unitcell,
        )
Beispiel #21
0
class Conformer():
    """
    A class for generating and editing 3D conformers of molecules
    """

    def __init__(self, smiles=None, rmg_molecule=None, index=0):

        self.energy = None
        self.index = index

        if (smiles or rmg_molecule):
            if smiles and rmg_molecule:
                assert rmg_molecule.isIsomorphic(RMGMolecule(
                    SMILES=smiles)), "SMILES string did not match RMG Molecule object"
                self.smiles = smiles
                self.rmg_molecule = rmg_molecule

            elif rmg_molecule:
                self.rmg_molecule = rmg_molecule
                self.smiles = rmg_molecule.toSMILES()

            else:
                self.smiles = smiles
                self.rmg_molecule = RMGMolecule(SMILES=smiles)

            self.rmg_molecule.updateMultiplicity()
            self.get_molecules()
            self.get_geometries()
            self._symmetry_number = None

        else:
            self.smiles = None
            self.rmg_molecule = None
            self.rdkit_molecule = None
            self.ase_molecule = None
            self.bonds = []
            self.angles = []
            self.torsions = []
            self.cistrans = []
            self.chiral_centers = []
            self._symmetry_number = None

    def __repr__(self):
        return '<Conformer "{}">'.format(self.smiles)

    def copy(self):
        copy_conf = Conformer()
        copy_conf.smiles = self.smiles
        copy_conf.rmg_molecule = self.rmg_molecule.copy()
        copy_conf.rdkit_molecule = self.rdkit_molecule.__copy__()
        copy_conf.ase_molecule = self.ase_molecule.copy()
        copy_conf.get_geometries()
        copy_conf.energy = self.energy
        return copy_conf

    @property
    def symmetry_number(self):
        if not self._symmetry_number:
            self._symmetry_number = self.calculate_symmetry_number()
        return self._symmetry_number

    def get_rdkit_mol(self):
        """
        A method for creating an rdkit geometry from an rmg mol
        """

        assert self.rmg_molecule, "Cannot create an RDKit geometry without an RMG molecule object"

        RDMol = self.rmg_molecule.toRDKitMol(removeHs=False)
        rdkit.Chem.AllChem.EmbedMolecule(RDMol)
        self.rdkit_molecule = RDMol

        mol_list = AllChem.MolToMolBlock(self.rdkit_molecule).split('\n')
        for i, atom in enumerate(self.rmg_molecule.atoms):
            j = i + 4
            coords = mol_list[j].split()[:3]
            for k, coord in enumerate(coords):
                coords[k] = float(coord)
            atom.coords = np.array(coords)

        return self.rdkit_molecule

    def get_ase_mol(self):
        """
        A method for creating an ase atoms object from an rdkit mol
        """

        if not self.rdkit_molecule:
            self.get_rdkit_mol()

        mol_list = AllChem.MolToMolBlock(self.rdkit_molecule).split('\n')
        ase_atoms = []
        for i, line in enumerate(mol_list):
            if i > 3:
                try:
                    atom0, atom1, bond, rest = line
                    atom0 = int(atom0)
                    atom0 = int(atom1)
                    bond = float(bond)
                except ValueError:
                    try:
                        x, y, z, symbol = line.split()[0:4]
                        x = float(x)
                        y = float(y)
                        z = float(z)
                        ase_atoms.append(
                            Atom(symbol=symbol, position=(x, y, z)))
                    except BaseException:
                        continue

        self.ase_molecule = Atoms(ase_atoms)

        return self.ase_molecule

    def get_molecules(self):
        if not self.rmg_molecule:
            self.rmg_molecule = RMGMolecule(SMILES=self.smiles)
        self.rdkit_molecule = self.get_rdkit_mol()
        self.ase_molecule = self.get_ase_mol()
        self.get_geometries()

        return self.rdkit_molecule, self.ase_molecule

    def view(self):
        """
        A method designed to create a 3D figure of the AutoTST_Molecule with py3Dmol from the rdkit_molecule
        """
        mb = Chem.MolToMolBlock(self.rdkit_molecule)
        p = py3Dmol.view(width=600, height=600)
        p.addModel(mb, "sdf")
        p.setStyle({'stick': {}})
        p.setBackgroundColor('0xeeeeee')
        p.zoomTo()
        return p.show()

    def get_bonds(self):
        """
        A method for identifying all of the bonds in a conformer
        """
        bond_list = []
        for bond in self.rdkit_molecule.GetBonds():
            bond_list.append((bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()))

        bonds = []
        for index, indices in enumerate(bond_list):
            i, j = indices

            length = self.ase_molecule.get_distance(i, j)
            center = False
            if ((self.rmg_molecule.atoms[i].label) and (
                    self.rmg_molecule.atoms[j].label)):
                center = True

            bond = Bond(index=index,
                        atom_indices=indices,
                        length=length,
                        reaction_center=center)
            mask = self.get_mask(bond)
            bond.mask = mask

            bonds.append(bond)

        self.bonds = bonds

        return self.bonds

    def get_angles(self):
        """
        A method for identifying all of the angles in a conformer
        """

        angle_list = []
        for atom1 in self.rdkit_molecule.GetAtoms():
            for atom2 in atom1.GetNeighbors():
                for atom3 in atom2.GetNeighbors():
                    if atom1.GetIdx() == atom3.GetIdx():
                        continue

                    to_add = (atom1.GetIdx(), atom2.GetIdx(), atom3.GetIdx())
                    if (to_add in angle_list) or (
                            tuple(reversed(to_add)) in angle_list):
                        continue
                    angle_list.append(to_add)

        angles = []
        for index, indices in enumerate(angle_list):
            i, j, k = indices

            degree = self.ase_molecule.get_angle(i, j, k)
            ang = Angle(index=index,
                        atom_indices=indices,
                        degree=degree,
                        mask=[])
            mask = self.get_mask(ang)
            reaction_center = False

            angles.append(Angle(index=index,
                                atom_indices=indices,
                                degree=degree,
                                mask=mask,
                                reaction_center=reaction_center))
        self.angles = angles
        return self.angles

    def get_torsions(self):
        """
        A method for identifying all of the torsions in a conformer
        """
        torsion_list = []
        for bond1 in self.rdkit_molecule.GetBonds():
            atom1 = bond1.GetBeginAtom()
            atom2 = bond1.GetEndAtom()
            if atom1.IsInRing() or atom2.IsInRing():
                # Making sure that bond1 we're looking at are not in a ring
                continue

            bond_list1 = list(atom1.GetBonds())
            bond_list2 = list(atom2.GetBonds())

            if not len(bond_list1) > 1 and not len(bond_list2) > 1:
                # Making sure that there are more than one bond attached to
                # the atoms we're looking at
                continue

            # Getting the 0th and 3rd atom and insuring that atoms
            # attached to the 1st and 2nd atom are not terminal hydrogens
            # We also make sure that all of the atoms are properly bound
            # together

            # If the above are satisfied, we append a tuple of the torsion our
            # torsion_list
            got_atom0 = False
            got_atom3 = False

            for bond0 in bond_list1:
                atomX = bond0.GetOtherAtom(atom1)
                # if atomX.GetAtomicNum() == 1 and len(atomX.GetBonds()) == 1:
                # This means that we have a terminal hydrogen, skip this
                # NOTE: for H_abstraction TSs, a non teminal H should exist
                #    continue
                if atomX.GetIdx() != atom2.GetIdx():
                    got_atom0 = True
                    atom0 = atomX

            for bond2 in bond_list2:
                atomY = bond2.GetOtherAtom(atom2)
                # if atomY.GetAtomicNum() == 1 and len(atomY.GetBonds()) == 1:
                # This means that we have a terminal hydrogen, skip this
                #    continue
                if atomY.GetIdx() != atom1.GetIdx():
                    got_atom3 = True
                    atom3 = atomY

            if not (got_atom0 and got_atom3):
                # Making sure atom0 and atom3 were not found
                continue

            # Looking to make sure that all of the atoms are properly bonded to
            # eached
            if (
                "SINGLE" in str(
                    self.rdkit_molecule.GetBondBetweenAtoms(
                        atom1.GetIdx(),
                        atom2.GetIdx()).GetBondType()) and self.rdkit_molecule.GetBondBetweenAtoms(
                    atom0.GetIdx(),
                    atom1.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms(
                    atom1.GetIdx(),
                    atom2.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms(
                        atom2.GetIdx(),
                    atom3.GetIdx())):

                torsion_tup = (atom0.GetIdx(), atom1.GetIdx(),
                               atom2.GetIdx(), atom3.GetIdx())

                already_in_list = False
                for torsion_entry in torsion_list:
                    a, b, c, d = torsion_entry
                    e, f, g, h = torsion_tup

                    if (b, c) == (f, g) or (b, c) == (g, f):
                        already_in_list = True

                if not already_in_list:
                    torsion_list.append(torsion_tup)

        torsions = []
        for index, indices in enumerate(torsion_list):
            i, j, k, l = indices

            dihedral = self.ase_molecule.get_dihedral(i, j, k, l)
            tor = Torsion(index=index,
                          atom_indices=indices,
                          dihedral=dihedral,
                          mask=[])
            mask = self.get_mask(tor)
            reaction_center = False

            torsions.append(Torsion(index=index,
                                    atom_indices=indices,
                                    dihedral=dihedral,
                                    mask=mask,
                                    reaction_center=reaction_center))

        self.torsions = torsions
        return self.torsions

    def get_cistrans(self):
        """
        A method for identifying all possible cistrans bonds in a molecule
        """
        torsion_list = []
        cistrans_list = []
        for bond1 in self.rdkit_molecule.GetBonds():
            atom1 = bond1.GetBeginAtom()
            atom2 = bond1.GetEndAtom()
            if atom1.IsInRing() or atom2.IsInRing():
                # Making sure that bond1 we're looking at are not in a ring
                continue

            bond_list1 = list(atom1.GetBonds())
            bond_list2 = list(atom2.GetBonds())

            if not len(bond_list1) > 1 and not len(bond_list2) > 1:
                # Making sure that there are more than one bond attached to
                # the atoms we're looking at
                continue

            # Getting the 0th and 3rd atom and insuring that atoms
            # attached to the 1st and 2nd atom are not terminal hydrogens
            # We also make sure that all of the atoms are properly bound
            # together

            # If the above are satisfied, we append a tuple of the torsion our
            # torsion_list
            got_atom0 = False
            got_atom3 = False

            for bond0 in bond_list1:
                atomX = bond0.GetOtherAtom(atom1)
                # if atomX.GetAtomicNum() == 1 and len(atomX.GetBonds()) == 1:
                # This means that we have a terminal hydrogen, skip this
                # NOTE: for H_abstraction TSs, a non teminal H should exist
                #    continue
                if atomX.GetIdx() != atom2.GetIdx():
                    got_atom0 = True
                    atom0 = atomX

            for bond2 in bond_list2:
                atomY = bond2.GetOtherAtom(atom2)
                # if atomY.GetAtomicNum() == 1 and len(atomY.GetBonds()) == 1:
                # This means that we have a terminal hydrogen, skip this
                #    continue
                if atomY.GetIdx() != atom1.GetIdx():
                    got_atom3 = True
                    atom3 = atomY

            if not (got_atom0 and got_atom3):
                # Making sure atom0 and atom3 were not found
                continue

            # Looking to make sure that all of the atoms are properly bonded to
            # eached
            if (
                "DOUBLE" in str(
                    self.rdkit_molecule.GetBondBetweenAtoms(
                        atom1.GetIdx(),
                        atom2.GetIdx()).GetBondType()) and self.rdkit_molecule.GetBondBetweenAtoms(
                    atom0.GetIdx(),
                    atom1.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms(
                    atom1.GetIdx(),
                    atom2.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms(
                        atom2.GetIdx(),
                    atom3.GetIdx())):

                torsion_tup = (atom0.GetIdx(), atom1.GetIdx(),
                               atom2.GetIdx(), atom3.GetIdx())

                already_in_list = False
                for torsion_entry in torsion_list:
                    a, b, c, d = torsion_entry
                    e, f, g, h = torsion_tup

                    if (b, c) == (f, g) or (b, c) == (g, f):
                        already_in_list = True

                if not already_in_list:
                    cistrans_list.append(torsion_tup)

        cistrans = []

        for ct_index, indices in enumerate(cistrans_list):
            i, j, k, l = indices

            b0 = self.rdkit_molecule.GetBondBetweenAtoms(i, j)
            b1 = self.rdkit_molecule.GetBondBetweenAtoms(j, k)
            b2 = self.rdkit_molecule.GetBondBetweenAtoms(k, l)

            b0.SetBondDir(Chem.BondDir.ENDUPRIGHT)
            b2.SetBondDir(Chem.BondDir.ENDDOWNRIGHT)

            Chem.AssignStereochemistry(self.rdkit_molecule, force=True)

            if "STEREOZ" in str(b1.GetStereo()):
                if round(self.ase_molecule.get_dihedral(i, j, k, l), -1) == 0:
                    atom = self.rdkit_molecule.GetAtomWithIdx(k)
                    bonds = atom.GetBonds()
                    for bond in bonds:
                        indexes = [
                            bond.GetBeginAtomIdx(),
                            bond.GetEndAtomIdx()]
                        if not ((sorted([j, k]) == sorted(indexes)) or (
                                sorted([k, l]) == sorted(indexes))):
                            break

                    for index in indexes:
                        if not (index in indices):
                            l = index
                            break

                indices = [i, j, k, l]
                stero = "Z"

            else:
                if round(
                    self.ase_molecule.get_dihedral(
                        i, j, k, l), -1) == 180:
                    atom = self.rdkit_molecule.GetAtomWithIdx(k)
                    bonds = atom.GetBonds()
                    for bond in bonds:
                        indexes = [
                            bond.GetBeginAtomIdx(),
                            bond.GetEndAtomIdx()]
                        if not ((sorted([j, k]) == sorted(indexes)) or (
                                sorted([k, l]) == sorted(indexes))):
                            break

                    for index in indexes:
                        if not (index in indices):
                            l = index
                            break

                indices = [i, j, k, l]
                stero = "E"

            dihedral = self.ase_molecule.get_dihedral(i, j, k, l)
            tor = CisTrans(index=ct_index,
                           atom_indices=indices,
                           dihedral=dihedral,
                           mask=[],
                           stero=stero)
            mask = self.get_mask(tor)
            reaction_center = False

            cistrans.append(CisTrans(index=ct_index,
                                     atom_indices=indices,
                                     dihedral=dihedral,
                                     mask=mask,
                                     stero=stero
                                     )
                            )

        self.cistrans = cistrans
        return self.cistrans

    def get_mask(self, geometry):
        """
        Getting the right hand mask for a geometry object:

        - self: an AutoTST Conformer object
        - geometry: a Bond, Angle, Dihedral, or Torsion object 


        """

        rdkit_atoms = self.rdkit_molecule.GetAtoms()
        if (isinstance(geometry, autotst.geometry.Torsion) or
                isinstance(geometry, autotst.geometry.CisTrans)):

            L1, L0, R0, R1 = geometry.atom_indices

            # trying to get the left hand side of this torsion
            LHS_atoms_index = [L0, L1]
            RHS_atoms_index = [R0, R1]

        elif isinstance(geometry, autotst.geometry.Angle):
            a1, a2, a3 = geometry.atom_indices
            LHS_atoms_index = [a2, a1]
            RHS_atoms_index = [a2, a3]

        elif isinstance(geometry, autotst.geometry.Bond):
            a1, a2 = geometry.atom_indices
            LHS_atoms_index = [a1]
            RHS_atoms_index = [a2]

        complete_RHS = False
        i = 0
        atom_index = RHS_atoms_index[0]
        while complete_RHS is False:
            try:
                RHS_atom = rdkit_atoms[atom_index]
                for neighbor in RHS_atom.GetNeighbors():
                    if (neighbor.GetIdx() in RHS_atoms_index) or (
                            neighbor.GetIdx() in LHS_atoms_index):
                        continue
                    else:
                        RHS_atoms_index.append(neighbor.GetIdx())
                i += 1
                atom_index = RHS_atoms_index[i]

            except IndexError:
                complete_RHS = True

        mask = [index in RHS_atoms_index for index in range(
            len(self.ase_molecule))]

        return mask

    def get_chiral_centers(self):
        """
        A method to identify
        """

        centers = rdkit.Chem.FindMolChiralCenters(
            self.rdkit_molecule, includeUnassigned=True)
        chiral_centers = []

        for index, center in enumerate(centers):
            atom_index, chirality = center

            chiral_centers.append(
                ChiralCenter(
                    index=index,
                    atom_index=atom_index,
                    chirality=chirality))

        self.chiral_centers = chiral_centers
        return self.chiral_centers

    def get_geometries(self):
        """
        A helper method to obtain all geometry things
        """

        self.bonds = self.get_bonds()
        self.angles = self.get_angles()
        self.torsions = self.get_torsions()
        self.cistrans = self.get_cistrans()
        self.chiral_centers = self.get_chiral_centers()

        return (
            self.bonds,
            self.angles,
            self.torsions,
            self.cistrans,
            self.chiral_centers)

    def update_coords(self):
        """
        A function that creates distance matricies for the RMG, ASE, and RDKit molecules and finds which
        (if any) are different. If one is different, this will update the coordinates of the other two
        with the different one. If all three are different, nothing will happen. If all are the same,
        nothing will happen.
        """
        rdkit_dm = rdkit.Chem.rdmolops.Get3DDistanceMatrix(self.rdkit_molecule)
        ase_dm = self.ase_molecule.get_all_distances()
        l = len(self.rmg_molecule.atoms)
        rmg_dm = np.zeros((l, l))

        for i, atom_i in enumerate(self.rmg_molecule.atoms):
            for j, atom_j in enumerate(self.rmg_molecule.atoms):
                rmg_dm[i][j] = np.linalg.norm(atom_i.coords - atom_j.coords)

        d1 = round(abs(rdkit_dm - ase_dm).max(), 3)
        d2 = round(abs(rdkit_dm - rmg_dm).max(), 3)
        d3 = round(abs(ase_dm - rmg_dm).max(), 3)

        if np.all(np.array([d1, d2, d3]) > 0):
            return False, None

        if np.any(np.array([d1, d2, d3]) > 0):
            if d1 == 0:
                diff = "rmg"
                self.update_coords_from("rmg")
            elif d2 == 0:
                diff = "ase"
                self.update_coords_from("ase")
            else:
                diff = "rdkit"
                self.update_coords_from("rdkit")

            return True, diff
        else:
            return True, None

    def update_coords_from(self, mol_type="ase"):
        """
        A method to update the coordinates of the RMG, RDKit, and ASE objects with a chosen object.
        """

        possible_mol_types = ["ase", "rmg", "rdkit"]

        assert (mol_type.lower() in possible_mol_types), "Please specifiy a valid mol type. Valid types are {}".format(
            possible_mol_types)

        if mol_type.lower() == "rmg":
            conf = self.rdkit_molecule.GetConformers()[0]
            ase_atoms = []
            for i, atom in enumerate(self.rmg_molecule.atoms):
                x, y, z = atom.coords
                symbol = atom.symbol

                conf.SetAtomPosition(i, [x, y, z])

                ase_atoms.append(Atom(symbol=symbol, position=(x, y, z)))

            self.ase_molecule = Atoms(ase_atoms)
            # self.calculate_symmetry_number()

        elif mol_type.lower() == "ase":
            conf = self.rdkit_molecule.GetConformers()[0]
            for i, position in enumerate(self.ase_molecule.get_positions()):
                self.rmg_molecule.atoms[i].coords = position
                conf.SetAtomPosition(i, position)

            # self.calculate_symmetry_number()

        elif mol_type.lower() == "rdkit":

            mol_list = AllChem.MolToMolBlock(self.rdkit_molecule).split('\n')
            for i, atom in enumerate(self.rmg_molecule.atoms):
                j = i + 4
                coords = mol_list[j].split()[:3]
                for k, coord in enumerate(coords):
                    coords[k] = float(coord)
                atom.coords = np.array(coords)

            self.get_ase_mol()
            # self.calculate_symmetry_number()

    def set_bond_length(self, bond_index, length):
        """
        This is a method to set bond lengths
        Variabels:
        - bond_index (int): the index of the bond you want to edit
        - length (float, int): the distance you want to set the bond (in angstroms)
        """

        assert isinstance(length, (float, int))

        matched = False
        for bond in self.bonds:
            if bond.index == bond_index:
                matched = True
                break

        if not matched:
            logging.info("Angle index provided is out of range. Nothing was changed.")
            return self

        i, j = bond.atom_indices
        self.ase_molecule.set_distance(
            a0=i,
            a1=j,
            distance=length,
            mask=bond.mask,
            fix=0
        )

        bond.length = length

        self.update_coords_from(mol_type="ase")
        return self

    def set_angle(self, angle_index, angle):
        """
        A method that will set the angle of an Angle object accordingly
        """

        assert isinstance(
            angle, (int, float)), "Plese provide a float or an int for the angle"

        matched = False
        for a in self.angles:
            if a.index == angle_index:
                matched = True
                break

        if not matched:
            logging.info("Angle index provided is out of range. Nothing was changed.")
            return self

        i, j, k = a.atom_indices
        self.ase_molecule.set_angle(
            a1=i,
            a2=j,
            a3=k,
            angle=angle,
            mask=a.mask
        )

        a.degree = angle

        self.update_coords_from(mol_type="ase")

        return self

    def set_torsion(self, torsion_index, dihedral):
        """
        A method that will set the diehdral angle of a Torsion object accordingly.
        """

        assert isinstance(
            dihedral, (int, float)), "Plese provide a float or an int for the diehdral angle"

        matched = False
        for torsion in self.torsions:
            if torsion.index == torsion_index:
                matched = True
                break

        if not matched:
            logging.info("Torsion index provided is out of range. Nothing was changed.")
            return self

        i, j, k, l = torsion.atom_indices
        self.ase_molecule.set_dihedral(
            a1=i,
            a2=j,
            a3=k,
            a4=l,
            angle=dihedral,
            mask=torsion.mask
        )
        torsion.dihedral = dihedral

        self.update_coords_from(mol_type="ase")

        return self

    def set_cistrans(self, cistrans_index, stero="E"):
        """
        A module that will set a corresponding cistrans bond to the proper E/Z config
        """

        assert stero.upper() in [
            "E", "Z"], "Please specify a valid stero direction."

        matched = False
        for cistrans in self.cistrans:
            if cistrans.index == cistrans_index:
                matched = True
                break

        if not matched:
            logging.info("CisTrans index provided is out of range. Nothing was changed.")
            return self

        if cistrans.stero == stero.upper():
            self.update_coords_from("ase")
            return self

        else:
            cistrans.stero = stero.upper()
            i, j, k, l = cistrans.atom_indices
            self.ase_molecule.rotate_dihedral(
                a1=i,
                a2=j,
                a3=k,
                a4=l,
                angle=float(180),
                mask=cistrans.mask
            )
            cistrans.stero = stero.upper()

            self.update_coords_from(mol_type="ase")
            return self

    def set_chirality(self, chiral_center_index, stero="R"):
        """
        A module that can set the orientation of a chiral center.
        """
        assert stero.upper() in ["R", "S"], "Specify a valid stero orientation"

        centers_dict = {
            'R': Chem.rdchem.ChiralType.CHI_TETRAHEDRAL_CW,
            'S': Chem.rdchem.ChiralType.CHI_TETRAHEDRAL_CCW
        }

        assert isinstance(chiral_center_index,
                          int), "Please provide an integer for the index"

        rdmol = self.rdkit_molecule.__copy__()

        match = False
        for chiral_center in self.chiral_centers:
            if chiral_center.index == chiral_center_index:
                match = True
                break

        if not match:
            logging.info("ChiralCenter index provided is out of range. Nothing was changed")
            return self

        rdmol.GetAtomWithIdx(chiral_center.atom_index).SetChiralTag(
            centers_dict[stero.upper()])

        rdkit.Chem.rdDistGeom.EmbedMolecule(rdmol)

        old_torsions = self.torsions[:] + self.cistrans[:]

        self.rdkit_molecule = rdmol
        self.update_coords_from(mol_type="rdkit")

        # Now resetting dihedral angles in case if they changed.

        for torsion in old_torsions:
            i, j, k, l = torsion.atom_indices

            self.ase_molecule.set_dihedral(
                a1=i,
                a2=j,
                a3=k,
                a4=l,
                mask=torsion.mask,
                angle=torsion.dihedral,
            )

        self.update_coords_from(mol_type="ase")

        return self

    def calculate_symmetry_number(self):
        from rmgpy.qm.symmetry import PointGroupCalculator
        from rmgpy.qm.qmdata import QMData

        atom_numbers = self.ase_molecule.get_atomic_numbers()
        coordinates = self.ase_molecule.get_positions()

        qmdata = QMData(
            groundStateDegeneracy=1,  # Only needed to check if valid QMData
            numberOfAtoms=len(atom_numbers),
            atomicNumbers=atom_numbers,
            atomCoords=(coordinates, str('angstrom')),
            energy=(0.0, str('kcal/mol'))  # Only needed to avoid error
        )
        settings = type(str(''), (), dict(symmetryPath=str(
            'symmetry'), scratchDirectory="."))()  # Creates anonymous class
        pgc = PointGroupCalculator(settings, self.smiles, qmdata)
        pg = pgc.calculate()
        #os.remove("{}.symm".format(self.smiles))

        if pg is not None:
            symmetry_number = pg.symmetryNumber
        else:
            symmetry_number = 1

        return symmetry_number
Beispiel #22
0
def write_pw_in(d,a,p):

    if 'iofile' in p.keys() :
        # write special varians of the pw input
        fh=open(os.path.join(d,p['iofile']+'.in'),'w')
    else :
        # write standard pw input
        fh=open(os.path.join(d,'pw.in'),'w')

    # ----------------------------------------------------------
    # CONTROL section
    # ----------------------------------------------------------

    fh.write(' &CONTROL\n')
    fh.write("    calculation = '%s',\n" % p['calc'])

    pwin_k=['tstress', 'tprnfor','nstep','pseudo_dir','outdir',
            'wfcdir', 'prefix','forc_conv_thr', 'etot_conv_thr']
    write_section_params(fh, pwin_k, p)
    fh.write(' /\n')
    
    # ----------------------------------------------------------
    # SYSTEM section
    # ----------------------------------------------------------

    fh.write(' &SYSTEM\n')
    if p['use_symmetry'] :
        # Need to use symmetry properly
        # create a dummy atoms object for primitive cell
        primcell=write_cell_params(fh,a,p)
        if primcell :
            # primitive cell has been found - let us use it
            cr=Atoms(cell=primcell[0],scaled_positions=primcell[1],numbers=primcell[2],pbc=True)
        else :
            # no primitive cell found - drop the symmetry
            cr=a
    else :
        cr=a
        p['ibrav']=0
    fh.write("    nat = %d,\n" % (cr.get_number_of_atoms()))
    fh.write("    ntyp = %d,\n" % (len(set(cr.get_atomic_numbers()))))
    pwin_k=['ecutwfc','ibrav','nbnd','occupations']
    write_section_params(fh, pwin_k, p)
    fh.write(' /\n')

    # ----------------------------------------------------------
    # ELECTRONS section
    # ----------------------------------------------------------

    fh.write(' &ELECTRONS\n')
    fh.write(' /\n')

    # ----------------------------------------------------------
    # | IONS section
    # ----------------------------------------------------------

    if p['calc'] in ['vc-relax', 'vc-md', 'md', 'relax']:
        fh.write(' &IONS\n')
        pwin_k=['ion_dynamics','ion_positions', 'phase_space', 'pot_extrapolation']
        write_section_params(fh, pwin_k, p)
        
        fh.write(' /\n')


    # ----------------------------------------------------------
    # | CELL section
    # ----------------------------------------------------------

    if p['calc'] in ['vc-relax', 'vc-md']:
        fh.write('&CELL\n')
        pwin_k=['cell_dynamics','press', 'cell_dofree']
        write_section_params(fh, pwin_k, p)
    
        fh.write('/\n')


    # ----------------------------------------------------------
    # Card: ATOMIC_SPECIES
    # ----------------------------------------------------------
        
    fh.write('ATOMIC_SPECIES\n')
    
    xc=p['xc']
    pp_type=p['pp_type']
    pp_format=p['pp_format']
    for nm, mass in set(zip(cr.get_chemical_symbols(),cr.get_masses())):
        fh.write("    %s %g %s_%s_%s.%s \n" % (nm, mass, nm, xc, pp_type, pp_format))

    # ----------------------------------------------------------
    # Card: ATOMIC_POSITIONS
    # ----------------------------------------------------------
    
    fh.write('ATOMIC_POSITIONS crystal\n')
    # Now we can write it out
    for n,v in zip(cr.get_chemical_symbols(),cr.get_scaled_positions()):
        fh.write("    %s %g %g %g\n" % (n, v[0], v[1], v[2]))

    # ----------------------------------------------------------
    # Card: CELL_PARAMETERS
    # ----------------------------------------------------------
    # Write out only if ibrav==0 - no symmetry used
    if not p['use_symmetry'] or p['ibrav']==0:
        fh.write('CELL_PARAMETERS angstrom\n')
        
        for v in cr.get_cell():
            fh.write('    %f %f %f\n' % tuple(v))
        
    # ----------------------------------------------------------
    # Card: K_POINTS
    # ----------------------------------------------------------

    if p['kpt_type'] in ['automatic','edos'] :
        fh.write('K_POINTS %s\n' % 'automatic')
    else :
        fh.write('K_POINTS %s\n' % p['kpt_type'])
        
    if p['kpt_type'] is 'automatic':
        fh.write('   %d %d %d   %d %d %d\n' % (tuple(p['kpts'])+tuple(p['kpt_shift'])))
    elif p['kpt_type'] is 'edos':
        fh.write('   %d %d %d   %d %d %d\n' % (tuple(p['nkdos'])+tuple([0,0,0])))
    elif not p['kpt_type'] is 'gamma':
        write_q_path(fh,p['qpath'],p['points'])
    fh.close()
Beispiel #23
0
		elif cell_type == 'ortho' and border_type == 'zigzag':
			up_sat.translate([0,-one_to_zero[1],0])
			up_sat.translate([bond_CC * 0.25,0,0])
		elif cell_type == 'ortho' and border_type == 'armchair':
			up_sat.translate(-one_to_zero)
			right_sat.translate([bond_CC * (3 ** .5) * 0.5 ,0,0])
			
		up_sat.translate(up_shift)
		up_sat.translate([0,bond_CH,0])

		gr = gr + down_sat + up_sat + left_sat + right_sat

gr.center(axis=2)

natoms = len(gr)
ncarbons = len((gr.get_atomic_numbers() == 6).nonzero()[0])

if show_atoms: view(gr)

if verbose:
    print(f'Generated graphene sheet with {natoms} atoms')
    print(f'Cell dimensions are {np.linalg.norm(gr.cell[0]):.2f} x {np.linalg.norm(gr.cell[1]):.2f} x {np.linalg.norm(gr.cell[2]):.2f} ang')


# ### 2. Build first neighbours list for each atom

if verbose: print('Building first neighbours list')

NNlist_i,NNlist_j = neighbor_list('ij', a=gr, cutoff={('C','C'): bond_CC+.2}, self_interaction=False)

carbon_atoms = np.unique(NNlist_i)
Beispiel #24
0
def write_cell_params(fh, a, p):
    '''
    Write the specification of the cell using a traditional A,B,C, 
    alpha, beta, gamma scheme. The symmetry and parameters are determined
    from the atoms (a) object. The atoms object is translated into a 
    primitive unit cell but *not* converted. This is just an internal procedure.
    If you wish to work with primitive unit cells in ASE, you need to make 
    a conversion yourself. The cell params are in angstrom.
    
    Input
    -----
        fh  file handle of the opened pw.in file
        a   atoms object
        p   parameters dictionary
        
    Output
    ------
        Primitive cell tuple of arrays: (lattice, atoms, atomic numbers)
    '''
    
    assert(p['use_symmetry'])
    
    # Get a spacegroup name and number for the a
    sg,sgn=spglib.get_spacegroup(a).split()
    # Extract the number
    sgn=int(sgn[1:-1])
    # Extract the lattice type
    ltyp=sg[0]
    
    # Find a primitive unit cell for the system
    # puc is a tuple of (lattice, atoms, atomic numbers)
    puc=spglib.find_primitive(a)
    cell=puc[0]
    apos=puc[1]
    anum=puc[2]
    icell=a.get_cell()
    A=norm(icell[0])
    B=norm(icell[1])
    C=norm(icell[2])

    # Select appropriate ibrav
    if sgn >= 195 :
        # Cubic lattice
        if   ltyp=='P':  
            p['ibrav']=1  # Primitive
            qepc=array([[1,0,0],[0,1,0],[0,0,1]])
        elif ltyp=='F':  
            p['ibrav']=2  # FCC
            qepc=array([[-1,0,1],[0,1,1],[-1,1,0]])/2.0
        elif ltyp=='I':  
            p['ibrav']=3  # BCC
            qepc=array([[1,1,1],[-1,1,1],[-1,-1,1]])/2.0
        else :
            print 'Impossible lattice symmetry! Contact the author!'
            raise NotImplementedError
        #a=sqrt(2)*norm(cell[0])
        qepc=A*qepc
        fh.write('      A = %f,\n' % (A,))
    elif sgn >= 143 :
        # Hexagonal and trigonal 
        if   ltyp=='P' :  
            p['ibrav']=4  # Primitive
            qepc=array([[1,0,0],[-1/2,sqrt(3)/2,0],[0,0,C/A]])
        elif ltyp=='R' :  
            p['ibrav']=5  # Trigonal rhombohedral
            raise NotImplementedError
        else :
            print 'Impossible lattice symmetry! Contact the author!'
            raise NotImplementedError
        qepc=A*qepc
        fh.write('      A = %f,\n' % (A,))
        fh.write('      C = %f,\n' % (C,))
    elif sgn >= 75 :
        raise NotImplementedError
    elif sgn ==1 :
        # P1 symmetry - no special primitive cell signal to the caller
        p['ibrav']=0
        return None
    else :
        raise NotImplementedError
    cp=Atoms(cell=puc[0], scaled_positions=puc[1], numbers=puc[2], pbc=True)
    qepc=Atoms(cell=qepc, 
        positions=cp.get_positions(), 
        numbers=cp.get_atomic_numbers(), pbc=True)
    return qepc.get_cell(), qepc.get_scaled_positions(), qepc.get_atomic_numbers()
Beispiel #25
0
def write_pw_in(d,a,p):

    if 'iofile' in p.keys() :
        # write special varians of the pw input
        fh=open(os.path.join(d,p['iofile']+'.in'),'w')
    else :
        # write standard pw input
        fh=open(os.path.join(d,'pw.in'),'w')

    # ----------------------------------------------------------
    # CONTROL section
    # ----------------------------------------------------------

    fh.write(' &CONTROL\n')
    fh.write("    calculation = '%s',\n" % p['calc'])

    pwin_k=['tstress', 'tprnfor','nstep','pseudo_dir','outdir',
            'wfcdir', 'prefix','forc_conv_thr', 'etot_conv_thr']
    write_section_params(fh, pwin_k, p)
    fh.write(' /\n')
    
    # ----------------------------------------------------------
    # SYSTEM section
    # ----------------------------------------------------------

    fh.write(' &SYSTEM\n')
    if p['use_symmetry'] :
        # Need to use symmetry properly
        # create a dummy atoms object for primitive cell
        primcell=write_cell_params(fh,a,p)
        if primcell :
            # primitive cell has been found - let us use it
            cr=Atoms(cell=primcell[0],scaled_positions=primcell[1],numbers=primcell[2],pbc=True)
        else :
            # no primitive cell found - drop the symmetry
            cr=a
    else :
        cr=a
        p['ibrav']=0
    fh.write("    nat = %d,\n" % (cr.get_number_of_atoms()))
    fh.write("    ntyp = %d,\n" % (len(set(cr.get_atomic_numbers()))))
    pwin_k=['ecutwfc','ibrav','nbnd','occupations','degauss','smearing','ecutrho','nbnd']
    # must also take into account degauss and smearing
    write_section_params(fh, pwin_k, p)
    fh.write(' /\n')

    # ----------------------------------------------------------
    # ELECTRONS section
    # ----------------------------------------------------------

    fh.write(' &ELECTRONS\n')
    # must also take into account mixing_beta mixing_mode and diagonalization
    pwin_k=['conv_thr','mixing_beta','mixing_mode','diagonalization',
        'mixing_ndim','electron_maxstep']
    write_section_params(fh, pwin_k, p)
    fh.write(' /\n')

    # ----------------------------------------------------------
    # | IONS section
    # ----------------------------------------------------------

    if p['calc'] in ['vc-relax', 'vc-md', 'md', 'relax']:
        fh.write(' &IONS\n')
        pwin_k=['ion_dynamics','ion_positions', 'phase_space', 'pot_extrapolation']
        write_section_params(fh, pwin_k, p)
        
        fh.write(' /\n')


    # ----------------------------------------------------------
    # | CELL section
    # ----------------------------------------------------------

    if p['calc'] in ['vc-relax', 'vc-md']:
        fh.write('&CELL\n')
        pwin_k=['cell_dynamics','press', 'cell_dofree']
        write_section_params(fh, pwin_k, p)
    
        fh.write('/\n')


    # ----------------------------------------------------------
    # Card: ATOMIC_SPECIES
    # ----------------------------------------------------------
        
    fh.write('ATOMIC_SPECIES\n')
    
    xc=p['xc']
    pp_type=p['pp_type']
    pp_format=p['pp_format']
    # we check if the desired potential exists. Get the PP locations
    if 'ESPRESSO_PSEUDO' in os.environ:
        pppaths = os.environ['ESPRESSO_PSEUDO']
    else:
        pppaths = p['pseudo_dir']  # default
    pppaths = pppaths.split(':')
    # search for element PP in each location (in principle with QE only one location)
    for nm, mass in set(zip(cr.get_chemical_symbols(),cr.get_masses())):
        name = "%s_%s_%s.%s" % (nm, xc, pp_type, pp_format)
        found = False
        
        for path in pppaths:
            filename = os.path.join(path, name)

            match = glob.glob(filename)
            if match: # the exact name as expected
                found = True
                name = match[0]
                
            if not found: # a more permissive name with element.*.format
                name = "%s.*.%s" % (nm, pp_format)
                filename = os.path.join(path, name)
                match = glob.glob(filename)
                if match:
                    found = True
                    name = match[0] # the first match
            if not found: # a more permissive name with element_*.format
                name = "%s_*.%s" % (nm, pp_format)
                filename = os.path.join(path, name)
                match = glob.glob(filename)
                if match:
                    found = True
                    name = match[0] # the first match
            if not found: # a more permissive name with just the element_*
                name = "%s_*" % (nm)
                filename = os.path.join(path, name)
                match = glob.glob(filename)
                if match:
                    found = True
                    name = match[0] # the first match
            if not found: # a more permissive name with just the element.*
                name = "%s.*" % (nm)
                filename = os.path.join(path, name)
                match = glob.glob(filename)
                if match:
                    found = True
                    name = match[0] # the first match

        if not found:
            raise RuntimeError('Espresso: No pseudopotential for %s. Aborting.' % nm)
        
        fh.write("    %s %g %s \n" % (nm, mass, os.path.basename(name)))

    # ----------------------------------------------------------
    # Card: ATOMIC_POSITIONS
    # ----------------------------------------------------------
    
    fh.write('ATOMIC_POSITIONS crystal\n')
    # Now we can write it out
    for n,v in zip(cr.get_chemical_symbols(),cr.get_scaled_positions()):
        fh.write("    %s %g %g %g\n" % (n, v[0], v[1], v[2]))

    # ----------------------------------------------------------
    # Card: CELL_PARAMETERS
    # ----------------------------------------------------------
    # Write out only if ibrav==0 - no symmetry used
    if not p['use_symmetry'] or p['ibrav']==0:
        fh.write('CELL_PARAMETERS angstrom\n')
        
        for v in cr.get_cell():
            fh.write('    %f %f %f\n' % tuple(v))
        
    # ----------------------------------------------------------
    # Card: K_POINTS
    # ----------------------------------------------------------

    if p['kpt_type'] in ['automatic','edos'] :
        fh.write('K_POINTS %s\n' % 'automatic')
    else :
        fh.write('K_POINTS %s\n' % p['kpt_type'])
        
    if p['kpt_type'] is 'automatic':
        fh.write('   %d %d %d   %d %d %d\n' % (tuple(p['kpts'])+tuple(p['kpt_shift'])))
    elif p['kpt_type'] is 'edos':
        fh.write('   %d %d %d   %d %d %d\n' % (tuple(p['nkdos'])+tuple([0,0,0])))
    elif not p['kpt_type'] is 'gamma':
        write_q_path(fh,p['qpath'],p['points'])
    fh.close()
Beispiel #26
0
def draw_BSsurf(interfc,
                bdcutoff=0.85,
                ascale=0.4,
                brad=0.1,
                outName=None,
                zLim=None,
                pseudoBond=False,
                hBond=False,
                title=''):
    plt.rcParams["font.family"] = "Dejavu Serif"
    print(' |- Drawing BS  pic of \t%s to\t%s'%\
        (interfc.tags, outName+'-bs.png'))
    atoms = interfc.get_allAtoms()
    if zLim is None:
        zLim = [(interfc.get_subPos()[:, 2].min() +
                 interfc.get_subPos()[:, 2].max()) / 2,
                interfc.get_pos()[:, 2].max()]
    atoms = atoms[[a.index for a in atoms \
        if zLim[0] < interfc.get_pos()[a.index][2] <= zLim[1]]]
    ori_cell = convert_cell(atoms.get_cell())
    atoms.set_positions(atoms.get_positions() +
                        (ori_cell[0] + ori_cell[1]) / 2)
    atoms.wrap()
    atoms = atoms * [2, 2, 1]
    atoms.set_positions(atoms.get_positions() -
                        (ori_cell[0] + ori_cell[1]) / 2)
    atoms.set_pbc([0, 0, 0])
    atoms = Atoms(sorted(atoms, key=lambda atm: atm.position[2]))
    allpos = atoms.get_positions()

    allnum = atoms.get_atomic_numbers()
    allpos = atoms.get_positions()
    allz = allpos[:, 2]
    # color gradient according to z
    # if max(allz) - min(allz) < zlim:
    #     allc=[1]*len(allz)

    adsList = interfc.get_adsList()
    bufList = interfc.get_bufList()
    adsList = [i for i in range(len(atoms)) \
        if atoms.get_positions()[i]-ori_cell[0]-ori_cell[1]\
            in interfc.get_pos()[adsList]]
    bufList = [i for i in range(len(atoms)) \
        if atoms.get_positions()[i]-ori_cell[0]-ori_cell[1]\
            in interfc.get_pos()[bufList]]
    allc = [
        0 if i in adsList  # Full color
        else 0.5 if i in bufList  # Shallow color
        else 1.0 for i in range(len(atoms))
    ]

    bdpair = [[i[0], i[1]] for i in get_bondpairs(atoms, bdcutoff)]
    hbpair = []
    if hBond:
        hbpair = getHBonds(atoms, allnum, bdpair)
    if pseudoBond:
        hbpair = getPseudoBonds(atoms, allnum, bdpair)
    plotCell(ori_cell[0] * 0 + ori_cell[1] * 0,
             ori_cell[0] * 1 + ori_cell[1] * 0,
             ori_cell[0] * 1 + ori_cell[1] * 1,
             ori_cell[0] * 0 + ori_cell[1] * 1,
             linwt=5)
    for i in range(len(atoms)):
        anum = allnum[i]
        apos = allpos[i]
        plotBondHalf(allnum, allpos, bdpair, i,\
            atomscale=ascale, bdrad=brad, mode='low',\
            colorparam=allc[i], linwt=1.5-allc[i]/2)
        plotAtom(anum, apos, scale=ascale,\
            colorparam=allc[i], linwt=1.5-allc[i]/2, enlarge=True)
        plotElliShine(anum,
                      apos,
                      atomscale=ascale,
                      shift=0.3,
                      scale=0.5,
                      enlarge=True)
        plotBondHalf(allnum, allpos, bdpair, i,\
            atomscale=ascale, bdrad=brad, mode='high',\
            colorparam=allc[i], linwt=1.5-allc[i]/2)
        plothHbond(allnum, allpos, hbpair, i,\
            atomscale=ascale)
    plt.axis('scaled')
    plt.xlim(min(ori_cell[0][0], ori_cell[1][0]),
             max(ori_cell[0][0], ori_cell[1][0]))
    plt.ylim(min(ori_cell[0][1], ori_cell[1][1]),
             max(ori_cell[0][1], ori_cell[1][1]))
    plt.tight_layout()
    plt.axis('off')
    plt.gca().xaxis.set_major_locator(plt.NullLocator())
    plt.gca().yaxis.set_major_locator(plt.NullLocator())
    plt.margins(0, 0)
    plt.title(title, fontsize='xx-large', fontweight='bold')
    if outName is not None:
        plt.savefig(outName+'-bs.png', dpi=100, \
            bbox_inches = "tight", transparent=True, pad_inches = 0)
    else:
        plt.show()
    plt.close()
Beispiel #27
0
    def test_gto_integration(self):
        """Tests that the completely analytical partial power spectrum with the
        GTO basis corresponds to the easier-to-code but less performant
        numerical integration done with python.
        """
        sigma = 0.55
        rcut = 2.0
        nmax = 2
        lmax = 2

        # Limits for radius
        r1 = 0.
        r2 = rcut+5

        # Limits for theta
        t1 = 0
        t2 = np.pi

        # Limits for phi
        p1 = 0
        p2 = 2*np.pi

        positions = np.array([[0.0, 0.0, 0.0], [-0.3, 0.5, 0.4]])
        symbols = np.array(["H", "C"])
        system = Atoms(positions=positions, symbols=symbols)

        atomic_numbers = system.get_atomic_numbers()
        elements = set(system.get_atomic_numbers())
        n_elems = len(elements)

        # Calculate the analytical power spectrum and the weights and decays of
        # the radial basis functions.
        soap = SOAP(atomic_numbers=atomic_numbers, lmax=lmax, nmax=nmax, sigma=sigma, rcut=rcut, crossover=True, sparse=False)
        analytical_power_spectrum = soap.create(system, positions=[[0, 0, 0]])[0]
        alphagrid = np.reshape(soap._alphas, [10, nmax])
        betagrid = np.reshape(soap._betas, [10, nmax, nmax])

        coeffs = np.zeros((n_elems, nmax, lmax+1, 2*lmax+1))
        for iZ, Z in enumerate(elements):
            indices = np.argwhere(atomic_numbers == Z)[0]
            elem_pos = positions[indices]
            for n in range(nmax):
                for l in range(lmax+1):
                    for im, m in enumerate(range(-l, l+1)):

                        # Calculate numerical coefficients
                        def soap_coeff(phi, theta, r):

                            # Regular spherical harmonic
                            ylm_comp = scipy.special.sph_harm(np.abs(m), l, phi, theta)  # NOTE: scipy swaps phi and theta

                            # Construct real (tesseral) spherical harmonics for
                            # easier integration without having to worry about the
                            # imaginary part
                            ylm_real = np.real(ylm_comp)
                            ylm_imag = np.imag(ylm_comp)
                            if m < 0:
                                ylm = np.sqrt(2)*(-1)**m*ylm_imag
                            elif m == 0:
                                ylm = ylm_comp
                            else:
                                ylm = np.sqrt(2)*(-1)**m*ylm_real

                            # Spherical gaussian type orbital
                            gto = 0
                            for i in range(nmax):
                                i_alpha = alphagrid[l, i]
                                i_beta = betagrid[l, n, i]
                                i_gto = i_beta*r**l*np.exp(-i_alpha*r**2)
                                gto += i_gto

                            # Atomic density
                            rho = 0
                            for i_pos in elem_pos:
                                ix = i_pos[0]
                                iy = i_pos[1]
                                iz = i_pos[2]
                                ri_squared = ix**2+iy**2+iz**2
                                rho += np.exp(-1/(2*sigma**2)*(r**2 + ri_squared - 2*r*(np.sin(theta)*np.cos(phi)*ix + np.sin(theta)*np.sin(phi)*iy + np.cos(theta)*iz)))

                            # Jacobian
                            jacobian = np.sin(theta)*r**2

                            return gto*ylm*rho*jacobian

                        cnlm = tplquad(
                            soap_coeff,
                            r1,
                            r2,
                            lambda r: t1,
                            lambda r: t2,
                            lambda r, theta: p1,
                            lambda r, theta: p2,
                            epsabs=0.001,
                            epsrel=0.001,
                        )
                        integral, error = cnlm
                        coeffs[iZ, n, l, im] = integral

        # Calculate the partial power spectrum
        numerical_power_spectrum = []
        for zi in range(n_elems):
            for zj in range(n_elems):
                for l in range(lmax+1):
                    for ni in range(nmax):
                        for nj in range(nmax):
                            if nj >= ni:
                                if zj >= zi:
                                    value = np.dot(coeffs[zi, ni, l, :], coeffs[zj, nj, l, :])
                                    prefactor = np.pi*np.sqrt(8/(2*l+1))
                                    value *= prefactor
                                    numerical_power_spectrum.append(value)

        # print("Numerical: {}".format(numerical_power_spectrum))
        # print("Analytical: {}".format(analytical_power_spectrum))

        self.assertTrue(np.allclose(numerical_power_spectrum, analytical_power_spectrum, atol=1e-15, rtol=0.01))
Beispiel #28
0
    def test_poly_integration(self):
        """Tests that the partial power spectrum with the polynomial basis done
        with C corresponds to the easier-to-code but less performant
        integration done with python.
        """
        sigma = 0.55
        rcut = 2.0
        nmax = 2
        lmax = 2

        # Limits for radius
        r1 = 0.
        r2 = rcut+5

        # Limits for theta
        t1 = 0
        t2 = np.pi

        # Limits for phi
        p1 = 0
        p2 = 2*np.pi

        positions = np.array([[0.0, 0.0, 0.0], [-0.3, 0.5, 0.4]])
        symbols = np.array(["H", "C"])
        system = Atoms(positions=positions, symbols=symbols)

        atomic_numbers = system.get_atomic_numbers()
        elements = set(system.get_atomic_numbers())
        n_elems = len(elements)

        # Calculate the overlap of the different polynomial functions in a
        # matrix S. These overlaps defined through the dot product over the
        # radial coordinate are analytically calculable: Integrate[(rc - r)^(a
        # + 2) (rc - r)^(b + 2) r^2, {r, 0, rc}]. Then the weights B that make
        # the basis orthonormal are given by B=S^{-1/2}
        S = np.zeros((nmax, nmax))
        for i in range(1, nmax+1):
            for j in range(1, nmax+1):
                S[i-1, j-1] = (2*(rcut)**(7+i+j))/((5+i+j)*(6+i+j)*(7+i+j))
        betas = sqrtm(np.linalg.inv(S))

        # Calculate the analytical power spectrum and the weights and decays of
        # the radial basis functions.
        soap = SOAP(atomic_numbers=atomic_numbers, lmax=lmax, nmax=nmax, sigma=sigma, rcut=rcut, rbf="polynomial", crossover=True, sparse=False)
        analytical_power_spectrum = soap.create(system, positions=[[0, 0, 0]])[0]

        coeffs = np.zeros((n_elems, nmax, lmax+1, 2*lmax+1))
        for iZ, Z in enumerate(elements):
            indices = np.argwhere(atomic_numbers == Z)[0]
            elem_pos = positions[indices]
            for n in range(nmax):
                for l in range(lmax+1):
                    for im, m in enumerate(range(-l, l+1)):

                        # Calculate numerical coefficients
                        def soap_coeff(phi, theta, r):

                            # Regular spherical harmonic
                            ylm_comp = scipy.special.sph_harm(np.abs(m), l, phi, theta)  # NOTE: scipy swaps phi and theta

                            # Construct real (tesseral) spherical harmonics for
                            # easier integration without having to worry about the
                            # imaginary part
                            ylm_real = np.real(ylm_comp)
                            ylm_imag = np.imag(ylm_comp)
                            if m < 0:
                                ylm = np.sqrt(2)*(-1)**m*ylm_imag
                            elif m == 0:
                                ylm = ylm_comp
                            else:
                                ylm = np.sqrt(2)*(-1)**m*ylm_real

                            # Polynomial basis
                            poly = 0
                            for k in range(1, nmax+1):
                                poly += betas[n, k-1]*(rcut-np.clip(r, 0, rcut))**(k+2)

                            # Atomic density
                            rho = 0
                            for i_pos in elem_pos:
                                ix = i_pos[0]
                                iy = i_pos[1]
                                iz = i_pos[2]
                                ri_squared = ix**2+iy**2+iz**2
                                rho += np.exp(-1/(2*sigma**2)*(r**2 + ri_squared - 2*r*(np.sin(theta)*np.cos(phi)*ix + np.sin(theta)*np.sin(phi)*iy + np.cos(theta)*iz)))

                            # Jacobian
                            jacobian = np.sin(theta)*r**2

                            return poly*ylm*rho*jacobian

                        cnlm = tplquad(
                            soap_coeff,
                            r1,
                            r2,
                            lambda r: t1,
                            lambda r: t2,
                            lambda r, theta: p1,
                            lambda r, theta: p2,
                            epsabs=0.0001,
                            epsrel=0.0001,
                        )
                        integral, error = cnlm
                        coeffs[iZ, n, l, im] = integral

        # Calculate the partial power spectrum
        numerical_power_spectrum = []
        for zi in range(n_elems):
            for zj in range(n_elems):
                for l in range(lmax+1):
                    for ni in range(nmax):
                        for nj in range(nmax):
                            if nj >= ni and zj >= zi:
                                value = np.dot(coeffs[zi, ni, l, :], coeffs[zj, nj, l, :])
                                prefactor = np.pi*np.sqrt(8/(2*l+1))
                                value *= prefactor
                                numerical_power_spectrum.append(value)

        # print("Numerical: {}".format(numerical_power_spectrum))
        # print("Analytical: {}".format(analytical_power_spectrum))

        self.assertTrue(np.allclose(numerical_power_spectrum, analytical_power_spectrum, atol=1e-15, rtol=0.01))
Beispiel #29
0
def parse_mol_info(fname, fcharges, axis, buffa, buffo, pbcbonds, printdih,
                   ignorebonds, ignoreimproper):
    iaxis = {"x": 0, "y": 1, "z": 2}
    if axis in iaxis:
        repaxis = iaxis[axis]
    else:
        print("Error: invalid axis")
        sys.exit(0)

    if fcharges:
        chargesLabel = {}
        with open(fcharges, "r") as f:
            for line in f:
                chargesLabel[line.split()[0]] = float(line.split()[1])

    # set openbabel file format
    base, ext = os.path.splitext(fname)
    obConversion = openbabel.OBConversion()
    obConversion.SetInAndOutFormats(ext[1:], "xyz")
    # trick to disable ring perception and make the ReadFile waaaay faster
    # Source: https://sourceforge.net/p/openbabel/mailman/openbabel-discuss/thread/56e1812d-396a-db7c-096d-d378a077853f%40ipcms.unistra.fr/#msg36225392
    obConversion.AddOption("b", openbabel.OBConversion.INOPTIONS)

    # read molecule to OBMol object
    mol = openbabel.OBMol()
    obConversion.ReadFile(mol, fname)
    mol.ConnectTheDots()  # necessary because of the 'b' INOPTION

    # split the molecules
    molecules = mol.Separate()

    # detect the molecules types
    mTypes = {}
    mapmTypes = {}
    atomIdToMol = {}
    nty = 0
    for i, submol in enumerate(molecules, start=1):
        atomiter = openbabel.OBMolAtomIter(submol)
        atlist = []
        for at in atomiter:
            atlist.append(at.GetAtomicNum())
            atomIdToMol[at.GetId()] = i
        foundType = None

        for ty in mTypes:
            # check if there's already a molecule of this type
            if atlist == mTypes[ty]:
                foundType = ty

        # if not, create a new type
        if not foundType:
            nty += 1
            foundType = nty
            mTypes[nty] = atlist

        mapmTypes[i] = foundType

    # get atomic labels from pdb
    idToAtomicLabel = {}
    if ext[1:] == "pdb":
        for res in openbabel.OBResidueIter(mol):
            for atom in openbabel.OBResidueAtomIter(res):
                if (atomIdToMol[atom.GetId()] > 1) and (len(mTypes) > 1):
                    idToAtomicLabel[
                        atom.GetId()] = res.GetAtomID(atom).strip() + str(
                            mapmTypes[atomIdToMol[atom.GetId()]])
                else:
                    idToAtomicLabel[atom.GetId()] = res.GetAtomID(atom).strip()
    else:
        if not ob3:
            etab = openbabel.OBElementTable()
        for atom in openbabel.OBMolAtomIter(mol):
            if (atomIdToMol[atom.GetId()] > 1) and (len(mTypes) > 1):
                if ob3:
                    idToAtomicLabel[atom.GetId()] = openbabel.GetSymbol(
                        atom.GetAtomicNum()) + str(
                            mapmTypes[atomIdToMol[atom.GetId()]])
                else:
                    idToAtomicLabel[atom.GetId()] = etab.GetSymbol(
                        atom.GetAtomicNum()) + str(
                            mapmTypes[atomIdToMol[atom.GetId()]])
            else:
                if ob3:
                    idToAtomicLabel[atom.GetId()] = openbabel.GetSymbol(
                        atom.GetAtomicNum())
                else:
                    idToAtomicLabel[atom.GetId()] = etab.GetSymbol(
                        atom.GetAtomicNum())

    # print(idToAtomicLabel)

    # identify atom types and get masses
    outMasses = "Masses\n\n"

    massTypes = {}
    mapTypes = {}
    nmassTypes = 0
    atomIterator = openbabel.OBMolAtomIter(mol)
    for atom in atomIterator:
        i = atom.GetId()
        if idToAtomicLabel[i] not in massTypes:
            nmassTypes += 1
            mapTypes[nmassTypes] = idToAtomicLabel[i]
            massTypes[idToAtomicLabel[i]] = nmassTypes
            outMasses += "\t%d\t%.3f\t# %s\n" % (
                nmassTypes, atom.GetAtomicMass(), idToAtomicLabel[i])

    # create atoms list
    outAtoms = "Atoms # full\n\n"

    xmin = float("inf")
    xmax = float("-inf")
    ymin = float("inf")
    ymax = float("-inf")
    zmin = float("inf")
    zmax = float("-inf")
    natoms = 0
    acoords = []
    for mnum, imol in enumerate(molecules, start=1):
        atomIterator = openbabel.OBMolAtomIter(imol)
        for atom in sorted(atomIterator, key=lambda x: x.GetId()):
            natoms += 1
            i = atom.GetId()
            apos = (atom.GetX(), atom.GetY(), atom.GetZ())
            acoords.append(Atom(atom.GetAtomicNum(), apos))

            # look for the maximum and minimum x for the box (improve later with numpy and all coordinates)
            if apos[0] > xmax:
                xmax = apos[0]
            if apos[0] < xmin:
                xmin = apos[0]
            if apos[1] > ymax:
                ymax = apos[1]
            if apos[1] < ymin:
                ymin = apos[1]
            if apos[2] > zmax:
                zmax = apos[2]
            if apos[2] < zmin:
                zmin = apos[2]

            if fcharges:
                outAtoms += "\t%d\t%d\t%d\t%.6f\t%.4f\t%.4f\t%.4f\t# %s\n" % (
                    i + 1, mnum, massTypes[idToAtomicLabel[i]],
                    chargesLabel[idToAtomicLabel[i]], atom.GetX(), atom.GetY(),
                    atom.GetZ(), idToAtomicLabel[i])
            else:
                outAtoms += "\t%d\t%d\t%d\tX.XXXXXX\t%.4f\t%.4f\t%.4f\t# %s\n" % (
                    i + 1, mnum, massTypes[idToAtomicLabel[i]], atom.GetX(),
                    atom.GetY(), atom.GetZ(), idToAtomicLabel[i])

    # define box shape and size
    try:
        fromBounds = False
        rcell = mol.GetData(12)
        cell = openbabel.toUnitCell(rcell)
        v1 = [
            cell.GetCellVectors()[0].GetX(),
            cell.GetCellVectors()[0].GetY(),
            cell.GetCellVectors()[0].GetZ()
        ]
        v2 = [
            cell.GetCellVectors()[1].GetX(),
            cell.GetCellVectors()[1].GetY(),
            cell.GetCellVectors()[1].GetZ()
        ]
        v3 = [
            cell.GetCellVectors()[2].GetX(),
            cell.GetCellVectors()[2].GetY(),
            cell.GetCellVectors()[2].GetZ()
        ]
        boxinfo = [v1, v2, v3]
        orthogonal = True
        for i, array in enumerate(boxinfo):
            for j in range(3):
                if i == j:
                    continue
                if not math.isclose(0., array[j], abs_tol=1e-6):
                    orthogonal = False
    except:
        fromBounds = True
        v1 = [xmax - xmin, 0., 0.]
        v2 = [0., ymax - ymin, 0.]
        v3 = [0., 0., zmax - zmin]
        orthogonal = True

    # add buffer
    if orthogonal:
        buf = []
        boxinfo = [v1, v2, v3]
        for i, val in enumerate(boxinfo[repaxis]):
            if i == repaxis:
                buf.append(val + buffa)
            else:
                buf.append(val)
        boxinfo[repaxis] = buf
        for i in range(3):
            if i == repaxis:
                continue
            buf = []
            for j, val in enumerate(boxinfo[i]):
                if j == i:
                    buf.append(val + buffo)
                else:
                    buf.append(val)
            boxinfo[i] = buf

    # print(boxinfo)

    # Duplicate to get the bonds in the PBC. Taken from (method _crd2bond):
    # https://github.com/tongzhugroup/mddatasetbuilder/blob/66eb0f15e972be0f5534dcda27af253cd8891ff2/mddatasetbuilder/detect.py#L213
    if pbcbonds:
        acoords = Atoms(acoords, cell=boxinfo, pbc=True)
        repatoms = acoords.repeat(
            2
        )[natoms:]  # repeat the unit cell in each direction (len(repatoms) = 7*natoms)
        tree = cKDTree(acoords.get_positions())
        d = tree.query(repatoms.get_positions(), k=1)[0]
        nearest = d < 8.
        ghost_atoms = repatoms[nearest]
        realnumber = np.where(nearest)[0] % natoms
        acoords += ghost_atoms

        write("replicated.xyz",
              acoords)  # write the structure with the replicated atoms

        # write new mol with new bonds
        nmol = openbabel.OBMol()
        nmol.BeginModify()
        for idx, (num, position) in enumerate(
                zip(acoords.get_atomic_numbers(), acoords.positions)):
            a = nmol.NewAtom(idx)
            a.SetAtomicNum(int(num))
            a.SetVector(*position)
        nmol.ConnectTheDots()
        # nmol.PerceiveBondOrders() # super slow becauses it looks for rings
        nmol.EndModify()
    else:
        acoords = Atoms(acoords, cell=boxinfo, pbc=False)
        nmol = openbabel.OBMol()
        nmol.BeginModify()
        for idx, (num, position) in enumerate(
                zip(acoords.get_atomic_numbers(), acoords.positions)):
            a = nmol.NewAtom(idx)
            a.SetAtomicNum(int(num))
            a.SetVector(*position)
        nmol.ConnectTheDots()
        # nmol.PerceiveBondOrders() # super slow becauses it looks for rings
        nmol.EndModify()

    # identify bond types and create bond list
    outBonds = "Bonds # harmonic\n\n"

    bondTypes = {}
    mapbTypes = {}
    nbondTypes = 0
    nbonds = 0
    bondsToDelete = []
    bondIterators = []
    if ignorebonds:
        sepmols = nmol.Separate()
        for smol in sepmols[1:]:
            bondIterators.append(openbabel.OBMolBondIter(smol))
    else:
        bondIterators.append(openbabel.OBMolBondIter(nmol))

    lastidx = 1
    for iterator in bondIterators:
        for i, bond in enumerate(iterator, lastidx):
            b1 = bond.GetBeginAtom().GetId()
            b2 = bond.GetEndAtom().GetId()

            # check if its a bond of the replica only
            if (b1 >= natoms) and (b2 >= natoms):
                bondsToDelete.append(bond)
                continue
            # remap to a real atom if needed
            if b1 >= natoms:
                b1 = realnumber[b1 - natoms]
            if b2 >= natoms:
                b2 = realnumber[b2 - natoms]

            # identify bond type
            btype1 = "%s - %s" % (idToAtomicLabel[b1], idToAtomicLabel[b2])
            btype2 = "%s - %s" % (idToAtomicLabel[b2], idToAtomicLabel[b1])

            if btype1 in bondTypes:
                bondid = bondTypes[btype1]
                bstring = btype1
            elif btype2 in bondTypes:
                bondid = bondTypes[btype2]
                bstring = btype2
            else:
                nbondTypes += 1
                mapbTypes[nbondTypes] = btype1
                bondid = nbondTypes
                bondTypes[btype1] = nbondTypes
                bstring = btype1

            nbonds += 1
            outBonds += "\t%d\t%d\t%d\t%d\t# %s\n" % (nbonds, bondid, b1 + 1,
                                                      b2 + 1, bstring)

        lastidx = i

    # delete the bonds of atoms from other replicas
    for bond in bondsToDelete:
        nmol.DeleteBond(bond)

    # identify angle types and create angle list
    angleTypes = {}
    mapaTypes = {}
    nangleTypes = 0
    nangles = 0
    angleIterators = []

    if ignorebonds:
        sepmols = nmol.Separate()
        for smol in sepmols[1:]:
            smol.FindAngles()
            angleIterators.append(openbabel.OBMolAngleIter(smol))
        prevnumatoms = sepmols[0].NumAtoms()
    else:
        nmol.FindAngles()
        angleIterators.append(openbabel.OBMolAngleIter(nmol))

    outAngles = "Angles # harmonic\n\n"

    lastidx = 1
    for j, iterator in enumerate(angleIterators, 1):
        for i, angle in enumerate(iterator, lastidx):
            if ignorebonds:
                a1 = angle[1] + prevnumatoms
                a2 = angle[0] + prevnumatoms
                a3 = angle[2] + prevnumatoms
            else:
                a1 = angle[1]
                a2 = angle[0]
                a3 = angle[2]

            # remap to a real atom if needed
            if a1 >= natoms:
                a1 = realnumber[a1 - natoms]
            if a2 >= natoms:
                a2 = realnumber[a2 - natoms]
            if a3 >= natoms:
                a3 = realnumber[a3 - natoms]

            atype1 = "%s - %s - %s" % (
                idToAtomicLabel[a1], idToAtomicLabel[a2], idToAtomicLabel[a3])
            atype2 = "%s - %s - %s" % (
                idToAtomicLabel[a3], idToAtomicLabel[a2], idToAtomicLabel[a1])

            if atype1 in angleTypes:
                angleid = angleTypes[atype1]
                astring = atype1
            elif atype2 in angleTypes:
                angleid = angleTypes[atype2]
                astring = atype2
            else:
                nangleTypes += 1
                mapaTypes[nangleTypes] = atype1
                angleid = nangleTypes
                angleTypes[atype1] = nangleTypes
                astring = atype1

            nangles += 1
            outAngles += "\t%d\t%d\t%d\t%d\t%d\t# %s\n" % (
                nangles, angleid, a1 + 1, a2 + 1, a3 + 1, astring)

        lastidx = i
        if ignorebonds:
            prevnumatoms += sepmols[j].NumAtoms()

    # identify dihedral types and create dihedral list
    if printdih:
        dihedralTypes = {}
        mapdTypes = {}
        ndihedralTypes = 0
        ndihedrals = 0
        dihedralIterators = []

        if ignorebonds:
            sepmols = nmol.Separate()
            for smol in sepmols[1:]:
                smol.FindTorsions()
                dihedralIterators.append(openbabel.OBMolTorsionIter(smol))
        else:
            nmol.FindTorsions()
            dihedralIterators.append(openbabel.OBMolTorsionIter(nmol))

        outDihedrals = "Dihedrals # charmmfsw\n\n"

        lastidx = 1
        for iterator in dihedralIterators:
            for i, dihedral in enumerate(iterator, lastidx):
                a1 = dihedral[0]
                a2 = dihedral[1]
                a3 = dihedral[2]
                a4 = dihedral[3]

                # remap to a real atom if needed
                if a1 >= natoms:
                    a1 = realnumber[a1 - natoms]
                if a2 >= natoms:
                    a2 = realnumber[a2 - natoms]
                if a3 >= natoms:
                    a3 = realnumber[a3 - natoms]
                if a4 >= natoms:
                    a4 = realnumber[a4 - natoms]

                dtype1 = "%s - %s - %s - %s" % (
                    idToAtomicLabel[a1], idToAtomicLabel[a2],
                    idToAtomicLabel[a3], idToAtomicLabel[a4])
                dtype2 = "%s - %s - %s - %s" % (
                    idToAtomicLabel[a4], idToAtomicLabel[a3],
                    idToAtomicLabel[a2], idToAtomicLabel[a1])

                if dtype1 in dihedralTypes:
                    dihedralid = dihedralTypes[dtype1]
                    dstring = dtype1
                elif dtype2 in dihedralTypes:
                    dihedralid = dihedralTypes[dtype2]
                    dstring = dtype2
                else:
                    ndihedralTypes += 1
                    mapdTypes[ndihedralTypes] = dtype1
                    dihedralid = ndihedralTypes
                    dihedralTypes[dtype1] = ndihedralTypes
                    dstring = dtype1

                ndihedrals += 1
                outDihedrals += "\t%d\t%d\t%d\t%d\t%d\t%d\t# %s\n" % (
                    ndihedrals, dihedralid, a1 + 1, a2 + 1, a3 + 1, a4 + 1,
                    dstring)

            lastidx = i

        if not ignoreimproper:
            # look for the improper dihedrals
            improperDihedralTypes = {}
            mapiDTypes = {}
            niDihedralTypes = 0
            niDihedrals = 0
            mollist = []

            if ignorebonds:
                sepmols = nmol.Separate()
                for smol in sepmols[1:]:
                    smol.PerceiveBondOrders()
                    mollist.append(smol)
            else:
                nmol.PerceiveBondOrders()
                mollist.append(nmol)

            outImpropers = "Impropers # harmonic\n\n"

            for imol in mollist:
                atomIterator = openbabel.OBMolAtomIter(imol)
                for atom in atomIterator:
                    try:
                        # print(atom.GetHyb(), atom.GetAtomicNum(), atom.GetValence())
                        expDegree = atom.GetValence()
                    except:
                        # print(atom.GetHyb(), atom.GetAtomicNum(), atom.GetExplicitDegree())
                        expDegree = atom.GetExplicitDegree()

                    # returns impropers for atoms with connected to other 3 atoms and SP2 hybridization
                    if atom.GetHyb() == 2 and expDegree == 3:
                        connectedAtoms = []
                        for atom2, depth in openbabel.OBMolAtomBFSIter(
                                imol,
                                atom.GetId() + 1):
                            if depth == 2:
                                connectedAtoms.append(atom2)

                        torsional = [
                            atom.GetId() + 1, connectedAtoms[0].GetId() + 1,
                            connectedAtoms[1].GetId() + 1,
                            connectedAtoms[2].GetId() + 1
                        ]

                        a1 = torsional[0] - 1
                        a2 = torsional[1] - 1
                        a3 = torsional[2] - 1
                        a4 = torsional[3] - 1

                        # remap to a real atom if needed
                        if a1 >= natoms:
                            a1 = realnumber[a1 - natoms]
                        if a2 >= natoms:
                            a2 = realnumber[a2 - natoms]
                        if a3 >= natoms:
                            a3 = realnumber[a3 - natoms]
                        if a4 >= natoms:
                            a4 = realnumber[a4 - natoms]

                        dtype1 = "%s - %s - %s - %s" % (
                            idToAtomicLabel[a1], idToAtomicLabel[a2],
                            idToAtomicLabel[a3], idToAtomicLabel[a4])
                        dtype2 = "%s - %s - %s - %s" % (
                            idToAtomicLabel[a4], idToAtomicLabel[a3],
                            idToAtomicLabel[a2], idToAtomicLabel[a1])

                        if dtype1 in improperDihedralTypes:
                            idihedralid = improperDihedralTypes[dtype1]
                            dstring = dtype1
                        elif dtype2 in improperDihedralTypes:
                            idihedralid = improperDihedralTypes[dtype2]
                            dstring = dtype2
                        else:
                            niDihedralTypes += 1
                            mapiDTypes[niDihedralTypes] = dtype1
                            idihedralid = niDihedralTypes
                            improperDihedralTypes[dtype1] = niDihedralTypes
                            dstring = dtype1

                        niDihedrals += 1
                        outImpropers += "\t%d\t%d\t%d\t%d\t%d\t%d\t# %s\n" % (
                            niDihedrals, idihedralid, a1 + 1, a2 + 1, a3 + 1,
                            a4 + 1, dstring)

    # print header
    if printdih and (ndihedrals > 0):
        if ignoreimproper or (niDihedrals == 0):
            header = "LAMMPS topology created from %s using pdb2lmp.py - By Henrique Musseli Cezar, 2020\n\n\t%d atoms\n\t%d bonds\n\t%d angles\n\t%d dihedrals\n\n\t%d atom types\n\t%d bond types\n\t%d angle types\n\t%d dihedral types\n\n" % (
                fname, natoms, nbonds, nangles, ndihedrals, nmassTypes,
                nbondTypes, nangleTypes, ndihedralTypes)
        else:
            header = "LAMMPS topology created from %s using pdb2lmp.py - By Henrique Musseli Cezar, 2020\n\n\t%d atoms\n\t%d bonds\n\t%d angles\n\t%d dihedrals\n\t%d impropers\n\n\t%d atom types\n\t%d bond types\n\t%d angle types\n\t%d dihedral types\n\t%d improper types\n\n" % (
                fname, natoms, nbonds, nangles, ndihedrals, niDihedrals,
                nmassTypes, nbondTypes, nangleTypes, ndihedralTypes,
                niDihedralTypes)
    else:
        header = "LAMMPS topology created from %s using pdb2lmp.py - By Henrique Musseli Cezar, 2020\n\n\t%d atoms\n\t%d bonds\n\t%d angles\n\n\t%d atom types\n\t%d bond types\n\t%d angle types\n\n" % (
            fname, natoms, nbonds, nangles, nmassTypes, nbondTypes,
            nangleTypes)

    # add box info
    if fromBounds:
        boxsize = [(xmin, xmax), (ymin, ymax), (zmin, zmax)]
        boxsize[repaxis] = (boxsize[repaxis][0] - buffa / 2.,
                            boxsize[repaxis][1] + buffa / 2.)
        for i in range(3):
            if i == repaxis:
                continue
            boxsize[i] = (boxsize[i][0] - buffo / 2.,
                          boxsize[i][1] + buffo / 2.)
        header += "\t%.8f\t%.8f\t xlo xhi\n\t%.8f\t%.8f\t ylo yhi\n\t%.8f\t%.8f\t zlo zhi\n" % (
            boxsize[0][0], boxsize[0][1], boxsize[1][0], boxsize[1][1],
            boxsize[2][0], boxsize[2][1])
    else:
        if orthogonal:
            header += "\t%.8f\t%.8f\t xlo xhi\n\t%.8f\t%.8f\t ylo yhi\n\t%.8f\t%.8f\t zlo zhi\n" % (
                0., boxinfo[0][0], 0., boxinfo[1][1], 0., boxinfo[2][2])
        else:
            header += "\t%.8f\t%.8f\t xlo xhi\n\t%.8f\t%.8f\t ylo yhi\n\t%.8f\t%.8f\t zlo zhi\n\t%.8f\t%.8f\t%.8f\t xy xz yz\n" % (
                0., boxinfo[0][0], 0., boxinfo[1][1], 0., boxinfo[2][2],
                boxinfo[1][0], boxinfo[2][0], boxinfo[2][1])

    # print Coeffs
    outCoeffs = "Pair Coeffs\n\n"

    for i in range(1, nmassTypes + 1):
        outCoeffs += "\t%d\teps\tsig\t# %s\n" % (i, mapTypes[i])

    outCoeffs += "\nBond Coeffs\n\n"

    for i in range(1, nbondTypes + 1):
        outCoeffs += "\t%d\tK\tr_0\t# %s\n" % (i, mapbTypes[i])

    outCoeffs += "\nAngle Coeffs\n\n"

    for i in range(1, nangleTypes + 1):
        outCoeffs += "\t%d\tK\ttetha_0 (deg)\t# %s\n" % (i, mapaTypes[i])

    if printdih and (ndihedrals > 0):
        outCoeffs += "\nDihedral Coeffs\n\n"

        for i in range(1, ndihedralTypes + 1):
            outCoeffs += "\t%d\tK\tn\tphi_0 (deg)\tw\t# %s\n" % (i,
                                                                 mapdTypes[i])

        if not ignoreimproper and (niDihedralTypes > 0):
            outCoeffs += "\nImproper Coeffs\n\n"

            for i in range(1, niDihedralTypes + 1):
                outCoeffs += "\t%d\tK\txi_0 (deg)\t# %s\n" % (i, mapiDTypes[i])

    if printdih and (ndihedrals > 0):
        if ignoreimproper or (niDihedralTypes == 0):
            return header + "\n" + outMasses + "\n" + outCoeffs + "\n" + outAtoms + "\n" + outBonds + "\n" + outAngles + "\n" + outDihedrals
        else:
            return header + "\n" + outMasses + "\n" + outCoeffs + "\n" + outAtoms + "\n" + outBonds + "\n" + outAngles + "\n" + outDihedrals + "\n" + outImpropers
    else:
        return header + "\n" + outMasses + "\n" + outCoeffs + "\n" + outAtoms + "\n" + outBonds + "\n" + outAngles