def test_bond_assignment(): # Check that if the pair isn't found then a length is still returned assert 1.4 < bonds.get_avg_bond_length(atom_i_label='X', atom_j_label='X') < 1.6 # Check that if the pair isn't found but the VdW radii are defined then # return something sensible assert 2.0 < bonds.get_avg_bond_length(atom_i_label='Ir', atom_j_label='As') < 3.0 # CH bond should be ~1.1 Å assert 0.8 < bonds.get_avg_bond_length(atom_i_label='C', atom_j_label='H') < 1.2
def set_attack_r0(self, species, shift_factor): """Set the ideal distance between a and c atoms in a substitution centre""" r0 = get_avg_bond_length(atom_i_label=species.atoms[self.a_atom].label, atom_j_label=species.atoms[self.c_atom].label) self.r0_ac = shift_factor * r0 return None
def add_capping_atom(atom_index, n_atom_index, graph, s_molecule): """ Add a capping atom. Example:: H / C_a---C_b - H -> C_a--H where C_a is numbered atom_index, \ C_b is numbered n_atom_index \ H Arguments: atom_index (int): n_atom_index (int): graph (nx.Graph): Current molecular graph of the stripped/truncated molecule s_molecule (autode.species.Species): Stripped molecule """ logger.info(f'Swapping saturated carbon {n_atom_index} next to atom ' f'{atom_index} for hydrogen') graph.add_node(n_atom_index, atom_label='H', stereo=False) # Relabel the atom in the stripped molecule s_molecule.atoms[n_atom_index].label = 'H' # Shift the added capping hydrogen to the 'ideal' E-H bond length curr_dist = s_molecule.distance(atom_index, n_atom_index) ideal_dist = get_avg_bond_length(s_molecule.atoms[atom_index].label, 'H') shift_vec = (s_molecule.atoms[n_atom_index].coord - s_molecule.atoms[atom_index].coord) shift_vec *= (ideal_dist - curr_dist) / curr_dist s_molecule.atoms[n_atom_index].translate(vec=shift_vec) return None
def add_stereochem(self, central_atom): """ Adds stereochemistry around an atom, by placing atoms are the correct coordinates for the stereochemistry. For tetrahedral centres, '@' means looking along the bond from the first atom bonded to the centre, the other atoms go anticlockwise in their index order in the atoms list. For alkene centres, '@' means the atoms go anticlockwise in their index order in the atoms list (this works as the same thing is applied to every centre) Arguments: central_atom (int): index of the atom having stereochemistry added around it """ vectors_dict = { '@td': np.array(([0, 0, 1], [0, 2 * np.sqrt(2) / 3, -1 / 3], [-np.sqrt(2 / 3), -np.sqrt(2) / 3, -1 / 3], [np.sqrt(2 / 3), -np.sqrt(2) / 3, -1 / 3])), '@@td': np.array(([0, 0, 1], [0, 2 * np.sqrt(2) / 3, -1 / 3], [np.sqrt(2 / 3), -np.sqrt(2) / 3, -1 / 3], [-np.sqrt(2 / 3), -np.sqrt(2) / 3, -1 / 3])), '@al': np.array(([1.0, 0.0, 0.0], [-0.5, np.sqrt(3) / 2, 0], [-0.5, -np.sqrt(3) / 2, 0])), '@@al': np.array(([1.0, 0.0, 0.0], [-0.5, -np.sqrt(3) / 2, 0], [-0.5, np.sqrt(3) / 2, 0])) } vectors = vectors_dict[self.stereochem_dict[central_atom]] self.stereocentres.append(central_atom) central_translation = -1 * self.atoms[central_atom].coord # centre the central atom self.shift_atom(central_atom, central_translation) bonded_atoms = [] for i, bond in enumerate(self.bonds): # get the bonded atom in the order meant for the stereochemistry if bond[0] == central_atom: bonded_atom = bond[1] elif bond[1] == central_atom: bonded_atom = bond[0] else: continue if self.stereochem_dict[central_atom] in ['@td', '@@td']: # for tetrahedral centres, this is how the ordering is defined if self.atoms[bonded_atom].label == 'H': if bonded_atoms[0] < central_atom: bonded_atoms.insert(1, bonded_atom) else: bonded_atoms.insert(0, bonded_atom) elif i in self.ring_closing_bond_list: bonded_atoms.insert(0, bonded_atom) else: bonded_atoms.append(bonded_atom) else: bonded_atoms.append(bonded_atom) for i, bonded_atom in enumerate(bonded_atoms): if bonded_atom in self.stereocentres: # don't want to lose the orientation around the old # stereocentres, so rotate it into the right position self.rotate_stereocluster(central_atom, bonded_atom, vectors[i]) bond_length = get_avg_bond_length(self.atoms[central_atom].label, self.atoms[bonded_atom].label) bonded_translation = (bond_length * vectors[i] - self.atoms[bonded_atom].coord) self.shift_atom(bonded_atom, bonded_translation, central_atom) self.add_cluster([central_atom] + bonded_atoms)
def make_graph(species, rel_tolerance=0.25, bond_list=None, allow_invalid_valancies=False): """ Make the molecular graph from the 'bonds' determined on a distance criteria or a smiles parser object. All attributes default to false:: Nodes attributes; (0) atom_label: Atomic symbol of this atom (1) stereo: Is this atom part of some stereochemistry e.g. R/S or E/Z Edge attributes; (1) pi: Is this bond a pi bond. If it is then there should be no rotation the bond axis in conformer generation (2) active: Is this bond being made/broken (applies only to TransitionState objects) Arguments: species (autode.species.Species): Keyword Arguments: rel_tolerance (float): bond_list (list(tuple)): allow_invalid_valancies (bool): """ logger.info('Generating molecular graph with NetworkX') graph = nx.Graph() # Add the atoms to the graph all are initially assumed not to be # stereocenters for i in range(species.n_atoms): graph.add_node(i, atom_label=species.atoms[i].label, stereo=False) # If bonds are specified then add edges to the graph and return if bond_list is not None: for bond in bond_list: graph.add_edge(bond[0], bond[1], pi=False, active=False) species.graph = graph return None else: # Loop over the unique pairs of atoms and add 'bonds' coords = species.coordinates dist_mat = distance_matrix(coords, coords) for i in get_atom_ids_sorted_type(species): # Iterate through the closest atoms to atom i for j in np.argsort(dist_mat[i]): if i == j: # Don't bond atoms to themselves continue # Get r_avg for this X-Y bond e.g. C-C -> 1.5 avg_bond_length = get_avg_bond_length(species.atoms[i].label, species.atoms[j].label) # If the distance between atoms i and j are less or equal to # 1.25x average length add a 'bond' if (dist_mat[i, j] <= avg_bond_length * (1.0 + rel_tolerance) and (i, j) not in graph.edges): graph.add_edge(i, j, pi=False, active=False) species.graph = graph set_graph_attributes(species) if not allow_invalid_valancies: remove_bonds_invalid_valancies(species) return None