def test_simple_heterocycle_mapping(iupac_pairs=[('benzene', 'pyridine')]): """ Test the ability to map conjugated heterocycles (that preserves all rings). Will assert that the number of ring members in both molecules is the same. """ # TODO: generalize this to test for ring breakage and closure. from openmoltools.openeye import iupac_to_oemol from openeye import oechem from perses.rjmc.topology_proposal import AtomMapper for iupac_pair in iupac_pairs: old_oemol, new_oemol = iupac_to_oemol(iupac_pair[0]), iupac_to_oemol( iupac_pair[1]) new_to_old_map = AtomMapper._get_mol_atom_map( old_oemol, new_oemol, allow_ring_breaking=False) #assert that the number of ring members is consistent in the mapping... num_hetero_maps = 0 for new_index, old_index in new_to_old_map.items(): old_atom, new_atom = old_oemol.GetAtom( oechem.OEHasAtomIdx(old_index)), new_oemol.GetAtom( oechem.OEHasAtomIdx(new_index)) if old_atom.IsInRing() and new_atom.IsInRing(): if old_atom.GetAtomicNum() != new_atom.GetAtomicNum(): num_hetero_maps += 1 assert num_hetero_maps > 0, f"there are no differences in atomic number mappings in {iupac_pair}"
def test_ring_breaking_detection(): """ Test the detection of ring-breaking transformations. """ from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, AtomMapper from openmoltools.openeye import iupac_to_oemol, generate_conformers molecule1 = iupac_to_oemol("naphthalene") molecule2 = iupac_to_oemol("benzene") molecule1 = generate_conformers(molecule1, max_confs=1) molecule2 = generate_conformers(molecule2, max_confs=1) # Allow ring breaking new_to_old_atom_map = AtomMapper._get_mol_atom_map( molecule1, molecule2, allow_ring_breaking=True) if not len(new_to_old_atom_map) > 0: filename = 'mapping-error.png' #render_atom_mapping(filename, molecule1, molecule2, new_to_old_atom_map) msg = 'Napthalene -> benzene transformation with allow_ring_breaking=True is not returning a valid mapping\n' msg += 'Wrote atom mapping to %s for inspection; please check this.' % filename msg += str(new_to_old_atom_map) raise Exception(msg) new_to_old_atom_map = AtomMapper._get_mol_atom_map( molecule1, molecule2, allow_ring_breaking=False) if new_to_old_atom_map is not None: # atom mapper should not retain _any_ atoms in default mode filename = 'mapping-error.png' #render_atom_mapping(filename, molecule1, molecule2, new_to_old_atom_map) msg = 'Napthalene -> benzene transformation with allow_ring_breaking=False is erroneously allowing ring breaking\n' msg += 'Wrote atom mapping to %s for inspection; please check this.' % filename msg += str(new_to_old_atom_map) raise Exception(msg)
def compare_energies(mol_name="naphthalene", ref_mol_name="benzene"): """ Make an atom map where the molecule at either lambda endpoint is identical, and check that the energies are also the same. """ from openmmtools import alchemy, states from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal from perses.annihilation.relative import HybridTopologyFactory import simtk.openmm as openmm from perses.utils.openeye import createSystemFromIUPAC from openmoltools.openeye import iupac_to_oemol, generate_conformers mol = iupac_to_oemol(mol_name) mol = generate_conformers(mol, max_confs=1) m, system, positions, topology = createSystemFromIUPAC(mol_name) refmol = iupac_to_oemol(ref_mol_name) refmol = generate_conformers(refmol, max_confs=1) #map one of the rings atom_map = SmallMoleculeSetProposalEngine._get_mol_atom_map(mol, refmol) #now use the mapped atoms to generate a new and old system with identical atoms mapped. This will result in the #same molecule with the same positions for lambda=0 and 1, and ensures a contiguous atom map effective_atom_map = {value: value for value in atom_map.values()} #make a topology proposal with the appropriate data: top_proposal = TopologyProposal(new_topology=topology, new_system=system, old_topology=topology, old_system=system, new_to_old_atom_map=effective_atom_map, new_chemical_state_key="n1", old_chemical_state_key='n2') factory = HybridTopologyFactory(top_proposal, positions, positions) alchemical_system = factory.hybrid_system alchemical_positions = factory.hybrid_positions platform = openmm.Platform.getPlatformByName("Reference") _, _, alch_zero_state, alch_one_state = utils.generate_endpoint_thermodynamic_states( alchemical_system, top_proposal) rp_list = [] for state in [alch_zero_state, alch_one_state]: integrator = openmm.VerletIntegrator(1) context = state.create_context(integrator, platform) samplerstate = states.SamplerState( positions=alchemical_positions, box_vectors=alchemical_system.getDefaultPeriodicBoxVectors()) samplerstate.apply_to_context(context) rp = state.reduced_potential(context) rp_list.append(rp) del context, integrator assert abs(rp_list[0] - rp_list[1]) < 1e-6
def create_molecule(iupac_name): """ Create an OEMol molecule from an IUPAC name. Parameters ---------- iupac_name : str The IUPAC name of the molecule to be created. Returns ------- molecule : openeye.oechem.OEMol A molecule with AM1-BCC charges. """ molecule = openeye.iupac_to_oemol(iupac_name) # Assign AM1-BCC charges using canonical scheme. # TODO: Replace wit updated gaff2xml scheme. molecule = assign_am1bcc_charges(molecule) # Assign conformations. from openeye import oeomega omega = oeomega.OEOmega() omega.SetMaxConfs(1) omega(molecule) return molecule
def run_oemol_test_suite(iupac='ethane'): """ Runs all of the oemol related tests for perses.utils.openeye Parameters --------- iupac : str, default 'ethane' """ from openmoltools.openeye import iupac_to_oemol import copy import numpy as np import simtk.unit as unit from openeye import oechem oemol = iupac_to_oemol(iupac) positions = test_extractPositionsFromOEMol(oemol) # shifting all of the positions by 1. A new_positions = np.zeros(np.shape(positions)) for atom in range(oemol.NumAtoms()): new_positions[atom] = copy.deepcopy(positions[atom]) + [1., 1., 1.]*unit.angstrom new_positions *= unit.angstrom molecule = test_giveOpenmmPositionsToOEMol(new_positions,oemol) smiles = oechem.OECreateSmiString(molecule,oechem.OESMILESFlag_DEFAULT | oechem.OESMILESFlag_Hydrogens) smiles_oemol = smiles_to_oemol(smiles) # check that the two systems have the same numbers of atoms assert (oemol.NumAtoms() == smiles_oemol.NumAtoms()), "Discrepancy between molecule generated from IUPAC and SMILES"
def createSystemFromIUPAC(iupac_name): """ Create an openmm system out of an oemol Parameters ---------- iupac_name : str IUPAC name Returns ------- molecule : openeye.oechem.OEMol OEMol molecule system : openmm.System object OpenMM system positions : [n,3] np.array of floats Positions topology : openmm.app.Topology object Topology """ # Create OEMol # TODO write our own of this function so we can be sure of the oe flags that are being used molecule = iupac_to_oemol(iupac_name) molecule = generate_conformers(molecule, max_confs=1) # generate openmm system, positions and topology system, positions, topology = OEMol_to_omm_ff(molecule) return (molecule, system, positions, topology)
def parameterize_molecule(molecule, implicitSolvent=app.OBC1, constraints=None, cleanup=True, verbose=False): """ Parameterize the specified molecule for AMBER. Parameters ---------- molecule : openeye.oechem.OEMol The molecule to be parameterized. implicitSolvent : default=app.OBC1 The implicit solvent model to use; one of [None, HCT, OBC1, OBC2, GBn, GBn2] constraints : default=None Constraints to use; one of [None, HBonds, AllBonds, HAngles] cleanup : bool, optional, default=False If True, work done in a temporary working directory will be deleted. Returns ------- system : simtk.openmm.System The OpenMM System of the molecule. topology : simtk.openmm.app.Topology The OpenMM topology of the molecule. positions : The positions of the molecule. gaff_molecule : oechem.OEMol The OEMol molecule with GAFF atom and bond types. """ # Create molecule and geometry. molecule = openeye.iupac_to_oemol(iupac_name) # Create a a temporary directory. working_directory = tempfile.mkdtemp() old_directory = os.getcwd() os.chdir(working_directory) # Parameterize molecule for AMBER (currently using old machinery for convenience) # TODO: Replace this with gaff2xml stuff amber_prmtop_filename = 'molecule.prmtop' amber_inpcrd_filename = 'molecule.inpcrd' amber_off_filename = 'molecule.off' oldmmtools.parameterizeForAmber(molecule, amber_prmtop_filename, amber_inpcrd_filename, charge_model=None, offfile=amber_off_filename) # Read in the molecule with GAFF atom and bond types print "Overwriting OEMol with GAFF atom and bond types..." gaff_molecule = oldmmtools.loadGAFFMolecule(molecule, amber_off_filename) # Load positions. inpcrd = app.AmberInpcrdFile(amber_inpcrd_filename) positions = inpcrd.getPositions() # Load system (with GB parameters). prmtop = app.AmberPrmtopFile(amber_prmtop_filename) system = prmtop.createSystem(implicitSolvent=implicitSolvent, constraints=constraints) # Clean up temporary files. os.chdir(old_directory) if cleanup: commands.getoutput('rm -r %s' % working_directory) else: print "Work done in %s..." % working_directory return [system, topology, positions, gaff_molecule]
def create_molecule(iupac_name): molecule = openeye.iupac_to_oemol(iupac_name) molecule = openeye.get_charges(molecule, max_confs=1) #import openeye.oeomega as om #omega = om.OEOmega() #omega.SetMaxConfs(1) #omega(molecule) return molecule
def generate_solvated_hybrid_test_topology(current_mol_name="naphthalene", proposed_mol_name="benzene", current_mol_smiles=None, proposed_mol_smiles=None, vacuum=False, render_atom_mapping=False): """ This function will generate a topology proposal, old positions, and new positions with a geometry proposal (either vacuum or solvated) given a set of input iupacs or smiles. The function will (by default) read the iupac names first. If they are set to None, then it will attempt to read a set of current and new smiles. An atom mapping pdf will be generated if specified. Arguments ---------- current_mol_name : str, optional name of the first molecule proposed_mol_name : str, optional name of the second molecule current_mol_smiles : str (default None) current mol smiles proposed_mol_smiles : str (default None) proposed mol smiles vacuum: bool (default False) whether to render a vacuum or solvated topology_proposal render_atom_mapping : bool (default False) whether to render the atom map of the current_mol_name and proposed_mol_name Returns ------- topology_proposal : perses.rjmc.topology_proposal The topology proposal representing the transformation current_positions : np.array, unit-bearing The positions of the initial system new_positions : np.array, unit-bearing The positions of the new system """ import simtk.openmm.app as app from openmoltools import forcefield_generators from openeye import oechem from openmoltools.openeye import iupac_to_oemol, generate_conformers, smiles_to_oemol from openmoltools import forcefield_generators import perses.utils.openeye as openeye from perses.utils.data import get_data_filename from perses.rjmc.topology_proposal import TopologyProposal, SystemGenerator, SmallMoleculeSetProposalEngine import simtk.unit as unit from perses.rjmc.geometry import FFAllAngleGeometryEngine if current_mol_name != None and proposed_mol_name != None: try: old_oemol, new_oemol = iupac_to_oemol( current_mol_name), iupac_to_oemol(proposed_mol_name) old_smiles = oechem.OECreateSmiString( old_oemol, oechem.OESMILESFlag_DEFAULT | oechem.OESMILESFlag_Hydrogens) new_smiles = oechem.OECreateSmiString( new_oemol, oechem.OESMILESFlag_DEFAULT | oechem.OESMILESFlag_Hydrogens) except: raise Exception( f"either {current_mol_name} or {proposed_mol_name} is not compatible with 'iupac_to_oemol' function!" ) elif current_mol_smiles != None and proposed_mol_smiles != None: try: old_oemol, new_oemol = smiles_to_oemol( current_mol_smiles), smiles_to_oemol(proposed_mol_smiles) old_smiles = oechem.OECreateSmiString( old_oemol, oechem.OESMILESFlag_DEFAULT | oechem.OESMILESFlag_Hydrogens) new_smiles = oechem.OECreateSmiString( new_oemol, oechem.OESMILESFlag_DEFAULT | oechem.OESMILESFlag_Hydrogens) except: raise Exception(f"the variables are not compatible") else: raise Exception( f"either current_mol_name and proposed_mol_name must be specified as iupacs OR current_mol_smiles and proposed_mol_smiles must be specified as smiles strings." ) old_oemol, old_system, old_positions, old_topology = openeye.createSystemFromSMILES( old_smiles, title="MOL") #correct the old positions old_positions = openeye.extractPositionsFromOEMol(old_oemol) old_positions = old_positions.in_units_of(unit.nanometers) new_oemol, new_system, new_positions, new_topology = openeye.createSystemFromSMILES( new_smiles, title="NEW") ffxml = forcefield_generators.generateForceFieldFromMolecules( [old_oemol, new_oemol]) old_oemol.SetTitle('MOL') new_oemol.SetTitle('MOL') old_topology = forcefield_generators.generateTopologyFromOEMol(old_oemol) new_topology = forcefield_generators.generateTopologyFromOEMol(new_oemol) if not vacuum: nonbonded_method = app.PME barostat = openmm.MonteCarloBarostat(1.0 * unit.atmosphere, 300.0 * unit.kelvin, 50) else: nonbonded_method = app.NoCutoff barostat = None gaff_xml_filename = get_data_filename("data/gaff.xml") system_generator = SystemGenerator( [gaff_xml_filename, 'amber99sbildn.xml', 'tip3p.xml'], barostat=barostat, forcefield_kwargs={ 'removeCMMotion': False, 'nonbondedMethod': nonbonded_method, 'constraints': app.HBonds, 'hydrogenMass': 4.0 * unit.amu }) system_generator._forcefield.loadFile(StringIO(ffxml)) proposal_engine = SmallMoleculeSetProposalEngine([old_smiles, new_smiles], system_generator, residue_name='MOL') geometry_engine = FFAllAngleGeometryEngine(metadata=None, use_sterics=False, n_bond_divisions=1000, n_angle_divisions=180, n_torsion_divisions=360, verbose=True, storage=None, bond_softening_constant=1.0, angle_softening_constant=1.0, neglect_angles=False) if not vacuum: #now to solvate modeller = app.Modeller(old_topology, old_positions) hs = [ atom for atom in modeller.topology.atoms() if atom.element.symbol in ['H'] and atom.residue.name not in ['MOL', 'OLD', 'NEW'] ] modeller.delete(hs) modeller.addHydrogens(forcefield=system_generator._forcefield) modeller.addSolvent(system_generator._forcefield, model='tip3p', padding=9.0 * unit.angstroms) solvated_topology = modeller.getTopology() solvated_positions = modeller.getPositions() solvated_positions = unit.quantity.Quantity(value=np.array([ list(atom_pos) for atom_pos in solvated_positions.value_in_unit_system(unit.md_unit_system) ]), unit=unit.nanometers) solvated_system = system_generator.build_system(solvated_topology) #now to create proposal top_proposal = proposal_engine.propose( current_system=solvated_system, current_topology=solvated_topology, current_mol=old_oemol, proposed_mol=new_oemol) new_positions, _ = geometry_engine.propose(top_proposal, solvated_positions, beta) if render_atom_mapping: from perses.utils.smallmolecules import render_atom_mapping print( f"new_to_old: {proposal_engine.non_offset_new_to_old_atom_map}" ) render_atom_mapping(f"{old_smiles}to{new_smiles}.png", old_oemol, new_oemol, proposal_engine.non_offset_new_to_old_atom_map) return top_proposal, solvated_positions, new_positions else: vacuum_system = system_generator.build_system(old_topology) top_proposal = proposal_engine.propose(current_system=vacuum_system, current_topology=old_topology, current_mol=old_oemol, proposed_mol=new_oemol) new_positions, _ = geometry_engine.propose(top_proposal, old_positions, beta) if render_atom_mapping: from perses.utils.smallmolecules import render_atom_mapping print(f"new_to_old: {top_proposal._new_to_old_atom_map}") render_atom_mapping(f"{old_smiles}to{new_smiles}.png", old_oemol, new_oemol, top_proposal._new_to_old_atom_map) return top_proposal, old_positions, new_positions