コード例 #1
0
def build_heteroleptic_cage(cage, max_cost):
    logger.info('Building a heteroleptic cage')
    logger.warning('Due to the very large space that needs to be minimised '
                   'only the *best* linker conformer is used')

    added_linkers, atoms = [], []

    for i, linker in enumerate(cage.linkers):

        linker.set_ranked_linker_possibilities(metal=cage.metal)

        linker_atoms, cost = get_linker_atoms_and_cost(linker.possibilities[0],
                                                       cage.cage_template.linkers[i],
                                                       atoms)

        logger.info(f'L-L repulsion + fit to template in building cage is {cost:.2f}')
        atoms += linker_atoms
        linker.dr = linker.possibilities[0].dr

    logger.warning('Heteroleptic cages will have the average dr of all linkers '
                   '- using the average')
    cage.dr = np.average(np.array([linker.dr for linker in cage.linkers]))

    # Add the metals from the template shifted by dr
    for metal in cage.cage_template.metals:
        metal_coord = cage.dr * metal.shift_vec / np.linalg.norm(metal.shift_vec) + metal.coord
        atoms.append(Atom(cage.metal, x=metal_coord[0], y=metal_coord[1], z=metal_coord[2]))

    cage.set_atoms(atoms)
    return None
コード例 #2
0
ファイル: linker.py プロジェクト: duartegroup/cgbind
    def _find_possible_donor_atoms(self):
        """
        For the atoms in the linker find all those capable of donating a
        'lone pair' to a metal i.e. being a donor/
        'X-atom'

        :return: (list(int)) donor atoms
        """
        donor_atom_ids = []

        for atom_id, atom in enumerate(self.atoms):

            if atom.label in heteroatoms:
                max_valency = get_max_valency(atom=atom)
                n_bonds = 0

                for bond in self.bonds:
                    if atom_id in bond:
                        n_bonds += 1

                # If the number of bonds is lower than the max valancy for that
                # atom then there should be a lone pair and a donor atom has
                # been found
                if n_bonds < max_valency:
                    donor_atom_ids.append(atom_id)

        logger.info(f'Found {len(donor_atom_ids)} possible X atoms')
        return donor_atom_ids
コード例 #3
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
    def _find_metallocage_mol(self):
        """
        From a list of distinct molecules find the metallocage. This is
        assumed to be the molecule with the highest frequency of metal_label
        atoms

        :return: atoms, metal_label label
        """
        mol_metals_and_freqs, metal = [], None

        for atoms in self.mols_atoms:
            metals_and_freq = dict.fromkeys(metals, 0)

            for atom in atoms:
                if atom.label in metals:
                    metals_and_freq[atom.label] += 1

            # Add the maximum frequency that any metal_label arises in the
            # structure
            metal = max(metals_and_freq, key=metals_and_freq.get)
            freq = metals_and_freq[metal]
            mol_metals_and_freqs.append((metal, freq))

            logger.info(f'Max metal_label frequencies in molecules are '
                        f'{metal} with n = {freq}')

        mol_id_with_max_metals, max_freq, max_metal = 0, 0, None
        for i, (metal, freq) in enumerate(mol_metals_and_freqs):
            if freq > max_freq:
                max_freq = freq
                mol_id_with_max_metals = i
                max_metal = metal

        logger.info(f'Found metal_label {max_metal}')
        return self.mols_atoms[mol_id_with_max_metals], max_metal
コード例 #4
0
def get_linker_atoms_and_cost(linker, template_linker, current_atoms, x_coords=None):
    """
    Get the xyzs of a linker that is fitted to a template_linker object and
    the associated cost function – i.e. the repulsion to the current cage
    structure

    :param linker: (Linker)
    :param template_linker: (Template.Linker)
    :param curr_coords: (list(list))
    :return: list(list)), float
    """

    if x_coords is None:
        x_coords = linker.get_xmotif_coordinates()

    # Ensure the shift amount dr is set
    if linker.dr is None:
        logger.error('Cannot build a cage dr was None')
        return linker.atoms, 9999999.9

    # Expand the template by an about dr
    shifted_coords = get_shifted_template_x_motif_coords(linker_template=template_linker,
                                                         dr=linker.dr)

    linker_coords, cost = get_fitted_linker_coords_and_cost(linker=linker,
                                                            template_x_coords=shifted_coords,
                                                            coords_to_fit=x_coords,
                                                            curr_coords=[atom.coord for atom in current_atoms])
    logger.info(f'Repulsive + fitting cost for adding the linker is {cost:.5f}')

    atoms = [Atom(linker.atoms[i].label, coord=linker_coords[i])
             for i in range(linker.n_atoms)]

    return atoms, cost
