Esempio n. 1
0
def void_find(meshobject, atoms, coarseness=1):
    """
    Defines the maximum spherical radius of probes in unoccupied space. Scans through the distance
    between the probe point and all atoms in the system, and defines the maximum radius of the probe
    as the smallest value of (probe_position - atom_position - vdw_radii).
    Coarseness factor reduces number of probe points by skipping over points in the grid.
    Args:
        meshobject: Mesh object
            Contains meshgrid and associated unit cell information.
        atoms: Atoms object
            Supplies coordinates and atomic information.
        coarseness: integer
            Skips over an integer number of points
    Return:
        void_centres: numpy array
            Atomic coordiantes of the void centres.
        void_radii: numpy array
            Radius of the void centres
    """

    import numpy as np
    from ase.data.vdw_alvarez import vdw_radii
    from ase.geometry import get_distances
    from carmm.analyse.meshgrid.meshgrid_functions import mol_mesh_pbc_check

    # Check atoms PBC and mesh PBC match.
    if meshobject.strict_mode:
        mol_mesh_pbc_check(meshobject.pbc, atoms.pbc)

    void_centres = []
    void_radii = []

    atom_radii = vdw_radii[atoms.numbers]

    atom_radii = np.array([atom_radii]).flatten()

    for i in range(0, meshobject.nx, coarseness):
        for j in range(0, meshobject.ny, coarseness):
            for k in range(0, meshobject.nz, coarseness):

                a_xx, a_yy, a_zz = meshobject.xx[i, j, k], meshobject.yy[
                    i, j, k], meshobject.zz[i, j, k]

                distances = get_distances(np.array([a_xx, a_yy, a_zz]),
                                          p2=atoms.positions,
                                          cell=meshobject.Cell,
                                          pbc=meshobject.pbc)

                distances = distances[1] - atom_radii

                min_distance = np.amin(distances)

                void_centres.append([a_xx, a_yy, a_zz])
                void_radii.append([min_distance])

    void_centres = np.array([void_centres])
    void_centres = void_centres[0]
    void_radii = np.array([void_radii]).flatten()

    return void_centres, void_radii
Esempio n. 2
0
def get_geometry(atoms, index, code):
    from ase.geometry import get_distances
    from collections import defaultdict
    #get possible rings, and max rings size
    ring_sizes = get_ring_sizes(code) * 2
    max_ring = max(ring_sizes)

    # repeat the unit cell so it is large enough to capture the max ring size
    # also turn this new larger unit cell into a graph
    G, large_atoms, repeat = atoms_to_graph(atoms, index, max_ring)
    index = [atom.index for atom in large_atoms if atom.tag == index][0]

    # to find all the rings associated with a T site, we need all the rings
    # associated with each oxygen bound to that T site. We will use networkx
    # neighbors to find those oxygens
    atoms = atoms.repeat(repeat)
    import networkx as nx
    com_dists = defaultdict(list)
    for n in nx.neighbors(G, index):
        c, paths, a = get_orings(large_atoms, n, code, validation='sastre')

        atoms, trans = center(atoms, large_atoms[n].tag)
        for p in paths:
            l = int(len(p) / 2)
            ring_atoms = atoms[p]
            rp = ring_atoms.get_positions()
            com = rp.mean(axis=0)
            positions = atoms.get_positions()
            distances = get_distances(com, positions)[1][0]
            for i, d in enumerate(distances):
                if i not in p:
                    com_dists[l].append(d)
    return com_dists
Esempio n. 3
0
def nearest(atoms1, atoms2, cell=None, pbc=None):
    """Return indices of nearest atoms"""
    p1 = atoms1.get_positions()
    p2 = atoms2.get_positions()
    vd_aac, d2_aa = get_distances(p1, p2, cell, pbc)
    i1, i2 = np.argwhere(d2_aa == d2_aa.min())[0]
    return i1, i2, vd_aac[i1, i2]
Esempio n. 4
0
def scale_cell(atoms):
    from ase.geometry import get_distances
    diff = 1
    sil = 3.1
    mult = 1
    si = [atom.index for atom in atoms if atom.symbol == 'Si']
    zsi = atoms[si]
    while diff > 0.01:
        cell = atoms.cell.cellpar()
        for i in range(3):
            cell[i] = cell[i] * mult
        zsi.set_cell(cell, scale_atoms=True)
        ncell = zsi.get_cell()
        positions = zsi.get_positions()
        distances = get_distances(positions, cell=ncell, pbc=[1, 1, 1])[1]
        temp = []
        for line in distances:
            masked_line = np.ma.masked_equal(line, 0.0, copy=False)
            temp.append(masked_line.min())
        silm = np.average(temp)
        diff = abs(silm - sil)
        mult = sil / silm
    atoms.set_cell(cell, scale_atoms=True)

    return atoms
Esempio n. 5
0
    def get_qm_cluster(self, atoms):

        if self.qm_buffer_mask is None:
            self.initialize_qm_buffer_mask(atoms)

        qm_cluster = atoms[self.qm_buffer_mask]
        del qm_cluster.constraints

        round_cell = False
        if self.qm_radius is None:
            round_cell = True
            # get all distances between qm atoms.
            # Treat all X, Y and Z directions independently
            # only distance between qm atoms is calculated
            # in order to estimate qm radius in thee directions
            R_qm, _ = get_distances(atoms.positions[self.qm_selection_mask],
                                    cell=atoms.cell,
                                    pbc=atoms.pbc)
            # estimate qm radius in three directions as 1/2
            # of max distance between qm atoms
            self.qm_radius = np.amax(np.amax(R_qm, axis=1), axis=0) * 0.5

        if atoms.cell.orthorhombic:
            cell_size = np.diagonal(atoms.cell)
        else:
            raise RuntimeError("NON-orthorhombic cell is not supported!")

        # check if qm_cluster should be left periodic
        # in periodic directions of the cell (cell[i] < qm_radius + buffer
        # otherwise change to non pbc
        # and make a cluster in a vacuum configuration
        qm_cluster_pbc = (atoms.pbc & (cell_size < 2.0 *
                                       (self.qm_radius + self.buffer_width)))

        # start with the original orthorhombic cell
        qm_cluster_cell = cell_size.copy()
        # create a cluster in a vacuum cell in non periodic directions
        qm_cluster_cell[~qm_cluster_pbc] = (2.0 *
                                            (self.qm_radius[~qm_cluster_pbc] +
                                             self.buffer_width + self.vacuum))

        if round_cell:
            # round the qm cell to the required tolerance
            qm_cluster_cell[~qm_cluster_pbc] = (np.round(
                (qm_cluster_cell[~qm_cluster_pbc]) / self.qm_cell_round_off) *
                                                self.qm_cell_round_off)

        qm_cluster.set_cell(Cell(np.diag(qm_cluster_cell)))
        qm_cluster.pbc = qm_cluster_pbc

        qm_shift = (0.5 * qm_cluster.cell.diagonal() -
                    qm_cluster.positions.mean(axis=0))

        if 'cell_origin' in qm_cluster.info:
            del qm_cluster.info['cell_origin']

        # center the cluster only in non pbc directions
        qm_cluster.positions[:, ~qm_cluster_pbc] += qm_shift[~qm_cluster_pbc]

        return qm_cluster
