def test_get_all_childs_of_atom(self): """ It tests the _get_all_childs_of_atom method used in the building process of the Impact template. """ LIGAND_PATH = get_data_file_path('ligands/malonate.pdb') FORCEFIELD_NAME = 'openff_unconstrained-1.2.1.offxml' molecule = Molecule(LIGAND_PATH) openff = OpenForceField(FORCEFIELD_NAME) parameters = openff.parameterize(molecule, charge_method='dummy') topology = Topology(molecule, parameters) impact = Impact(topology) absolute_parent = impact._get_absolute_parent_atom() childs = impact._get_all_childs_of_atom(absolute_parent, 'side chain') assert [a.PDB_name for a in childs] == \ ['_C1_', '_C3_'], \ 'Unexpected side-chain-child atoms: {}'.format(childs) childs = impact._get_all_childs_of_atom(absolute_parent, 'core') assert [a.PDB_name for a in childs] == \ ['_H1_', '_H2_'], \ 'Unexpected core-child atoms: {}'.format(childs)
def _generate_parameters(self): """ It generates the parameters of the molecule (from the input_file) as DataLocal in the output folder. """ import peleffy from peleffy.topology import Molecule from peleffy.template import Impact from peleffy.solvent import OBC2 from peleffy.main import handle_output_paths import os # Forcefield and charges method forcefield = 'openff_unconstrained-1.2.0.offxml' charges_method = 'am1bcc' # Create representation of a particular molecule PATH_molecule = os.path.join(os.getcwd(), 'output', 'ligand.pdb') molecule = Molecule(PATH_molecule) # Saving paths rotamer_library_output_path, impact_output_path, solvent_output_path = \ handle_output_paths(molecule = molecule, output =os.path.join(os.getcwd(),'output'), as_datalocal = True ) # Generate its rotamer library rotamer_library = peleffy.topology.RotamerLibrary(molecule) rotamer_library.to_file(rotamer_library_output_path) # Generate its parameters and template file molecule.parameterize(forcefield, charges_method=charges_method) impact = Impact(molecule) impact.write(impact_output_path) # Generate its solvent parameters solvent = OBC2(molecule) solvent.to_json_file(solvent_output_path)
def test_input(self): """ It tests that the topology given to Impact() is of the correct format, peleffy.topology.Topology. """ from peleffy.forcefield.parameters import BaseParameterWrapper LIGAND_PATH = 'ligands/benzene.pdb' ligand_path = get_data_file_path(LIGAND_PATH) molecule = Molecule(ligand_path) parameters = BaseParameterWrapper() topology = Topology(molecule, parameters) # Impact() gets nothing as argument with pytest.raises(TypeError): _ = Impact() # Impact() gets a non Topology object with pytest.raises(TypeError): _ = Impact('passing a str instead of a Topology') # This should work _ = Impact(topology)
def _generate_parameters(self, smiles, mol_id, output_path, forcefield='openff_unconstrained-1.2.0.offxml', charges_method='am1bcc'): """ It generates the parameters of the molecule (from the input_file) as DataLocal in the output folder. Parameters ---------- smiles : str The smiles tag representing the molecule to minimize mol_id : str Unique id to identify the molecule to minimize output_path : str The output path where parameters will be saved forcefield : str The Open Force Field force field to generate the parameters with charges_method : str The charges method to calculate the partial charges with """ import peleffy from peleffy.topology import Molecule from peleffy.template import Impact from peleffy.solvent import OBC2 from peleffy.main import handle_output_paths import os # Create representation of a particular molecule molecule = Molecule(smiles=smiles, name=mol_id, tag='UNL') # Save molecule to PDB file molecule.to_pdb_file(os.path.join(output_path, 'ligand.pdb')) # Saving paths rotamer_library_output_path, impact_output_path, \ solvent_output_path = handle_output_paths(molecule=molecule, output=output_path, as_datalocal=True) # Generate its rotamer library rotamer_library = peleffy.topology.RotamerLibrary(molecule) rotamer_library.to_file(rotamer_library_output_path) # Generate its parameters and template file molecule.parameterize(forcefield, charges_method=charges_method) impact = Impact(molecule) impact.write(impact_output_path) # Generate its solvent parameters solvent = OBC2(molecule) solvent.to_json_file(solvent_output_path)
def _generate_parameters(self, molecule, output_path, forcefield, charges_method, force_parameterization): """ It generates the parameters of the molecule (from the input_file) as DataLocal in the output folder. Parameters ---------- molecule : an peleffy.topology.Molecule object The molecule to run the PELE workflow on output_path : str The output path where results will be saved forcefield : str The Open Force Field force field to generate the parameters with charges_method : str The charges method to calculate the partial charges with force_parameterization : bool If the molecule is already parameterized, do we need to force a new parameterization? """ import peleffy from peleffy.template import Impact from peleffy.solvent import OBC2 from peleffy.utils import OutputPathHandler output_handler = OutputPathHandler(molecule, output_path=output_path, as_datalocal=True) # Saving paths rotamer_library_output_path = output_handler.get_rotamer_library_path() impact_output_path = output_handler.get_impact_template_path() solvent_output_path = output_handler.get_solvent_template_path() # Generate rotamer library rotamer_library = peleffy.topology.RotamerLibrary(molecule) rotamer_library.to_file(rotamer_library_output_path) # Generate parameters if force_parameterization or not molecule.parameterized: molecule.parameterize(forcefield, charges_method=charges_method) # Save template file impact = Impact(molecule) impact.write(impact_output_path) # Generate solvent parameters solvent = OBC2(molecule) solvent.to_json_file(solvent_output_path)
def __get_template_and_rot(self, template_path='grw', rot_path='GRW.rot.assign', rot_res=30): os.environ['SCHRODINGER'] = self.sch_path m = Molecule(self.ligand_pdb, core_constraints=[' CA ', ' C ', ' N '], rotamer_resolution=rot_res) if self.__forcefield == 'OPLS2005': ff = OPLS2005ForceField() if self.__forcefield == 'OpenForceField': # Not tested yet ff = OpenForceField('openff_unconstrained-1.2.0.offxml') parameters = ff.parameterize(m) topology = Topology(m, parameters) impact = Impact(topology) impact.to_file(template_path) aa_template = self.__create_aa_template_path() cov.correct_template(template_path, aa_template) print("Template modified in {}.".format(template_path)) rotamer_library = RotamerLibrary(m) rotamer_library.to_file(rot_path) print("Rotamer library stored in {}".format(rot_path))
def test_get_absolute_parent_atom(self): """ It tests the _get_absolute_parent_atom method used in the building process of the Impact template. """ LIGAND_PATH = get_data_file_path('ligands/malonate.pdb') FORCEFIELD_NAME = 'openff_unconstrained-1.2.1.offxml' molecule = Molecule(LIGAND_PATH) openff = OpenForceField(FORCEFIELD_NAME) parameters = openff.parameterize(molecule, charge_method='dummy') topology = Topology(molecule, parameters) impact = Impact(topology) absolute_parent = impact._get_absolute_parent_atom() assert absolute_parent.PDB_name == '_C2_', \ 'Unexpected absolute parent atom: {}'.format(absolute_parent)
def test_get_core_atoms(self): """ It tests the _get_core_atoms method used in the building process of the Impact template. """ LIGAND_PATH = get_data_file_path('ligands/malonate.pdb') FORCEFIELD_NAME = 'openff_unconstrained-1.2.1.offxml' molecule = Molecule(LIGAND_PATH) openff = OpenForceField(FORCEFIELD_NAME) parameters = openff.parameterize(molecule, charge_method='dummy') topology = Topology(molecule, parameters) impact = Impact(topology) core_atoms = impact._get_core_atoms() assert [a.PDB_name for a in core_atoms] == \ ['_C2_', '_H1_', '_H2_'], \ 'Unexpected core atoms: {}'.format(core_atoms)
def parallel_run(output_path, solvent, charge_method, pele_exec, pele_src, pele_license, forcefield_name, forcefield, entry): """Parallel runner.""" cid, tag, exp_v = entry try: molecule = Molecule(smiles=tag, name=cid, tag='LIG') if forcefield_name is not None: molecule.parameterize(forcefield_name, charge_method=charge_method) elif forcefield is not None: molecule.set_forcefield(forcefield) molecule.parameterize(charge_method=charge_method) if ((molecule.forcefield.type == 'OPLS2005') or (molecule.forcefield.type == 'OpenFF + OPLS2005' and molecule.forcefield._nonbonding == 'opls2005')): if solvent == 'OBC': # Generate OBC parameters for OPLS2005 from peleffy.template import Impact os.makedirs(os.path.join(output_path, cid), exist_ok=True) impact = Impact(molecule) impact.write(os.path.join(output_path, cid, 'ligz')) os.makedirs(os.path.join(output_path, cid, 'DataLocal/OBC/'), exist_ok=True) os.system('python2 {}scripts/solventOBCParamsGenerator.py '. format(pele_src) + os.path.join(output_path, cid, 'ligz') + ' >> /dev/null') shutil.copy( '{}Data/OBC/solventParamsHCTOBC.txt'.format(pele_src), os.path.join(output_path, cid, 'DataLocal/OBC/')) os.system('cat ' + os.path.join( output_path, cid, 'ligz_OBCParams.txt >> ' + os.path.join(output_path, cid, 'DataLocal/OBC/solventParamsHCTOBC.txt'))) os.remove(os.path.join(output_path, cid, 'ligz')) os.remove(os.path.join(output_path, cid, 'ligz_OBCParams.txt')) forcefield_tag = 'OPLS2005' else: forcefield_tag = 'OpenForceField' # Minimization pele_vacuum_min = PELEMinimization(pele_exec, pele_src, pele_license, solvent_type='VACUUM', output_path=output_path, forcefield=forcefield_tag) pele_vacuum_out = pele_vacuum_min.run(molecule, output_file='vacuum_out.txt') pele_obc_min = PELEMinimization(pele_exec, pele_src, pele_license, solvent_type=solvent, output_path=output_path, forcefield=forcefield_tag) pele_obc_out = pele_obc_min.run(molecule, output_file='solvent_out.txt') # Calculate energetic difference difference = compute_energies(pele_vacuum_out, pele_obc_out)[2] return tuple((cid, difference, exp_v)) except Exception as e: print('Exception found with compound {}: '.format(cid) + str(e))
def test_writer_OFF(self): """ It tests the writer attribute of the Impact class using OFF to parameterize. """ TEMPLATE_METZ = get_data_file_path('tests/metz') TEMPLATE_MATZ = get_data_file_path('tests/malz') TEMPLATE_ETLZ = get_data_file_path('tests/etlz') with tempfile.TemporaryDirectory() as tmpdir: with temporary_cd(tmpdir): # Generates the template for methane pdb_path = get_data_file_path('ligands/methane.pdb') molecule = Molecule(pdb_path) openff = OpenForceField(self.OPENFF_FORCEFIELD) parameters = openff.parameterize(molecule) topology = Topology(molecule, parameters) # Generates the impact template for methane impact = Impact(topology) impact.to_file('metz') # Compare the reference template and the generated template compare_files(file1=TEMPLATE_METZ, file2='metz') # Generates the template for malonate pdb_path = get_data_file_path('ligands/malonate.pdb') molecule = Molecule(pdb_path) openff = OpenForceField(self.OPENFF_FORCEFIELD) parameters = openff.parameterize(molecule) topology = Topology(molecule, parameters) # Generates the impact template for malonate impact = Impact(topology) impact.to_file('malz') # Compare the reference template and the generated template compare_files(file1=TEMPLATE_MATZ, file2='malz') # Generates the template for ethylene pdb_path = get_data_file_path('ligands/ethylene.pdb') molecule = Molecule( pdb_path, tag='ETL' ) # Note that in this case we are assigning a tag to the molecule which will be used in the Impact template openff = OpenForceField(self.OPENFF_FORCEFIELD) parameters = openff.parameterize(molecule) topology = Topology(molecule, parameters) # Generates the impact template for ethylene impact = Impact(topology) impact.to_file('etlz') # Compare the reference template and the generated template compare_files(file1=TEMPLATE_ETLZ, file2='etlz')
def test_writer_OPLS(self): """ It tests the writer attribute of the Impact class using OPLS to parameterize. """ from .utils import parameterize_opls2005 TEMPLATE_METZ_OPLS = get_data_file_path('tests/OPLS_metz') TEMPLATE_MALZ_OPLS = get_data_file_path('tests/OPLS_malz') TEMPLATE_ETLZ_OPLS = get_data_file_path('tests/OPLS_etlz') with tempfile.TemporaryDirectory() as tmpdir: with temporary_cd(tmpdir): # Generates the template for methane using OPLS opls2005 = OPLS2005ForceField() pdb_path = get_data_file_path('ligands/methane.pdb') molecule = Molecule(pdb_path) ffld_file = get_data_file_path('tests/MET_ffld_output.txt') parameters = parameterize_opls2005(opls2005, molecule, ffld_file) topology = Topology(molecule, parameters) # Generates the impact template for methane impact = Impact(topology) impact.to_file('metz') # Compare the reference template and the generated template compare_files(file1=TEMPLATE_METZ_OPLS, file2='metz') # Generates the template for malonate using OPLS opls2005 = OPLS2005ForceField() pdb_path = get_data_file_path('ligands/malonate.pdb') molecule = Molecule(pdb_path) ffld_file = get_data_file_path('tests/MAL_ffld_output.txt') parameters = parameterize_opls2005(opls2005, molecule, ffld_file) topology = Topology(molecule, parameters) # Generates the impact template for malonate impact = Impact(topology) impact.to_file('malz') # Compare the reference template and the generated template compare_files(file1=TEMPLATE_MALZ_OPLS, file2='malz') # Generates the template for ethylene using OPLS opls2005 = OPLS2005ForceField() pdb_path = get_data_file_path('ligands/ethylene.pdb') molecule = Molecule(pdb_path, tag='ETL') ffld_file = get_data_file_path('tests/ETL_ffld_output.txt') parameters = parameterize_opls2005(opls2005, molecule, ffld_file) topology = Topology(molecule, parameters) # Generates the impact template for ethylene impact = Impact(topology) impact.to_file('etlz') # Compare the reference template and the generated template compare_files(file1=TEMPLATE_ETLZ_OPLS, file2='etlz')
def run_peleffy(pdb_file, forcefield_name=DEFAULT_OFF_FORCEFIELD, resolution=DEFAULT_RESOLUTION, charge_method=DEFAULT_CHARGE_METHOD, charges_from_file=None, chain=None, exclude_terminal_rotamers=True, output=None, with_solvent=False, as_datalocal=False, conformation_path=None): """ It runs peleffy. Parameters ---------- pdb_file : str The path to the pdb_file to parameterize with peleffy forcefield_name : str The name of an OpenForceField's forcefield resolution : float The resolution in degrees for the rotamer library. Default is 30 charge_method : str The name of the method to use to compute partial charges. Default is 'am1bcc' charges_from_file : str The file containing the partial charges to assign to the molecule. Default is None chain : str Chain to the molecule if the PDB contains multiple molecules. exclude_terminal_rotamers : bool Whether to exclude terminal rotamers or not output : str Path where output files will be saved with_solvent : bool Whether to generate and save the solvent parameters for the input molecule or not as_datalocal : bool Whether to save output files following PELE's DataLocal hierarchy or not conformation_path: str Path to the BCE server outupt to use to extract dihedral angles dihedral_mode: str Select what kind of dihedrals to extract (all or only flexible) """ if charges_from_file is not None: charge_method_str = 'file\n' \ + ' - Charge file: {}'.format(charges_from_file) charge_method = 'dummy' else: charge_method_str = charge_method log = Logger() log.info('-' * 60) log.info('Open Force Field parameterizer for PELE', peleffy.__version__) log.info('-' * 60) log.info(' - General:') log.info(' - Input PDB:', pdb_file) log.info(' - Output path:', output) log.info(' - Write solvent parameters:', with_solvent) log.info(' - DataLocal-like output:', as_datalocal) log.info(' - Parameterization:') log.info(' - Force field:', forcefield_name) log.info(' - Charge method:', charge_method_str) log.info(' - Rotamer library:') log.info(' - Resolution:', resolution) log.info(' - Exclude terminal rotamers:', exclude_terminal_rotamers) log.info('-' * 60) from peleffy.topology import Molecule, BCEConformations from peleffy.template import Impact from peleffy.solvent import OBC2 from peleffy.forcefield import ForceFieldSelector from peleffy.topology import Topology from peleffy.utils import parse_charges_from_mae from peleffy.utils.input import PDBFile if not output: output = os.getcwd() # Initialize molecule if chain is not None: PDBreader = PDBFile(pdb_file) molecule = PDBreader.get_molecules_from_chain( selected_chain=chain, rotamer_resolution=resolution, exclude_terminal_rotamers=exclude_terminal_rotamers) else: molecule = Molecule( pdb_file, rotamer_resolution=resolution, exclude_terminal_rotamers=exclude_terminal_rotamers) # Initialize force field ff_selector = ForceFieldSelector() forcefield = ff_selector.get_by_name(forcefield_name) output_handler = OutputPathHandler(molecule, forcefield, output_path=output, as_datalocal=as_datalocal) # if conformation_path is set, we don't want a rotamer library if conformation_path is None: rotamer_library = peleffy.topology.RotamerLibrary(molecule) rotamer_library.to_file(output_handler.get_rotamer_library_path()) # Parameterize molecule with the selected force field log.info(' - Parameterizing molecule') parameters = forcefield.parameterize(molecule, charge_method=charge_method) # Update charge parameters from the MAE file if charges_from_file is not None: parameters = parse_charges_from_mae(charges_from_file, parameters) # Generate the molecular topology topology = Topology(molecule, parameters) log.info(' - Parameters were built successfully:') log.info(' - {} atoms'.format(len(topology.atoms))) log.info(' - {} bonds'.format(len(topology.bonds))) log.info(' - {} torsions'.format(len(topology.angles))) log.info(' - {} propers'.format(len(topology.propers))) log.info(' - {} impropers'.format(len(topology.impropers))) # Generate the impact template impact = Impact(topology) impact.to_file(output_handler.get_impact_template_path()) # Generate the solvent template if with_solvent: solvent = OBC2(topology) solvent.to_file(output_handler.get_solvent_template_path()) if conformation_path is not None: conformations = BCEConformations(topology, conformation_path) conformations.calculate() conformations.save(output_handler.get_conformation_library_path()) log.info(' - All files were generated successfully:') if conformation_path is None: log.info(' - {}'.format(output_handler.get_rotamer_library_path())) log.info(' - {}'.format(output_handler.get_impact_template_path())) if conformation_path is not None: log.info(' - {}'.format( output_handler.get_conformation_library_path())) if with_solvent: log.info(' - {}'.format(output_handler.get_solvent_template_path())) log.info('-' * 60)
def parametrize_ligands_from(self, pdb_file, ppp_file=None): """ Generates forcefield templates and rotamer files for ligands, then copies the ones provided by the user (if any). Parameters ---------- pdb_file : str Path to the PDB file from which all HET groups will be extracted and parametrized (syst.system). ppp_file : str Path to the PDB file preprocessed by PPP with changed atom names. Raises ------ LigandPreparationError if any error is obtained throughout the ligand preparation process """ from pele_platform.Utilities.Helpers import helpers from pele_platform.Checker.pdb_checker import PDBChecker from peleffy.topology import RotamerLibrary, Topology from peleffy.utils import OutputPathHandler from peleffy.template import Impact from peleffy.forcefield.parameters import BaseParameterWrapper pdb_file = PDBChecker(pdb_file, self.working_dir).check() ligand_core_constraints = self._fix_atom_names( self.ligand_resname, self.ligand_core_constraints, pdb_file) hetero_molecules = self.extract_ligands( pdb_file=pdb_file, gridres=self.gridres, exclude_terminal_rotamers=self.exclude_terminal_rotamers, ligand_resname=self.ligand_resname, ligand_core_constraints=ligand_core_constraints, ) # retrieve PDB atom names from second PDB file (if any is supplied) if ppp_file is not None: hetero_residues = [ molecule.tag.strip() for molecule in hetero_molecules ] pdb_atom_names = helpers.retrieve_atom_names( ppp_file, hetero_residues) else: pdb_atom_names = None rotamer_library_path, impact_template_paths = None, None rotamers_to_skip, templates_to_skip = self._check_external_files( hetero_molecules) topologies = list() for molecule in hetero_molecules: # Check if we need to skip the current molecule if molecule.tag.strip() in self.ligands_to_skip: continue # Handle paths output_handler = OutputPathHandler( molecule, self.forcefield, as_datalocal=self.as_datalocal, output_path=self.working_dir, ) rotamer_library_path = output_handler.get_rotamer_library_path() impact_template_path = output_handler.get_impact_template_path() # This boolean indicates whether we needed to reparameterize # this ligand with OPLS2005 or not opls_reparameterization = False # Parameterize molecule if we do not have a template for it # specified in the input.yaml nor in Data folder if molecule.tag.strip() not in templates_to_skip: # Generate rotamer library (only if the molecule is # the ligand to perturb if its rotamer library is not # supposed to be skipped) if (molecule.tag.strip() == self.ligand_resname and molecule.tag.strip() not in rotamers_to_skip): # ToDo branches = self.molecule.rotamers rotamer_library = RotamerLibrary(molecule) rotamer_library.to_file(rotamer_library_path) # Try to parametrize with OPLS2005 if OpenFF fails try: parameters = self.forcefield.parameterize( molecule, self.charge_parametrization_method) except (subprocess.CalledProcessError, TypeError, KeyError) as e1: warnings.warn(f"Could not parameterize residue " f"{molecule.tag.strip()} with the selected " f"forcefield. The following error was " f"obtained: {e1}") default = "OPLS2005" if self.forcefield.type == default: raise custom_errors.LigandPreparationError( f"Could not parametrize {molecule.tag.strip()}") fallback_forcefield = self._retrieve_forcefield(default) try: parameters = fallback_forcefield.parameterize(molecule) warnings.warn(f"Parametrized with {default} " f"instead.") except subprocess.CalledProcessError as e2: raise custom_errors.LigandPreparationError( f"Could not parametrize {molecule.tag.strip()}. " f"The error was {e2}.") opls_reparameterization = True try: topology = Topology(molecule, parameters) topologies.append(topology) # In case we reparameterized the templates with OPLS, # we need to convert atom types to OFFT (unique atom # type for OpenFF). Otherwise, PELE will complain about it if opls_reparameterization: for atom in topology.atoms: atom.set_OPLS_type("OFFT") # Iterate over topology atoms and change their names, if necessary if pdb_atom_names is not None: for atom, new_atom_name in zip( topology.atoms, pdb_atom_names[molecule]): atom._PDB_name = new_atom_name impact = Impact(topology) impact.to_file(impact_template_path) impact_template_paths = [ os.path.dirname(impact_template_path) ] print(f"Parametrized {molecule.tag.strip()}.") except AssertionError as e: raise custom_errors.LigandPreparationError( f"Failed to parametrize residue {molecule.tag.strip()}. You can skip it or " f"parametrize manually (see documentation: " f"https://nostrumbiodiscovery.github.io/pele_platform/errors/index.html#parametrization" f"). The error raised was: {e}.") # Even though molecule has not been parameterized, we might need # to generate its solvent parameters if it is not in PELE data. # So, we need to save its topology elif molecule not in constants.in_pele_data: empty_params = BaseParameterWrapper( ) # Needed to create a topology topology = Topology(molecule, empty_params) topologies.append(topology) # Handle solvent template self._handle_solvent_template(topologies) # Copy external parameters, supplied by the user, if any if not rotamer_library_path: rotamer_library_path = os.path.join(self.working_dir, self.ROTAMER_LIBRARY_PATH) if not impact_template_paths: impact_template_paths = [ os.path.join(self.working_dir, self.OPLS_IMPACT_TEMPLATE_PATH), os.path.join(self.working_dir, self.OFF_IMPACT_TEMPLATE_PATH), ] for path in impact_template_paths: if not os.path.exists(path): os.makedirs(path) self._copy_external_parameters( os.path.dirname(rotamer_library_path), impact_template_paths, )