コード例 #5
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
    def __init__(self, arch_name, mol2_filename):
        """
        Initialise a Template object

        :param arch_name: (str) Name of the architecture
        :param mol2_filename: (str) Name of the .mol2 file to read a structure
                            from e.g. downloaded from the CCDC
        """

        self.arch_name = arch_name

        all_atoms = mol2file_to_atoms(filename=mol2_filename)
        self.mols_atoms = find_mols_in_xyzs(atoms=all_atoms)
        self.atoms, self.metal_label = self._find_metallocage_mol()

        self.metals = self._find_metals()
        self.n_metals = len(self.metals)
        assert self.n_metals > 0
        logger.info(f'Found {self.n_metals} metals')

        self.bonds = get_bond_list_from_atoms(self.atoms)
        self.x_atoms = self._find_donor_atoms()  # donor atom ids

        self.coords = np.array([atom.coord for atom in self.atoms])

        # centroid of the cage ~ average metal_label coordinate
        self.centroid = self._find_centroid()

        self.linkers = self._find_linkers()
        self.n_linkers = len(self.linkers)
        logger.info(f'Found {self.n_linkers} linkers')

        self._set_shift_vectors()
コード例 #6
0
ファイル: input_output.py プロジェクト: duartegroup/cgbind
def xyzfile_to_atoms(filename):
    """
    Convert a standard xyz file into a list of atoms

    :param filename: (str)
    :return: (list(cgbind.atoms.Atom))
    """
    logger.info(f'Converting {filename} to list of atoms')

    if not filename.endswith('.xyz'):
        logger.error('Could not read .xyz file')
        raise FileMalformatted

    atoms = []

    with open(filename, 'r') as xyz_file:
        xyz_lines = xyz_file.readlines()[2:]
        for line in xyz_lines:
            atom_label, x, y, z = line.split()
            atoms.append(Atom(atom_label, float(x), float(y), float(z)))

    if len(atoms) == 0:
        logger.error(f'Could not read xyz lines in {filename}')
        raise FileMalformatted

    return atoms
コード例 #7
0
ファイル: x_motifs.py プロジェクト: duartegroup/cgbind
def get_maximally_connected_x_motifs(x_motifs, x_atoms):
    """
    Given a list of Xmotifs find those that are maximally connected, i.e.
    the ones that contain all the donor atoms but also are the largest in size

    :param x_motifs: (list(cgbind.x_motifs.Xmotif)
    :param x_atoms: (list(int))
    :return:
    """

    #                      X motif lengths sorted from high to low
    for x_motif_length in reversed(sorted(set([len(x) for x in x_motifs]))):

        new_x_motifs = [x for x in x_motifs if len(x) == x_motif_length]

        # Add all the atom ids of the xmotifs to a single list
        x_motifs_atoms = []
        for x_motif in new_x_motifs:
            x_motifs_atoms += x_motif.atom_ids

        # All the donor (X) atoms need to be in the full list
        if all(x_atom in x_motifs_atoms for x_atom in x_atoms):
            logger.info(f'Returning {len(new_x_motifs)} Xmotifs each with '
                        f'{len(new_x_motifs[0])} atoms')
            return new_x_motifs

    logger.critical('Could not find a set of x motifs of the same length with'
                    ' all the donor atoms')
    raise CgbindCritical
コード例 #8
0
def get_bond_list_from_atoms(atoms, relative_tolerance=0.2):
    """Determine the 'bonds' between atoms defined in a xyzs list.

    :param atoms: (list(cgbind.atoms.Atom))
    :param relative_tolerance: (float)
    :return: (list(tuple(int)))
    """
    logger.info(f'Getting bond list from xyzs. Maximum bond is '
                f'{1 + relative_tolerance}x average')

    bond_list = []

    for i in range(len(atoms)):
        for j in range(len(atoms)):
            if i > j:
                # Calculate the distance between the two points in Å
                dist = np.linalg.norm(atoms[j].coord - atoms[i].coord)

                i_j_bond_length = get_avg_bond_length(atoms[i].label, atoms[j].label)

                if dist < i_j_bond_length * (1.0 + relative_tolerance):
                    bond_list.append((i, j))

    if len(bond_list) == 0:
        logger.warning('Bond list is empty')

    return bond_list