Esempio n. 6
0
def gofr_snapshot(axes, pos, rmax, rmin=0, nbin=40, gofr_norm=None):
    """ calculate the pair correlation function of a snapshot of a crystal structure given the 'axes' of the simulation cell and the positions ('pos') of atoms inside the cell.
    'rmax' is the maximum pair distance to histogram. 'rmin','rmax', and 'nbin' are passed to numpy.histogram to generate the results.
    return 3 lists: r, g(r), and g(r) normalization.
    Note: if a gofr_norm is given, then it will not be recalculated. This can save time if the simulation box does not change.
    Args: 
      axes (np.array): crystal lattice vectors.
      pos (np.array):  positions of atoms. 
      rmax (float): maximum distance to calculate g(r) to.
      rmin (float,optional): minimum distance to calculate g(r) to, default is 0.
      nbin (int,optional): number of bins, default is 40.
      gofr_norm (float,optional): normalization factor for g(r), default is to recalculate.
    Returns:
      (np.array,np.array,float): (r,g(r),normalization) """
    from ase.geometry import get_distances
    from qharv.inspect.axes_pos import volume
    cell_volume = volume(axes)
    nptcl = len(pos)
    dtable = get_distances(pos, cell=axes, pbc=True)[1]

    # upper triagular distance matrix (i.e. unique pair distances)
    i_triu = np.triu_indices(nptcl, m=nptcl, k=1)
    pair_dists = dtable[i_triu]

    counts, ticks = np.histogram(pair_dists, range=(rmin, rmax), bins=nbin)
    myx = (ticks[:-1] + ticks[1:]) / 2.
    dx = myx[1] - myx[0]

    if gofr_norm is None:
        # gofr_norm = 4*np.pi*myx**2.*dx * nptcl*(nptcl-1)/2./struct.volume
        gofr_norm = 4 * np.pi * myx**2. * dx * nptcl**2. / cell_volume / 2.
    # end if
    myy = counts / gofr_norm

    return myx, myy, gofr_norm
Esempio n. 7
0
def distance_point2point(x_1, y_1, z_1, x_2, y_2, z_2, meshobject):
    """
    Function finds the distance between two points (defined in cartesian co-ordinates).
    Args:
        x_1: float
            x coordinate of point 1.
        y_1: float
            y coordinate of point 1.
        z_1: float
            z coordinate of point 1.
        x_2: float
            x coordinate of point 2.
        y_2: float
            y coordinate of point 2.
        z_2: float2
            z coordinate of point 2.
        meshobject: Mesh object
            Object storing meshgrid and PBC conditions
    Returns:
        o_distance: Distance between points 1 and 2.
    """

    from ase.geometry import get_distances

    o_distance = get_distances([x_1, y_1, z_1],
                               p2=[x_2, y_2, z_2],
                               cell=meshobject.Cell,
                               pbc=meshobject.pbc)

    return o_distance
Esempio n. 8
0
    def add(self):
        newatoms = self.get_atoms()
        if newatoms is None:  # Error dialog was shown
            return

        newcenter = self.getcoords()

        # Not newatoms.center() because we want the same centering method
        # used for adding atoms relative to selections (mean).
        previous_center = newatoms.positions.mean(0)
        newatoms.positions += newcenter - previous_center

        atoms = self.gui.atoms
        if len(atoms) and self.picky.value:
            from ase.geometry import get_distances
            disps, dists = get_distances(atoms.positions, newatoms.positions)
            mindist = dists.min()
            if mindist < 0.5:
                ui.showerror(
                    _('Bad positions'),
                    _('Atom would be less than 0.5 Å from '
                      'an existing atom.  To override, '
                      'uncheck the check positions option.'))
                return

        self.gui.add_atoms_and_select(newatoms)
Esempio n. 9
0
 def _isclose(self, molid1, molid2):
     name = f'{molid1}-{molid2}'
     if name not in self._distances:
         self._distances[name] = np.min(
             get_distances(self._atoms[self._mols[molid1 - 1]].positions,
                           self._atoms[self._mols[molid2 - 1]].positions,
                           cell=self._atoms.get_cell(),
                           pbc=self._atoms.get_pbc())[1]) <= self.cutoff
     return self._distances[name]
Esempio n. 10
0
def test_qm_buffer_mask(qm_calc, mm_calc, bulk_at):
    """
    test number of atoms in qm_buffer_mask for
    spherical region in a fully periodic cell
    also tests that "region" array returns the same mapping
    """

    alat = bulk_at.cell[0, 0]
    N_cell_geom = 10
    at0 = bulk_at * N_cell_geom
    r = at0.get_distances(0, np.arange(len(at0)), mic=True)
    print("N_cell", N_cell_geom, 'N_MM', len(at0), "Size", N_cell_geom * alat)
    qm_rc = 5.37  # cutoff for EMC()

    for R_QM in [
            1.0e-3,  # one atom in the center
            alat / np.sqrt(2.0) + 1.0e-3,  # should give 12 nearest
            # neighbours + central atom
            alat + 1.0e-3
    ]:  # should give 18 neighbours + central atom

        at = at0.copy()
        qm_mask = r < R_QM
        qm_buffer_mask_ref = r < 2 * qm_rc + R_QM
        # exclude atoms that are too far (in case of non spherical region)
        # this is the old way to do it
        _, r_qm_buffer = get_distances(at.positions[qm_buffer_mask_ref],
                                       at.positions[qm_mask], at.cell, at.pbc)
        updated_qm_buffer_mask = np.ones_like(at[qm_buffer_mask_ref])
        for i, r_qm in enumerate(r_qm_buffer):
            if r_qm.min() > 2 * qm_rc:
                updated_qm_buffer_mask[i] = False

        qm_buffer_mask_ref[qm_buffer_mask_ref] = updated_qm_buffer_mask
        '''
        print(f'R_QM             {R_QM}   N_QM        {qm_mask.sum()}')
        print(f'R_QM + buffer: {2 * qm_rc + R_QM:.2f}'
              f' N_QM_buffer {qm_buffer_mask_ref.sum()}')
        print(f'                     N_total:    {len(at)}')
        '''
        qmmm = ForceQMMM(at, qm_mask, qm_calc, mm_calc, buffer_width=2 * qm_rc)
        # build qm_buffer_mask and test it
        qmmm.initialize_qm_buffer_mask(at)
        # print(f'      Calculator N_QM_buffer:'
        #       f'    {qmmm.qm_buffer_mask.sum().sum()}')
        assert qmmm.qm_buffer_mask.sum() == qm_buffer_mask_ref.sum()
        # same test for qmmm.get_cluster()
        qm_cluster = qmmm.get_qm_cluster(at)
        assert len(qm_cluster) == qm_buffer_mask_ref.sum()
        # test region mappings
        region = qmmm.get_region_from_masks(at)
        qm_mask_region = region == "QM"
        assert qm_mask_region.sum() == qm_mask.sum()
        buffer_mask_region = region == "buffer"
        assert qm_mask_region.sum() + \
               buffer_mask_region.sum() == qm_buffer_mask_ref.sum()
