def fftype(mol, rtfFile=None, prmFile=None, method='GAFF2', acCharges=None, tmpDir=None, netcharge=None): """ Assing atom types and force field parameters for a given molecule. Additionally, atom masses and improper dihedral are set. Optionally, atom charges can be set if `acCharges` is set (see below). The assignment can be done: 1. For CHARMM CGenFF_2b6 with MATCH (method = 'CGenFF_2b6'); 2. For AMBER GAFF with antechamber (method = 'GAFF'); 3. For AMBER GAFF2 with antechamber (method = 'GAFF2'); Parameters ---------- mol : Molecule Molecule to use for the assignment rtfFile : str Path to a RTF file from which to read the topology prmFile : str Path to a PRM file from which to read the parameters method : str Atomtyping assignment method. Use :func:`fftype.listFftypemethods <htmd.parameterization.fftype.listFftypemethods>` to get a list of available methods. Default: :func:`fftype.defaultFftypemethod <htmd.parameterization.fftype.defaultFftypemethod>` acCharges : str Optionally assign charges with antechamber. Check `antechamber -L` for available options. Note: only works for GAFF and GAFF2. tmpDir: str Directory for temporary files. If None, a directory is created and deleted automatically. netcharge : float The net charge of the molecule. Returns ------- prm : :class:`ParameterSet <parmed.parameters.ParameterSet>` object Returns a parmed ParameterSet object with the parameters. mol : :class:`Molecule <htmd.molecule.molecule.Molecule>` object The modified Molecule object with the matching atom types for the ParameterSet """ import parmed if method not in fftypemethods: raise ValueError('Invalid method {}. Available methods {}'.format( method, ','.join(fftypemethods))) if method == 'CGenFF_2b6' and acCharges: raise ValueError('acCharges') if netcharge is None: netcharge = int(round(np.sum(mol.charge))) logger.warning( 'Molecular charge is set to {} by adding up the atomic charges'. format(netcharge)) if rtfFile and prmFile: from htmd.parameterization.readers import readRTF logger.info('Reading FF parameters from {} and {}'.format( rtfFile, prmFile)) prm = parmed.charmm.CharmmParameterSet(rtfFile, prmFile) names, elements, atomtypes, charges, masses, impropers = readRTF( rtfFile) else: logger.info('Assigning atom types with {}'.format(method)) renamed_mol = _canonicalizeAtomNames(mol) # Create a temporary directory with TemporaryDirectory() as tmpdir: # HACK to keep the files tmpdir = tmpdir if tmpDir is None else tmpDir logger.debug('Temporary directory: {}'.format(tmpdir)) if method in ('GAFF', 'GAFF2'): from htmd.molecule.molecule import Molecule from htmd.parameterization.readers import readPREPI, readFRCMOD # Write the molecule to a file renamed_mol.write(os.path.join(tmpdir, 'mol.mol2')) atomtype = method.lower() # Set arguments cmd = [ 'antechamber', '-at', atomtype, '-nc', str(netcharge), '-fi', 'mol2', '-i', 'mol.mol2', '-fo', 'prepi', '-o', 'mol.prepi' ] if acCharges is not None: cmd += ['-c', acCharges] # Run antechamber with TemporaryFile() as stream: if subprocess.call( cmd, cwd=tmpdir, stdout=stream, stderr=stream) != 0: raise RuntimeError('"antechamber" failed') stream.seek(0) for line in stream.readlines(): logger.debug(line) # Set arguments cmd = [ 'parmchk2', '-f', 'prepi', '-s', atomtype, '-i', 'mol.prepi', '-o', 'mol.frcmod', '-a', 'Y' ] # Run parmchk2 with TemporaryFile() as stream: if subprocess.call( cmd, cwd=tmpdir, stdout=stream, stderr=stream) != 0: raise RuntimeError('"parmchk2" failed') stream.seek(0) for line in stream.readlines(): logger.debug(line) # Check if antechamber did changes in atom names (and suggest the user to fix the names) acmol = Molecule(os.path.join(tmpdir, 'NEWPDB.PDB'), type='pdb') acmol.name = np.array([n.upper() for n in acmol.name]).astype(np.object) changed_mol_acmol = np.setdiff1d(renamed_mol.name, acmol.name) changed_acmol_mol = np.setdiff1d(acmol.name, renamed_mol.name) if len(changed_mol_acmol) != 0 or len(changed_acmol_mol) != 0: raise RuntimeError( 'Initial atom names {} were changed by antechamber to {}. ' 'This probably means that the start of the atom name does not match ' 'element symbol. ' 'Please check the molecule.' ''.format(','.join(changed_mol_acmol), ','.join(changed_acmol_mol))) # Read the results prm = parmed.amber.AmberParameterSet( os.path.join(tmpdir, 'mol.frcmod')) names, atomtypes, charges, impropers = readPREPI( renamed_mol, os.path.join(tmpdir, 'mol.prepi')) masses, elements = readFRCMOD( atomtypes, os.path.join(tmpdir, 'mol.frcmod')) elif method == 'CGenFF_2b6': from htmd.parameterization.readers import readRTF # Write the molecule to a file renamed_mol.write(os.path.join(tmpdir, 'mol.pdb')) # Set arguments cmd = [ 'match-typer', '-charge', str(netcharge), '-forcefield', 'top_all36_cgenff_new', 'mol.pdb' ] # Run match-type with TemporaryFile() as stream: if subprocess.call( cmd, cwd=tmpdir, stdout=stream, stderr=stream) != 0: raise RuntimeError('"match-typer" failed') stream.seek(0) for line in stream.readlines(): logger.debug(line) prm = parmed.charmm.CharmmParameterSet( os.path.join(tmpdir, 'mol.rtf'), os.path.join(tmpdir, 'mol.prm')) names, elements, atomtypes, charges, masses, impropers = readRTF( os.path.join(tmpdir, 'mol.rtf')) else: raise ValueError('Invalid method {}'.format(method)) assert np.all(renamed_mol.name == names) assert np.all(mol.element == elements) mol = mol.copy() mol.atomtype = atomtypes mol.masses = masses mol.impropers = impropers if acCharges is not None: mol.charge = charges return prm, mol
def fftype(mol, rtfFile=None, prmFile=None, method=FFTypeMethod.CGenFF_2b6, acCharges=None, tmpDir=None, netcharge=None): """ Function to assign atom types and force field parameters for a given molecule. The assigment can be done: 1. For CHARMM CGenFF_2b6 with MATCH (method = FFTypeMethod.CGenFF_2b6); 2. For AMBER GAFF with antechamber (method = FFTypeMethod.GAFF); 3. For AMBER GAFF2 with antechamber (method = FFTypeMethod.GAFF2); Parameters ---------- mol : FFMolecule Molecule to use for the assigment rtfFile : str Path to a RTF file from which to read the topology prmFile : str Path to a PRM file from which to read the parameters method : FFTypeMethod Assigment method acCharges : str Optionally assign charges with antechamber. Check `antechamber -L` for available options. Caution: This will overwrite any charges defined in the mol2 file. tmpDir: str Directory for temporary files. If None, a directory is created and deleted automatically. netcharge : float The net charge of the molecule. Returns ------- prm : :class:`ParameterSet<parmed.parameters.ParameterSet>` object Returns a parmed ParameterSet object with the parameters. mol : :class:`Molecule <htmd.molecule.molecule.Molecule>` object The modified Molecule object with the matching atom types for the ParameterSet """ if netcharge is None: netcharge = np.sum(mol.charge) netcharge = int(round(netcharge)) if rtfFile and prmFile: logger.info('Reading FF parameters from {} and {}'.format( rtfFile, prmFile)) prm = parmed.charmm.CharmmParameterSet(rtfFile, prmFile) names, elements, atomtypes, charges, masses, impropers = readRTF( rtfFile) # addParmedResidue(prm, names, elements, atomtypes, charges, impropers) else: logger.info('Assigned atom types with {}'.format(method.name)) # Find the executables if method == FFTypeMethod.GAFF or method == FFTypeMethod.GAFF2: antechamber_binary = shutil.which("antechamber") if not antechamber_binary: raise RuntimeError("antechamber executable not found") parmchk2_binary = shutil.which("parmchk2") if not parmchk2_binary: raise RuntimeError("parmchk2 executable not found") elif method == FFTypeMethod.CGenFF_2b6: match_binary = shutil.which("match-typer") if not match_binary: raise RuntimeError("match-typer executable not found") else: raise ValueError('method') # Create a temporary directory with TemporaryDirectory() as tmpdir: # HACK to keep the files tmpdir = tmpdir if tmpDir is None else tmpDir if method == FFTypeMethod.GAFF or method == FFTypeMethod.GAFF2: # Write the molecule to a file mol.write(os.path.join(tmpdir, 'mol.mol2')) # Run antechamber if method == FFTypeMethod.GAFF: atomtype = "gaff" elif method == FFTypeMethod.GAFF2: atomtype = "gaff2" else: raise ValueError('method') cmd = [ antechamber_binary, '-at', atomtype, '-nc', str(netcharge), '-fi', 'mol2', '-i', 'mol.mol2', '-fo', 'prepi', '-o', 'mol.prepi' ] if acCharges is not None: cmd += ['-c', acCharges] returncode = subprocess.call(cmd, cwd=tmpdir) if returncode != 0: raise RuntimeError('"antechamber" failed') # Run parmchk2 returncode = subprocess.call([ parmchk2_binary, '-f', 'prepi', '-i', 'mol.prepi', '-o', 'mol.frcmod', '-a', 'Y' ], cwd=tmpdir) if returncode != 0: raise RuntimeError('"parmchk2" failed') # Read the results prm = parmed.amber.AmberParameterSet( os.path.join(tmpdir, 'mol.frcmod')) names, elements, atomtypes, charges, masses, impropers = readPREPI( mol, os.path.join(tmpdir, 'mol.prepi')) # addParmedResidue(prm, names, elements, atomtypes, charges, impropers) elif method == FFTypeMethod.CGenFF_2b6: # Write the molecule to a file mol.write(os.path.join(tmpdir, 'mol.pdb')) # Run match-type returncode = subprocess.call([ match_binary, '-charge', str(netcharge), '-forcefield', 'top_all36_cgenff_new', 'mol.pdb' ], cwd=tmpdir) if returncode != 0: raise RuntimeError('"match-typer" failed') prm = parmed.charmm.CharmmParameterSet( os.path.join(tmpdir, 'mol.rtf'), os.path.join(tmpdir, 'mol.prm')) names, elements, atomtypes, charges, masses, impropers = readRTF( os.path.join(tmpdir, 'mol.rtf')) # addParmedResidue(prm, names, elements, atomtypes, charges, impropers) else: raise ValueError('Invalide method {}'.format(method)) # Substituting values from the read-in topology mol.name = names mol.element = elements mol.atomtype = atomtypes mol.charge = charges mol.impropers = impropers if len(mol.masses) == 0: mol.masses = masses return prm, mol