コード例 #9
0
ファイル: cage_subt.py プロジェクト: duartegroup/cgbind
    def _add_substrate(self):
        """
        Add a substrate to a cage by minimising the energy from
        self.energy_func

        :return: None
        """
        logger.info('Adding the substrate to the center of the cage defined by'
                    ' the COM')
        logger.info(f'Using {self.energy_func.__name__}')

        # For electrostatic addition need partial atomic charges
        if self.energy_func.__name__ in ['electrostatic', 'electrostatic_fast']:

            estimate = True if self.energy_func.__name__ == 'electrostatic_fast' else False
            self.cage.charges = self.cage.get_charges(estimate=estimate)
            self.substrate.charges = self.substrate.get_charges(estimate=estimate)

            if self.cage.charges is None or self.substrate.charges is None:
                logger.error('Could not get partial atomic charges')
                return None

        xyzs = add_substrate.add_substrate_com(self)
        self.set_atoms(xyzs)

        return None
コード例 #10
0
def is_geom_reasonable(molecule):
    """
    For an xyz list check to ensure the geometry is sensible, before an
    optimisation is carried out. There should be no distances smaller than
     0.7 Å

    :param molecule: (cgbind.molecule.BaseStruct)
    :return: (bool)
    """
    logger.info('Checking to see whether the geometry is reasonable')

    coords = molecule.get_coords()

    # Compute the distance matrix with all i,j pairs, thus add 1 to the
    # diagonals to remove the d(ii) = 0 components that would otherwise result
    #  in an unreasonable geometry

    dist_mat = distance_matrix(coords, coords) + np.identity(len(coords))

    if np.min(dist_mat) < 0.8:
        logger.warning('There is a distance < 0.8 Å. There is likely a problem'
                       ' with the geometry')
        return False
    if np.max(dist_mat) > 1000:
        logger.warning('There is a distance > 1000 Å. There is likely a '
                       'problem with the geometry')
        return False

    return True
コード例 #11
0
ファイル: x_motifs.py プロジェクト: duartegroup/cgbind
def find_x_motifs(linker):
    """
    Find the X motifs in a structure which correspond to the X atoms and their
    nearest neighbours. These may be joined if they are bonded

    :return: (list(list(int)))
    """
    def centroid_atom_distance(atom_i):
        return np.linalg.norm(linker.atoms[atom_i].coord - linker.com)

    x_motifs = []

    for donor_atom in linker.x_atoms:
        x_motif = []

        # Add all the atoms that are connected to the donor atom
        for (i, j) in linker.bonds:
            if donor_atom == i and j not in x_motif:
                x_motif.append(j)
            if donor_atom == j not in x_motif:
                x_motif.append(i)

        logger.info(f'X atom {donor_atom} had {len(x_motif)} '
                    f'nearest neighbours')
        x_motif.append(donor_atom)
        x_motifs.append(x_motif)

    # Get all the combinations of x motifs with length > 2 up to the total
    # number of x_motifs
    x_motif_combinations = powerset(s=deepcopy(x_motifs))

    logger.info(f'Have {len(list(powerset(s=deepcopy(x_motifs))))} groups of X'
                f' motifs to determine if they are bonded')
    for i, x_motif_group in enumerate(x_motif_combinations):
        logger.info(f'Determining if all {len(x_motif_group)} x motifs in this'
                    f' group are bonded')

        x_motif_group_atom_indexes = []
        for x_motif in x_motif_group:
            x_motif_group_atom_indexes += list(x_motif)

        if is_fully_connected(x_motif_group_atom_indexes, bonds=linker.bonds):
            logger.info(f'X-motifs are bonded')
            x_motifs.append(list(set(x_motif_group_atom_indexes)))

    logger.info(f'Found {len(x_motifs)} X motifs in the linker, '
                f'with {set([len(x) for x in x_motifs])} atoms')

    # Order the x_motifs according to the centroid – coord
    # distance: smallest -> largest
    sorted_x_motifs_ids = [
        sorted(list(x_motif), key=centroid_atom_distance)
        for x_motif in x_motifs
    ]

    return [
        Xmotif(atom_ids=motif, coords=[linker.atoms[i].coord for i in motif])
        for motif in sorted_x_motifs_ids
    ]
