def test_ring_breaking_detection(): """ Test the detection of ring-breaking transformations. """ from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine from perses.tests.utils import createOEMolFromIUPAC from perses.tests.utils import render_atom_mapping molecule1 = createOEMolFromIUPAC("naphthalene") molecule2 = createOEMolFromIUPAC("benzene") # Allow ring breaking new_to_old_atom_map = SmallMoleculeSetProposalEngine._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 = SmallMoleculeSetProposalEngine._get_mol_atom_map( molecule1, molecule2, allow_ring_breaking=False) 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=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 test_mapping_strength_levels(pairs_of_smiles=[('Cc1ccccc1','c1ccc(cc1)N'),('CC(c1ccccc1)','O=C(c1ccccc1)'),('Oc1ccccc1','Sc1ccccc1')],test=True): correct_results = {0:{'default': (3,2), 'weak':(3,2), 'strong':(4,3)}, 1:{'default': (7,3), 'weak':(6,2), 'strong':(7,3)}, 2:{'default': (1,1), 'weak':(1,1), 'strong':(2,2)}} mapping = ['weak','default','strong'] for example in mapping: for index, (lig_a, lig_b) in enumerate(pairs_of_smiles): print(f"conducting {example} mapping with ligands {lig_a}, {lig_b}") initial_molecule = smiles_to_oemol(lig_a) proposed_molecule = smiles_to_oemol(lig_b) molecules = [Molecule.from_openeye(mol) for mol in [initial_molecule, proposed_molecule]] system_generator = SystemGenerator(forcefields = forcefield_files, barostat=barostat, forcefield_kwargs=forcefield_kwargs,nonperiodic_forcefield_kwargs=nonperiodic_forcefield_kwargs, small_molecule_forcefield = 'gaff-1.81', molecules=molecules, cache=None) proposal_engine = SmallMoleculeSetProposalEngine([initial_molecule, proposed_molecule], system_generator) initial_system, initial_positions, initial_topology = OEMol_to_omm_ff(initial_molecule, system_generator) print(f"running now with map strength {example}") proposal = proposal_engine.propose(initial_system, initial_topology, map_strength = example) print(lig_a, lig_b,'length OLD and NEW atoms',len(proposal.unique_old_atoms), len(proposal.unique_new_atoms)) if test: render_atom_mapping(f'{index}-{example}.png', initial_molecule, proposed_molecule, proposal._new_to_old_atom_map) assert ( (len(proposal.unique_old_atoms), len(proposal.unique_new_atoms)) == correct_results[index][example]), f"the mapping failed, correct results are {correct_results[index][example]}" print(f"the mapping worked!!!") print()
def test_ring_breaking_detection(): """ Test the detection of ring-breaking transformations. """ from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine 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 = SmallMoleculeSetProposalEngine._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 = SmallMoleculeSetProposalEngine._get_mol_atom_map( molecule1, molecule2, allow_ring_breaking=False) 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=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 generate_vacuum_hybrid_topology(mol_name="naphthalene", ref_mol_name="benzene"): from topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal import simtk.openmm.app as app from openmoltools import forcefield_generators from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename m, unsolv_old_system, pos_old, top_old = createSystemFromIUPAC(mol_name) refmol = createOEMolFromIUPAC(ref_mol_name) initial_smiles = oechem.OEMolToSmiles(m) final_smiles = oechem.OEMolToSmiles(refmol) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) solvated_system = forcefield.createSystem(top_old) gaff_filename = get_data_filename('data/gaff.xml') system_generator = SystemGenerator([gaff_filename, 'amber99sbildn.xml', 'tip3p.xml']) geometry_engine = FFAllAngleGeometryEngine() proposal_engine = SmallMoleculeSetProposalEngine( [initial_smiles, final_smiles], system_generator, residue_name=mol_name) #generate topology proposal topology_proposal = proposal_engine.propose(solvated_system, top_old) #generate new positions with geometry engine new_positions, _ = geometry_engine.propose(topology_proposal, pos_old, beta) return topology_proposal, pos_old, new_positions
def generate_solvated_hybrid_test_topology(current_mol_name="naphthalene", proposed_mol_name="benzene"): """ Generate a test solvated topology proposal, current positions, and new positions triplet from two IUPAC molecule names. Parameters ---------- current_mol_name : str, optional name of the first molecule proposed_mol_name : str, optional name of the second molecule 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 perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename current_mol, unsolv_old_system, pos_old, top_old = createSystemFromIUPAC(current_mol_name) proposed_mol = createOEMolFromIUPAC(proposed_mol_name) initial_smiles = oechem.OEMolToSmiles(current_mol) final_smiles = oechem.OEMolToSmiles(proposed_mol) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) modeller = app.Modeller(top_old, pos_old) modeller.addSolvent(forcefield, model='tip3p', padding=9.0*unit.angstrom) solvated_topology = modeller.getTopology() solvated_positions = modeller.getPositions() solvated_system = forcefield.createSystem(solvated_topology, nonbondedMethod=app.PME, removeCMMotion=False) barostat = openmm.MonteCarloBarostat(1.0*unit.atmosphere, temperature, 50) solvated_system.addForce(barostat) gaff_filename = get_data_filename('data/gaff.xml') system_generator = SystemGenerator([gaff_filename, 'amber99sbildn.xml', 'tip3p.xml'], barostat=barostat, forcefield_kwargs={'removeCMMotion': False, 'nonbondedMethod': app.PME}) geometry_engine = geometry.FFAllAngleGeometryEngine() proposal_engine = SmallMoleculeSetProposalEngine( [initial_smiles, final_smiles], system_generator, residue_name=current_mol_name) #generate topology proposal topology_proposal = proposal_engine.propose(solvated_system, solvated_topology) #generate new positions with geometry engine new_positions, _ = geometry_engine.propose(topology_proposal, solvated_positions, beta) return topology_proposal, solvated_positions, new_positions
def generate_top_pos_sys(topology, old_oemol, new_oemol, system, positions, system_generator, map_strength): """generate point mutation engine, geometry_engine, and conduct topology proposal, geometry propsal, and hybrid factory generation""" #create the point mutation engine print(f"generating point mutation engine") proposal_engine = SmallMoleculeSetProposalEngine(['CCCCO', 'CCCCS'], system_generator, map_strength=map_strength, residue_name='MOL') #create a geometry engine print(f"generating geometry engine") geometry_engine = FFAllAngleGeometryEngine(metadata=None, use_sterics=False, n_bond_divisions=100, 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, use_14_nonbondeds=False) #create a top proposal print(f"making topology proposal") topology_proposal = proposal_engine.propose(system, topology, old_oemol, new_oemol) #make a geometry proposal forward print(f"making geometry proposal") forward_new_positions, logp_proposal = geometry_engine.propose( topology_proposal, positions, beta) #create a hybrid topology factory f"making forward hybridtopologyfactory" forward_htf = HybridTopologyFactory(topology_proposal=topology_proposal, current_positions=positions, new_positions=forward_new_positions, use_dispersion_correction=False, functions=None, softcore_alpha=None, bond_softening_constant=1.0, angle_softening_constant=1.0, soften_only_new=False, neglected_new_angle_terms=[], neglected_old_angle_terms=[], softcore_LJ_v2=True, softcore_electrostatics=True, softcore_LJ_v2_alpha=0.85, softcore_electrostatics_alpha=0.3, softcore_sigma_Q=1.0, interpolate_old_and_new_14s=False, omitted_terms=None) return topology_proposal, forward_new_positions, forward_htf
def generate_vacuum_hostguest_proposal(current_mol_name="B2", proposed_mol_name="MOL"): """ Generate a test vacuum topology proposal, current positions, and new positions triplet from two IUPAC molecule names. Parameters ---------- current_mol_name : str, optional name of the first molecule proposed_mol_name : str, optional name of the second molecule 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 """ from openmoltools import forcefield_generators from openmmtools import testsystems from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename host_guest = testsystems.HostGuestVacuum() unsolv_old_system, pos_old, top_old = host_guest.system, host_guest.positions, host_guest.topology ligand_topology = [res for res in top_old.residues()] current_mol = forcefield_generators.generateOEMolFromTopologyResidue(ligand_topology[1]) # guest is second residue in topology proposed_mol = createOEMolFromSMILES('C1CC2(CCC1(CC2)C)C') initial_smiles = oechem.OEMolToSmiles(current_mol) final_smiles = oechem.OEMolToSmiles(proposed_mol) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) solvated_system = forcefield.createSystem(top_old, removeCMMotion=False) gaff_filename = get_data_filename('data/gaff.xml') system_generator = SystemGenerator([gaff_filename, 'amber99sbildn.xml', 'tip3p.xml'], forcefield_kwargs={'removeCMMotion': False, 'nonbondedMethod': app.NoCutoff}) geometry_engine = geometry.FFAllAngleGeometryEngine() proposal_engine = SmallMoleculeSetProposalEngine( [initial_smiles, final_smiles], system_generator, residue_name=current_mol_name) #generate topology proposal topology_proposal = proposal_engine.propose(solvated_system, top_old, current_mol=current_mol, proposed_mol=proposed_mol) #generate new positions with geometry engine new_positions, _ = geometry_engine.propose(topology_proposal, pos_old, beta) return topology_proposal, pos_old, new_positions
def generate_vacuum_topology_proposal(current_mol_name="benzene", proposed_mol_name="toluene"): """ Generate a test vacuum topology proposal, current positions, and new positions triplet from two IUPAC molecule names. Parameters ---------- current_mol_name : str, optional name of the first molecule proposed_mol_name : str, optional name of the second molecule 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 """ from openmoltools import forcefield_generators from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename current_mol, unsolv_old_system, pos_old, top_old = createSystemFromIUPAC(current_mol_name) proposed_mol = createOEMolFromIUPAC(proposed_mol_name) initial_smiles = oechem.OEMolToSmiles(current_mol) final_smiles = oechem.OEMolToSmiles(proposed_mol) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) solvated_system = forcefield.createSystem(top_old, removeCMMotion=False) gaff_filename = get_data_filename('data/gaff.xml') system_generator = SystemGenerator([gaff_filename, 'amber99sbildn.xml', 'tip3p.xml'], forcefield_kwargs={'removeCMMotion': False, 'nonbondedMethod': app.NoCutoff}) geometry_engine = geometry.FFAllAngleGeometryEngine() proposal_engine = SmallMoleculeSetProposalEngine( [initial_smiles, final_smiles], system_generator, residue_name=current_mol_name) #generate topology proposal topology_proposal = proposal_engine.propose(solvated_system, top_old, current_mol=current_mol, proposed_mol=proposed_mol) #generate new positions with geometry engine new_positions, _ = geometry_engine.propose(topology_proposal, pos_old, beta) return topology_proposal, pos_old, new_positions
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 SmallMoleculeSetProposalEngine 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 = SmallMoleculeSetProposalEngine._get_mol_atom_map( old_oemol, new_oemol, atom_expr=None, bond_expr=None, verbose=False, 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 generate_hybrid_test_topology(mol_name="naphthalene", ref_mol_name="benzene"): """ Generate a test topology proposal and positions for the hybrid test. """ from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC mol = createOEMolFromIUPAC(mol_name) m, system, positions, topology = createSystemFromIUPAC(mol_name) refmol = createOEMolFromIUPAC(ref_mol_name) #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') return top_proposal, positions
def test_small_molecule_proposals(): """ Make sure the small molecule proposal engine generates molecules """ from perses.rjmc import topology_proposal from openmoltools import forcefield_generators from collections import defaultdict from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine import openeye.oechem as oechem list_of_smiles = ['CCCC','CCCCC','CCCCCC'] gaff_xml_filename = get_data_filename('data/gaff.xml') stats_dict = defaultdict(lambda: 0) system_generator = topology_proposal.SystemGenerator([gaff_xml_filename]) proposal_engine = topology_proposal.SmallMoleculeSetProposalEngine(list_of_smiles, system_generator) initial_molecule = generate_initial_molecule('CCCC') initial_system, initial_positions, initial_topology = oemol_to_omm_ff(initial_molecule, "MOL") proposal = proposal_engine.propose(initial_system, initial_topology) for i in range(50): #positions are ignored here, and we don't want to run the geometry engine new_proposal = proposal_engine.propose(proposal.old_system, proposal.old_topology) stats_dict[new_proposal.new_chemical_state_key] += 1 #check that the molecule it generated is actually the smiles we expect matching_molecules = [res for res in proposal.new_topology.residues() if res.name=='MOL'] if len(matching_molecules) != 1: raise ValueError("More than one residue with the same name!") mol_res = matching_molecules[0] oemol = forcefield_generators.generateOEMolFromTopologyResidue(mol_res) smiles = SmallMoleculeSetProposalEngine.canonicalize_smiles(oechem.OEMolToSmiles(oemol)) assert smiles == proposal.new_chemical_state_key proposal = new_proposal
def test_small_molecule_proposals(): """ Make sure the small molecule proposal engine generates molecules """ list_of_smiles = ['CCCC','CCCCC','CCCCCC'] list_of_mols = [] for smi in list_of_smiles: mol = smiles_to_oemol(smi) list_of_mols.append(mol) molecules = [Molecule.from_openeye(mol) for mol in list_of_mols] stats_dict = defaultdict(lambda: 0) system_generator = SystemGenerator(forcefields = forcefield_files, barostat=barostat, forcefield_kwargs=forcefield_kwargs, nonperiodic_forcefield_kwargs=nonperiodic_forcefield_kwargs, small_molecule_forcefield = small_molecule_forcefield, molecules=molecules, cache=None) proposal_engine = topology_proposal.SmallMoleculeSetProposalEngine(list_of_mols, system_generator) initial_system, initial_positions, initial_topology, = OEMol_to_omm_ff(list_of_mols[0], system_generator) proposal = proposal_engine.propose(initial_system, initial_topology) for i in range(50): #positions are ignored here, and we don't want to run the geometry engine new_proposal = proposal_engine.propose(proposal.old_system, proposal.old_topology) stats_dict[new_proposal.new_chemical_state_key] += 1 #check that the molecule it generated is actually the smiles we expect matching_molecules = [res for res in proposal.new_topology.residues() if res.name=='MOL'] if len(matching_molecules) != 1: raise ValueError("More than one residue with the same name!") mol_res = matching_molecules[0] oemol = generateOEMolFromTopologyResidue(mol_res) smiles = SmallMoleculeSetProposalEngine.canonicalize_smiles(oechem.OEMolToSmiles(oemol)) assert smiles == proposal.new_chemical_state_key proposal = new_proposal
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 generate_solvated_hybrid_test_topology(mol_name="naphthalene", ref_mol_name="benzene"): from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal import simtk.openmm.app as app from openmoltools import forcefield_generators from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename mol = createOEMolFromIUPAC(mol_name) m, unsolv_system, pos, top = createSystemFromIUPAC(mol_name) refmol = createOEMolFromIUPAC(ref_mol_name) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator( forcefield_generators.gaffTemplateGenerator) #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()} modeller = app.Modeller(top, pos) modeller.addSolvent(forcefield, model='tip3p', padding=9.0 * unit.angstrom) topology = modeller.getTopology() positions = modeller.getPositions() system = forcefield.createSystem(topology, nonbondedMethod=app.PME) n_atoms_old_system = unsolv_system.getNumParticles() n_atoms_after_solvation = system.getNumParticles() for i in range(n_atoms_old_system, n_atoms_after_solvation): effective_atom_map[i] = i 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') return top_proposal, positions
def test_molecular_atom_mapping(): """ Test the creation of atom maps between pairs of molecules from the JACS benchmark set. """ from openeye import oechem from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine from perses.tests.utils import createOEMolFromSMILES from perses.tests.utils import render_atom_mapping from itertools import combinations # Test mappings for JACS dataset ligands for dataset_name in [ 'CDK2' ]: #, 'p38', 'Tyk2', 'Thrombin', 'PTP1B', 'MCL1', 'Jnk1', 'Bace']: # Read molecules dataset_path = 'data/schrodinger-jacs-datasets/%s_ligands.sdf' % dataset_name mol2_filename = resource_filename('perses', dataset_path) ifs = oechem.oemolistream(mol2_filename) molecules = list() for mol in ifs.GetOEGraphMols(): molecules.append(oechem.OEGraphMol(mol)) # Build atom map for some transformations. #for (molecule1, molecule2) in combinations(molecules, 2): # too slow molecule1 = molecules[0] for i, molecule2 in enumerate(molecules[1:]): new_to_old_atom_map = SmallMoleculeSetProposalEngine._get_mol_atom_map( molecule1, molecule2) # Make sure we aren't mapping hydrogens onto anything else atoms1 = [atom for atom in molecule1.GetAtoms()] atoms2 = [atom for atom in molecule2.GetAtoms()] #for (index2, index1) in new_to_old_atom_map.items(): # atom1, atom2 = atoms1[index1], atoms2[index2] # if (atom1.GetAtomicNum()==1) != (atom2.GetAtomicNum()==1): filename = 'mapping-error-%d.png' % i render_atom_mapping(filename, molecule1, molecule2, new_to_old_atom_map) #msg = 'Atom atomic number %d is being mapped to atomic number %d\n' % (atom1.GetAtomicNum(), atom2.GetAtomicNum()) msg = 'molecule 1 : %s\n' % oechem.OECreateIsoSmiString(molecule1) msg += 'molecule 2 : %s\n' % oechem.OECreateIsoSmiString(molecule2) msg += 'Wrote atom mapping to %s for inspection; please check this.' % filename msg += str(new_to_old_atom_map) print(msg)
def generate_solvated_hybrid_test_topology(mol_name="naphthalene", ref_mol_name="benzene"): from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal import simtk.openmm.app as app from openmoltools import forcefield_generators from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename mol = createOEMolFromIUPAC(mol_name) m, unsolv_system, pos, top = createSystemFromIUPAC(mol_name) refmol = createOEMolFromIUPAC(ref_mol_name) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) #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()} modeller = app.Modeller(top, pos) modeller.addSolvent(forcefield, model='tip3p', padding=9.0*unit.angstrom) topology = modeller.getTopology() positions = modeller.getPositions() system = forcefield.createSystem(topology, nonbondedMethod=app.PME) n_atoms_old_system = unsolv_system.getNumParticles() n_atoms_after_solvation = system.getNumParticles() for i in range(n_atoms_old_system, n_atoms_after_solvation): effective_atom_map[i] = i 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') return top_proposal, positions
def generate_hybrid_test_topology(mol_name="naphthalene", ref_mol_name="benzene"): """ Generate a test topology proposal and positions for the hybrid test. """ from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC mol = createOEMolFromIUPAC(mol_name) m, system, positions, topology = createSystemFromIUPAC(mol_name) refmol = createOEMolFromIUPAC(ref_mol_name) #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') return top_proposal, positions
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 perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal from perses.annihilation.new_relative import HybridTopologyFactory import simtk.openmm as openmm from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC mol_name = "naphthalene" ref_mol_name = "benzene" mol = createOEMolFromIUPAC(mol_name) m, system, positions, topology = createSystemFromIUPAC(mol_name) refmol = createOEMolFromIUPAC(ref_mol_name) #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 integrator = openmm.VerletIntegrator(1) platform = openmm.Platform.getPlatformByName("Reference") context = openmm.Context(alchemical_system, integrator, platform) context.setPositions(alchemical_positions) functions = { 'lambda_sterics': '2*lambda * step(0.5 - lambda) + (1.0 - step(0.5 - lambda))', 'lambda_electrostatics': '2*(lambda - 0.5) * step(lambda - 0.5)', 'lambda_bonds': 'lambda', 'lambda_angles': 'lambda', 'lambda_torsions': 'lambda' } #set all to zero for parm in functions.keys(): context.setParameter(parm, 0.0) initial_energy = context.getState(getEnergy=True).getPotentialEnergy() #set all to one for parm in functions.keys(): context.setParameter(parm, 1.0) final_energy = context.getState(getEnergy=True).getPotentialEnergy() if np.abs(final_energy - initial_energy) > 1.0e-6 * unit.kilojoule_per_mole: raise Exception( "The energy at the endpoints was not equal for molecule %s" % mol_name)
def __init__(self, molecules: List[str], output_filename: str, ncmc_switching_times: Dict[str, int], equilibrium_steps: Dict[str, int], timestep: unit.Quantity, initial_molecule: str=None, geometry_options: Dict=None): self._molecules = [SmallMoleculeSetProposalEngine.canonicalize_smiles(molecule) for molecule in molecules] environments = ['explicit', 'vacuum'] temperature = 298.15 * unit.kelvin pressure = 1.0 * unit.atmospheres constraints = app.HBonds self._storage = NetCDFStorage(output_filename) self._ncmc_switching_times = ncmc_switching_times self._n_equilibrium_steps = equilibrium_steps self._geometry_options = geometry_options # Create a system generator for our desired forcefields. from perses.rjmc.topology_proposal import SystemGenerator system_generators = dict() from pkg_resources import resource_filename gaff_xml_filename = resource_filename('perses', 'data/gaff.xml') barostat = openmm.MonteCarloBarostat(pressure, temperature) system_generators['explicit'] = SystemGenerator([gaff_xml_filename, 'tip3p.xml'], forcefield_kwargs={'nonbondedMethod': app.PME, 'nonbondedCutoff': 9.0 * unit.angstrom, 'implicitSolvent': None, 'constraints': constraints, 'ewaldErrorTolerance': 1e-5, 'hydrogenMass': 3.0*unit.amu}, barostat=barostat) system_generators['vacuum'] = SystemGenerator([gaff_xml_filename], forcefield_kwargs={'nonbondedMethod': app.NoCutoff, 'implicitSolvent': None, 'constraints': constraints, 'hydrogenMass': 3.0*unit.amu}) # # Create topologies and positions # topologies = dict() positions = dict() from openmoltools import forcefield_generators forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) # Create molecule in vacuum. from perses.tests.utils import createOEMolFromSMILES, extractPositionsFromOEMOL if initial_molecule: smiles = initial_molecule else: smiles = np.random.choice(molecules) molecule = createOEMolFromSMILES(smiles) topologies['vacuum'] = forcefield_generators.generateTopologyFromOEMol(molecule) positions['vacuum'] = extractPositionsFromOEMOL(molecule) # Create molecule in solvent. modeller = app.Modeller(topologies['vacuum'], positions['vacuum']) modeller.addSolvent(forcefield, model='tip3p', padding=9.0 * unit.angstrom) topologies['explicit'] = modeller.getTopology() positions['explicit'] = modeller.getPositions() # Set up the proposal engines. proposal_metadata = {} proposal_engines = dict() for environment in environments: proposal_engines[environment] = SmallMoleculeSetProposalEngine(self._molecules, system_generators[environment]) # Generate systems systems = dict() for environment in environments: systems[environment] = system_generators[environment].build_system(topologies[environment]) # Define thermodynamic state of interest. thermodynamic_states = dict() thermodynamic_states['explicit'] = states.ThermodynamicState(system=systems['explicit'], temperature=temperature, pressure=pressure) thermodynamic_states['vacuum'] = states.ThermodynamicState(system=systems['vacuum'], temperature=temperature) # Create SAMS samplers from perses.samplers.samplers import ExpandedEnsembleSampler, SAMSSampler mcmc_samplers = dict() exen_samplers = dict() sams_samplers = dict() for environment in environments: storage = NetCDFStorageView(self._storage, envname=environment) if self._geometry_options: n_torsion_divisions = self._geometry_options['n_torsion_divsions'][environment] use_sterics = self._geometry_options['use_sterics'][environment] else: n_torsion_divisions = 180 use_sterics = False geometry_engine = geometry.FFAllAngleGeometryEngine(storage=storage, n_torsion_divisions=n_torsion_divisions, use_sterics=use_sterics) move = mcmc.LangevinSplittingDynamicsMove(timestep=timestep, splitting="V R O R V", n_restart_attempts=10) chemical_state_key = proposal_engines[environment].compute_state_key(topologies[environment]) if environment == 'explicit': sampler_state = states.SamplerState(positions=positions[environment], box_vectors=systems[environment].getDefaultPeriodicBoxVectors()) else: sampler_state = states.SamplerState(positions=positions[environment]) mcmc_samplers[environment] = mcmc.MCMCSampler(thermodynamic_states[environment], sampler_state, move) exen_samplers[environment] = ExpandedEnsembleSampler(mcmc_samplers[environment], topologies[environment], chemical_state_key, proposal_engines[environment], geometry_engine, options={'nsteps': self._ncmc_switching_times[environment]}, storage=storage, ncmc_write_interval=self._ncmc_switching_times[environment]) exen_samplers[environment].verbose = True sams_samplers[environment] = SAMSSampler(exen_samplers[environment], storage=storage) sams_samplers[environment].verbose = True # Create test MultiTargetDesign sampler. from perses.samplers.samplers import MultiTargetDesign target_samplers = {sams_samplers['explicit']: 1.0, sams_samplers['vacuum']: -1.0} designer = MultiTargetDesign(target_samplers, storage=self._storage) # Store things. self.molecules = molecules self.environments = environments self.topologies = topologies self.positions = positions self.system_generators = system_generators self.proposal_engines = proposal_engines self.thermodynamic_states = thermodynamic_states self.mcmc_samplers = mcmc_samplers self.exen_samplers = exen_samplers self.sams_samplers = sams_samplers self.designer = designer
def __init__(self): super(abl_src_affinity, self).__init__() #solvents = ['vacuum', 'explicit'] # TODO: Add 'implicit' once GBSA parameterization for small molecules is working solvents = ['explicit'] # DEBUG components = [ 'src-imatinib', 'abl-imatinib' ] # TODO: Add 'ATP:kinase' complex to enable resistance design padding = 9.0 * unit.angstrom explicit_solvent_model = 'tip3p' setup_path = 'data/abl-src' thermodynamic_states = dict() temperature = 300 * unit.kelvin pressure = 1.0 * unit.atmospheres # Construct list of all environments environments = list() for solvent in solvents: for component in components: environment = solvent + '-' + component environments.append(environment) # Read SMILES from CSV file of clinical kinase inhibitors. from pkg_resources import resource_filename smiles_filename = resource_filename( 'perses', 'data/clinical-kinase-inhibitors.csv') import csv molecules = list() with open(smiles_filename, 'r') as csvfile: csvreader = csv.reader(csvfile, delimiter=',', quotechar='"') for row in csvreader: name = row[0] smiles = row[1] molecules.append(smiles) # Add current molecule molecules.append( 'Cc1ccc(cc1Nc2nccc(n2)c3cccnc3)NC(=O)c4ccc(cc4)C[NH+]5CCN(CC5)C') self.molecules = molecules # Expand molecules without explicit stereochemistry and make canonical isomeric SMILES. molecules = sanitizeSMILES(self.molecules) # Create a system generator for desired forcefields from perses.rjmc.topology_proposal import SystemGenerator from pkg_resources import resource_filename gaff_xml_filename = resource_filename('perses', 'data/gaff.xml') system_generators = dict() system_generators['explicit'] = SystemGenerator( [gaff_xml_filename, 'amber99sbildn.xml', 'tip3p.xml'], forcefield_kwargs={ 'nonbondedMethod': app.CutoffPeriodic, 'nonbondedCutoff': 9.0 * unit.angstrom, 'implicitSolvent': None, 'constraints': None }, use_antechamber=True) system_generators['implicit'] = SystemGenerator( [gaff_xml_filename, 'amber99sbildn.xml', 'amber99_obc.xml'], forcefield_kwargs={ 'nonbondedMethod': app.NoCutoff, 'implicitSolvent': app.OBC2, 'constraints': None }, use_antechamber=True) system_generators['vacuum'] = SystemGenerator( [gaff_xml_filename, 'amber99sbildn.xml'], forcefield_kwargs={ 'nonbondedMethod': app.NoCutoff, 'implicitSolvent': None, 'constraints': None }, use_antechamber=True) # Copy system generators for all environments for solvent in solvents: for component in components: environment = solvent + '-' + component system_generators[environment] = system_generators[solvent] # Load topologies and positions for all components from simtk.openmm.app import PDBFile, Modeller topologies = dict() positions = dict() for component in components: pdb_filename = resource_filename( 'perses', os.path.join(setup_path, '%s.pdb' % component)) print(pdb_filename) pdbfile = PDBFile(pdb_filename) topologies[component] = pdbfile.topology positions[component] = pdbfile.positions # Construct positions and topologies for all solvent environments for solvent in solvents: for component in components: environment = solvent + '-' + component if solvent == 'explicit': # Create MODELLER object. modeller = app.Modeller(topologies[component], positions[component]) modeller.addSolvent( system_generators[solvent].getForceField(), model='tip3p', padding=9.0 * unit.angstrom) topologies[environment] = modeller.getTopology() positions[environment] = modeller.getPositions() else: environment = solvent + '-' + component topologies[environment] = topologies[component] positions[environment] = positions[component] # Set up the proposal engines. from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine proposal_metadata = {} proposal_engines = dict() for environment in environments: proposal_engines[environment] = SmallMoleculeSetProposalEngine( molecules, system_generators[environment], residue_name='MOL') # Generate systems systems = dict() for environment in environments: systems[environment] = system_generators[environment].build_system( topologies[environment]) # Define thermodynamic state of interest. from perses.samplers.thermodynamics import ThermodynamicState thermodynamic_states = dict() temperature = 300 * unit.kelvin pressure = 1.0 * unit.atmospheres for component in components: for solvent in solvents: environment = solvent + '-' + component if solvent == 'explicit': thermodynamic_states[environment] = ThermodynamicState( system=systems[environment], temperature=temperature, pressure=pressure) else: thermodynamic_states[environment] = ThermodynamicState( system=systems[environment], temperature=temperature) # Create SAMS samplers from perses.samplers.samplers import SamplerState, MCMCSampler, ExpandedEnsembleSampler, SAMSSampler mcmc_samplers = dict() exen_samplers = dict() sams_samplers = dict() for solvent in solvents: for component in components: environment = solvent + '-' + component chemical_state_key = proposal_engines[ environment].compute_state_key(topologies[environment]) if solvent == 'explicit': thermodynamic_state = ThermodynamicState( system=systems[environment], temperature=temperature, pressure=pressure) sampler_state = SamplerState( system=systems[environment], positions=positions[environment], box_vectors=systems[environment]. getDefaultPeriodicBoxVectors()) else: thermodynamic_state = ThermodynamicState( system=systems[environment], temperature=temperature) sampler_state = SamplerState( system=systems[environment], positions=positions[environment]) mcmc_samplers[environment] = MCMCSampler( thermodynamic_state, sampler_state) mcmc_samplers[ environment].nsteps = 5 # reduce number of steps for testing mcmc_samplers[environment].verbose = True exen_samplers[environment] = ExpandedEnsembleSampler( mcmc_samplers[environment], topologies[environment], chemical_state_key, proposal_engines[environment], options={'nsteps': 5}) exen_samplers[environment].verbose = True sams_samplers[environment] = SAMSSampler( exen_samplers[environment]) sams_samplers[environment].verbose = True thermodynamic_states[environment] = thermodynamic_state # Create test MultiTargetDesign sampler. # TODO: Replace this with inhibitor:kinase and ATP:kinase ratio from perses.samplers.samplers import MultiTargetDesign target_samplers = { sams_samplers['explicit-src-imatinib']: 1.0, sams_samplers['explicit-abl-imatinib']: -1.0 } designer = MultiTargetDesign(target_samplers) designer.verbose = True # Store things. self.molecules = molecules self.environments = environments self.topologies = topologies self.positions = positions self.system_generators = system_generators self.systems = systems self.proposal_engines = proposal_engines self.thermodynamic_states = thermodynamic_states self.mcmc_samplers = mcmc_samplers self.exen_samplers = exen_samplers self.sams_samplers = sams_samplers self.designer = designer # This system must currently be minimized. minimize(self)
def generate_vacuum_hostguest_proposal(current_mol_name="B2", proposed_mol_name="MOL"): """ Generate a test vacuum topology proposal, current positions, and new positions triplet from two IUPAC molecule names. Parameters ---------- current_mol_name : str, optional name of the first molecule proposed_mol_name : str, optional name of the second molecule 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 """ from openmoltools import forcefield_generators from openmmtools import testsystems from perses.utils.openeye import smiles_to_oemol from perses.utils.data import get_data_filename host_guest = testsystems.HostGuestVacuum() unsolv_old_system, old_positions, top_old = host_guest.system, host_guest.positions, host_guest.topology ligand_topology = [res for res in top_old.residues()] current_mol = forcefield_generators.generateOEMolFromTopologyResidue( ligand_topology[1]) # guest is second residue in topology proposed_mol = smiles_to_oemol('C1CC2(CCC1(CC2)C)C') initial_smiles = oechem.OEMolToSmiles(current_mol) final_smiles = oechem.OEMolToSmiles(proposed_mol) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator( forcefield_generators.gaffTemplateGenerator) solvated_system = forcefield.createSystem(top_old, removeCMMotion=False) gaff_filename = get_data_filename('data/gaff.xml') system_generator = SystemGenerator( [gaff_filename, 'amber99sbildn.xml', 'tip3p.xml'], forcefield_kwargs={ 'removeCMMotion': False, 'nonbondedMethod': app.NoCutoff }) geometry_engine = geometry.FFAllAngleGeometryEngine() proposal_engine = SmallMoleculeSetProposalEngine( [initial_smiles, final_smiles], system_generator, residue_name=current_mol_name) #generate topology proposal topology_proposal = proposal_engine.propose(solvated_system, top_old, current_mol=current_mol, proposed_mol=proposed_mol) #generate new positions with geometry engine new_positions, _ = geometry_engine.propose(topology_proposal, old_positions, beta) return topology_proposal, old_positions, new_positions
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
def generate_vacuum_topology_proposal(current_mol_name="benzene", proposed_mol_name="toluene", forcefield_kwargs=None, system_generator_kwargs=None): """ Generate a test vacuum topology proposal, current positions, and new positions triplet from two IUPAC molecule names. Constraints are added to the system by default. To override this, set ``forcefield_kwargs = None``. Parameters ---------- current_mol_name : str, optional name of the first molecule proposed_mol_name : str, optional name of the second molecule forcefield_kwargs : dict, optional, default=None Additional arguments to ForceField in addition to 'removeCMMotion': False, 'nonbondedMethod': app.NoCutoff system_generator_kwargs : dict, optional, default=None Dict passed onto SystemGenerator 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 """ from openmoltools import forcefield_generators from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename gaff_filename = get_data_filename('data/gaff.xml') default_forcefield_kwargs = {'removeCMMotion': False, 'nonbondedMethod': app.NoCutoff, 'constraints' : app.HBonds} forcefield_kwargs = default_forcefield_kwargs.update(forcefield_kwargs) if (forcefield_kwargs is not None) else default_forcefield_kwargs system_generator_kwargs = system_generator_kwargs if (system_generator_kwargs is not None) else dict() system_generator = SystemGenerator([gaff_filename, 'amber99sbildn.xml', 'tip3p.xml'], forcefield_kwargs=forcefield_kwargs, **system_generator_kwargs) old_oemol = createOEMolFromIUPAC(current_mol_name) from openmoltools.forcefield_generators import generateTopologyFromOEMol old_topology = generateTopologyFromOEMol(old_oemol) old_positions = extractPositionsFromOEMOL(old_oemol) old_smiles = oechem.OEMolToSmiles(old_oemol) old_system = system_generator.build_system(old_topology) new_oemol = createOEMolFromIUPAC(proposed_mol_name) new_smiles = oechem.OEMolToSmiles(new_oemol) geometry_engine = geometry.FFAllAngleGeometryEngine() proposal_engine = SmallMoleculeSetProposalEngine( [old_smiles, new_smiles], system_generator, residue_name=current_mol_name) #generate topology proposal topology_proposal = proposal_engine.propose(old_system, old_topology, current_mol=old_oemol, proposed_mol=new_oemol) # show atom mapping filename = str(current_mol_name)+str(proposed_mol_name)+'.pdf' render_atom_mapping(filename, old_oemol, new_oemol, topology_proposal.new_to_old_atom_map) #generate new positions with geometry engine new_positions, _ = geometry_engine.propose(topology_proposal, old_positions, beta) # DEBUG: Zero out bonds and angles for one system #print('Zeroing bonds of old system') #for force in topology_proposal.old_system.getForces(): # if force.__class__.__name__ == 'HarmonicAngleForce': # for index in range(force.getNumAngles()): # p1, p2, p3, angle, K = force.getAngleParameters(index) # K *= 0.0 # force.setAngleParameters(index, p1, p2, p3, angle, K) # if False and force.__class__.__name__ == 'HarmonicBondForce': # for index in range(force.getNumBonds()): # p1, p2, r0, K = force.getBondParameters(index) # K *= 0.0 # force.setBondParameters(index, p1, p2, r0, K) # DEBUG : Box vectors #box_vectors = np.eye(3) * 100 * unit.nanometers #topology_proposal.old_system.setDefaultPeriodicBoxVectors(box_vectors[0,:], box_vectors[1,:], box_vectors[2,:]) #topology_proposal.new_system.setDefaultPeriodicBoxVectors(box_vectors[0,:], box_vectors[1,:], box_vectors[2,:]) return topology_proposal, old_positions, new_positions
def compare_energies(mol_name="naphthalene", ref_mol_name="benzene", atom_expression=['Hybridization'], bond_expression=['Hybridization']): """ Make an atom map where the molecule at either lambda endpoint is identical, and check that the energies are also the same. """ from openmmtools.constants import kB from openmmtools import alchemy, states from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine from perses.annihilation.relative import HybridTopologyFactory from perses.rjmc.geometry import FFAllAngleGeometryEngine import simtk.openmm as openmm from perses.utils.openeye import iupac_to_oemol, extractPositionsFromOEMol, generate_conformers from perses.utils.openeye import generate_expression from openmmforcefields.generators import SystemGenerator from openmoltools.forcefield_generators import generateTopologyFromOEMol from perses.tests.utils import validate_endstate_energies temperature = 300 * unit.kelvin # Compute kT and inverse temperature. kT = kB * temperature beta = 1.0 / kT ENERGY_THRESHOLD = 1e-6 atom_expr, bond_expr = generate_expression( atom_expression), generate_expression(bond_expression) mol = iupac_to_oemol(mol_name) mol = generate_conformers(mol, max_confs=1) refmol = iupac_to_oemol(ref_mol_name) refmol = generate_conformers(refmol, max_confs=1) from openforcefield.topology import Molecule molecules = [Molecule.from_openeye(oemol) for oemol in [refmol, mol]] barostat = None forcefield_files = ['amber14/protein.ff14SB.xml', 'amber14/tip3p.xml'] forcefield_kwargs = { 'removeCMMotion': False, 'ewaldErrorTolerance': 1e-4, 'nonbondedMethod': app.NoCutoff, 'constraints': app.HBonds, 'hydrogenMass': 4 * unit.amus } system_generator = SystemGenerator(forcefields=forcefield_files, barostat=barostat, forcefield_kwargs=forcefield_kwargs, small_molecule_forcefield='gaff-2.11', molecules=molecules, cache=None) topology = generateTopologyFromOEMol(refmol) system = system_generator.create_system(topology) positions = extractPositionsFromOEMol(refmol) proposal_engine = SmallMoleculeSetProposalEngine([refmol, mol], system_generator) proposal = proposal_engine.propose(system, topology, atom_expr=atom_expr, bond_expr=bond_expr) geometry_engine = FFAllAngleGeometryEngine() new_positions, _ = geometry_engine.propose( proposal, positions, beta=beta, validate_energy_bookkeeping=False) _ = geometry_engine.logp_reverse(proposal, new_positions, positions, beta) #make a topology proposal with the appropriate data: factory = HybridTopologyFactory(proposal, positions, new_positions) if not proposal.unique_new_atoms: assert geometry_engine.forward_final_context_reduced_potential == None, f"There are no unique new atoms but the geometry_engine's final context reduced potential is not None (i.e. {self._geometry_engine.forward_final_context_reduced_potential})" assert geometry_engine.forward_atoms_with_positions_reduced_potential == None, f"There are no unique new atoms but the geometry_engine's forward atoms-with-positions-reduced-potential in not None (i.e. { self._geometry_engine.forward_atoms_with_positions_reduced_potential})" vacuum_added_valence_energy = 0.0 else: added_valence_energy = geometry_engine.forward_final_context_reduced_potential - geometry_engine.forward_atoms_with_positions_reduced_potential if not proposal.unique_old_atoms: assert geometry_engine.reverse_final_context_reduced_potential == None, f"There are no unique old atoms but the geometry_engine's final context reduced potential is not None (i.e. {self._geometry_engine.reverse_final_context_reduced_potential})" assert geometry_engine.reverse_atoms_with_positions_reduced_potential == None, f"There are no unique old atoms but the geometry_engine's atoms-with-positions-reduced-potential in not None (i.e. { self._geometry_engine.reverse_atoms_with_positions_reduced_potential})" subtracted_valence_energy = 0.0 else: subtracted_valence_energy = geometry_engine.reverse_final_context_reduced_potential - geometry_engine.reverse_atoms_with_positions_reduced_potential zero_state_error, one_state_error = validate_endstate_energies( factory._topology_proposal, factory, added_valence_energy, subtracted_valence_energy, beta=1.0 / (kB * temperature), ENERGY_THRESHOLD=ENERGY_THRESHOLD, platform=openmm.Platform.getPlatformByName('Reference')) return factory
def test_logp_forward_check_for_vacuum_topology_proposal(current_mol_name = 'propane', proposed_mol_name = 'octane', num_iterations = 100, neglect_angles = True): """ Generate a test vacuum topology proposal, current positions, and new positions triplet from two IUPAC molecule names. Assert that the logp_forward < 1e3. This assertion will fail if the proposal order tool proposed the placement of the a carbon before a previously defined carbon in the alkane. Parameters ---------- current_mol_name : str, optional name of the first molecule proposed_mol_name : str, optional name of the second molecule 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 """ from openmoltools import forcefield_generators from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC, get_data_filename current_mol, unsolv_old_system, pos_old, top_old = createSystemFromIUPAC(current_mol_name) proposed_mol = createOEMolFromIUPAC(proposed_mol_name) initial_smiles = oechem.OEMolToSmiles(current_mol) final_smiles = oechem.OEMolToSmiles(proposed_mol) gaff_xml_filename = get_data_filename("data/gaff.xml") forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) solvated_system = forcefield.createSystem(top_old, removeCMMotion=False) gaff_filename = get_data_filename('data/gaff.xml') system_generator = SystemGenerator([gaff_filename, 'amber99sbildn.xml', 'tip3p.xml'], forcefield_kwargs={'removeCMMotion': False, 'nonbondedMethod': app.NoCutoff}) geometry_engine = geometry.FFAllAngleGeometryEngine(n_bond_divisions=100, n_angle_divisions=180, n_torsion_divisions=360, neglect_angles = neglect_angles) proposal_engine = SmallMoleculeSetProposalEngine( [initial_smiles, final_smiles], system_generator, residue_name=current_mol_name) #generate topology proposal topology_proposal = proposal_engine.propose(solvated_system, top_old, current_mol=current_mol, proposed_mol=proposed_mol) # show atom mapping filename = str(current_mol_name)+str(proposed_mol_name)+'.pdf' render_atom_mapping(filename,current_mol,proposed_mol,topology_proposal.new_to_old_atom_map) total_works = [] for _ in range(num_iterations): #generate new positions with geometry engine new_positions, logp_forward = geometry_engine.propose(topology_proposal, pos_old, beta) logp_reverse = geometry_engine.logp_reverse(topology_proposal, new_positions, pos_old, beta) #now just render forward and backward work work_fwd = logp_forward + geometry_engine.forward_final_context_reduced_potential - geometry_engine.forward_atoms_with_positions_reduced_potential work_bkwd = logp_reverse + geometry_engine.reverse_atoms_with_positions_reduced_potential - geometry_engine.reverse_final_context_reduced_potential total_work = logp_forward - logp_reverse + geometry_engine.forward_final_context_reduced_potential - geometry_engine.reverse_final_context_reduced_potential total_works.append(total_work) print("forward, backward works : {}, {}".format(work_fwd, work_bkwd)) print("total_work: {}".format(total_work)) assert abs(work_fwd - work_bkwd - total_work) < 1, "The difference of fwd and backward works is not equal to the total work (within 1kT)" assert logp_forward < 1e3, "A heavy atom was proposed in an improper order"
topology = topology.to_openmm() topology.setPeriodicBoxVectors(system.getDefaultPeriodicBoxVectors()) ifs = oechem.oemolistream() ifs.open(ligand_filename) # get the list of molecules mol_list = [oechem.OEMol(mol) for mol in ifs.GetOEMols()] for idx, mol in enumerate(mol_list): mol.SetTitle("MOL{}".format(idx)) oechem.OETriposAtomNames(mol) initial_mol = mol_list[initial_ligand] proposal_mol = mol_list[proposal_ligand] proposal_smiles = SmallMoleculeSetProposalEngine.canonicalize_smiles(oechem.OECreateCanSmiString(proposal_mol)) current_smiles = SmallMoleculeSetProposalEngine.canonicalize_smiles(oechem.OECreateCanSmiString(initial_mol)) barostat = openmm.MonteCarloBarostat(1.0*unit.atmosphere, temperature, 50) system_generator = SystemGenerator(['amber14/protein.ff14SB.xml', 'gaff.xml', 'amber14/tip3p.xml', 'MCL1_ligands.xml'], barostat=barostat, forcefield_kwargs={'nonbondedMethod': app.PME, 'constraints': app.HBonds, 'hydrogenMass': 4 * unit.amus}, use_antechamber=False) atom_mapper_filename = os.path.join(setup_directory, "{}_atom_mapper.json".format(project_prefix)) with open(atom_mapper_filename, 'r') as infile: atom_mapper = SmallMoleculeAtomMapper.from_json(infile.read()) proposal_engine = PremappedSmallMoleculeSetProposalEngine(atom_mapper, system_generator) topology_proposal = proposal_engine.propose(system, topology, current_smiles=current_smiles, proposed_mol=proposal_mol, map_index=map_index)
def __init__(self, molecules: List[str], output_filename: str, ncmc_switching_times: Dict[str, int], equilibrium_steps: Dict[str, int], timestep: unit.Quantity, initial_molecule: str=None, geometry_options: Dict=None): self._molecules = [SmallMoleculeSetProposalEngine.canonicalize_smiles(molecule) for molecule in molecules] environments = ['explicit', 'vacuum'] temperature = 298.15 * unit.kelvin pressure = 1.0 * unit.atmospheres constraints = app.HBonds self._storage = NetCDFStorage(output_filename) self._ncmc_switching_times = ncmc_switching_times self._n_equilibrium_steps = equilibrium_steps self._geometry_options = geometry_options # Create a system generator for our desired forcefields. from perses.rjmc.topology_proposal import SystemGenerator system_generators = dict() from pkg_resources import resource_filename gaff_xml_filename = resource_filename('perses', 'data/gaff.xml') barostat = openmm.MonteCarloBarostat(pressure, temperature) system_generators['explicit'] = SystemGenerator([gaff_xml_filename, 'tip3p.xml'], forcefield_kwargs={'nonbondedCutoff': 9.0 * unit.angstrom, 'implicitSolvent': None, 'constraints': constraints, 'ewaldErrorTolerance': 1e-5, 'hydrogenMass': 3.0*unit.amu}, periodic_forcefield_kwargs = {'nonbondedMethod': app.PME} barostat=barostat) system_generators['vacuum'] = SystemGenerator([gaff_xml_filename], forcefield_kwargs={'implicitSolvent': None, 'constraints': constraints, 'hydrogenMass': 3.0*unit.amu}, nonperiodic_forcefield_kwargs = {'nonbondedMethod': app.NoCutoff}) # # Create topologies and positions # topologies = dict() positions = dict() from openmoltools import forcefield_generators forcefield = app.ForceField(gaff_xml_filename, 'tip3p.xml') forcefield.registerTemplateGenerator(forcefield_generators.gaffTemplateGenerator) # Create molecule in vacuum. from perses.utils.openeye import extractPositionsFromOEMol from openmoltools.openeye import smiles_to_oemol, generate_conformers if initial_molecule: smiles = initial_molecule else: smiles = np.random.choice(molecules) molecule = smiles_to_oemol(smiles) molecule = generate_conformers(molecule, max_confs=1) topologies['vacuum'] = forcefield_generators.generateTopologyFromOEMol(molecule) positions['vacuum'] = extractPositionsFromOEMol(molecule) # Create molecule in solvent. modeller = app.Modeller(topologies['vacuum'], positions['vacuum']) modeller.addSolvent(forcefield, model='tip3p', padding=9.0 * unit.angstrom) topologies['explicit'] = modeller.getTopology() positions['explicit'] = modeller.getPositions() # Set up the proposal engines. proposal_metadata = {} proposal_engines = dict() for environment in environments: proposal_engines[environment] = SmallMoleculeSetProposalEngine(self._molecules, system_generators[environment]) # Generate systems systems = dict() for environment in environments: systems[environment] = system_generators[environment].build_system(topologies[environment]) # Define thermodynamic state of interest. thermodynamic_states = dict() thermodynamic_states['explicit'] = states.ThermodynamicState(system=systems['explicit'], temperature=temperature, pressure=pressure) thermodynamic_states['vacuum'] = states.ThermodynamicState(system=systems['vacuum'], temperature=temperature) # Create SAMS samplers from perses.samplers.samplers import ExpandedEnsembleSampler, SAMSSampler mcmc_samplers = dict() exen_samplers = dict() sams_samplers = dict() for environment in environments: storage = NetCDFStorageView(self._storage, envname=environment) if self._geometry_options: n_torsion_divisions = self._geometry_options['n_torsion_divsions'][environment] use_sterics = self._geometry_options['use_sterics'][environment] else: n_torsion_divisions = 180 use_sterics = False geometry_engine = geometry.FFAllAngleGeometryEngine(storage=storage, n_torsion_divisions=n_torsion_divisions, use_sterics=use_sterics) move = mcmc.LangevinSplittingDynamicsMove(timestep=timestep, splitting="V R O R V", n_restart_attempts=10) chemical_state_key = proposal_engines[environment].compute_state_key(topologies[environment]) if environment == 'explicit': sampler_state = states.SamplerState(positions=positions[environment], box_vectors=systems[environment].getDefaultPeriodicBoxVectors()) else: sampler_state = states.SamplerState(positions=positions[environment]) mcmc_samplers[environment] = mcmc.MCMCSampler(thermodynamic_states[environment], sampler_state, move) exen_samplers[environment] = ExpandedEnsembleSampler(mcmc_samplers[environment], topologies[environment], chemical_state_key, proposal_engines[environment], geometry_engine, options={'nsteps': self._ncmc_switching_times[environment]}, storage=storage, ncmc_write_interval=self._ncmc_switching_times[environment]) exen_samplers[environment].verbose = True sams_samplers[environment] = SAMSSampler(exen_samplers[environment], storage=storage) sams_samplers[environment].verbose = True # Create test MultiTargetDesign sampler. from perses.samplers.samplers import MultiTargetDesign target_samplers = {sams_samplers['explicit']: 1.0, sams_samplers['vacuum']: -1.0} designer = MultiTargetDesign(target_samplers, storage=self._storage) # Store things. self.molecules = molecules self.environments = environments self.topologies = topologies self.positions = positions self.system_generators = system_generators self.proposal_engines = proposal_engines self.thermodynamic_states = thermodynamic_states self.mcmc_samplers = mcmc_samplers self.exen_samplers = exen_samplers self.sams_samplers = sams_samplers self.designer = designer
topologies[environment] = modeller.getTopology() positions[environment] = modeller.getPositions() else: environment = solvent + '-' + component topologies[environment] = topologies[component] positions[environment] = positions[component] # Set up the proposal engines. from perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine proposal_metadata = { } proposal_engines = dict() for environment in environments: storage = None if self.storage: storage = NetCDFStorageView(self.storage, envname=environment) proposal_engines[environment] = SmallMoleculeSetProposalEngine(molecules, system_generators[environment], residue_name='MOL', storage=storage) # Generate systems systems = dict() for environment in environments: systems[environment] = system_generators[environment].build_system(topologies[environment]) # Define thermodynamic state of interest. from perses.samplers.thermodynamics import ThermodynamicState thermodynamic_states = dict() temperature = 300*unit.kelvin pressure = 1.0*unit.atmospheres for component in components: for solvent in solvents: environment = solvent + '-' + component if solvent == 'explicit':
topology = topology.to_openmm() topology.setPeriodicBoxVectors(system.getDefaultPeriodicBoxVectors()) ifs = oechem.oemolistream() ifs.open(ligand_filename) # get the list of molecules mol_list = [oechem.OEMol(mol) for mol in ifs.GetOEMols()] for idx, mol in enumerate(mol_list): mol.SetTitle("MOL{}".format(idx)) oechem.OETriposAtomNames(mol) initial_mol = mol_list[initial_ligand] proposal_mol = mol_list[proposal_ligand] proposal_smiles = SmallMoleculeSetProposalEngine.canonicalize_smiles( oechem.OECreateCanSmiString(proposal_mol)) current_smiles = SmallMoleculeSetProposalEngine.canonicalize_smiles( oechem.OECreateCanSmiString(initial_mol)) barostat = openmm.MonteCarloBarostat(1.0 * unit.atmosphere, temperature, 50) system_generator = SystemGenerator( [ 'amber14/protein.ff14SB.xml', 'gaff.xml', 'amber14/tip3p.xml', 'MCL1_ligands.xml' ], barostat=barostat, forcefield_kwargs={ 'nonbondedMethod': app.PME, 'constraints': app.HBonds,
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 perses.rjmc.topology_proposal import SmallMoleculeSetProposalEngine, TopologyProposal from perses.annihilation.new_relative import HybridTopologyFactory import simtk.openmm as openmm from perses.tests.utils import createOEMolFromIUPAC, createSystemFromIUPAC mol_name = "naphthalene" ref_mol_name = "benzene" mol = createOEMolFromIUPAC(mol_name) m, system, positions, topology = createSystemFromIUPAC(mol_name) refmol = createOEMolFromIUPAC(ref_mol_name) #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 integrator = openmm.VerletIntegrator(1) platform = openmm.Platform.getPlatformByName("Reference") context = openmm.Context(alchemical_system, integrator, platform) context.setPositions(alchemical_positions) functions = { 'lambda_sterics' : '2*lambda * step(0.5 - lambda) + (1.0 - step(0.5 - lambda))', 'lambda_electrostatics' : '2*(lambda - 0.5) * step(lambda - 0.5)', 'lambda_bonds' : 'lambda', 'lambda_angles' : 'lambda', 'lambda_torsions' : 'lambda' } #set all to zero for parm in functions.keys(): context.setParameter(parm, 0.0) initial_energy = context.getState(getEnergy=True).getPotentialEnergy() #set all to one for parm in functions.keys(): context.setParameter(parm, 1.0) final_energy = context.getState(getEnergy=True).getPotentialEnergy() if np.abs(final_energy - initial_energy) > 1.0e-6*unit.kilojoule_per_mole: raise Exception("The energy at the endpoints was not equal for molecule %s" % mol_name)