def _makeDihedralFittingSetFromQMResults(self, atoms, results): # Extract the valid QM poses and energies from the QM result set # Evaluate the MM on those poses ffe = FFEvaluate(self) ret = QMFittingSet() ret.name = "%s-%s-%s-%s" % (self._rtf.names[atoms[0]], self._rtf.names[ atoms[1]], self._rtf.names[atoms[2]], self._rtf.names[atoms[3]]) completed = 0 qmin = 1.e100 for q in results: if q.completed and not q.errored: if q.energy < qmin: qmin = q.energy completed = 0 for q in results: if q.completed and not q.errored: if ( q.energy - qmin ) < 20.: # Only fit against QM points < 20 kcal above the minimum mmeval = ffe.evaluate(q.coords) if mmeval["vdw"] < 200: completed += 1 phi = getPhi(q.coords, atoms) ret.qm.append(q.energy - qmin) ret.mm_original.append(mmeval['total']) ret.coords.append(q.coords) if phi > 180.: phi -= 360. ret.phi.append(phi) else: print( "Omitting optimised pose for phi=%f (MM VDW too high)" % phi) else: print( "Omitting optimised QM pose (QM energy too high %f)" % q.energy) mmin = min(ret.mm_original) # roughly align the qm with the mm for q in range(completed): ret.mm_original[q] = ret.mm_original[q] - mmin ret.N = completed if completed < 5: raise RuntimeError( "Fewer than 5 valid QM points. Not enough to fit!") return ret
def _makeDihedralFittingSetFromQMResults(self, atoms, results): # Extract the valid QM poses and energies from the QM result set # Evaluate the MM on those poses ffe = FFEvaluate(self) ret = QMFittingSet() ret.name = "%s-%s-%s-%s" % ( self._rtf.names[atoms[0]], self._rtf.names[atoms[1]], self._rtf.names[atoms[2]], self._rtf.names[atoms[3]]) completed = 0 qmin = 1.e100 for q in results: if q.completed and not q.errored: if q.energy < qmin: qmin = q.energy completed = 0 for q in results: if q.completed and not q.errored: if (q.energy - qmin) < 20.: # Only fit against QM points < 20 kcal above the minimum mmeval = ffe.evaluate(q.coords) if mmeval["vdw"] < 200: completed += 1 phi = getPhi(q.coords, atoms) ret.qm.append(q.energy - qmin) ret.mm_original.append(mmeval['total']) ret.coords.append(q.coords) if phi > 180.: phi -= 360. ret.phi.append(phi) else: print("Omitting optimised pose for phi=%f (MM VDW too high)" % phi) else: print("Omitting optimised pose for phi=%f (QM energy too high)" % phi) mmin = min(ret.mm_original) # roughly align the qm with the mm for q in range(completed): ret.mm_original[q] = ret.mm_original[q] - mmin ret.N = completed if completed < 5: raise RuntimeError("Fewer than 5 valid QM points. Not enough to fit!") return ret
def fitSoftTorsion(self, phi, geomopt=True): found = False phi_to_fit = None frozens = [] dih_index = 0 i = 0 bkp_coords = self.coords.copy() for d in self._soft_dihedrals: if (d.atoms == phi).all(): phi_to_fit = d dih_index = i frozens.append(d.atoms) else: if not geomopt: frozens.append(d.atoms) i += 1 if not phi_to_fit: raise ValueError("specified phi is not a recognised soft dihedral") self._makeDihedralUnique(phi_to_fit) atoms = phi_to_fit.atoms left = phi_to_fit.left right = phi_to_fit.right equivs = phi_to_fit.equivalents step = 10 # degrees nstep = int(360 / step) cset = np.zeros((self.natoms, 3, nstep)) i = 0 for phi in range(-180, 180, step): cset[:, :, i] = setPhi(self.coords[:, :, 0], atoms, left, right, phi) i += 1 mol = self.copy() mol.coords = cset dirname = "dihedral-single-point" if geomopt: dirname = "dihedral-opt" dih_name = "%s-%s-%s-%s" % (self.name[atoms[0]], self.name[atoms[1]], self.name[atoms[2]], self.name[atoms[3]]) fitdir = os.path.join(self.outdir, dirname, dih_name, self.output_directory_name()) try: os.makedirs(fitdir, exist_ok=True) except: raise OSError('Directory {} could not be created. Check if you have permissions.'.format(fitdir)) qmset = QMCalculation(mol, charge=self.netcharge, directory=fitdir, frozen=frozens, optimize=geomopt, theory=self.theory, solvent=self.solvent, basis=self.basis, execution=self.execution, code=self.qmcode) ret = self._makeDihedralFittingSetFromQMResults(atoms, qmset.results()) # Get the initial parameters of the dihedral we are going to fit param = self._prm.dihedralParam(self._rtf.type_by_index[atoms[0]], self._rtf.type_by_index[atoms[1]], self._rtf.type_by_index[atoms[2]], self._rtf.type_by_index[atoms[3]]) # Save these parameters as the best fit (fit to beat) best_param = np.zeros((13)) for t in range(6): best_param[t] = param[t].k0 best_param[t + 6] = param[t].phi0 best_param[12] = 0. # print(param) # Evalaute the mm potential with this dihedral zeroed out # The objective function will try to fit to the delta between # The QM potential and the this modified mm potential for t in param: t.k0 = t.phi0 = 0. t.e14 = 1. # Always fit with e14 scaling of 1. per CHARMM self._prm.updateDihedral(param) ffe = FFEvaluate(self) # print(ffe.evaluate( ret.coords[0] ) ) # input # Now evaluate the ff without the dihedral being fitted for t in range(ret.N): mm_zeroed = (ffe.evaluate(ret.coords[t])["total"]) ret.mm_delta.append(ret.qm[t] - mm_zeroed) ret.mm_zeroed.append(mm_zeroed) mmin1 = min(ret.mm_zeroed) mmin2 = min(ret.mm_delta) for t in range(ret.N): ret.mm_zeroed[t] = ret.mm_zeroed[t] - mmin1 ret.mm_delta[t] = ret.mm_delta[t] - mmin2 self._fitDihedral_results = ret self._fitDihedral_phi = param # Now measure all of the soft dihedrals phis that are mapped to this dihedral ret.phis = [] for i in range(ret.N): ret.phis.append([ret.phi[i]]) for e in equivs: ret.phis[i].append(getPhi(ret.coords[i], e)) # print ("EQUIVALENT DIHEDRALS FOR THIS DIHEDRAL" ) # print(equivs) # print ("PHI VALUES TO FIT") # print (ret.phis) # Set up the NOLOPT fit # There are 13 parameters, k,phi for n=1,2,3,4,5,6 and a shift N = 13 # initial guess, st = np.zeros(13) # bounds best_chisq = self._fitDihedral_objective(best_param) # print("CHISQ of initial = %f" % ( best_chisq ) ) # Now zero out the terms of the dihedral we are going to fit bar = ProgressBar(64, description="Fitting") for i in range(64): (bounds, start) = self._fitDihedral_make_bounds(i) xopt = optimize.minimize(self._fitDihedral_objective, start, method="L-BFGS-B", bounds=bounds, options={'disp': False}) chisq = self._fitDihedral_objective(xopt.x) # print( "CHISQ of fit = %f " % (chisq) ) if (chisq < best_chisq): best_chisq = chisq best_param = xopt.x bar.progress() bar.stop() # print("Best ChiSQ = %f" %(best_chisq) ) # Update the target dihedral with the optimized parameters # print(param) # print(best_param ) for i in range(6): param[i].k0 = best_param[0 + i] param[i].phi0 = best_param[6 + i] self._prm.updateDihedral(param) # print(param) param = self._prm.dihedralParam(self._rtf.type_by_index[atoms[0]], self._rtf.type_by_index[atoms[1]], self._rtf.type_by_index[atoms[2]], self._rtf.type_by_index[atoms[3]]) # print(param) # Finally evaluate the fitted potential ffe = FFEvaluate(self) for t in range(ret.N): ret.mm_fitted.append(ffe.evaluate(ret.coords[t])["total"]) mmin = min(ret.mm_fitted) chisq = 0. # print( "QM energies" ) # print( ret.qm ) for t in range(ret.N): ret.mm_fitted[t] = ret.mm_fitted[t] - mmin delta = ret.mm_fitted[t] - ret.qm[t] chisq = chisq + (delta * delta) ret.chisq = chisq # TODO Score it self.coords = bkp_coords return ret
def fitSoftTorsion(self, phi, geomopt=True): found = False phi_to_fit = None frozens = [] dih_index = 0 i = 0 bkp_coords = self.coords.copy() for d in self._soft_dihedrals: if (d.atoms == phi).all(): phi_to_fit = d dih_index = i frozens.append(d.atoms) else: if not geomopt: frozens.append(d.atoms) i += 1 if not phi_to_fit: raise ValueError("specified phi is not a recognised soft dihedral") self._makeDihedralUnique(phi_to_fit) atoms = phi_to_fit.atoms left = phi_to_fit.left right = phi_to_fit.right equivs = phi_to_fit.equivalents step = 10 # degrees nstep = int(360 / step) cset = np.zeros((self.natoms, 3, nstep)) i = 0 for phi in range(-180, 180, step): cset[:, :, i] = setPhi(self.coords[:, :, 0], atoms, left, right, phi) i += 1 mol = self.copy() mol.coords = cset dirname = "dihedral-single-point" if geomopt: dirname = "dihedral-opt" dih_name = "%s-%s-%s-%s" % (self.name[atoms[0]], self.name[atoms[1]], self.name[atoms[2]], self.name[atoms[3]]) fitdir = os.path.join(self.outdir, dirname, dih_name, self.output_directory_name()) try: os.makedirs(fitdir, exist_ok=True) except: raise OSError( 'Directory {} could not be created. Check if you have permissions.' .format(fitdir)) qmset = QMCalculation(mol, charge=self.netcharge, directory=fitdir, frozen=frozens, optimize=geomopt, theory=self.theory, solvent=self.solvent, basis=self.basis, execution=self.execution, code=self.qmcode) ret = self._makeDihedralFittingSetFromQMResults(atoms, qmset.results()) # Get the initial parameters of the dihedral we are going to fit param = self._prm.dihedralParam(self._rtf.type_by_index[atoms[0]], self._rtf.type_by_index[atoms[1]], self._rtf.type_by_index[atoms[2]], self._rtf.type_by_index[atoms[3]]) # Save these parameters as the best fit (fit to beat) best_param = np.zeros((13)) for t in range(6): best_param[t] = param[t].k0 best_param[t + 6] = param[t].phi0 best_param[12] = 0. # print(param) # Evalaute the mm potential with this dihedral zeroed out # The objective function will try to fit to the delta between # The QM potential and the this modified mm potential for t in param: t.k0 = t.phi0 = 0. #t.e14 = 1. # Use whatever e14 has been inherited for the type self._prm.updateDihedral(param) ffe = FFEvaluate(self) # print(ffe.evaluate( ret.coords[0] ) ) # input # Now evaluate the ff without the dihedral being fitted for t in range(ret.N): mm_zeroed = (ffe.evaluate(ret.coords[t])["total"]) ret.mm_delta.append(ret.qm[t] - mm_zeroed) ret.mm_zeroed.append(mm_zeroed) mmin1 = min(ret.mm_zeroed) mmin2 = min(ret.mm_delta) for t in range(ret.N): ret.mm_zeroed[t] = ret.mm_zeroed[t] - mmin1 ret.mm_delta[t] = ret.mm_delta[t] - mmin2 self._fitDihedral_results = ret self._fitDihedral_phi = param # Now measure all of the soft dihedrals phis that are mapped to this dihedral ret.phis = [] for i in range(ret.N): ret.phis.append([ret.phi[i]]) for e in equivs: ret.phis[i].append(getPhi(ret.coords[i], e)) # print ("EQUIVALENT DIHEDRALS FOR THIS DIHEDRAL" ) # print(equivs) # print ("PHI VALUES TO FIT") # print (ret.phis) # Set up the NOLOPT fit # There are 13 parameters, k,phi for n=1,2,3,4,5,6 and a shift N = 13 # initial guess, st = np.zeros(13) # bounds best_chisq = self._fitDihedral_objective(best_param) # print("CHISQ of initial = %f" % ( best_chisq ) ) # Now zero out the terms of the dihedral we are going to fit bar = ProgressBar(64, description="Fitting") for i in range(64): (bounds, start) = self._fitDihedral_make_bounds(i) xopt = optimize.minimize(self._fitDihedral_objective, start, method="L-BFGS-B", bounds=bounds, options={'disp': False}) chisq = self._fitDihedral_objective(xopt.x) # print( "CHISQ of fit = %f " % (chisq) ) if (chisq < best_chisq): best_chisq = chisq best_param = xopt.x bar.progress() bar.stop() # print("Best ChiSQ = %f" %(best_chisq) ) # Update the target dihedral with the optimized parameters # print(param) # print(best_param ) for i in range(6): param[i].k0 = best_param[0 + i] param[i].phi0 = best_param[6 + i] self._prm.updateDihedral(param) # print(param) param = self._prm.dihedralParam(self._rtf.type_by_index[atoms[0]], self._rtf.type_by_index[atoms[1]], self._rtf.type_by_index[atoms[2]], self._rtf.type_by_index[atoms[3]]) # print(param) # Finally evaluate the fitted potential ffe = FFEvaluate(self) for t in range(ret.N): ret.mm_fitted.append(ffe.evaluate(ret.coords[t])["total"]) mmin = min(ret.mm_fitted) chisq = 0. # print( "QM energies" ) # print( ret.qm ) for t in range(ret.N): ret.mm_fitted[t] = ret.mm_fitted[t] - mmin delta = ret.mm_fitted[t] - ret.qm[t] chisq = chisq + (delta * delta) ret.chisq = chisq # TODO Score it self.coords = bkp_coords return ret