コード例 #12
0
ファイル: calculations.py プロジェクト: duartegroup/cgbind
def singlepoint(molecule, method, keywords, n_cores=None):
    """
    Run a single point energy evaluation on a molecule

    :param molecule: (object)
    :param method: (autode.ElectronicStructureMethod)
    :param keywords: (list(str)) Keywords to use for the electronic structure
    calculation e.g. ['Opt', 'PBE', 'def2-SVP']
    :param n_cores: (int) Number of cores to use
    :return:
    """
    logger.info('Running single point calculation')

    n_cores = Config.n_cores if n_cores is None else int(n_cores)

    try:
        from autode.calculation import Calculation
        from autode.wrappers.XTB import xtb
        from autode.wrappers.ORCA import orca
        from autode.wrappers.keywords import SinglePointKeywords

    except ModuleNotFoundError:
        logger.error('autode not found. Calculations not available')
        raise RequiresAutodE

    if keywords is None:
        if method == orca:
            keywords = SinglePointKeywords(
                ['SP', 'M062X', 'def2-TZVP', 'RIJCOSX', 'def2/J', 'SlowConv'])

            logger.warning('No keywords were set for the single point but an '
                           'ORCA calculation was requested. '
                           f'Using {str(keywords)}')

        elif method == xtb:
            keywords = xtb.keywords.sp

        else:
            logger.critical('No keywords were set for the single-point '
                            'calculation')
            raise Exception

    else:
        # If the keywords are specified as a list convert them to a set of
        # OptKeywords, required for autodE
        if type(keywords) is list:
            keywords = SinglePointKeywords(keywords)

    sp = Calculation(name=molecule.name + '_sp',
                     molecule=molecule,
                     method=method,
                     keywords=keywords,
                     n_cores=n_cores)
    sp.run()
    molecule.energy = sp.get_energy()

    return None
コード例 #13
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
    def _find_linkers(self):
        logger.info('Stripping the metals from the structure')
        atoms_no_metals = [
            atom for atom in self.atoms if self.metal_label != atom.label
        ]

        logger.info('Finding the distinct linker molecules ')
        linkers_atoms = find_mols_in_xyzs(atoms=atoms_no_metals,
                                          allow_same=True)

        linkers = []
        # Add the x_atoms which are contained within each linker, that were
        # found bonded to each metal_label
        for atoms in linkers_atoms:
            coords = np.array([atom.coord for atom in atoms])
            linker_x_atoms = []

            # Iterate through the coordinates until one matches that of the
            #  full template
            for i, coord in enumerate(coords):
                for donor_atom_id in self.x_atoms:
                    if list(coord) == list(self.coords[donor_atom_id]):
                        linker_x_atoms.append(i)
                        break

            linkers.append(Linker(atoms=atoms, x_atoms=linker_x_atoms))
            logger.info(f'Linker has {len(linker_x_atoms)} donor atoms')

        logger.info(f'Found {len(linkers_atoms)} linkers each with '
                    f'{len(linker_x_atoms)} donor atoms')
        return linkers