def get_ads_dist(atoms, ads0, ads1='H'):
    index0 = [i for i in range(len(atoms)) if atoms[i].symbol == ads0]
    index1 = [i for i in range(len(atoms)) if atoms[i].symbol == ads1]
    dist = []
    D, D_len = get_distances(atoms.positions[index0],
                             atoms.positions[index1],
                             atoms.cell,
                             pbc=True)

    return np.max(D_len)
Esempio n. 12
0
def slab_positions2ads_index(atoms, slab, species):
    """Return the indexes of adsorbate atoms identified by comparing positions
    to a reference slab structure.

    Parameters
    ----------
    atoms : object
    """
    composition = string2symbols(species)
    ads_atoms = []
    for symbol in composition:
        if (composition.count(symbol) == atoms.get_chemical_symbols().count(
                symbol)):
            ads_atoms += [
                atom.index for atom in atoms if atom.symbol == symbol
            ]

    ua_ads, uc_ads = np.unique(ads_atoms, return_counts=True)
    ua_comp, uc_comp = np.unique(composition, return_counts=True)
    if ua_ads == ua_comp and uc_ads == uc_comp:
        return ads_atoms

    p_a = atoms.get_positions()
    p_r = slab.get_positions()

    for s in composition:
        if s in np.array(atoms.get_chemical_symbols())[ads_atoms]:
            continue
        symbol_count = composition.count(s)
        index_a = np.where(np.array(atoms.get_chemical_symbols()) == s)[0]
        index_r = np.where(np.array(slab.get_chemical_symbols()) == s)[0]
        _, dist = get_distances(p_a[index_a, :],
                                p2=p_r[index_r, :],
                                cell=atoms.cell,
                                pbc=True)
        # Assume all slab atoms are closest to their reference counterpart.
        deviations = np.min(dist, axis=1)
        # Sort deviations.
        ascending = np.argsort(deviations)
        # The highest deviations are assumed to be new atoms.
        ads_atoms += list(index_a[ascending[-symbol_count:]])

    # Final check.
    ua_ads, uc_ads = np.unique(np.array(
        atoms.get_chemical_symbols())[ads_atoms].sort(),
                               return_counts=True)
    ua_comp, uc_comp = np.unique(composition.sort(), return_counts=True)
    if ua_ads != ua_comp:
        msg = str(ua_ads) + " != " + str(ua_comp)
        raise AssertionError(msg)
    elif uc_ads != uc_comp:
        msg = str(uc_ads) + " != " + str(uc_comp)
        raise AssertionError(msg)

    return ads_atoms
    def is_desorbed(self):
        desorbed = False
        D, D_len = get_distances(self.B.positions, cell=self.B.cell, pbc=True)

        indexM = np.argmin(D_len[-1, :-1])
        dist_S = D_len[-1, indexM]

        if dist_S > (cradii[self.B[-1].number] +
                     cradii[self.B[indexM].number]) * 2:
            print('DESORBED FROM SLAB')
            desorbed = True
        return desorbed
Esempio n. 14
0
def test_antisymmetry():
    size = 2
    atoms = FaceCenteredCubic(size=[size, size, size],
                              symbol='Cu',
                              latticeconstant=2,
                              pbc=(1, 1, 1))

    vmin, vlen = get_distances(atoms.get_positions(),
                               cell=atoms.cell,
                               pbc=True)
    assert (vlen == vlen.T).all()

    for i, j in itertools.combinations(range(len(atoms)), 2):
        assert (vmin[i, j] == -vmin[j, i]).all()
def add_CH4_SS(mof, site_idx, ads_pos):
    """
	Add CH4 to the structure from single-site model

	Args:
		mof (ASE Atoms object): starting ASE Atoms object of structure

		site_idx (int): ASE index of site based on single-site model

		ads_pos (array): 1D numpy array for the best adsorbate position
			
	Returns:
		mof (ASE Atoms object): ASE Atoms object with adsorbate
	"""

    #Get CH4 parameters
    CH4 = molecule('CH4')
    CH_length = CH4.get_distance(0, 1)
    CH_angle = CH4.get_angle(1, 0, 2)
    CH2_dihedral = CH4.get_dihedral(2, 1, 0, 4)
    CH_length = CH4.get_distance(0, 1)
    CH_angle = CH4.get_angle(1, 0, 2)
    CH_dihedral = CH4.get_dihedral(2, 1, 0, 4)

    #Add CH4 to ideal adsorption position
    CH4[0].position = ads_pos

    #Make one of the H atoms colinear with adsorption site and C
    D, D_len = get_distances([ads_pos],
                             mof[site_idx].position,
                             cell=mof.cell,
                             pbc=mof.pbc)
    r_vec = D[0, 0]
    r = (r_vec / np.linalg.norm(r_vec)) * CH_length

    #Construct rest of CH4 using Z-matrix format
    CH4[1].position = ads_pos + r
    CH4.set_distance(0, 2, CH_length, fix=0, mic=True)
    CH4.set_angle(1, 0, 2, CH_angle)
    CH4.set_distance(0, 3, CH_length, fix=0, mic=True)
    CH4.set_angle(1, 0, 3, CH_angle)
    CH4.set_dihedral(2, 1, 0, 3, -CH_dihedral)
    CH4.set_distance(0, 4, CH_length, fix=0, mic=True)
    CH4.set_angle(1, 0, 4, CH_angle)
    CH4.set_dihedral(2, 1, 0, 4, CH2_dihedral)

    #Add CH4 molecule to the structure
    mof.extend(CH4)

    return mof
Esempio n. 16
0
    def initialize_qm_buffer_mask(self, atoms):
        """
        Initialises system to perform qm calculation
        """
        # calculate the distances between all atoms and qm atoms
        # qm_distance_matrix is a [N_QM_atoms x N_atoms] matrix
        _, qm_distance_matrix = get_distances(
            atoms.positions[self.qm_selection_mask], atoms.positions,
            atoms.cell, atoms.pbc)

        self.qm_buffer_mask = np.zeros(len(atoms), dtype=bool)

        # every r_qm is a matrix of distances
        # from an atom in qm region and all atoms with size [N_atoms]
        for r_qm in qm_distance_matrix:
            self.qm_buffer_mask[r_qm < self.buffer_width] = True
Esempio n. 17
0
def calc_gofr(bin_edges, axesl, posl):
  from ase.geometry import get_distances
  grl = []
  for axes, pos in zip(axesl, posl):
    # get unique pair separations
    drij, rij = get_distances(pos, cell=axes, pbc=[1, 1, 1])
    idx = np.triu_indices(len(rij), k=1)
    dists = rij[idx]
    # histogram
    volume = np.linalg.det(axes)
    natom = len(pos)
    gr_norm = gofr_norm(bin_edges, natom, volume)
    gr1 = gofr_count(bin_edges, dists)*gr_norm
    grl.append(gr1)
  grm, gre = yl_ysql(grl)
  return grm, gre
Esempio n. 18
0
def slab_indices(slab0, slab1, mask=None):
    """Match the indices of similar atoms between two slabs."""
    n = len(slab0)
    if mask is None:
        mask = np.arange(n)
    matching = np.arange(n)

    ipos = slab0.positions[mask]
    fpos = slab1.positions[mask]

    d = get_distances(ipos, fpos, cell=slab0.cell, pbc=slab0.pbc)[1]

    matching[mask] = np.argmin(d, axis=1)
    atoms = sort(slab0, matching)

    return atoms
