示例#1
0
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
示例#2
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
示例#3
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
示例#4
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
示例#5
0
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
示例#6
0
    def _set_shift_vectors(self):
        """
        For the linkers set the shift vectors for the x motifs. These are the
        centroid -> closest metal_label atom
        vector

        :return: None
        """
        metal_atom_ids = [metal.atom_id for metal in self.metals]

        for linker in self.linkers:
            for x_motif in linker.x_motifs:
                motif_atom_id = x_motif.atom_ids[0]

                closest_metal_id = None
                closest_dist = 99999.9
                for metal_id in metal_atom_ids:
                    dist = np.linalg.norm(linker.coords[motif_atom_id] -
                                          self.coords[metal_id])
                    if dist < closest_dist:
                        closest_dist = dist
                        closest_metal_id = metal_id

                shift_vec = self.coords[closest_metal_id] - self.centroid

                # Set the shift vector for the metal closest to this x_motif
                for metal in self.metals:
                    if metal.atom_id == closest_metal_id:
                        metal.shift_vec = shift_vec

                # Set the shift vector for the x motif
                x_motif.shift_vec = shift_vec
                x_motif.r = np.linalg.norm(x_motif.shift_vec)
                x_motif.norm_shift_vec = x_motif.shift_vec / x_motif.r

        metal_coords = np.array([metal.coord for metal in self.metals])
        metals_dists = distance_matrix(metal_coords, metal_coords)

        # Ensure that all the metals have shift vectors
        for i, metal in enumerate(self.metals):
            if metal.shift_vec is not None:
                continue

            logger.warning('Unassigned shift vector – setting the same'
                           'as the closest metal to this one')
            closest_metals = np.argsort(np.min(metals_dists, axis=0))

            # Set the shift vector of this metal as the one closest
            # that is not itself
            for metal_idx in closest_metals[1:]:
                if self.metals[metal_idx].shift_vec is not None:
                    metal.shift_vec = self.metals[metal_idx].shift_vec
                    break

        return None
示例#7
0
def get_avg_bond_length(atom_i_label, atom_j_label):
    """Get the average bond length from either a molecule and a bond or two
    atom labels (e.g. atom_i_label = 'C' atom_j_label = 'H')"""
    key1, key2 = atom_i_label + atom_j_label, atom_j_label + atom_i_label

    if key1 in avg_bond_lengths.keys():
        return avg_bond_lengths[key1]
    elif key2 in avg_bond_lengths.keys():
        return avg_bond_lengths[key2]
    else:
        logger.warning(f'Couldn\'t find a default bond length for '
                       f'({atom_i_label},{atom_j_label}) using 1.5 Å')
        return 1.5
示例#8
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
示例#9
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
示例#10
0
def get_cost_metal_x_atom_interaction(x_motifs, linker, metal):
    """
    Calculate a cost based on the favourability of the M--X interaction

    :param x_motifs: ((list(Xmotif))
    :param linker: (Linker)
    :param metal: (str)
    :return:
    """

    if metal is None:
        logger.warning('Could not sort x motifs list. Metal was not specified')
        return 0

    fav_x_atoms = get_metal_favoured_heteroatoms(metal=metal)
    cost = 0

    for x_motif in x_motifs:
        for atom_id in x_motif.atom_ids:
            atom_label = linker.atoms[atom_id].label
            if atom_label in fav_x_atoms:
                cost += 10 * fav_x_atoms.index(atom_label)

    return cost
示例#11
0
def optimise(molecule,
             method,
             keywords,
             n_cores=None,
             cartesian_constraints=None):
    """
    Optimise a molecule

    :param molecule: (object)
    :param method: (autode.ElectronicStructureMethod)
    :param keywords: (list(str)) Keywords to use for the electronic structure c
                                 alculation e.g. ['Opt', 'PBE', 'def2-SVP']
    :param n_cores: (int) Number of cores to use
    :param cartesian_constraints: (list(int)) List of atom ids to constrain
    :return:
    """
    logger.info('Running an optimisation 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 OptKeywords

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

    if keywords is None:
        if method == orca:

            keywords = OptKeywords(['LooseOpt', 'PBE', 'D3BJ', 'def2-SVP'])
            logger.warning(f'No keywords were set for the optimisation but an'
                           f' ORCA calculation was requested. '
                           f'Using {str(keywords)}')

        elif method == xtb:
            keywords = xtb.keywords.opt

        else:
            logger.critical('No keywords were set for the optimisation '
                            '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 = OptKeywords(keywords)

    opt = Calculation(name=molecule.name + '_opt',
                      molecule=molecule,
                      method=method,
                      keywords=keywords,
                      n_cores=n_cores,
                      cartesian_constraints=cartesian_constraints)
    opt.run()
    molecule.energy = opt.get_energy()
    molecule.set_atoms(atoms=opt.get_final_atoms())

    return None
示例#12
0
def build_homoleptic_cage(cage, max_cost):
    """
    Construct the geometry (atoms) of a homoleptic cage

    :param cage: (Cage)
    :param max_cost: (float) Maximum cost to break out of the loop over
    :return:
    """

    # Get the list of Linkers ordered by the best fit to the template
    cage.linkers[0].set_ranked_linker_possibilities(metal=cage.metal)
    logger.info(f'Have {len(cage.linkers[0].possibilities)} linkers to fit')

    min_cost, best_linker = 99999999.9, None
    atoms, cage_cost = [], 99999999.9

    # For all the possible linker conformer / Xmotif set possibilities
    for linker in cage.linkers[0].possibilities:

        # Atoms for and cost in building this cage
        atoms, cage_cost = [], 0.0

        # Coordinates of the X motif atoms in this linker - used to rotate
        x_coords = linker.get_xmotif_coordinates()

        for i, template_linker in enumerate(cage.cage_template.linkers):
            linker_atoms, cost = get_linker_atoms_and_cost(linker,
                                                           template_linker,
                                                           atoms,
                                                           x_coords)
            cage_cost += cost
            atoms += linker_atoms

        if cage_cost < min_cost:
            min_cost = cage_cost
            best_linker = deepcopy(linker)

        if cage_cost < max_cost:
            logger.info(f'Total L-L repulsion + fit to template in building '
                        f'cage is {cage_cost:.2f}')
            break

    # If there is no break due to a small repulsion then build the best
    # possible cage
    if cage_cost > max_cost:
        if best_linker is None:
            logger.error('Could not achieve the required cost threshold for '
                         'building the cage')
            return None
        else:
            logger.warning('Failed to reach the threshold. Returning the cage '
                           'that minimises the L-L repulsion')
            atoms = []
            for i, template_linker in enumerate(cage.cage_template.linkers):
                linker_atoms, _ = get_linker_atoms_and_cost(best_linker,
                                                            template_linker,
                                                            atoms)
                atoms += linker_atoms

    # Set the delta r for the whole cage
    cage.dr = best_linker.dr

    # Add the metals from the template shifted by dr
    for metal in cage.cage_template.metals:

        if cage.dr is None:
            raise CannotBuildCage('Cage had no shift distance (∆r)')

        if metal.shift_vec is None:
            raise CannotBuildCage('Template shift vector not defined')

        metal_coord = cage.dr * metal.shift_vec / np.linalg.norm(metal.shift_vec) + metal.coord
        atoms.append(Atom(cage.metal, coord=metal_coord))

    cage.set_atoms(atoms)
    return None