コード例 #14
0
ファイル: linker.py プロジェクト: duartegroup/cgbind
    def __init__(self,
                 arch_name,
                 smiles=None,
                 name='linker',
                 charge=0,
                 n_confs=300,
                 filename=None,
                 use_etdg_confs=False):
        """
        Metallocage Linker. Inherits from cgbind.molecule.Molecule

        :param arch_name: (str) Name of the architecture
        :param smiles: (str) SMILES string
        :param name: (str) Linker name
        :param charge: (int)
        :param n_confs: (int) Number of initial conformers to search through
        :param filename: (str)
        :param use_etdg_confs: (bool) Use a different, sometimes better,
                               conformer generation algorithm
        """
        logger.info(f'Initialising a Linker object for {name} with {n_confs} '
                    f'conformers')

        self.arch = None  #: (Arch object) Metallocage architecture
        self._set_arch(arch_name)

        # May exit here if the specified architecture is not found
        super(Linker, self).__init__(smiles=smiles,
                                     name=name,
                                     charge=charge,
                                     n_confs=n_confs,
                                     filename=filename,
                                     use_etdg_confs=use_etdg_confs)

        # Allow linker construction with no atoms
        if self.n_atoms == 0:
            return

        self._check_structure()
        self.cage_template = get_template(
            arch_name=arch_name)  #: (Template object) Metallocage template

        self.x_atoms = self._find_possible_donor_atoms(
        )  #: (list(int)) List of donor atom ids
        self.x_motifs = find_x_motifs(self)  #: (list(Xmotif object))
        check_x_motifs(self, linker_template=self.cage_template.linkers[0])
        self._strip_possible_x_motifs_on_connectivity()
        self.dr = None  #: (float) Template shift distance

        self.possibilities = []
コード例 #15
0
ファイル: substrate.py プロジェクト: duartegroup/cgbind
    def __init__(self, smiles=None, name='substrate', n_confs=1, charge=0,
                 mult=1, filename=None, solvent=None):
        """
        Substrate. Inherits from cgbind.molecule.Molecule

        :param smiles: (str) SMILES string
        :param name: (str) Molecule name
        :param n_confs: (int) Number of conformers to initialise with
        :param charge: (int) Charge on the molecule
        :param mult: (int) Spin multiplicity on the molecule
        :param filename: (str)
        """

        logger.info('Initialising a Substrate object for {}'.format(name))
        super().__init__(smiles=smiles, name=name, charge=charge, n_confs=n_confs,
                         mult=mult, filename=filename, solvent=solvent)
コード例 #16
0
    def __init__(self,
                 smiles=None,
                 name='molecule',
                 charge=0,
                 mult=1,
                 n_confs=1,
                 filename=None,
                 solvent=None,
                 use_etdg_confs=False):
        """
        Molecule. Inherits from cgbind.molecule.BaseStruct

        :param smiles: (str) SMILES string
        :param name: (str) Molecule name
        :param n_confs: (int) Number of conformers to initialise with
        :param charge: (int) Charge on the molecule
        :param mult: (int) Spin multiplicity on the molecule
        :param filename: (str)
        :param use_etdg_confs: (bool) Use an alternate conformer generation algorithm
        """
        logger.info('Initialising a Molecule object for {}'.format(name))

        super(Molecule, self).__init__(name=name,
                                       charge=charge,
                                       mult=mult,
                                       filename=filename,
                                       solvent=solvent)

        self.smiles = smiles  #: (str) SMILES string
        self.n_confs = n_confs  #: (int) Number of conformers initialised with

        self.mol_obj = None  #: (RDKit.mol object)

        self.n_rot_bonds = None  #: (int) Number of rotatable bonds
        self.n_h_donors = None  #: (int) Number of H-bond donors
        self.n_h_acceptors = None  #: (int) Number of H-bond acceptors
        self.volume = None  #: (float) Molecular volume in Å^3
        self.bonds = None  #: (list(tuple)) List of bonds defined by atom ids

        self.conformers = None  #: (list(BaseStruct)) List of conformers

        if smiles:
            self._init_smiles(smiles, use_etdg_confs=use_etdg_confs)

        if filename is not None:
            self.bonds = get_bond_list_from_atoms(self.atoms)
            self.conformers = [deepcopy(self)]
コード例 #17
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
    def _find_metals(self):
        logger.info(f'Getting metals with label {self.metal_label}')
        metals = []
        try:
            for i in range(len(self.atoms)):
                if self.atoms[i].label == self.metal_label:
                    metals.append(
                        Metal(label=self.metal_label,
                              atom_id=i,
                              coord=self.atoms[i].coord))

            return metals

        except (TypeError, IndexError, AttributeError):
            logger.error('Could not get metal_label atom ids. Returning None')

        return None