Esempio n. 19
0
 def command(self, reference_positions, positions, velocities,
             previous_positions, previous_velocities, pbc, cell):
     _, distance_matrix = get_distances(p1=reference_positions,
                                        p2=positions,
                                        cell=cell,
                                        pbc=pbc)
     closest_reference = np.argmin(distance_matrix, axis=0)
     is_at_home = (closest_reference == np.arange(
         len(positions)))[:, np.newaxis]
     is_away = 1 - is_at_home
     return {
         'positions': is_at_home * positions + is_away * previous_positions,
         'velocities':
         is_at_home * velocities + is_away * -previous_velocities,
         'reflected': is_away.astype(bool).flatten()
     }
Esempio n. 20
0
def get_interatomic_distances(atoms, D=None, scale=None, direction=None):
    if D is not None:
        D[:, :, direction] *= scale
        D.shape = (-1, 3)
        distances = np.sqrt((D**2).sum(1))
        D.shape = (-1, len(atoms), 3)
        distances.shape = (-1, len(atoms))

    else:
        D, distances = get_distances(atoms.positions,
                                     cell=atoms.cell,
                                     pbc=True)

    min_cell_width = np.min(np.linalg.norm(atoms.cell, axis=1))
    min_cell_width *= np.ones(len(atoms))
    np.fill_diagonal(distances, min_cell_width)

    return D, distances
Esempio n. 21
0
    def is_reconstructed(self, xy_cutoff=0.3, z_cutoff=0.4):
        """Compare initial and final slab configuration 
        to determine if slab reconstructs during relaxation

        xy_cutoff: Allowed xy-movement is determined from 
                   the covalent radii as:
                   xy_cutoff * np.mean(cradii)
        z_cutoff:  Allowed z-movement is determined as
                   z_cutoff * cradii_i
        """

        assert self.A, \
            'Initial slab geometry needed to classify reconstruction'

        # remove adsorbate
        A = self.A[:-1].copy()
        B = self.B[:-1].copy()

        # Order wrt x-positions
        x_indices = np.argsort(A.positions[:, 0])
        A = A[x_indices]
        B = B[x_indices]
        a = A.positions
        b = B.positions

        allowed_z_movement = z_cutoff * cradii[A.get_atomic_numbers()]
        allowed_xy_movement = \
            xy_cutoff * np.mean(cradii[A.get_atomic_numbers()])

        D, D_len = get_distances(p1=a, p2=b, cell=A.cell, pbc=True)
        d_xy = np.linalg.norm(np.diagonal(D)[:2], axis=0)
        d_z = np.diagonal(D)[2:][0]

        cond1 = np.all(d_xy < allowed_xy_movement)
        cond2 = np.all([d_z[i] < allowed_z_movement[i] for i in range(len(a))])

        if cond1 and cond2:  # not reconstructed
            return False
        else:
            return True
Esempio n. 22
0
def grid_within_cutoff(df,atoms,max_dist,site_pos,partition=1e6):
	"""
	Reduces grid dataframe into data within max_dist of active site

	Args:
		df (pandas df object): df containing energy grid details (x,y,z,E)

		atoms (ASE Atoms object): Atoms object of structure
		
		max_dist (float): maximum distance from active site to consider
		
		site_pos (array): numpy array of the adsorption site
		
		partition (float): how many data points to partition the df for. This
		is used to prevent memory overflow errors. Decrease if memory errors
		arise.
	
	Returns:
		new_df (pandas df object): modified df only around max_dist from active
		site and also with a new distance (d) column
	"""
	df['d'] = ''
	n_loops = int(np.ceil(len(df)/partition))
	new_df = pd.DataFrame()

	#Only consider data within max_dist of active site. Cut up the original
	#dataframe into chunks defined by partition to prevent memory issues
	for i in range(n_loops):
		if i == n_loops-1:
			idx = np.arange(i*int(partition),len(df))
		else:
			idx = np.arange(i*int(partition),(i+1)*int(partition))
		D,D_len = get_distances([site_pos],df.loc[idx,['x','y','z']].values,
			cell=atoms.cell,pbc=atoms.pbc)
		D_len.shape = (-1,)
		df.loc[idx,'d'] = D_len
	new_df = df[df['d'] <= max_dist]

	return new_df
Esempio n. 23
0
    def add(self):
        newatoms = self.get_atoms()
        if newatoms is None:  # Error dialog was shown
            return

        newcenter = self.getcoords()

        # Not newatoms.center() because we want the same centering method
        # used for adding atoms relative to selections (mean).
        previous_center = newatoms.positions.mean(0)
        newatoms.positions += newcenter - previous_center

        atoms = self.gui.atoms

        if len(atoms) and self.picky.value:
            from ase.geometry import get_distances
            disps, dists = get_distances(atoms.positions, newatoms.positions)
            mindist = dists.min()
            if mindist < 0.5:
                ui.showerror(
                    _('Bad positions'),
                    _('Atom would be less than 0.5 Å from '
                      'an existing atom.  To override, '
                      'uncheck the check positions option.'))
                return

        atoms += newatoms

        if len(atoms) > self.gui.images.maxnatoms:
            self.gui.images.initialize(list(self.gui.images),
                                       self.gui.images.filenames)

        self.gui.images.selected[:] = False

        # 'selected' array may be longer than current atoms
        self.gui.images.selected[len(atoms) - len(newatoms):len(atoms)] = True
        self.gui.set_frame()
        self.gui.draw()
Esempio n. 24
0
def sphere(atoms, paths, cutoff):
    '''
    Method for determining valid rings by ensuring that non ring atoms are not
    within a cutoff distance of the center of mass of the path.
    '''
    from ase.geometry import get_distances
    valid_paths = []
    for p in paths:
        flag = True
        if len(p) > 10:
            ring_atoms = atoms[p]
            com = ring_atoms.get_center_of_mass()
            positions = atoms.get_positions()
            distances = get_distances(com, positions)[1][0]

            for i, d in enumerate(distances):
                if d < cutoff and i not in p:
                    flag = False
                    break
        if flag:
            valid_paths.append(p)

    return valid_paths
