def attack_cost(reactant, subst_centres, attacking_mol_idx, a=1.0, b=1.0, c=1.0, d=10.0): """ Calculate the 'attack cost' for a molecule attacking in e.g. a substitution or elimination reaction:: C = Σ_ac a * (r_ac - r^0_ac)^2 + Σ_acx b * (1 - cos(θ)) + Σ_acx c*(1 + cos(φ)) + Σ_ij d/r_ij^4 where:: cos(θ) = (v_ann • v_cx / |v_ann||v_cx|) cos(φ) = (v_ca • v_cx / |v_ca||v_cx|) Returns: (float): Cost """ coords = reactant.get_coordinates() cost = 0 for subst_centre in subst_centres: r_ac = reactant.get_distance(atom_i=subst_centre.a_atom, atom_j=subst_centre.c_atom) cost += a * (r_ac - subst_centre.r0_ac)**2 # Attack vector is the average of all the nearest neighbour atoms, # unless it is flat a_nn_coords = [coords[atom_index] - coords[subst_centre.a_atom] for atom_index in subst_centre.a_atom_nn] if len(a_nn_coords) == 0: # The attacking atom has no nearest neighbours thus take the # attack vector to be a unit vector v_ann = np.array([1.0, 0.0, 0.0]) else: v_ann = -np.average(np.array(a_nn_coords), axis=0) if length(v_ann) < 1E-1: # Attacking atom is planar. Compute the perpendicular from two # nearest neighbours v_ann = np.cross(coords[subst_centre.a_atom] - coords[subst_centre.a_atom_nn[0]], coords[subst_centre.a_atom] - coords[subst_centre.a_atom_nn[1]]) v_cx = coords[subst_centre.x_atom] - coords[subst_centre.c_atom] # b(1 - cos(θ)) cost += b * (1 - np.dot(v_ann, v_cx) / (length(v_ann) * length(v_cx))) v_ca = coords[subst_centre.a_atom] - coords[subst_centre.c_atom] # c(1 + cos(φ)) cost += c * (1 + np.dot(v_ca, v_cx) / (length(v_ca) * length(v_cx))) repulsion = reactant.calc_repulsion(mol_index=attacking_mol_idx) cost += d * repulsion return cost
def add_dummy_atom(reactant, bond_rearrangement): """ Add a dummy atom above or below the plane of the reactant as a temporary X atom Arguments: reactant (autode.complex.ReactantComplex): bond_rearrangement (autode.bond_rearrangement.BondRearrangement): """ logger.info('Adding dummy X atom so a substitution center can be found') fbond = bond_rearrangement.fbonds[0] bbond = bond_rearrangement.bbonds[0] components = connected_components(reactant.graph) if len(components) != 2: raise NotImplementedError('Must have two components for dummy add') mol1_idxs, mol2_idxs = components # Find the central atom as the atom index that is in the forming bond but # also contains all indexes of the breaking bond if fbond[0] in mol1_idxs and all(idx in mol2_idxs for idx in bbond): c_atom = fbond[1] else: c_atom = fbond[0] # Nearest neighbours to the central atom used to generate the normal # along which the dummy atom is placed c_atom_nns = list(reactant.graph.neighbors(c_atom)) if len(c_atom_nns) < 2: raise NotImplementedError('Cannot place dummy atom') cn1, cn2 = c_atom_nns[:2] coords = reactant.get_coordinates() # Calculate the normal from the vectors to two of the neighbours position = np.cross(coords[cn1] - coords[c_atom], coords[cn2] - coords[c_atom]) position /= length(position) # Add the dummy atom to a position on the top/bottom face logger.warning('Adding a dummy atom to the set of atoms') reactant.atoms.append(DummyAtom(*position)) # Add the breaking bond to the bond rearrangement temporarily bond_rearrangement.bbonds.append([c_atom, len(reactant.atoms) - 1]) return None
def get_distance(self, atom_i, atom_j): """Get the distance between two atoms in the species""" return length(self.atoms[atom_i].coord - self.atoms[atom_j].coord)
def test_length(): assert geom.length(np.ones(3)) == np.linalg.norm(np.ones(3))
def test_length(): assert geom.length(np.array([1.0, 1.0, 1.0])) == np.linalg.norm( np.array([1.0, 1.0, 1.0]))