コード例 #18
0
ファイル: cage.py プロジェクト: duartegroup/cgbind
    def _init_homoleptic_cage(self, linker):
        logger.info(f'Initialising a homoleptic cage')
        self.homoleptic = True

        if not self._is_linker_reasonable(linker):
            logger.error('Linker was not reasonable')
            return

        if self.name == 'cage':
            # Only override the default name
            self.name = 'cage_' + linker.name

        self.arch = linker.arch
        self.linkers = [linker for _ in range(linker.arch.n_linkers)]
        self.cage_template = linker.cage_template

        return None
コード例 #19
0
ファイル: cage.py プロジェクト: duartegroup/cgbind
    def _build(self, max_cost):
        logger.info('Building a cage geometry')
        assert self.homoleptic or self.heteroleptic

        if self.homoleptic:
            build_homoleptic_cage(self, max_cost)

        if self.heteroleptic:
            build_heteroleptic_cage(self, max_cost)

        if self.reasonable_geometry:
            if self.n_atoms != self.arch.n_metals + sum(
                [linker.n_atoms for linker in self.linkers]):
                logger.error('Failed to build a cage')
                self.reasonable_geometry = False
                return None

        return None
コード例 #20
0
def calc_com(atoms):
    """
    Calculate the centre of mass for a list of xyzs

    :param atoms: (list(cgbind.atoms.Atom))
    :return: (np.ndarray) shape: 3
    """
    logger.info('Calculating centre of mass ')

    com = np.zeros(3)
    total_mass = 0.0

    for atom in atoms:
        atom_mass = get_atomic_mass(atom)
        total_mass += atom_mass

        com += atom_mass * atom.coord

    return com / total_mass
コード例 #21
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
    def save_template(self):
        """
        Save the template to ./lib/self.arch_name.obj

        :return: None
        """
        logger.info('Saving metallocage template')
        assert self.n_metals > 0
        assert all(metal.shift_vec is not None for metal in self.metals)

        # Templates will be saved to here/lib/
        folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                   'lib')

        with open(os.path.join(folder_path, self.arch_name + '.obj'),
                  'wb') as pickled_file:
            pickle.dump(self, file=pickled_file)

        return None
コード例 #22
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
    def _find_donor_atoms(self):
        """
        Find the donor atoms or 'x_atoms' in a metallocage. Will be bonded to
        the metal_label with a bond distance up to
        1.1 x the value defined in cgbind.bonds.

        :return: (list(int))
        """
        logger.info('Getting the donor (x) atoms in a structure')

        donor_atoms = []
        for (i, j) in self.bonds:
            if i in [metal.atom_id for metal in self.metals]:
                donor_atoms.append(j)

            if j in [metal.atom_id for metal in self.metals]:
                donor_atoms.append(i)

        logger.info(f'Found {len(donor_atoms)} donor atoms in the structure')
        return donor_atoms
コード例 #23
0
ファイル: cage.py プロジェクト: duartegroup/cgbind
    def get_metal_atom_ids(self):
        """
        Get the atom ids of the self.metal atoms in the xyzs

        :return: (list(int))
        """

        logger.info(f'Getting metal_label atom ids with label {self.metal}')
        if self.n_atoms == 0:
            logger.error('Could not get metal atom ids. xyzs were None')
            return None

        try:
            return [
                i for i in range(self.n_atoms)
                if self.atoms[i].label == self.metal
            ]
        except TypeError or IndexError or AttributeError:
            logger.error('Could not get metal label atom ids. Returning None')
            return None
コード例 #24
0
def get_fitted_linker_coords_and_cost(linker, template_x_coords, coords_to_fit, curr_coords):
    """
    For a linker get the best mapping onto a list of template X coords
    (e.g. NCN motifs in a pyridyl donor) these will this can be achieved in
    normal or reverse order of the coordinates as to maximise the distance to
    the rest of the metallocage structure. Also returns a measure of the
    repulsion to the rest of the cage structure

    :param linker: (Linker object)
    :param template_x_coords: (list(np.ndarray))
    :param coords_to_fit: (list(np.ndarray))
    :param curr_coords: (list(list))
    :return: (list(np.ndarray)), (float)
    """
    min_cost, best_linker_coords = 99999999999.9, None

    coord_set = [coords_to_fit]

    if Config.allow_permutations:
        logger.info('Allowing permutations of coords - only reverse')
        coord_set.append(list(reversed(coords_to_fit)))

    for coords in coord_set:
        new_linker_coords, cost = get_kfitted_coords_and_cost(linker=linker,
                                                              template_x_coords=template_x_coords,
                                                              coords_to_fit=coords)
        if len(curr_coords) == 0:
            return new_linker_coords, 0.0

        repulsion = np.sum(np.power(distance_matrix(new_linker_coords, curr_coords), -12))

        # Add the linker with the least repulsion to the rest of the structure
        if repulsion + cost < min_cost:
            best_linker_coords = new_linker_coords
            min_cost = repulsion + cost

    if best_linker_coords is None:
        logger.warning('Fitted linker coords could not be found')
        best_linker_coords = linker.get_coords()

    return best_linker_coords, min_cost