Esempio n. 25
0
def atoms_to_graph(atoms,index,max_ring):
    '''
    Helper function to repeat a unit cell enough times to capture the largest
    possible ring, and turn the new larger cell into a graph object.

    RETURNS:
    G = graph object representing zeolite framework in new larger cell
    large_atoms = ASE atoms object of the new larger cell framework
    repeat = array showing the number of times the cell was repeated: [x,y,z]
    '''

    # repeat cell, center the cell, and wrap the atoms back into the cell
    cell = atoms.cell.cellpar()[:3]
    repeat = []
    for i,c in enumerate(cell):
        if c/2 < max_ring/2+5:
            l = c
            re = 1
            while l/2 < max_ring/2+5:
                re += 1
                l = c*re

            repeat.append(re)
        else:
            repeat.append(1)
    large_atoms = atoms.copy()
    large_atoms = large_atoms.repeat(repeat)
    center = large_atoms.get_center_of_mass()
    trans = center - large_atoms.positions[index]
    large_atoms.translate(trans)
    large_atoms.wrap()

    # remove atoms that won't contribute to wrings
    from ase.geometry import get_distances
    cell = large_atoms.get_cell()
    pbc = [1,1,1]
    p1 = large_atoms[index].position
    positions = large_atoms.get_positions()
    distances = get_distances(p1,positions)[1][0]

    delete = []
    for i,l in enumerate(distances):
        if l>max_ring/2+5:
            delete.append(i)
    inds = [atom.index for atom in large_atoms]
    large_atoms.set_tags(inds)
    atoms = large_atoms.copy()
    del large_atoms[delete]

    matrix = np.zeros([len(large_atoms),len(large_atoms)]).astype(int)
    positions = large_atoms.get_positions()



    tsites = [atom.index for atom in large_atoms if atom.symbol != 'O']
    tpositions = positions[tsites]
    osites = [atom.index for atom in large_atoms if atom.index not in tsites]
    opositions = positions[osites]
    distances = get_distances(tpositions,opositions)[1]

    for i,t in enumerate(tsites):
        dists = distances[i]
        idx = np.nonzero(dists<2)[0]
        for o in idx:
            matrix[t,osites[o]]=1
            matrix[osites[o],t]=1
    # now we make the graph
    import networkx as nx
    G = nx.from_numpy_matrix(matrix)
    # G.remove_nodes_from(delete)
    return G, large_atoms, repeat
def distance_pbc(a1, a2, cell, pbc):
    D, D_len = geometry.get_distances(a1, a2, cell=cell, pbc=pbc)
    cell_trans = [0, 0, 0]
    return [D[0, 0, 0], D[1, 1, 1], D[2, 2, 2]], D_len[0, 0], cell_trans
def _match_positions(structure: Atoms,
                     reference: Atoms) -> Tuple[Atoms, float, float]:
    """Matches the atoms in the input `structure` to the sites in the
    `reference` structure. The function returns tuple the first element of which
    is a copy of the `reference` structure, in which the chemical species are
    assigned to comply with the `structure`. The second and third element
    of the tuple represent the maximum and average distance between relaxed and
    reference sites.

    Parameters
    ----------
    structure
        structure with relaxed positions
    reference
        structure with idealized positions

    Raises
    ------
    ValueError
        if the cell metrics of the two input structures do not match
    ValueError
        if the periodic boundary conditions of the two input structures do not match
    ValueError
        if the input structure contains more atoms than the reference structure
    """

    if not np.all(reference.pbc == structure.pbc):
        msg = 'The boundary conditions of reference and relaxed structures do not match.'
        msg += '\n  reference: ' + str(reference.pbc)
        msg += '\n  relaxed: ' + str(structure.pbc)
        raise ValueError(msg)

    if len(structure) > len(reference):
        msg = 'The relaxed structure contains more atoms than the reference structure.'
        msg += '\n  reference: ' + str(len(reference))
        msg += '\n  relaxed: ' + str(len(structure))
        raise ValueError(msg)

    if not np.all(np.isclose(reference.cell, structure.cell)):
        msg = 'The cell metrics of reference and relaxed structures do not match.'
        msg += '\n  reference: ' + str(reference.cell)
        msg += '\n  relaxed: ' + str(structure.cell)
        raise ValueError(msg)

    # compute distances between reference and relaxed positions
    _, dists = get_distances(reference.positions,
                             structure.positions,
                             cell=reference.cell,
                             pbc=reference.pbc)
    # pad matrix with zeros to obtain square matrix
    n, m = dists.shape
    cost_matrix = np.pad(dists, ((0, 0), (0, abs(n - m))),
                         mode='constant',
                         constant_values=0)
    # find optimal mapping using Kuhn-Munkres (Hungarian) algorithm
    row_ind, col_ind = linear_sum_assignment(cost_matrix)

    # compile new configuration with supplementary information
    mapped = reference.copy()
    displacement_magnitudes = []
    displacements = []
    minimum_distances = []
    n_dist_max = min(len(mapped), 3)
    warning = None
    for i, j in zip(row_ind, col_ind):
        atom = mapped[i]
        if j >= len(structure):
            # vacant site in reference structure
            atom.symbol = 'X'
            displacement_magnitudes.append(None)
            displacements.append(3 * [None])
            minimum_distances.append(n_dist_max * [None])
        else:
            atom.symbol = structure[j].symbol
            dvecs, drs = get_distances([structure[j].position],
                                       [reference[i].position],
                                       cell=reference.cell,
                                       pbc=reference.pbc)
            displacement_magnitudes.append(drs[0][0])
            displacements.append(dvecs[0][0])
            # distances to the next three available sites
            minimum_distances.append(sorted(dists[:, j])[:n_dist_max])
            if len(structure) > 1:
                if drs[0][0] > min(dists[:, j]) + 1e-6:
                    logger.warning(
                        'An atom was mapped to a site that was further '
                        'away than the closest site (that site was already '
                        'occupied by another atom).')
                    warning = 'possible_ambiguity_in_mapping'
                elif minimum_distances[-1][0] > 0.9 * minimum_distances[-1][1]:
                    logger.warning(
                        'An atom was approximately equally far from its '
                        'two closest sites.')
                    warning = 'possible_ambiguity_in_mapping'

    displacement_magnitudes = np.array(displacement_magnitudes,
                                       dtype=np.float64)
    mapped.new_array('Displacement', displacements, float, shape=(3, ))
    mapped.new_array('Displacement_Magnitude', displacement_magnitudes, float)
    mapped.new_array('Minimum_Distances',
                     minimum_distances,
                     float,
                     shape=(n_dist_max, ))

    drmax = np.nanmax(displacement_magnitudes)
    dravg = np.nanmean(displacement_magnitudes)

    return mapped, drmax, dravg, warning
