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=os.getcwd()) 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=os.getcwd()) 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 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 res = minimize(goalFunc, coords.reshape(-1), method='L-BFGS-B', jac=True, options={ 'ftol': 0, 'gtol': 0.05 }) endcoords = res.x.reshape((natoms, 3)).copy() if restrained_dihedrals: for fi in forceidx[::-1]: self.system.removeForce(fi) return endcoords
def _calcDihedralAngles(self, mol, dihedrals, sincos=True): from htmd.numbautil 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 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(.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 _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 equivalent dihedral atom indices self._equivalent_indices = [] for idihed, dihedral in enumerate(self.dihedrals): found = False for parameterizableDihedral in self._parameterizable_dihedrals: if np.all(list(parameterizableDihedral[0]) == dihedral): self._equivalent_indices.append(parameterizableDihedral) found = True break if not found: raise ValueError('%s is not recognized as a parameterizable dihedral\n' % self._names[idihed]) # Get reference QM energies and rotamer coordinates self._valid_qm_results = self._getValidQMResults() self._reference_energies = [] self._coords = [] for results in self._valid_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 for the fitted equivalent dihedral self._angle_values = [] for rotamer_coords, equivalent_indices in zip(self._coords, self._equivalent_indices): angle_values = [] for coords in rotamer_coords: angle_values.append([np.rad2deg(dihedralAngle(coords[indices, :, 0])) for indices in equivalent_indices]) self._angle_values.append(np.array(angle_values)) self._angle_values_rad = [np.deg2rad(angle_values)[:, :, None] for angle_values in self._angle_values] self._parameterizable_dihedral_atomtypes = [tuple(self.molecule.atomtype[idx]) for idx in self.dihedrals] # Calculated initial MM energies ff = FFEvaluate(self.molecule, self.parameters) self._initial_energies = [] for rotamer_coords in self._coords: self._initial_energies.append(np.array([ff.calculateEnergies(coords[:, :, 0])['total'] for coords in rotamer_coords]))
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 equivalent dihedral atom indices self._equivalent_indices = [] for idihed, dihedral in enumerate(self.dihedrals): found = False for parameterizableDihedral in self._parameterizable_dihedrals: if np.all(list(parameterizableDihedral[0]) == dihedral): self._equivalent_indices.append(parameterizableDihedral) found = True break if not found: raise ValueError('%s is not recognized as a parameterizable dihedral\n' % self._names[idihed]) # Get reference QM energies and rotamer coordinates self._valid_qm_results = self._getValidQMResults() self._reference_energies = [] self._coords = [] for results in self._valid_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 for the fitted equivalent dihedral self._angle_values = [] for rotamer_coords, equivalent_indices in zip(self._coords, self._equivalent_indices): angle_values = [] for coords in rotamer_coords: angle_values.append([np.rad2deg(dihedralAngle(coords[indices, :, 0])) for indices in equivalent_indices]) self._angle_values.append(np.array(angle_values)) self._angle_values_rad = [np.deg2rad(angle_values)[:, :, None] for angle_values in self._angle_values] self._parameterizable_dihedral_atomtypes = [tuple(self.molecule.atomtype[idx]) for idx in self.dihedrals] # Calculated initial MM energies ff = FFEvaluate(self.molecule, self.parameters) self._initial_energies = [] for rotamer_coords in self._coords: self._initial_energies.append(np.array([ff.run(coords[:, :, 0])['total'] for coords in rotamer_coords]))
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 self._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)) self._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 self._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 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))