コード例 #25
0
ファイル: calculations.py プロジェクト: duartegroup/cgbind
def get_charges(molecule):
    """
    Get the partial atomic charges with XTB (tested with v. 6.2) will generate
    then trash a temporary directory

    :return:
    """
    logger.info('Getting charges')

    try:
        from autode.calculation import Calculation
        from autode.wrappers.XTB import xtb
        from autode.exceptions import MethodUnavailable
        from autode.wrappers.keywords import SinglePointKeywords

    except ModuleNotFoundError:
        logger.error('autode not found. Calculations not available')
        raise RequiresAutodE

    # Run the calculation
    try:
        xtb_sp = Calculation(name=molecule.name + '_xtb_sp',
                             molecule=molecule,
                             method=xtb,
                             n_cores=1,
                             keywords=xtb.keywords.sp)
        xtb_sp.run()

        charges = xtb_sp.get_atomic_charges()

    except MethodUnavailable:
        logger.error('Could not calculate without an XTB install')
        return None

    if len(charges) == molecule.n_atoms:
        return charges

    else:
        logger.error('XTB failed to generate charges')
        return None
コード例 #26
0
ファイル: x_motifs.py プロジェクト: duartegroup/cgbind
def check_x_motifs(linker=None, linker_template=None):
    if linker is None and linker_template is not None:
        if not all([
                motif.n_atoms == linker_template.x_motifs[0].n_atoms
                for motif in linker_template.x_motifs
        ]):
            logger.critical('Found x motifs in the structure that have '
                            'different number of atoms')
            raise CgbindCritical
        else:
            return None

    if not all([
            motif.n_atoms == linker_template.x_motifs[0].n_atoms
            for motif in linker.x_motifs
    ]):
        logger.warning('Found x motifs in the structure that have different '
                       'number of atoms')
        logger.info('Stripping the motifs with the wrong number of atoms')

        linker.x_motifs = [
            motif for motif in linker.x_motifs
            if motif.n_atoms == linker_template.x_motifs[0].n_atoms
        ]
        logger.info(f'Now have {len(linker.x_motifs)} motifs in the linker')

    if len(linker.x_motifs) == 0:
        raise CgbindCritical('Have 0 Xmotifs – cannot build a cage. '
                             'Is the template correct?')

    if len(linker.x_motifs) > 0:
        logger.info(f'Number of atoms in the x motifs is '
                    f'{linker.x_motifs[0].n_atoms}')
    return None
コード例 #27
0
ファイル: cage.py プロジェクト: duartegroup/cgbind
    def get_cavity_vol(self):
        """
        For a cage extract the cavity volume defined as the volume of the
        largest sphere, centered on the cage centroid that may be constructed
        while r < r(midpoint--closest atom)

        :return: (float) Cavity volume in Å^3
        """
        logger.info('Calculating maximum enclosed sphere')

        min_centriod_atom_dist = 999.9
        centroid, min_atom_dist_id = None, None

        try:
            centroid = self.get_centroid()
            if centroid is None:
                logger.error('Could not find the cage centroid. Returning 0.0')
                return 0.0

            # Compute the smallest distance to the centroid
            for i in range(self.n_atoms):
                dist = np.linalg.norm(self.atoms[i].coord - centroid)
                if dist < min_centriod_atom_dist:
                    min_centriod_atom_dist = dist
                    min_atom_dist_id = i

        except TypeError or ValueError or AttributeError:
            pass

        if min_atom_dist_id is not None:
            vdv_radii = get_vdw_radii(atom=self.atoms[min_atom_dist_id])
            # V = 4/3 π r^3, where r is the centroid -> closest atom distance,
            # minus it's VdW volume
            return (4.0 / 3.0) * np.pi * (min_centriod_atom_dist -
                                          vdv_radii)**3

        else:
            logger.error(
                'Could not calculate the cavity volume. Returning 0.0')
            return 0.0