def add_small_molecules(FF, ff_string):

    if ff_string == 'TraPPE':
        SM_constants = small_molecule_constants.TraPPE
    elif ff_string == 'TIP4P_2005_long':
        SM_constants = small_molecule_constants.TIP4P_2005_long
        FF.pair_data['special_bonds'] = 'lj 0.0 0.0 1.0 coul 0.0 0.0 0.0'
    elif ff_string == 'TIP4P_cutoff':
        SM_constants = small_molecule_constants.TIP4P_cutoff
        FF.pair_data['special_bonds'] = 'lj/coul 0.0 0.0 1.0'
    elif ff_string == 'TIP4P_2005_cutoff':
        SM_constants = small_molecule_constants.TIP4P_cutoff
        FF.pair_data['special_bonds'] = 'lj/coul 0.0 0.0 1.0'
    elif ff_string == 'Ions':
        SM_constants = small_molecule_constants.Ions
        FF.pair_data['special_bonds'] = 'lj/coul 0.0 0.0 1.0'
    # insert more force fields here if needed
    else:
        raise ValueError('the small molecule force field', ff_string,
                         'is not defined')

    SG = FF.system['graph']
    SMG = FF.system['SM_graph']

    if len(SMG.nodes()) > 0 and len(SMG.edges()) == 0:

        print(
            'there are no small molecule bonds in the CIF, calculating based on covalent radii...'
        )
        atoms = Atoms()

        offset = min(SMG.nodes())

        for node, data in SMG.nodes(data=True):
            #print(node, data)
            atoms.append(
                Atom(data['element_symbol'], data['cartesian_position']))

        atoms.set_cell(FF.system['box'])
        unit_cell = atoms.get_cell()
        cutoffs = neighborlist.natural_cutoffs(atoms)
        NL = neighborlist.NewPrimitiveNeighborList(
            cutoffs,
            use_scaled_positions=False,
            self_interaction=False,
            skin=0.10)  # shorten the cutoff a bit
        NL.build([True, True, True], unit_cell, atoms.get_positions())

        for i in atoms:

            nbors = NL.get_neighbors(i.index)[0]

            for j in nbors:

                bond_length = get_distances(i.position,
                                            p2=atoms[j].position,
                                            cell=unit_cell,
                                            pbc=[True, True, True])
                bond_length = np.round(bond_length[1][0][0], 3)
                SMG.add_edge(i.index + offset,
                             j + offset,
                             bond_length=bond_length,
                             bond_order='1.0',
                             bond_type='S')

        NMOL = len(list(nx.connected_components(SMG)))
        print(NMOL, 'small molecules were recovered after bond calculation')

    mol_flag = 1
    max_ind = FF.system['max_ind']
    index = max_ind

    box = FF.system['box']
    a, b, c, alpha, beta, gamma = box
    pi = np.pi
    ax = a
    ay = 0.0
    az = 0.0
    bx = b * np.cos(gamma * pi / 180.0)
    by = b * np.sin(gamma * pi / 180.0)
    bz = 0.0
    cx = c * np.cos(beta * pi / 180.0)
    cy = (c * b * np.cos(alpha * pi / 180.0) - bx * cx) / by
    cz = (c**2.0 - cx**2.0 - cy**2.0)**0.5
    unit_cell = np.asarray([[ax, ay, az], [bx, by, bz], [cx, cy, cz]]).T
    inv_unit_cell = np.linalg.inv(unit_cell)

    add_nodes = []
    add_edges = []
    comps = []

    for comp in nx.connected_components(SMG):

        mol_flag += 1
        comp = sorted(list(comp))
        ID_string = sorted([SMG.nodes[n]['element_symbol'] for n in comp])
        ID_string = [(key, len(list(group)))
                     for key, group in groupby(ID_string)]
        ID_string = ''.join([str(e) for c in ID_string for e in c])
        comps.append(ID_string)

        for n in comp:

            data = SMG.nodes[n]

            SMG.nodes[n]['mol_flag'] = str(mol_flag)

            if ID_string == 'H2O1':
                SMG.nodes[n][
                    'force_field_type'] = SMG.nodes[n]['element_symbol'] + '_w'
            else:
                SMG.nodes[n]['force_field_type'] = SMG.nodes[n][
                    'element_symbol'] + '_' + ID_string

        # add COM sites where relevant, extend this list as new types are added
        if ID_string in ('O2', 'N2'):

            coords = []
            anchor = SMG.nodes[comp[0]]['fractional_position']

            for n in comp:

                data = SMG.nodes[n]
                data['mol_flag'] = str(mol_flag)
                fcoord = data['fractional_position']
                mic = PBC3DF_sym(fcoord, anchor)
                fcoord += mic[1]
                ccoord = np.dot(unit_cell, fcoord)
                coords.append(ccoord)

            ccom = np.average(coords, axis=0)
            fcom = np.dot(inv_unit_cell, ccom)
            index += 1

            if ID_string == 'O2':
                fft = 'O_com'
            elif ID_string == 'N2':
                fft = 'N_com'

            ndata = {
                'element_symbol': 'NA',
                'mol_flag': mol_flag,
                'index': index,
                'force_field_type': fft,
                'cartesian_position': ccom,
                'fractional_position': fcom,
                'charge': 0.0,
                'replication': np.array([0.0, 0.0, 0.0]),
                'duplicated_version_of': None
            }
            edata = {'sym_code': None, 'length': None, 'bond_type': None}

            add_nodes.append([index, ndata])
            add_edges.extend([(index, comp[0], edata),
                              (index, comp[1], edata)])

    for n, data in add_nodes:
        SMG.add_node(n, **data)
    for e0, e1, data in add_edges:
        SMG.add_edge(e0, e1, **data)

    ntypes = max([FF.atom_types[ty] for ty in FF.atom_types])
    maxatomtype_wsm = max([FF.atom_types[ty] for ty in FF.atom_types])

    maxbondtype_wsm = max([bty for bty in FF.bond_data['params']])
    maxangletype_wsm = max([aty for aty in FF.angle_data['params']])

    nbonds = max([i for i in FF.bond_data['params']])
    nangles = max([i for i in FF.angle_data['params']])

    try:
        ndihedrals = max([i for i in FF.dihedral_data['params']])
    except ValueError:
        ndihedrals = 0
    try:
        nimpropers = max([i for i in FF.improper_data['params']])
    except ValueError:
        nimpropers = 0

    new_bond_types = {}
    new_angle_types = {}
    new_dihedral_types = {}
    new_improper_types = {}

    for subG, ID_string in zip(
        [SMG.subgraph(c).copy() for c in nx.connected_components(SMG)], comps):

        constants = SM_constants[ID_string]

        # add new atom types
        for name, data in sorted(subG.nodes(data=True), key=lambda x: x[0]):

            fft = data['force_field_type']
            chg = constants['pair']['charges'][fft]
            data['charge'] = chg
            SG.add_node(name, **data)

            try:

                FF.atom_types[fft] += 0

            except KeyError:

                ntypes += 1
                FF.atom_types[fft] = ntypes
                style = constants['pair']['style']
                vdW = constants['pair']['vdW'][fft]
                FF.pair_data['params'][FF.atom_types[fft]] = (style, vdW[0],
                                                              vdW[1])
                FF.pair_data['comments'][FF.atom_types[fft]] = [fft, fft]
                FF.atom_masses[fft] = mass_key[data['element_symbol']]

                if 'hybrid' not in FF.pair_data[
                        'style'] and style != FF.pair_data['style']:
                    FF.pair_data['style'] = ' '.join(
                        ['hybrid', FF.pair_data['style'], style])
                elif 'hybrid' in FF.pair_data[
                        'style'] and style in FF.pair_data['style']:
                    pass
                elif 'hybrid' in FF.pair_data[
                        'style'] and style not in FF.pair_data['style']:
                    FF.pair_data['style'] += ' ' + style

        # add new bonds
        used_bonds = []
        ty = nbonds
        for e0, e1, data in subG.edges(data=True):

            bonds = constants['bonds']
            fft_i = SG.nodes[e0]['force_field_type']
            fft_j = SG.nodes[e1]['force_field_type']
            # make sure the order corresponds to that in the molecule dictionary
            bond = tuple(sorted([fft_i, fft_j]))

            try:

                style = bonds[bond][0]

                if bond not in used_bonds:

                    ty = ty + 1
                    new_bond_types[bond] = ty
                    FF.bond_data['params'][ty] = list(bonds[bond])
                    FF.bond_data['comments'][ty] = list(bond)

                    used_bonds.append(bond)

                if 'hybrid' not in FF.bond_data[
                        'style'] and style != FF.bond_data['style']:
                    FF.bond_data['style'] = ' '.join(
                        ['hybrid', FF.bond_data['style'], style])
                elif 'hybrid' in FF.bond_data[
                        'style'] and style in FF.bond_data['style']:
                    pass
                elif 'hybrid' in FF.bond_data[
                        'style'] and style not in FF.bond_data['style']:
                    FF.bond_data['style'] += ' ' + style

                if ty in FF.bond_data['all_bonds']:
                    FF.bond_data['count'] = (FF.bond_data['count'][0] + 1,
                                             FF.bond_data['count'][1] + 1)
                    FF.bond_data['all_bonds'][ty].append((e0, e1))
                else:
                    FF.bond_data['count'] = (FF.bond_data['count'][0] + 1,
                                             FF.bond_data['count'][1] + 1)
                    FF.bond_data['all_bonds'][ty] = [(e0, e1)]

            except KeyError:
                pass

        # add new angles
        used_angles = []
        ty = nangles
        for name, data in subG.nodes(data=True):

            angles = constants['angles']
            nbors = list(subG.neighbors(name))

            for comb in combinations(nbors, 2):

                j = name
                i, k = comb
                fft_i = subG.nodes[i]['force_field_type']
                fft_j = subG.nodes[j]['force_field_type']
                fft_k = subG.nodes[k]['force_field_type']

                angle = sorted((fft_i, fft_k))
                angle = (angle[0], fft_j, angle[1])

                try:

                    style = angles[angle][0]
                    FF.angle_data['count'] = (FF.angle_data['count'][0] + 1,
                                              FF.angle_data['count'][1])

                    if angle not in used_angles:

                        ty = ty + 1
                        new_angle_types[angle] = ty
                        FF.angle_data['count'] = (FF.angle_data['count'][0],
                                                  FF.angle_data['count'][1] +
                                                  1)
                        FF.angle_data['params'][ty] = list(angles[angle])
                        FF.angle_data['comments'][ty] = list(angle)

                        used_angles.append(angle)

                    if 'hybrid' not in FF.angle_data[
                            'style'] and style != FF.angle_data['style']:
                        FF.angle_data['style'] = ' '.join(
                            ['hybrid', FF.angle_data['style'], style])
                    elif 'hybrid' in FF.angle_data[
                            'style'] and style in FF.angle_data['style']:
                        pass
                    elif 'hybrid' in FF.angle_data[
                            'style'] and style not in FF.angle_data['style']:
                        FF.angle_data['style'] += ' ' + style

                    if ty in FF.angle_data['all_angles']:
                        FF.angle_data['all_angles'][ty].append((i, j, k))
                    else:
                        FF.angle_data['all_angles'][ty] = [(i, j, k)]

                except KeyError:
                    pass

        # add new dihedrals

    FF.bond_data['count'] = (FF.bond_data['count'][0],
                             len(FF.bond_data['params']))
    FF.angle_data['count'] = (FF.angle_data['count'][0],
                              len(FF.angle_data['params']))

    if 'tip4p' in FF.pair_data['style']:

        for ty, pair in FF.pair_data['comments'].items():
            fft = pair[0]
            if fft == 'O_w':
                FF.pair_data['O_type'] = ty
            if fft == 'H_w':
                FF.pair_data['H_type'] = ty

        for ty, bond in FF.bond_data['comments'].items():
            if sorted(bond) == ['H_w', 'O_w']:
                FF.pair_data['H2O_bond_type'] = ty

        for ty, angle in FF.angle_data['comments'].items():
            if angle == ['H_w', 'O_w', 'H_w']:
                FF.pair_data['H2O_angle_type'] = ty

        if 'long' in FF.pair_data['style']:
            FF.pair_data[
                'M_site_dist'] = 0.1546  # only TIP4P/2005 is implemented
        elif 'cut' in FF.pair_data[
                'style'] and ff_string == 'TIP4P_2005_cutoff':
            FF.pair_data['M_site_dist'] = 0.1546
        elif 'cut' in FF.pair_data['style'] and ff_string == 'TIP4P_cutoff':
            FF.pair_data['M_site_dist'] = 0.1500
