def test_attack_cost(): subst_centers = get_substitution_centres(reactant=reac_complex, bond_rearrangement=bond_rearr, shift_factor=2) atoms = [ Atom('F', -2.99674, -0.35248, 0.17493), Atom('Cl', 1.63664, 0.02010, -0.05829), Atom('C', -0.14524, -0.00136, 0.00498), Atom('H', -0.52169, -0.54637, -0.86809), Atom('H', -0.45804, -0.50420, 0.92747), Atom('H', -0.51166, 1.03181, -0.00597) ] ideal_complex = ReactantComplex(f, ch3cl) ideal_complex.set_atoms(atoms) attack_cost = substitution.attack_cost(reac_complex, subst_centers, attacking_mol_idx=0) ideal_attack_cost = substitution.attack_cost(ideal_complex, subst_centers, attacking_mol_idx=0) # The cost function should be larger for the randomly located reaction # complex compared to the ideal assert attack_cost > ideal_attack_cost
def test_attack_cost(): subst_centers = substitution.get_substitution_centres(reactant=reac_complex, bond_rearrangement=bond_rearr, shift_factor=2) ideal_complex = ReactantComplex(f, ch3cl) ideal_complex.set_atoms(atoms=xyz_file_to_atoms(os.path.join(here, 'data', 'sn2_reac_complex.xyz'))) attack_cost = substitution.attack_cost(reac_complex, subst_centers, attacking_mol_idx=0) ideal_attack_cost = substitution.attack_cost(ideal_complex, subst_centers, attacking_mol_idx=0) # The cost function should be larger for the randomly located reaction complex compared to the ideal assert attack_cost > ideal_attack_cost
def test_subst(): reactant = Reactant(name='sn2_r', atoms=xyz_file_to_atoms('reactant.xyz')) # SN2' bond rearrangement bond_rearr = BondRearrangement(forming_bonds=[(0, 1)], breaking_bonds=[(3, 4)]) subst_centers = get_substitution_centres(reactant, bond_rearr, shift_factor=1.0) assert len(subst_centers) == 1 # get_substitution_centres should add a dummy atom so the ACX angle is # defined assert len(reactant.atoms) == 11
def test_subst_centre(): subst_centers = substitution.get_substitution_centres( reactant=reac_complex, bond_rearrangement=bond_rearr, shift_factor=2) # Only one atom gets attacked in an SN2 assert len(subst_centers) == 1 sc = subst_centers[0] assert type(sc) is substitution.SubstitutionCentre assert reac_complex.atoms[sc.a_atom].label == 'F' assert reac_complex.atoms[sc.c_atom].label == 'C' assert reac_complex.atoms[sc.x_atom].label == 'Cl' # The attacking flouride ion doesn't have any nearest neighbours assert len(sc.a_atom_nn) == 0 # The attacking atom to substitution centre atom ideal bond length # should be ~ 3 Å assert type(sc.r0_ac) is float assert 2.0 < sc.r0_ac < 5.0
def translate_rotate_reactant(reactant, bond_rearrangement, shift_factor, n_iters=10): """ Shift a molecule in the reactant complex so that the attacking atoms (a_atoms) are pointing towards the attacked atoms (l_atoms) Arguments: reactant (autode.complex.ReactantComplex): bond_rearrangement (autode.bond_rearrangement.BondRearrangement): shift_factor (float): n_iters (int): Number of iterations of translation/rotation to perform to (hopefully) find the global minima """ if not hasattr(reactant, 'molecules'): logger.warning('Cannot rotate/translate component, not a Complex') return if len(reactant.molecules) < 2: logger.info('Reactant molecule does not need to be translated or ' 'rotated') return logger.info('Rotating/translating into a reactive conformation... running') # This function can add dummy atoms for e.g. SN2' reactions where there # is not a A -- C -- Xattern for the substitution centre subst_centres = get_substitution_centres(reactant, bond_rearrangement, shift_factor=shift_factor) if all(sc.a_atom in reactant.get_atom_indexes(mol_index=0) for sc in subst_centres): attacking_mol = 0 else: attacking_mol = 1 # Disable the logger to prevent rotation/translations printing logger.disabled = True # Find the global minimum for inplace rotation, translation and rotation min_cost, opt_x = None, None for _ in range(n_iters): res = minimize(get_cost_rotate_translate, x0=np.random.random(11), method='BFGS', tol=0.1, args=(reactant, subst_centres, attacking_mol)) if min_cost is None or res.fun < min_cost: min_cost = res.fun opt_x = res.x # Renable the logger logger.disabled = False logger.info(f'Minimum cost for translating/rotating is {min_cost:.3f}') # Translate/rotation the attacking molecule optimally reactant.rotate_mol(axis=opt_x[:3], theta=opt_x[3], mol_index=attacking_mol) reactant.translate_mol(vec=opt_x[4:7], mol_index=attacking_mol) reactant.rotate_mol(axis=opt_x[7:10], theta=opt_x[10], mol_index=attacking_mol) logger.info(' ... done') reactant.print_xyz_file() # Remove any dummy atoms that may have been added # in alt_substitution_centres reactant.atoms = [atom for atom in reactant.atoms if atom.label != 'D'] return None