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 _fit_charges(mol, args, qm, atom_types): from parameterize.charge import ( fitGasteigerCharges, fitChargesWithAntechamber, fitESPCharges, symmetrizeCharges, ) from parameterize.parameterization.util import ( guessBondType, getFixedChargeAtomIndices, getDipole, _qm_method_name, ) from parameterize.parameterization.detect import detectEquivalentAtoms logger.info("=== Atomic charge fitting ===") logger.info("Method: {}".format(args.charge_type)) if args.charge_type == "None": # TODO move to argument validation if len(args.fix_charge) > 0: logger.warning("Flag --fix-charge does not have effect!") logger.info("Atomic charges are taken from {}".format(args.filename)) elif args.charge_type == "Gasteiger": # TODO move to argument validation if len(args.fix_charge) > 0: logger.warning("Flag --fix-charge does not have effect!") # TODO move to _prepare_molecule if np.any(mol.bondtype == "un"): logger.info("Guessing bond types") mol = guessBondType(mol) mol = fitGasteigerCharges(mol, atom_types=atom_types) charge = int(round(np.sum(mol.charge))) if args.charge != charge: logger.warning( f"Molecular charge is {args.charge}, but Gasteiger atomic charges add up to {charge}!" ) args.charge = charge elif args.charge_type == "AM1-BCC": # TODO move to argument validation if len(args.fix_charge) > 0: logger.warning("Flag --fix-charge does not have effect!") mol = fitChargesWithAntechamber(mol, type="bcc", molCharge=args.charge) mol = symmetrizeCharges(mol) elif args.charge_type == "ESP": # Detect equivalent atom groups logger.info("Equivalent atom groups:") atom_groups = [ group for group in detectEquivalentAtoms(mol)[0] if len(group) > 1 ] for atom_group in atom_groups: logger.info(" {}".format(", ".join(mol.name[list(atom_group)]))) # Select the atoms with fixed charges fixed_atom_indices = getFixedChargeAtomIndices(mol, args.fix_charge) # Create an ESP directory espDir = os.path.join(args.outdir, "esp", _qm_method_name(qm)) os.makedirs(espDir, exist_ok=True) charge = int(round(np.sum(mol.charge))) if args.charge != charge: logger.warning( "Molecular charge is set to {}, but atomic charges add up to {}" "".format(args.charge, charge)) if len(args.fix_charge) > 0: raise RuntimeError( "Flag --fix-charge cannot be used when atomic charges are inconsistent with passed " "molecular charge {}".format(args.charge)) mol.charge[:] = args.charge / mol.numAtoms # Set random number generator seed if args.seed: np.random.seed(args.seed) # Fit ESP charges mol, extra = fitESPCharges(mol, qm, espDir, fixed=fixed_atom_indices) # Print QM dipole logger.info( "QM dipole: {:6.3f} {:6.3f} {:6.3f}; total: {:6.3f}".format( *extra["qm_dipole"])) else: raise ValueError() # Print MM dipole mm_dipole = getDipole(mol) logger.info("MM dipole: {:6.3f} {:6.3f} {:6.3f}; total: {:6.3f}".format( *mm_dipole)) # Print the new charges logger.info("Atomic charges:") for name, charge in zip(mol.name, mol.charge): logger.info(" {:4s}: {:6.3f}".format(name, charge)) logger.info("Molecular charge: {:6.3f}".format(np.sum(mol.charge))) return mol
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(0.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