def test_restrained_dihedrals(self): quad = [2, 0, 1, 3] angle = np.rad2deg(dihedralAngle(self.h2o2_90.coords[quad, :, 0])) self.assertEqualFloat(89.999544881803772, angle, tol=1e-7) with TemporaryDirectory(dir=self.testDir) as tmpDir: self.qm.molecule = self.h2o2_90 self.qm.theory = "BLYP" # HF fails self.qm.basis = "3-21G" self.qm.optimize = True self.qm.restrained_dihedrals = np.array([quad]) self.qm.directory = tmpDir result = self.qm.run()[0] self.assertFalse(result.errored) angle = np.rad2deg(dihedralAngle(result.coords[quad, :, 0])) self.assertEqualFloat(89.999541178019271, angle, tol=1e-7) with TemporaryDirectory(dir=self.testDir) as tmpDir: self.qm.restrained_dihedrals = None self.qm.directory = tmpDir result = self.qm.run()[0] self.assertFalse(result.errored) angle = np.rad2deg(dihedralAngle(result.coords[quad, :, 0])) if isinstance(self.qm, Psi4): self.assertEqualFloat(179.5169310744747, angle, tol=1e-3) # Unstable results else: self.assertEqualFloat(-168.9488713666722, angle, tol=1e-5) # Unstable results
def test_restrained_dihedrals(self): quad = [2, 0, 1, 3] angle = np.rad2deg(dihedralAngle(self.h2o2_90.coords[quad, :, 0])) self.assertAlmostEqual(89.999544881803772, angle, places=5) with TemporaryDirectory(dir=self.testDir) as tmpDir: self.qm.molecule = self.h2o2_90 self.qm.theory = 'BLYP' # HF fails self.qm.basis = '3-21G' self.qm.optimize = True self.qm.restrained_dihedrals = np.array([quad]) self.qm.directory = tmpDir result = self.qm.run()[0] self.assertFalse(result.errored) angle = np.rad2deg(dihedralAngle(result.coords[quad, :, 0])) self.assertAlmostEqual(89.999541178019271, angle, places=5) with TemporaryDirectory(dir=self.testDir) as tmpDir: self.qm.restrained_dihedrals = None self.qm.directory = tmpDir result = self.qm.run()[0] self.assertFalse(result.errored) angle = np.rad2deg(dihedralAngle(result.coords[quad, :, 0])) self.assertAlmostEqual(179.51690845119924, angle, places=3) # Unstable results
def retrieve(self): ff = FFEvaluate(self.molecule, self._parameters) results = [] for iframe in range(self.molecule.numFrames): self.molecule.frame = iframe directory = os.path.join(self.directory, '%05d' % iframe) os.makedirs(directory, exist_ok=True) pickleFile = os.path.join(directory, 'data.pkl') if self._completed(directory): with open(pickleFile, 'rb') as fd: result = pickle.load(fd) logger.info('Loading QM data from %s' % pickleFile) else: result = QMResult() result.errored = False result.coords = self.molecule.coords[:, :, iframe:iframe + 1].copy() if self.optimize: opt = nlopt.opt(nlopt.LN_COBYLA, result.coords.size) opt.set_min_objective(lambda x, _: ff.calculateEnergies(x.reshape((-1, 3)))['total']) if self.restrained_dihedrals is not None: for dihedral in self.restrained_dihedrals: indices = dihedral.copy() ref_angle = dihedralAngle(self.molecule.coords[indices, :, iframe]) def constraint(x, _): coords = x.reshape((-1, 3)) angle = dihedralAngle(coords[indices]) return np.sin(.5*(angle - ref_angle)) opt.add_equality_constraint(constraint) opt.set_xtol_abs(1e-3) # Similar to Psi4 default opt.set_maxeval(1000*opt.get_dimension()) opt.set_initial_step(1e-3) result.coords = opt.optimize(result.coords.ravel()).reshape((-1, 3, 1)) logger.info('Optimization status: %d' % opt.last_optimize_result()) result.energy = ff.calculateEnergies(result.coords[:, :, 0])['total'] result.dipole = getDipole(self.molecule) if self.optimize: assert opt.last_optimum_value() == result.energy # A self-consistency test # Compute ESP values if self.esp_points is not None: assert self.molecule.numFrames == 1 result.esp_points = self.esp_points distances = cdist(result.esp_points, result.coords[:, :, 0]) # Angstrom distances *= const.physical_constants['Bohr radius'][0] / const.angstrom # Angstrom --> Bohr result.esp_values = np.dot(np.reciprocal(distances), self.molecule.charge) # Hartree/Bohr with open(pickleFile, 'wb') as fd: pickle.dump(result, fd) results.append(result) return results
def _calcDihedralAngles(self, mol, dihedrals, sincos=True): from moleculekit.dihedral import dihedralAngle metric = np.zeros((np.size(mol.coords, 2), len(dihedrals))) for i, dih in enumerate(dihedrals): metric[:, i] = np.rad2deg(dihedralAngle(mol.coords[dih, :, :])) if sincos: sc_metric = np.zeros((np.size(metric, 0), np.size(metric, 1) * 2)) sc_metric[:, 0::2] = np.sin(metric * np.pi / 180.) sc_metric[:, 1::2] = np.cos(metric * np.pi / 180.) metric = sc_metric return metric.astype(np.float32)
def _setup(self): if len(self.dihedrals) != len(self.qm_results): raise ValueError('The number of dihedral and QM result sets has to be the same!') # Get dihedral names self._names = ['-'.join(self.molecule.name[dihedral]) for dihedral in self.dihedrals] # Get all equivalent dihedrals all_equivalent_dihedrals = detectParameterizableDihedrals(self.molecule) all_equivalent_dihedrals = {tuple(dihedrals[0]): dihedrals for dihedrals in all_equivalent_dihedrals} # Choose the selected dihedrals equivalent_dihedrals = [] for dihedral, name in zip(self.dihedrals, self._names): if tuple(dihedral) not in all_equivalent_dihedrals: raise ValueError('{} is not a parameterizable dihedral!'.format(name)) equivalent_dihedrals.append(all_equivalent_dihedrals[tuple(dihedral)]) # Get dihedral atom types self._dihedral_atomtypes = [findDihedralType(tuple(self.molecule.atomtype[dihedral]), self.parameters) for dihedral in self.dihedrals] # Get reference QM energies and rotamer coordinates self._reference_energies = [] self._coords = [] for results in self.qm_results: self._reference_energies.append(np.array([result.energy for result in results])) self._coords.append([result.coords for result in results]) # Calculate dihedral angle values # [# of scans, # of dihedrals, # of conformations, # of equivalents] self._angle_values = [] for scan_coords in self._coords: scan_angle_values = [] for equivalent_indices in equivalent_dihedrals: angle_values = [] for coords in scan_coords: angle_values.append([dihedralAngle(coords[indices, :, 0]) for indices in equivalent_indices]) scan_angle_values.append(np.array(angle_values)) self._angle_values.append(scan_angle_values) # Calculated initial MM energies ff = FFEvaluate(self.molecule, self.parameters) self._initial_energies = [] for scan_coords in self._coords: energies = [ff.calculateEnergies(coords[:, :, 0])['total'] for coords in scan_coords] self._initial_energies.append(np.array(energies)) # Make result directories os.makedirs(self.result_directory, exist_ok=True)
def minimize(self, coords, restrained_dihedrals): if restrained_dihedrals is not None: for dihedral in restrained_dihedrals: indices = dihedral.copy() ref_angle = dihedralAngle(coords[indices, :, 0]) def constraint(x, _): coords = x.reshape((-1, 3)) angle = dihedralAngle(coords[indices]) return np.sin(0.5 * (angle - ref_angle)) self.opt.add_equality_constraint(constraint) self.opt.set_xtol_abs(1e-3) # Similar to Psi4 default self.opt.set_maxeval(1000 * self.opt.get_dimension()) self.opt.set_initial_step(1e-3) return self.opt.optimize(coords.ravel()).reshape((-1, 3, 1))
def retrieve(self): prmtop = self._get_prmtop() system = prmtop.createSystem() groups = {force.getForceGroup() for force in system.getForces()} if self.optimize: if self.restrained_dihedrals is not None: restraint = openmm.PeriodicTorsionForce() restraint.setForceGroup(max(groups) + 1) for dihedral in self.restrained_dihedrals: restraint.addTorsion(*tuple(map(int, dihedral)), periodicity=1, phase=0, k=-1000 * unit.kilocalorie_per_mole) system.addForce(restraint) simulation = app.Simulation(prmtop.topology, system, openmm.VerletIntegrator(1 * unit.femtosecond), openmm.Platform.getPlatformByName('CPU')) results = [] molecule_copy = self.molecule.copy() for iframe in range(self.molecule.numFrames): self.molecule.frame = iframe molecule_copy.frame = iframe directory = os.path.join(self.directory, '%05d' % iframe) os.makedirs(directory, exist_ok=True) pickleFile = os.path.join(directory, 'data.pkl') if self._completed(directory): with open(pickleFile, 'rb') as fd: results.append(pickle.load(fd)) logger.info('Loading QM data from %s' % pickleFile) continue simulation.context.setPositions(self.molecule.coords[:, :, iframe] * unit.angstrom) if self.optimize: if self.restrained_dihedrals is not None: for i, dihedral in enumerate(self.restrained_dihedrals): ref_angle = np.rad2deg(dihedralAngle(self.molecule.coords[dihedral, :, iframe])) parameters = restraint.getTorsionParameters(i) parameters[5] = ref_angle * unit.degree restraint.setTorsionParameters(i, *parameters) restraint.updateParametersInContext(simulation.context) simulation.minimizeEnergy(tolerance=0.001 * unit.kilocalorie_per_mole) state = simulation.context.getState(getEnergy=True, getPositions=True, groups=groups) result = QMResult() result.charge = self.charge result.errored = False result.energy = state.getPotentialEnergy().value_in_unit(unit.kilocalorie_per_mole) result.coords = state.getPositions(asNumpy=True).value_in_unit(unit.angstrom).reshape((-1, 3, 1)) result.dipole = getDipole(self.molecule) if self.esp_points is not None: assert self.molecule.numFrames == 1 result.esp_points = self.esp_points distances = cdist(result.esp_points, result.coords[:, :, 0]) # Angstrom distances *= const.physical_constants['Bohr radius'][0] / const.angstrom # Angstrom --> Bohr result.esp_values = np.dot(np.reciprocal(distances), self.molecule.charge) # Hartree/Bohr results.append(result) with open(pickleFile, 'wb') as fd: pickle.dump(result, fd) self.molecule.write(os.path.join(directory, 'mol-init.mol2')) # Write an optimiz molecule_copy.coords[:, :, iframe] = result.coords[:, :, 0] molecule_copy.write(os.path.join(directory, 'mol.mol2')) # Write an optimiz return results
def constraint(x, _): coords = x.reshape((-1, 3)) angle = dihedralAngle(coords[indices]) return np.sin(.5*(angle - ref_angle))
def minimize(self, coords, restrained_dihedrals=None, maxeval=None): from simtk import unit from simtk.openmm import app, PeriodicTorsionForce import simtk.openmm as mm from scipy.optimize import minimize forceidx = [] if restrained_dihedrals: f = PeriodicTorsionForce() for dihedral in restrained_dihedrals: theta0 = dihedralAngle(coords[dihedral]) f.addTorsion( *tuple(map(int, dihedral)), periodicity=1, phase=theta0, k=-10000 * unit.kilocalories_per_mole ) fidx = self.system.addForce(f) forceidx.append(fidx) if coords.ndim == 3: coords = coords[:, :, 0] natoms = coords.shape[0] integrator = mm.LangevinIntegrator(0, 0, 0) sim = app.Simulation( self.structure.topology, self.system, integrator, self.platform, self.platprop, ) def goalFunc(x): sim.context.setPositions(x.reshape((natoms, 3)) * unit.angstrom) state = sim.context.getState(getEnergy=True, getForces=True) energy = state.getPotentialEnergy().value_in_unit( unit.kilocalories_per_mole ) forces = state.getForces(asNumpy=True).value_in_unit( unit.kilocalories_per_mole / unit.angstrom ) grad = -forces.reshape(-1) return energy, grad force_tolerance = 0.1 # kcal/mol/A max_attempts = 50 best_result = None best_force = np.inf for i in range(max_attempts): result = minimize( goalFunc, coords.reshape(-1), method="L-BFGS-B", jac=True, options={"ftol": 0, "gtol": force_tolerance}, ) max_force = np.abs(result.jac).max() if max_force < best_force: best_force = max_force best_result = result if max_force > force_tolerance: # Try to continue minimization by restarting the minimizer result = minimize( goalFunc, result.x, method="L-BFGS-B", jac=True, options={"ftol": 0, "gtol": force_tolerance}, ) max_force = np.abs(result.jac).max() if max_force < best_force: best_force = max_force best_result = result if max_force <= force_tolerance: break if best_force > force_tolerance: logger.warning( "Did not manage to minimize structure to the desired force tolerance. Best minimized structure had a max force component {:.2f} kcal/mol/A. Threshold is {}".format( best_force, force_tolerance ) ) minimized_coords = best_result.x.reshape((natoms, 3)).copy() if restrained_dihedrals: for fi in forceidx[::-1]: self.system.removeForce(fi) return minimized_coords