def cif_read_pymatgen(filename, charges=False, coplanarity_tolerance=0.1):

    valencies = {
        'C': 4.0,
        'Si': 4.0,
        'Ge': 4.0,
        'N': 3.0,
        'P': 3.0,
        'As': 3.0,
        'Sb': 3.0,
        'O': 2.0,
        'S': 2.0,
        'Se': 2.0,
        'Te': 2.0,
        'F': 1.0,
        'Cl': 1.0,
        'Br': 1.0,
        'I': 1.0,
        'H': 1.0,
        'X': 1.0
    }

    bond_types = {0.5: 'S', 1.0: 'S', 1.5: 'A', 2.0: 'D', 3.0: 'T'}

    with open(filename, 'r') as f:
        f = f.read()
        f = filter(None, f.split('\n'))

    charge_list = []
    charge_switch = False

    for line in f:

        s = line.split()
        if '_atom_site_charge' in s:
            charge_switch = True
        if '_loop' in s:
            charge_switch = False

        if len(s) > 5:
            if charges:
                if charge_switch:
                    charge_list.append(float(s[-1]))

    cif = CifParser(filename)
    struct = cif.get_structures(primitive=False)[0]
    atoms = AseAtomsAdaptor.get_atoms(struct)
    unit_cell = atoms.get_cell()
    inv_uc = inv(unit_cell.T)
    elements = atoms.get_chemical_symbols()
    small_skin_metals = ('Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy',
                         'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Ba', 'La')

    if any(i in elements for i in ('Zn')):
        skin = 0.30
    if any(i in elements for i in small_skin_metals):
        skin = 0.05
    else:
        skin = 0.20

    print('skin for bond calculation is', skin)

    if not charges:
        charge_list = [0.0 for a in atoms]

    cutoffs = neighborlist.natural_cutoffs(atoms)
    NL = neighborlist.NewPrimitiveNeighborList(
        cutoffs, use_scaled_positions=False, self_interaction=False,
        skin=skin)  # default atom cutoffs work well
    NL.build([True, True, True], unit_cell, atoms.get_positions())

    G = nx.Graph()
    for a in atoms:
        G.add_node(a.index, element_symbol=a.symbol, position=a.position)

    for i in atoms:

        nbors = NL.get_neighbors(i.index)[0]
        isym = i.symbol

        for j in nbors:

            jsym = atoms[j].symbol

            if (isym not in metals) and (jsym not in metals) and not any(
                    e == 'X' for e in [isym, jsym]):
                try:
                    bond = bonds.CovalentBond(struct[i.index], struct[j])
                    bond_order = bond.get_bond_order()
                except ValueError:
                    bond_order = 1.0
            elif (isym == 'X' or jsym
                  == 'X') and (isym not in metals) and (jsym not in metals):
                bond_order = 1.0
            else:
                bond_order = 0.5

            bond_length = get_distances(i.position,
                                        p2=atoms[j].position,
                                        cell=unit_cell,
                                        pbc=[True, True, True])
            bond_length = np.round(bond_length[1][0][0], 3)

            G.add_edge(i.index,
                       j,
                       bond_length=bond_length,
                       bond_order=bond_order,
                       bond_type='',
                       pymatgen_bond_order=bond_order)

    NMG = G.copy()
    edge_list = list(NMG.edges())

    for e0, e1 in edge_list:

        sym0 = NMG.nodes[e0]['element_symbol']
        sym1 = NMG.nodes[e1]['element_symbol']

        if sym0 in metals or sym1 in metals:
            NMG.remove_edge(e0, e1)

    for i, data in G.nodes(data=True):

        isym = data['element_symbol']
        nbors = list(G.neighbors(i))
        nbor_symbols = [G.nodes[n]['element_symbol'] for n in nbors]
        nonmetal_nbor_symbols = [n for n in nbor_symbols if n not in metals]

        # remove C-M bonds if C is also bonded to carboxylate atoms, these are almost always wrong
        for n, nsym in zip(nbors, nbor_symbols):
            if isym == 'C' and sorted(nonmetal_nbor_symbols) == [
                    'C', 'O', 'O'
            ] and nsym in metals:
                G.remove_edge(i, n)

    ### intial bond typing, guessed from rounding pymatgen bond orders
    linkers = nx.connected_components(NMG)
    aromatic_atoms = []
    for linker in linkers:

        SG = NMG.subgraph(linker)

        for i, data in SG.nodes(data=True):

            isym = data['element_symbol']
            nbors = list(G.neighbors(i))
            nbor_symbols = [G.nodes[n]['element_symbol'] for n in nbors]
            nonmetal_nbor_symbols = [
                n for n in nbor_symbols if n not in metals
            ]
            CB = nx.cycle_basis(SG)

            check_cycles = True
            if len(CB) < 3:
                check_cycles = False

            cyloc = None
            if check_cycles:
                for cy in range(len(CB)):
                    if i in CB[cy]:
                        cyloc = cy

            bond_orders = []
            for n, nsym in zip(nbors, nbor_symbols):

                edge_data = G[n][i]
                bond_order = edge_data['bond_order']

                if bond_order < 1.0 and bond_order != 0.5:
                    bond_order = 1.0
                # shortest observed single bond had order 1.321
                elif 1.00 <= bond_order < 1.33:
                    bond_order = 1.0
                elif 1.33 <= bond_order < 1.75:
                    bond_order = 1.5
                # bond orders tend to be on the high end for aromatic compounds
                elif 1.75 <= bond_order < 2.00:
                    bond_order = 1.5
                elif 2.00 <= bond_order < 3.00:
                    bond_order = round(bond_order)

                # bonds between two disparate cycles or cycles and non-cycles should have order 1.0
                if check_cycles and cyloc != None:
                    if n not in CB[cyloc]:
                        bond_order = 1.0

                if any(i in metals
                       for i in nbor_symbols) and isym == 'O' and nsym == 'C':
                    bond_order = 1.5

                if nsym in metals:
                    bond_order = 0.5

                if isym == 'C' and len(nbor_symbols) == 4:
                    bond_order = 1.0

                if isym == 'C' and sorted(nbor_symbols) == ['C', 'O', 'O']:
                    if nsym == 'C':
                        bond_order = 1.0
                    elif nsym == 'O':
                        bond_order = 1.5
                    else:
                        pass

                edge_data['bond_order'] = bond_order
                bond_orders.append(bond_order)
                edge_data['bond_type'] = bond_types[bond_order]

        all_cycles = nx.simple_cycles(nx.to_directed(SG))
        all_cycles = set(
            [tuple(sorted(cy)) for cy in all_cycles if len(cy) > 4])

        ### assign aromatic bond orders as 1.5 (in most cases they will be already)
        for cycle in all_cycles:

            # rotate the ring normal vec onto the z-axis to determine coplanarity
            coords = np.array([G.nodes[c]['position'] for c in cycle])
            fcoords = np.dot(inv_uc, coords.T).T
            anchor = fcoords[0]
            fcoords = np.array(
                [vec - PBC3DF_sym(anchor, vec)[1] for vec in fcoords])
            coords = np.dot(unit_cell.T, fcoords.T).T

            coords -= np.average(coords, axis=0)

            vec0 = coords[0]
            vec1 = coords[1]

            normal = np.cross(vec0, vec1)
            RZ = M(normal, np.array([0.0, 0.0, 1.0]))
            coords = np.dot(RZ, coords.T).T
            maxZ = max([abs(z) for z in coords[:, -1]])

            # if coplanar make all bond orders 1.5
            if maxZ < coplanarity_tolerance:

                aromatic_atoms.extend(list(cycle))
                cycle_subgraph = SG.subgraph(cycle)

                for e0, e1 in cycle_subgraph.edges():
                    G[e0][e1]['bond_order'] = 1.5

    for i, data in G.nodes(data=True):

        isym = data['element_symbol']
        bond_orders = [G[i][n]['bond_order'] for n in G.neighbors(i)]
        total_bond_order = np.sum(bond_orders)
        bond_orders = [str(o) for o in bond_orders]
        nbor_symbols = ' '.join(
            [G.nodes[n]['element_symbol'] for n in G.neighbors(i)])

        if isym not in metals and total_bond_order != valencies[isym]:
            message = ' '.join([
                str(isym), 'has total bond order',
                str(total_bond_order), 'with neighbors', nbor_symbols,
                'and bond orders'
            ] + bond_orders)
            warnings.warn(message)

    elems = atoms.get_chemical_symbols()
    names = [a.symbol + str(a.index) for a in atoms]
    ccoords = atoms.get_positions()
    fcoords = atoms.get_scaled_positions()

    bond_list = []
    for e0, e1, data in G.edges(data=True):

        sym0 = G.nodes[e0]['element_symbol']
        sym1 = G.nodes[e1]['element_symbol']

        name0 = sym0 + str(e0)
        name1 = sym1 + str(e1)

        bond_list.append(
            [name0, name1, '.', data['bond_type'], data['bond_length']])

    A, B, C = unit_cell.lengths()
    alpha, beta, gamma = unit_cell.angles()

    return elems, names, ccoords, fcoords, charge_list, bond_list, (
        A, B, C, alpha, beta, gamma), np.asarray(unit_cell).T
Esempio n. 30
0
step = 0

file_2C = open(data_base + str("2C.dat"), "w")
file_3C = open(data_base + str("3C.dat"), "w")
file_4C = open(data_base + str("4C.dat"), "w")

file_2O = open(data_base + str("2O.dat"), "w")
file_3O = open(data_base + str("3O.dat"), "w")

# Build descriptors from positions (train set only)
sigma_ = 0.3  # 3*sigma ~ 2.7A relatively large spread
cutoff_ = 3.5  # cut_off SOAP,
nmax_ = 3
lmax_ = 3
distances = asegeom.get_distances(traj[step].positions,
                                  pbc=traj[step].pbc,
                                  cell=traj[step].cell)[1]
soaps = desc.createDescriptorsSingleSOAP(traj[step], species, sigma_, cutoff_,
                                         nmax_, lmax_, periodic)

cut_off = 1.75
cut_low = 1.6
cut_high = 1.9

to_ignore_mask = np.array([
    sum((distances[atom, :] > cut_low) & (distances[atom, :] < cut_high))
    for atom in range(n_atoms)
]) < 1
label_naive = np.array(
    [sum(distances[atom, :] < cut_off) - 1 for atom in range(n_atoms)])
max_neighbours = np.amax(