def writeParameters(mol, parameters, qm, method, netcharge, outdir, original_coords=None): paramDir = os.path.join(outdir, 'parameters', method.name, _qm_method_name(qm)) os.makedirs(paramDir, exist_ok=True) typemap = None extensions = ('mol2', 'pdb', 'coor') if method == FFTypeMethod.CGenFF_2b6: extensions += ('psf', 'rtf', 'prm') # TODO: remove? f = open(os.path.join(paramDir, "input.namd"), "w") tmp = '''parameters mol.prm paraTypeCharmm on coordinates mol.pdb bincoordinates mol.coor temperature 0 timestep 0 1-4scaling 1.0 exclude scaled1-4 outputname .out outputenergies 1 structure mol.psf cutoff 20. switching off stepsPerCycle 1 rigidbonds none cellBasisVector1 50. 0. 0. cellBasisVector2 0. 50. 0. cellBasisVector3 0. 0. 50. run 0''' print(tmp, file=f) f.close() elif method in (FFTypeMethod.GAFF, FFTypeMethod.GAFF2): # types need to be remapped because Amber FRCMOD format limits the type to characters # writeFrcmod does this on the fly and returns a mapping that needs to be applied to the mol # TODO: get rid of this mapping frcFile = os.path.join(paramDir, 'mol.frcmod') typemap = getAtomTypeMapping(parameters) writeFRCMOD(mol, parameters, frcFile, typemap=typemap) logger.info('Write FRCMOD file: %s' % frcFile) tleapFile = os.path.join(paramDir, 'tleap.in') with open(tleapFile, 'w') as file_: file_.write('loadAmberParams mol.frcmod\n') file_.write('A = loadMol2 mol.mol2\n') file_.write('saveAmberParm A structure.prmtop mol.crd\n') file_.write('quit\n') logger.info('Write tleap input file: %s' % tleapFile) # TODO: remove? f = open(os.path.join(paramDir, "input.namd"), "w") tmp = '''parmfile structure.prmtop amber on coordinates mol.pdb bincoordinates mol.coor temperature 0 timestep 0 1-4scaling 0.83333333 exclude scaled1-4 outputname .out outputenergies 1 cutoff 20. switching off stepsPerCycle 1 rigidbonds none cellBasisVector1 50. 0. 0. cellBasisVector2 0. 50. 0. cellBasisVector3 0. 0. 50. run 0''' print(tmp, file=f) f.close() else: raise NotImplementedError def remapAtomTypes(mol): tmpmol = mol if typemap is not None: tmpmol = mol.copy() tmpmol.atomtype[:] = [ typemap[atomtype] for atomtype in mol.atomtype ] return tmpmol tmpmol = remapAtomTypes(mol) for ext in extensions: file_ = os.path.join(paramDir, "mol." + ext) if ext == 'prm': writePRM(mol, parameters, file_) elif ext == 'rtf': writeRTF(mol, parameters, netcharge, file_) else: tmpmol.write(file_) logger.info('Write %s file: %s' % (ext.upper(), file_)) if original_coords is not None: molFile = os.path.join(paramDir, 'mol-orig.mol2') tmpmol.coords = original_coords tmpmol.write(molFile) logger.info( 'Write MOL2 file (with original coordinates): {}'.format(molFile))
def main_parameterize(arguments=None): args = getArgumentParser().parse_args(args=arguments) if not os.path.exists(args.filename): raise ValueError('File %s cannot be found' % args.filename) method_map = { 'GAFF': FFTypeMethod.GAFF, 'GAFF2': FFTypeMethod.GAFF2, 'CGENFF': FFTypeMethod.CGenFF_2b6 } methods = [method_map[method] for method in args.forcefield] # Get RTF and PRM file names rtfFile, prmFile = None, None if args.rtf_prm: rtfFile, prmFile = args.rtf_prm # Create a queue for QM if args.queue == 'local': queue = LocalCPUQueue() elif args.queue == 'Slurm': queue = SlurmQueue(_configapp=args.code.lower()) elif args.queue == 'LSF': queue = LsfQueue(_configapp=args.code.lower()) elif args.queue == 'PBS': queue = PBSQueue() # TODO: configure elif args.queue == 'AceCloud': queue = AceCloudQueue() # TODO: configure queue.groupname = args.groupname queue.hashnames = True else: raise NotImplementedError # Override default ncpus if args.ncpus: logger.info('Overriding ncpus to {}'.format(args.ncpus)) queue.ncpu = args.ncpus if args.memory: logger.info('Overriding memory to {}'.format(args.memory)) queue.memory = args.memory # Create a QM object if args.code == 'Psi4': qm = Psi4() elif args.code == 'Gaussian': qm = Gaussian() else: raise NotImplementedError # This is for debugging only! if args.fake_qm: qm = FakeQM2() logger.warning('Using FakeQM') # Get rotatable dihedral angles mol = Molecule(args.filename) mol = canonicalizeAtomNames(mol) mol, equivalents, all_dihedrals = getEquivalentsAndDihedrals(mol) netcharge = args.charge if args.charge is not None else int( round(np.sum(mol.charge))) if args.list: print('\n === Parameterizable dihedral angles of {} ===\n'.format( args.filename)) with open('torsions.txt', 'w') as fh: for dihedral in all_dihedrals: dihedral_name = '-'.join(mol.name[list(dihedral[0])]) print(' {}'.format(dihedral_name)) fh.write(dihedral_name + '\n') print() sys.exit(0) # Set up the QM object qm.theory = args.theory qm.basis = args.basis qm.solvent = args.environment qm.queue = queue qm.charge = netcharge # Select which dihedrals to fit parameterizable_dihedrals = [list(dih[0]) for dih in all_dihedrals] if len(args.dihedral) > 0: all_dihedral_names = [ '-'.join(mol.name[list(dihedral[0])]) for dihedral in all_dihedrals ] parameterizable_dihedrals = [] for dihedral_name in args.dihedral: if dihedral_name not in all_dihedral_names: raise ValueError( '%s is not recognized as a rotatable dihedral angle' % dihedral_name) parameterizable_dihedrals.append( list( all_dihedrals[all_dihedral_names.index(dihedral_name)][0])) # Print arguments print('\n === Arguments ===\n') for key, value in vars(args).items(): print('{:>12s}: {:s}'.format(key, str(value))) print('\n === Parameterizing %s ===\n' % args.filename) for method in methods: print(" === Fitting for %s ===\n" % method.name) printReport(mol, netcharge, equivalents, all_dihedrals) parameters, mol = fftype(mol, method=method, rtfFile=rtfFile, prmFile=prmFile, netcharge=args.charge) if isinstance(qm, FakeQM2): qm._parameters = parameters # Copy the molecule to preserve initial coordinates orig_coor = mol.coords.copy() # Update B3LYP to B3LYP-D3 # TODO: this is silent and not documented stuff if qm.theory == 'B3LYP': qm.correction = 'D3' # Update basis sets # TODO: this is silent and not documented stuff if netcharge < 0 and qm.solvent == 'vacuum': if qm.basis == '6-31G*': qm.basis = '6-31+G*' if qm.basis == 'cc-pVDZ': qm.basis = 'aug-cc-pVDZ' logger.info('Changing basis sets to %s' % qm.basis) # Minimize molecule if args.minimize: print('\n == Minimizing ==\n') mol = minimize(mol, qm, args.outdir) # Fit ESP charges if args.fit_charges: print('\n == Fitting ESP charges ==\n') # Set random number generator seed if args.seed: np.random.seed(args.seed) # Select the atoms with fixed charges fixed_atom_indices = getFixedChargeAtomIndices( mol, args.fix_charge) # Fit ESP charges mol, _, esp_charges, qm_dipole = fitCharges( mol, qm, args.outdir, fixed=fixed_atom_indices) # Print dipoles logger.info('QM dipole: %f %f %f; %f' % tuple(qm_dipole)) mm_dipole = getDipole(mol) if np.all(np.isfinite(mm_dipole)): logger.info('MM dipole: %f %f %f; %f' % tuple(mm_dipole)) else: logger.warning( 'MM dipole cannot be computed. Check if elements are detected correctly.' ) # Fit dihedral angle parameters if args.fit_dihedral: print('\n == Fitting dihedral angle parameters ==\n') # Set random number generator seed if args.seed: np.random.seed(args.seed) # Invent new atom types for dihedral atoms mol, originaltypes = inventAtomTypes(mol, parameterizable_dihedrals, equivalents) parameters = recreateParameters(mol, originaltypes, parameters) parameters = createMultitermDihedralTypes(parameters) if isinstance(qm, FakeQM2): qm._parameters = parameters # Fit the parameters fitDihedrals(mol, qm, method, parameters, all_dihedrals, parameterizable_dihedrals, args.outdir, geomopt=args.optimize_dihedral) # Output the FF parameters print('\n == Writing results ==\n') writeParameters(mol, parameters, qm, method, netcharge, args.outdir, original_coords=orig_coor) # Write energy file energyFile = os.path.join(args.outdir, 'parameters', method.name, _qm_method_name(qm), 'energies.txt') printEnergies(mol, parameters, energyFile) logger.info('Write energy file: %s' % energyFile)
def _fit_charges(mol, args, qm, atom_types): from htmd.charge import fitGasteigerCharges, fitChargesWithAntechamber, fitESPCharges, symmetrizeCharges from htmd.parameterization.util import guessBondType, getFixedChargeAtomIndices, getDipole, _qm_method_name from htmd.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: raise RuntimeError( 'Molecular charge is set to {}, but Gasteiger atomic charges add up to {}.' .format(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 main_parameterize(arguments=None): args = getArgumentParser().parse_args(args=arguments) if not os.path.exists(args.filename): raise ValueError('File %s cannot be found' % args.filename) method_map = {'GAFF': FFTypeMethod.GAFF, 'GAFF2': FFTypeMethod.GAFF2, 'CGENFF': FFTypeMethod.CGenFF_2b6} methods = [method_map[method] for method in args.forcefield] # Get RTF and PRM file names rtfFile, prmFile = None, None if args.rtf_prm: rtfFile, prmFile = args.rtf_prm # Create a queue for QM if args.queue == 'local': queue = LocalCPUQueue() elif args.queue == 'Slurm': queue = SlurmQueue(_configapp=args.code.lower()) elif args.queue == 'LSF': queue = LsfQueue(_configapp=args.code.lower()) elif args.queue == 'PBS': queue = PBSQueue() # TODO: configure elif args.queue == 'AceCloud': queue = AceCloudQueue() # TODO: configure queue.groupname = args.groupname queue.hashnames = True else: raise NotImplementedError # Override default ncpus if args.ncpus: logger.info('Overriding ncpus to {}'.format(args.ncpus)) queue.ncpu = args.ncpus if args.memory: logger.info('Overriding memory to {}'.format(args.memory)) queue.memory = args.memory # Create a QM object if args.code == 'Psi4': qm = Psi4() elif args.code == 'Gaussian': qm = Gaussian() else: raise NotImplementedError # This is for debugging only! if args.fake_qm: qm = FakeQM2() logger.warning('Using FakeQM') # Get rotatable dihedral angles mol = Molecule(args.filename) mol = canonicalizeAtomNames(mol) mol, equivalents, all_dihedrals = getEquivalentsAndDihedrals(mol) netcharge = args.charge if args.charge is not None else int(round(np.sum(mol.charge))) if args.list: print('\n === Parameterizable dihedral angles of {} ===\n'.format(args.filename)) with open('torsions.txt', 'w') as fh: for dihedral in all_dihedrals: dihedral_name = '-'.join(mol.name[list(dihedral[0])]) print(' {}'.format(dihedral_name)) fh.write(dihedral_name+'\n') print() sys.exit(0) # Set up the QM object qm.theory = args.theory qm.basis = args.basis qm.solvent = args.environment qm.queue = queue qm.charge = netcharge # Select which dihedrals to fit parameterizable_dihedrals = [list(dih[0]) for dih in all_dihedrals] if len(args.dihedral) > 0: all_dihedral_names = ['-'.join(mol.name[list(dihedral[0])]) for dihedral in all_dihedrals] parameterizable_dihedrals = [] for dihedral_name in args.dihedral: if dihedral_name not in all_dihedral_names: raise ValueError('%s is not recognized as a rotatable dihedral angle' % dihedral_name) parameterizable_dihedrals.append(list(all_dihedrals[all_dihedral_names.index(dihedral_name)][0])) # Print arguments print('\n === Arguments ===\n') for key, value in vars(args).items(): print('{:>12s}: {:s}'.format(key, str(value))) print('\n === Parameterizing %s ===\n' % args.filename) for method in methods: print(" === Fitting for %s ===\n" % method.name) printReport(mol, netcharge, equivalents, all_dihedrals) parameters, mol = fftype(mol, method=method, rtfFile=rtfFile, prmFile=prmFile, netcharge=args.charge) if isinstance(qm, FakeQM2): qm._parameters = parameters # Copy the molecule to preserve initial coordinates orig_coor = mol.coords.copy() # Update B3LYP to B3LYP-D3 # TODO: this is silent and not documented stuff if qm.theory == 'B3LYP': qm.correction = 'D3' # Update basis sets # TODO: this is silent and not documented stuff if netcharge < 0 and qm.solvent == 'vacuum': if qm.basis == '6-31G*': qm.basis = '6-31+G*' if qm.basis == 'cc-pVDZ': qm.basis = 'aug-cc-pVDZ' logger.info('Changing basis sets to %s' % qm.basis) # Minimize molecule if args.minimize: print('\n == Minimizing ==\n') mol = minimize(mol, qm, args.outdir) # Fit ESP charges if args.fit_charges: print('\n == Fitting ESP charges ==\n') # Set random number generator seed if args.seed: np.random.seed(args.seed) # Select the atoms with fixed charges fixed_atom_indices = getFixedChargeAtomIndices(mol, args.fix_charge) # Fit ESP charges mol, _, esp_charges, qm_dipole = fitCharges(mol, qm, args.outdir, fixed=fixed_atom_indices) # Print dipoles logger.info('QM dipole: %f %f %f; %f' % tuple(qm_dipole)) mm_dipole = getDipole(mol) if np.all(np.isfinite(mm_dipole)): logger.info('MM dipole: %f %f %f; %f' % tuple(mm_dipole)) else: logger.warning('MM dipole cannot be computed. Check if elements are detected correctly.') # Fit dihedral angle parameters if args.fit_dihedral: print('\n == Fitting dihedral angle parameters ==\n') # Set random number generator seed if args.seed: np.random.seed(args.seed) # Invent new atom types for dihedral atoms mol, originaltypes = inventAtomTypes(mol, parameterizable_dihedrals, equivalents) parameters = recreateParameters(mol, originaltypes, parameters) parameters = createMultitermDihedralTypes(parameters) if isinstance(qm, FakeQM2): qm._parameters = parameters # Fit the parameters fitDihedrals(mol, qm, method, parameters, all_dihedrals, parameterizable_dihedrals, args.outdir, geomopt=args.optimize_dihedral) # Output the FF parameters print('\n == Writing results ==\n') writeParameters(mol, parameters, qm, method, netcharge, args.outdir, original_coords=orig_coor) # Write energy file energyFile = os.path.join(args.outdir, 'parameters', method.name, _qm_method_name(qm), 'energies.txt') printEnergies(mol, parameters, energyFile) logger.info('Write energy file: %s' % energyFile)
def writeParameters(mol, parameters, qm, method, netcharge, outdir, original_coords=None): paramDir = os.path.join(outdir, 'parameters', method.name, _qm_method_name(qm)) os.makedirs(paramDir, exist_ok=True) typemap = None extensions = ('mol2', 'pdb', 'coor') if method == FFTypeMethod.CGenFF_2b6: extensions += ('psf', 'rtf', 'prm') # TODO: remove? f = open(os.path.join(paramDir, "input.namd"), "w") tmp = '''parameters mol.prm paraTypeCharmm on coordinates mol.pdb bincoordinates mol.coor temperature 0 timestep 0 1-4scaling 1.0 exclude scaled1-4 outputname .out outputenergies 1 structure mol.psf cutoff 20. switching off stepsPerCycle 1 rigidbonds none cellBasisVector1 50. 0. 0. cellBasisVector2 0. 50. 0. cellBasisVector3 0. 0. 50. run 0''' print(tmp, file=f) f.close() elif method in (FFTypeMethod.GAFF, FFTypeMethod.GAFF2): # types need to be remapped because Amber FRCMOD format limits the type to characters # writeFrcmod does this on the fly and returns a mapping that needs to be applied to the mol # TODO: get rid of this mapping frcFile = os.path.join(paramDir, 'mol.frcmod') typemap = getAtomTypeMapping(parameters) writeFRCMOD(mol, parameters, frcFile, typemap=typemap) logger.info('Write FRCMOD file: %s' % frcFile) tleapFile = os.path.join(paramDir, 'tleap.in') with open(tleapFile, 'w') as file_: file_.write('loadAmberParams mol.frcmod\n') file_.write('A = loadMol2 mol.mol2\n') file_.write('saveAmberParm A structure.prmtop mol.crd\n') file_.write('quit\n') logger.info('Write tleap input file: %s' % tleapFile) # TODO: remove? f = open(os.path.join(paramDir, "input.namd"), "w") tmp = '''parmfile structure.prmtop amber on coordinates mol.pdb bincoordinates mol.coor temperature 0 timestep 0 1-4scaling 0.83333333 exclude scaled1-4 outputname .out outputenergies 1 cutoff 20. switching off stepsPerCycle 1 rigidbonds none cellBasisVector1 50. 0. 0. cellBasisVector2 0. 50. 0. cellBasisVector3 0. 0. 50. run 0''' print(tmp, file=f) f.close() else: raise NotImplementedError def remapAtomTypes(mol): tmpmol = mol if typemap is not None: tmpmol = mol.copy() tmpmol.atomtype[:] = [typemap[atomtype] for atomtype in mol.atomtype] return tmpmol tmpmol = remapAtomTypes(mol) for ext in extensions: file_ = os.path.join(paramDir, "mol." + ext) if ext == 'prm': writePRM(mol, parameters, file_) elif ext == 'rtf': writeRTF(mol, parameters, netcharge, file_) else: tmpmol.write(file_) logger.info('Write %s file: %s' % (ext.upper(), file_)) if original_coords is not None: molFile = os.path.join(paramDir, 'mol-orig.mol2') tmpmol.coords = original_coords tmpmol.write(molFile) logger.info('Write MOL2 file (with original coordinates): {}'.format(molFile))