コード例 #28
0
    def set_atoms(self, atoms=None, coords=None):
        """
        Set the xyzs of a molecular structure

        :param atoms: (list(cgbind.atoms.Atom))
        :param coords: (np.ndarray) n_atoms x 3 positions of the atoms
        :return: None
        """
        # Reset the atoms in this species using an array of coordinates
        if coords is not None:
            assert type(coords) == np.ndarray
            assert coords.shape == (self.n_atoms, 3)

            # Set the coordinates on a copy of the atoms
            atoms = deepcopy(self.atoms)
            for i, coord in enumerate(coords):
                atoms[i].coord = coord

        # Reset the atoms, number of atoms and the centre of mass
        if atoms is not None:
            assert type(atoms) == list
            assert len(atoms) > 0
            assert hasattr(atoms[0], 'label')
            assert hasattr(atoms[0], 'coord')
            assert len(atoms[0].coord) == 3

            self.atoms = atoms
            self.n_atoms = len(atoms)
            self.com = calc_com(atoms=self.atoms)

            self.reasonable_geometry = is_geom_reasonable(self)
            logger.info(f'Geometry is reasonable: {self.reasonable_geometry}')

        else:
            self.reasonable_geometry = False
            logger.warning(
                'xyzs were None -> n_atoms also None & geometry is *not* reasonable'
            )

        return None
コード例 #29
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
def find_mols_in_xyzs(atoms, allow_same=False):
    """
    From a list of xyzs determine the bonds in the system, thus the distinct
    molecules in the system

    :param atoms: (list(cgbind.atoms.Atom))
    :param allow_same: (bool) add only the unique molecules (False) or add
                       every molecule (True)
    :return: (list(xyzs))
    """

    logger.info('Finding the distinct molecules in the system')
    # Get the 'molecules' for which each atom is contained in
    full_graph = nx.Graph()
    [
        full_graph.add_node(n, atom_label=atoms[n].label)
        for n in range(len(atoms))
    ]
    bond_list = get_bond_list_from_atoms(atoms)

    for (u, v) in bond_list:
        full_graph.add_edge(u, v)

    unique_mols, unique_mol_ids = [], []
    connected_molecules = [
        list(mol) for mol in nx.connected_components(full_graph)
    ]

    for molecule in connected_molecules:
        mol_atom_labels = sorted([atoms[n].label for n in molecule])
        if mol_atom_labels not in unique_mols or allow_same:
            unique_mols.append(mol_atom_labels)
            unique_mol_ids.append(molecule)

    unique_mol_atoms = [[atoms[n] for n in mol_ids]
                        for mol_ids in unique_mol_ids]
    logger.info(f'Found {len(unique_mol_atoms)} molecule(s)')

    return unique_mol_atoms
コード例 #30
0
ファイル: templates.py プロジェクト: duartegroup/cgbind
    def __init__(self, atoms, x_atoms):
        """
        Make a template linker object from the corresponding xyzs and the donor
        atoms which were bonded to the metals which form the basis of the cage

        :param atoms: (list(list))
        :param x_atoms: (list(int)) Donor atom ids in the xyzs
        """
        logger.info('Generating a template linker...')
        self.atoms = atoms  #: (list(list))
        self.x_atoms = x_atoms  #: (list(int)) List of donor atoms in the linker
        self.coords = np.array([atom.coord for atom in atoms
                                ])  #: (list(np.ndarray)) Linker coordinates
        self.bonds = get_bond_list_from_atoms(self.atoms)  #: (list(Atom))
        self.com = calc_com(atoms)  #: (np.ndarray)

        self.x_motifs = find_x_motifs(self)  #: (list(Xmotif objects)
        self.x_motifs = get_maximally_connected_x_motifs(self.x_motifs,
                                                         x_atoms=x_atoms)

        check_x_motifs(linker_template=self
                       )  # check that the x_motifs are the same length