def ase_popt(embedder, coords, atomnos, constrained_indexes=None, steps=500, targets=None, safe=False, safe_mask=None, traj=None, logfunction=None, title='temp'): ''' embedder: TSCoDe embedder object coords: atomnos: constrained_indexes: safe: if True, adds a potential that prevents atoms from scrambling safe_mask: bool array, with False for atoms to be excluded when calculating bonds to preserve traj: if set to a string, traj is used as a filename for the bending trajectory. not only the atoms will be printed, but also all the orbitals and the active pivot. ''' atoms = Atoms(atomnos, positions=coords) atoms.calc = get_ase_calc(embedder) constraints = [] if constrained_indexes is not None: for i, c in enumerate(constrained_indexes): i1, i2 = c tgt_dist = norm_of(coords[i1] - coords[i2]) if targets is None else targets[i] constraints.append(Spring(i1, i2, tgt_dist)) if safe: constraints.append( PreventScramblingConstraint( graphize(coords, atomnos, safe_mask), atoms, double_bond_protection=embedder.options.double_bond_protection, fix_angles=embedder.options.fix_angles_in_deformation)) atoms.set_constraint(constraints) t_start_opt = time.perf_counter() with LBFGS(atoms, maxstep=0.1, logfile=None, trajectory=traj) as opt: opt.run(fmax=0.05, steps=steps) iterations = opt.nsteps new_structure = atoms.get_positions() success = (iterations < 499) if logfunction is not None: exit_str = 'REFINED' if success else 'MAX ITER' logfunction( f' - {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})' ) energy = atoms.get_total_energy() * 23.06054194532933 #eV to kcal/mol return new_structure, energy, success
def confab_operator(filename, options, logfunction=None): ''' ''' if logfunction is not None: logfunction( f'--> Performing conformational search and optimization on {filename}' ) data = read_xyz(filename) if len(data.atomcoords) > 1: raise InputError( f'Requested conformational search on file {filename} that already contains more than one structure.' ) if len( tuple( connected_components(graphize(data.atomcoords[0], data.atomnos)))) > 1: raise InputError(( f'Requested conformational search on a molecular complex (file {filename}). ' 'Confab is not suited for this task, and using TSCoDe\'s csearch> operator ' 'is a better idea.')) if len(set(data.atomnos) - {1, 6, 7, 8, 9, 15, 16, 17, 35, 53}) != 0: raise InputError(( 'Requested conformational search on a molecule that contains atoms different ' 'than the ones for which OpenBabel Force Fields are parametrized. Please ' 'perform this conformational search through the more sophisticated and better ' 'integrated csearch> operator, part of the TSCoDe program.')) confname = filename[:-4] + '_confab.xyz' with suppress_stdout_stderr(): check_call( f'obabel {filename} -O {confname} --confab --rcutoff 0.5 --original' .split(), stdout=DEVNULL, stderr=STDOUT) # running Confab through Openbabel data = read_xyz(confname) conformers = data.atomcoords if len(conformers) > 10 and not options.let: conformers = conformers[0:10] logfunction( f'Will use only the best 10 conformers for TSCoDe embed.\n') os.remove(confname) with open(confname, 'w') as f: for i, conformer in enumerate(conformers): write_xyz(conformer, data.atomnos, f, title=f'Generated conformer {i}') return confname
def molecule_check(old_coords, new_coords, atomnos, max_newbonds=0): ''' Checks if two molecules have the same bonds between the same atomic indexes ''' old_bonds = {(a, b) for a, b in list(graphize(old_coords, atomnos).edges) if a != b} new_bonds = {(a, b) for a, b in list(graphize(new_coords, atomnos).edges) if a != b} delta_bonds = (old_bonds | new_bonds) - (old_bonds & new_bonds) if len(delta_bonds) > max_newbonds: return False return True
def scramble_check(TS_structure, TS_atomnos, constrained_indexes, mols_graphs, max_newbonds=0) -> bool: ''' Check if a transition state structure has scrambled during some optimization steps. If more than a given number of bonds changed (formed or broke) the structure is considered scrambled, and the method returns False. ''' assert len(TS_structure) == sum( [len(graph.nodes) for graph in mols_graphs]) bonds = set() for i, graph in enumerate(mols_graphs): pos = 0 while i != 0: pos += len(mols_graphs[i - 1].nodes) i -= 1 for bond in [ tuple(sorted((a + pos, b + pos))) for a, b in list(graph.edges) if a != b ]: bonds.add(bond) # creating bond set containing all bonds present in the desired transition state new_bonds = { tuple(sorted((a, b))) for a, b in list(graphize(TS_structure, TS_atomnos).edges) if a != b } delta_bonds = (bonds | new_bonds) - (bonds & new_bonds) # delta_bonds -= {tuple(sorted(pair)) for pair in constrained_indexes} for bond in delta_bonds.copy(): for a1, a2 in constrained_indexes: if (a1 in bond) or (a2 in bond): delta_bonds -= {bond} # removing bonds involving constrained atoms: they are not counted as scrambled bonds if len(delta_bonds) > max_newbonds: return False return True
def __init__(self, filename, reactive_indexes=None, debug=False): ''' Initializing class properties: reading conformational ensemble file, aligning conformers to first and centering them in origin. :params filename: Input file name. Can be anything, .xyz preferred :params reactive_indexes: Index of atoms that will link during the desired reaction. May be either int or list of int. :params hyper: bool, whether to calculate orbitals positions ''' if not os.path.isfile(filename): if '.' in filename: raise SyntaxError(( f'Molecule {filename} cannot be read. Please check your syntax.' )) raise SyntaxError(( f'The program is trying to read something that is not a valid molecule input ({filename}). ' + 'If this looks like a keyword, it is probably faulted by a syntax error.' )) self.rootname = filename.split('.')[0] self.name = filename self.debug = debug if isinstance(reactive_indexes, np.ndarray): self.reactive_indexes = reactive_indexes else: self.reactive_indexes = np.array(reactive_indexes) if isinstance( reactive_indexes, (tuple, list)) else () ccread_object = read_xyz(filename) if ccread_object is None: raise CCReadError(f'Cannot read file {filename}') coordinates = np.array(ccread_object.atomcoords) # if coordinates.shape[0] > 5: # coordinates = coordinates[0:5] # # Do not keep more than 5 conformations self.atomnos = ccread_object.atomnos self.position = np.array([0, 0, 0], dtype=float) # used in Embedder class self.rotation = np.identity( 3) # used in Embedder class - rotation matrix assert all([ len(coordinates[i]) == len(coordinates[0]) for i in range(1, len(coordinates)) ]), 'Ensembles must have constant atom number.' # Checking that ensemble has constant length if self.debug: print( f'DEBUG--> Initializing object {filename}\nDEBUG--> Found {len(coordinates)} structures with {len(coordinates[0])} atoms' ) self.centroid = np.sum(np.sum(coordinates, axis=0), axis=0) / ( len(coordinates) * len(coordinates[0])) if self.debug: print('DEBUG--> Centroid was', self.centroid) self.atomcoords = coordinates - self.centroid self.graph = graphize(self.atomcoords[0], self.atomnos) # show_graph(self) self.atoms = np.array([ atom for structure in self.atomcoords for atom in structure ]) # single list with all atomic positions if self.debug: print(f'DEBUG--> Total of {len(self.atoms)} atoms')
def ase_adjust_spacings(embedder, structure, atomnos, constrained_indexes, title=0, traj=None): ''' embedder: TSCoDe embedder object structure: TS candidate coordinates to be adjusted atomnos: 1-d array with element numbering for the TS constrained_indexes: (n,2)-shaped array of indexes to be distance constrained mols_graphs: list of NetworkX graphs, ordered as the single molecules in the TS title: number to be used for referring to this structure in the embedder log traj: if set to a string, traj+'.traj' is used as a filename for the refinement trajectory. ''' atoms = Atoms(atomnos, positions=structure) atoms.calc = get_ase_calc(embedder) springs = [ Spring(indexes[0], indexes[1], dist) for indexes, dist in embedder.target_distances.items() ] # adding springs to adjust the pairings for which we have target distances nci_indexes = [ indexes for letter, indexes in embedder.pairings_table.items() if letter in ('x', 'y', 'z') ] halfsprings = [HalfSpring(i1, i2, 2.5) for i1, i2 in nci_indexes] # HalfSprings get atoms involved in NCIs together if they are more than 2.5A apart, # but lets them achieve their natural equilibrium distance when closer psc = PreventScramblingConstraint( graphize(structure, atomnos), atoms, double_bond_protection=embedder.options.double_bond_protection, fix_angles=embedder.options.fix_angles_in_deformation) atoms.set_constraint(springs + halfsprings + [psc]) t_start_opt = time.perf_counter() try: with LBFGS(atoms, maxstep=0.2, logfile=None, trajectory=traj) as opt: opt.run(fmax=0.05, steps=500) # initial coarse refinement with # Springs, Half Springs and PSC for spring in springs: spring.tighten() atoms.set_constraint(springs) # Tightening Springs to improve # spacings accuracy opt.run(fmax=0.05, steps=200) # final accurate refinement iterations = opt.nsteps new_structure = atoms.get_positions() success = scramble_check(new_structure, atomnos, constrained_indexes, embedder.graphs) exit_str = 'REFINED' if success else 'SCRAMBLED' except PropertyNotImplementedError: exit_str = 'CRASHED' embedder.log( f' - {embedder.options.calculator} {embedder.options.theory_level} refinement: Structure {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})', p=False) if exit_str == 'CRASHED': return None, None, False energy = atoms.get_total_energy() * 23.06054194532933 #eV to kcal/mol return new_structure, energy, success