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 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 main_parameterize(arguments=None, progress=None): from htmd.parameterization.parameterset import recreateParameters, createMultitermDihedralTypes, inventAtomTypes from htmd.parameterization.util import detectChiralCenters, scanDihedrals, filterQMResults, minimize progress = progress if callable(progress) else lambda x: None logger.info('===== Parameterize =====') # Parse arguments parser = getArgumentParser() args = parser.parse_args(args=arguments) _printArguments(args) # Validate arguments if args.fake_qm and args.nnp: raise ValueError('FakeQM and NNP are not compatible') # Configure loggers if args.debug: logger.setLevel(logging.DEBUG) logger.debug(sys.argv[1:]) # Deprecation warnings # TODO remove at some point of time if args.minimize is not parser.get_default('minimize'): raise DeprecationWarning('Use `--min-type` instead.') if args.optimize_dihedral is not parser.get_default('optimize_dihedral'): raise DeprecationWarning('Use `--scan-type` instead.') # Get a molecule and check its validity progress('Prepare the molecule') mol = _prepare_molecule(args) # Preserve the initial molecule initial_mol = mol.copy() # Select dihedral angles to parameterize progress('Detect dihedral angles') selected_dihedrals = _select_dihedrals(mol, args) # Get a queue queue = _get_queue(args) # Get QM calculators qm_calculator = None qm_name = '' if args.min_type == 'qm' or args.charge_type == 'ESP' or ( args.fit_dihedral and not args.nnp): qm_calculator = _get_qm_calculator(args, queue) qm_name = '{}/{}'.format(qm_calculator.theory, qm_calculator.basis) if args.fake_qm: qm_name = 'Fake QM' # Get NNP calculators nnp_calculator = None nnp_name = '' if args.nnp: nnp_calculator = _get_nnp_calculator(args, queue) nnp_name = args.nnp # Set the reference calculator if args.nnp: ref_calculator = nnp_calculator ref_name = nnp_name else: ref_calculator = qm_calculator ref_name = qm_name logger.info('Reference method: {}'.format(ref_name)) # Assign atom types and initial force field parameters progress('Assign atom types and initial parameters') mol, parameters = _get_initial_parameters(mol, args) # Assign initial atomic charges, if needed progress('Assign initial atomic charges') mol = _fit_initial_charges(mol, args, initial_mol.atomtype) # Geometry minimization # TODO refactor if args.min_type != 'None': progress('Optimize geometry') logger.info(' === Geometry minimization ===') if args.min_type == 'mm': logger.info('Model: MM with the initial force field parameters') elif args.min_type == 'qm': logger.info('Model: the reference method') else: raise ValueError() # Set parameters for the fake QM if args.fake_qm: assert not args.nnp ref_calculator._parameters = parameters # Detect chiral centers initial_chiral_centers = detectChiralCenters( mol, atom_types=initial_mol.atomtype) mm_minimizer = None if args.min_type == 'mm': from htmd.qm.custom import OMMMinimizer mm_minimizer = OMMMinimizer(mol, parameters) # Minimize molecule mol = minimize(mol, ref_calculator, args.outdir, min_type=args.min_type, mm_minimizer=mm_minimizer) # TODO print minimization status # Check if the chiral center hasn't changed during the minimization chiral_centers = detectChiralCenters(mol, atom_types=initial_mol.atomtype) if initial_chiral_centers != chiral_centers: raise RuntimeError( 'Chiral centers have changed during the minimization: ' '{} --> {}'.format(initial_chiral_centers, chiral_centers)) # Fit charges progress('Assign atomic charges') mol = _fit_charges(mol, args, qm_calculator, initial_mol.atomtype) # Scan dihedrals and fit parameters # TODO refactor if len(selected_dihedrals) > 0: from htmd.parameterization.dihedral import DihedralFitting # Slow import progress('Scan dihedral angles') logger.info('=== Dihedral angle scanning ===') if args.dihed_opt_type == 'None': logger.info('Dihedral scanning: static') elif args.dihed_opt_type == 'mm': logger.info( 'Dihedral scanning: minimized with MM (using the initial force field parameters)' ) elif args.dihed_opt_type == 'qm': logger.info( 'Dihedral scanning: minimized with the reference method') else: raise ValueError() # Recreate MM minimizer now that charges have been fitted mm_minimizer = None if args.dihed_opt_type == 'mm': from htmd.qm.custom import OMMMinimizer mm_minimizer = OMMMinimizer(mol, parameters) # Set parameters for the fake QM if args.fake_qm: assert not args.nnp ref_calculator._parameters = parameters # Scan dihedral angles scan_results = scanDihedrals(mol, ref_calculator, selected_dihedrals, args.outdir, scan_type=args.dihed_opt_type, mm_minimizer=mm_minimizer) # Filter scan results scan_results = filterQMResults(scan_results, mol=initial_mol) logger.info('Valid rotamers:') for idihed, (dihedral, results) in enumerate( zip(selected_dihedrals, scan_results)): dihed_name = '-'.join(mol.name[list(dihedral)]) logger.info(' {:2d}: {}: {}'.format(idihed, dihed_name, len(results))) if len(results) < 13: raise RuntimeError( 'Less than 13 valid rotamers for {} dihedral. ' 'Not enough for fitting!'.format(dihed_name)) logger.info('=== Dihedral parameter fitting ===') # Invent new atom types for dihedral atoms progress('Create new atom types') old_types = mol.atomtype mol, initial_types = inventAtomTypes(mol, selected_dihedrals) parameters = recreateParameters(mol, initial_types, parameters) parameters = createMultitermDihedralTypes(parameters) logger.info('Assign atom with new atom types:') for name, old_type, new_type in zip(mol.name, old_types, mol.atomtype): if old_type != new_type: logger.info(' {:4s} : {:6s} --> {:6s}'.format( name, old_type, new_type)) # Set random number generator seed if args.seed: np.random.seed(args.seed) # Fit the dihedral parameters progress('Fit dihedral angle parameters') df = DihedralFitting() df.parameters = parameters df.molecule = mol df.dihedrals = selected_dihedrals df.qm_results = scan_results df.num_iterations = args.dihed_num_iterations df.fit_type = args.dihed_fit_type df.result_directory = os.path.join(args.outdir, 'parameters', args.forcefield) # In case of FakeQM, the initial parameters are set to zeros. # It prevents DihedralFitting class from cheating :D if args.fake_qm: df.zeroed_parameters = True # Fit dihedral parameters parameters = df.run() # Plot dihedral profiles plot_dir = os.path.join(args.outdir, 'parameters', args.forcefield, 'plots') os.makedirs(plot_dir, exist_ok=True) df.plotConformerEnergies(plot_dir, ref_name=ref_name) for idihed in range(len(df.dihedrals)): df.plotDihedralEnergies(idihed, plot_dir, ref_name=ref_name) # Output the parameters and other results progress('Output results') _output_results(mol, parameters, initial_mol.coords, args)