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
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
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
def extract_conformers_from_rdkit_mol_object(mol_obj, conf_ids): """ Generate xyz lists for all the conformers in conf_ids :param mol_obj: Molecule object :param conf_ids: (list) list of conformer ids to convert to xyz :return: (list(list(cgbind.atoms.Atom))) """ conformers = [] for i in range(len(conf_ids)): mol_block_lines = Chem.MolToMolBlock(mol_obj, confId=conf_ids[i]).split('\n') atoms = [] for line in mol_block_lines: split_line = line.split() if len(split_line) == 16: atom_label, x, y, z = split_line[3], split_line[0], split_line[ 1], split_line[2] atoms.append(Atom(atom_label, float(x), float(y), float(z))) conformer = BaseStruct() conformer.set_atoms(atoms) conformers.append(conformer) if len(conformers) == 0: raise CgbindCritical( 'Length of conformer xyz list was 0. RDKit failed') return conformers
def test_atoms(): assert atoms.get_atomic_number(atom=Atom('P', 0.0, 0.0, 0.0)) == 15 # For unknown atoms types the default atomic number is 6 assert atoms.get_atomic_number(atom=Atom('XX', 0.0, 0.0, 0.0)) == 6 assert 11.99 < atoms.get_atomic_mass(atom=Atom('C', 0.0, 0.0, 0.0)) < 12.02 # For unknown atoms types the default atomic mass is 10 assert 9 < atoms.get_atomic_mass(atom=Atom('XX', 0.0, 0.0, 0.0)) < 11 assert 1.1 < atoms.get_vdw_radii(atom=Atom('H', 0.0, 0.0, 0.0)) < 1.3 # For unknown atoms types the default van der Walls radii is 2.0 Å assert 1.9 < atoms.get_vdw_radii(atom=Atom('XX', 0.0, 0.0, 0.0)) < 2.1 assert atoms.get_max_valency(atom=Atom('H', 0.0, 0.0, 0.0)) == 1 assert atoms.get_max_valency(atom=Atom('C', 0.0, 0.0, 0.0)) == 4 assert atoms.get_max_valency(atom=Atom('XX', 0.0, 0.0, 0.0)) == 6
def test_reasonable_geom(): tmp = Molecule() tmp.set_atoms([Atom('H', 0.0, 0.0, 0.0), Atom('H', 0.1, 0.0, 0.0)]) assert geom.is_geom_reasonable(tmp) is False tmp.set_atoms([Atom('H', 0.0, 0.0, 0.0), Atom('H', 1.0, 0.0, 0.0)]) assert geom.is_geom_reasonable(tmp) is True tmp.set_atoms([Atom('H', 0.0, 0.0, 0.0), Atom('H', 1001, 0.0, 0.0)]) assert geom.is_geom_reasonable(tmp) is False
def molfile_to_atoms(filename): """ Convert a .mol file to a list of atoms :param filename: (str) :return: (list(Atom)) """ """ e.g. for methane: _____________________ OpenBabel03272015013D 5 4 0 0 0 0 0 0 0 0999 V2000 -0.2783 0.0756 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 0.7917 0.0756 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 -0.6349 -0.9294 -0.0876 H 0 0 0 0 0 0 0 0 0 0 0 0 -0.6349 0.6539 -0.8266 H 0 0 0 0 0 0 0 0 0 0 0 0 -0.6349 0.5022 0.9141 H 0 0 0 0 0 0 0 0 0 0 0 0 1 2 1 0 0 0 0 1 3 1 0 0 0 0 1 4 1 0 0 0 0 1 5 1 0 0 0 0 M END _____________________ """ atoms = [] if not filename.endswith('.mol'): logger.error('Could not read .mol file') raise FileMalformatted with open(filename, 'r') as mol_file: mol_lines = mol_file.readlines()[3:] try: n_atoms = int(mol_lines[0].split()[0]) except ValueError: raise FileMalformatted for line in mol_lines[1:n_atoms + 1]: x, y, z, atom_label = line.split()[:4] 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
def xyz_file_to_atoms(filename): """/ From an .xyz file get a list of atoms :param filename: (str) .xyz filename :return: (list(Atom)) """ logger.info(f'Getting atoms from {filename}') atoms = [] if not filename.endswith('.xyz'): raise FileMalformatted # Open the file that exists and should(!) be in the correct format with open(filename, 'r') as xyz_file: try: # First item in an xyz file is the number of atoms n_atoms = int(xyz_file.readline().split()[0]) except IndexError: raise FileMalformatted # XYZ lines should be the following 2 + n_atoms lines xyz_lines = xyz_file.readlines()[1:n_atoms + 1] for line in xyz_lines: try: atom_label, x, y, z = line.split()[:4] atoms.append(Atom(atomic_symbol=atom_label, x=x, y=y, z=z)) except (IndexError, TypeError, ValueError): raise FileMalformatted return atoms
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
def mol2file_to_atoms(filename): """ Convert a .mol file into a standard set of atoms :param filename: (str) :return: (lis(Atom)) """ logger.info('Converting .mol2 file to atoms') if not filename.endswith('.mol2'): logger.error('Could not read .mol2 file') raise FileMalformatted mol_file_lines = open(filename, 'r').readlines() # Get the unformatted atoms from the .mol2 file. The atom labels will not # be standard atoms, xyz_block = [], False for n_line, line in enumerate(mol_file_lines): if '@' in line and xyz_block: break if xyz_block: try: atom_label, x, y, z = line.split()[1:5] try: atoms.append(Atom(atom_label, float(x), float(y), float(z))) except TypeError: logger.error('There was a problem with the .mol2 file') raise FileMalformatted except IndexError: logger.error('There was a problem with the .mol2 file') raise FileMalformatted # e.g. @<TRIPOS>ATOM # 1 Pd1 -2.1334 12.0093 11.5778 Pd 1 RES1 2.0000 if '@' in line and 'ATOM' in line and len( mol_file_lines[n_line + 1].split()) == 9: xyz_block = True # Fix any atom labels for atom in atoms: if len(atom.label) == 1: continue # e.g. P1 or C58 elif atom.label[0].isalpha() and not atom.label[1].isalpha(): atom.label = atom.label[0] # e.g. Pd10 elif atom.label[0].isalpha() and atom.label[1].isalpha(): atom.label = atom.label[:2].title() else: logger.error('Unrecognised atom type') raise FileMalformatted return atoms
def test_atom_class(): x = Atom('X', 0.0, 0.0, 0.0) assert x.coord.shape == (3, ) assert all(x.coord == np.zeros(3)) assert len(str(x).split()) == 4 # Atom symbol, x, y, z
from cgbind import geom import numpy as np from cgbind.atoms import Atom from cgbind.molecule import Molecule h2_atoms = [Atom('H', 0.0, 0.0, 0.0), Atom('H', 1.0, 0.0, 0.0)] def test_com(): com = geom.calc_com(atoms=h2_atoms) ideal_com = np.array([0.5, 0.0, 0.0]) assert np.abs(np.average(com - ideal_com)) < 1E-5 def test_normed_vector(): coord1 = np.array([0.0, 0.0, 0.0]) coord2 = np.array([2.0, 0.0, 0.0]) ideal_normed_vector = np.array([1.0, 0.0, 0.0]) normed_vector = geom.calc_normalised_vector(coord1, coord2) assert np.abs(np.average(ideal_normed_vector - normed_vector)) < 1E-5 def test_rot_matix(): axis = np.array([0.0, 0.0, 1.0]) theta = np.pi # angle in radians rot_matix = geom.rotation_matrix(axis, theta)