def test_generate_endpoint_thermodynamic_states(): """ test whether the hybrid system zero and one thermodynamic states have the appropriate lambda values """ topology_proposal, current_positions, new_positions = utils.generate_solvated_hybrid_test_topology( current_mol_name='propane', proposed_mol_name='pentane', vacuum=False) hybrid_factory = HybridTopologyFactory(topology_proposal, current_positions, new_positions, use_dispersion_correction=True) #get the relevant thermodynamic states: _, _, lambda_zero_thermodynamic_state, lambda_one_thermodynamic_state = utils.generate_endpoint_thermodynamic_states( hybrid_factory.hybrid_system, topology_proposal) # check the parameters for each state lambda_protocol = [ 'lambda_sterics_core', 'lambda_electrostatics_core', 'lambda_sterics_insert', 'lambda_electrostatics_insert', 'lambda_sterics_delete', 'lambda_electrostatics_delete' ] for value in lambda_protocol: if getattr(lambda_zero_thermodynamic_state, value) != 0.: raise Exception( 'Interaction {} not set to 0. at lambda = 0. {} set to {}'. format(value, value, getattr(lambda_one_thermodynamic_state, value))) if getattr(lambda_one_thermodynamic_state, value) != 1.: raise Exception( 'Interaction {} not set to 1. at lambda = 1. {} set to {}'. format(value, value, getattr(lambda_one_thermodynamic_state, value)))
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_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 test_create_endstates(): """ test the creation of unsampled endstates """ from pkg_resources import resource_filename smiles_filename = resource_filename("perses", os.path.join("data", "test.smi")) fe_setup = RelativeFEPSetup(ligand_input=smiles_filename, old_ligand_index=0, new_ligand_index=1, forcefield_files=[], small_molecule_forcefield='gaff-2.11', phases=['vacuum']) hybrid_factory = HybridTopologyFactory( topology_proposal=fe_setup._vacuum_topology_proposal, current_positions=fe_setup._vacuum_positions_old, new_positions=fe_setup._vacuum_positions_new, neglected_new_angle_terms=fe_setup._vacuum_forward_neglected_angles, neglected_old_angle_terms=fe_setup._vacuum_reverse_neglected_angles, softcore_LJ_v2=True, interpolate_old_and_new_14s=False) zero_state_error, one_state_error = validate_endstate_energies( fe_setup._vacuum_topology_proposal, hybrid_factory, added_energy=fe_setup._vacuum_added_valence_energy, subtracted_energy=fe_setup._vacuum_subtracted_valence_energy, beta=beta, platform=openmm.Platform.getPlatformByName('Reference'), ENERGY_THRESHOLD=ENERGY_THRESHOLD) lambda_alchemical_state = RelativeAlchemicalState.from_system( hybrid_factory.hybrid_system) lambda_protocol = LambdaProtocol(functions='default') lambda_alchemical_state.set_alchemical_parameters(0.0, lambda_protocol) thermodynamic_state = CompoundThermodynamicState( ThermodynamicState(hybrid_factory.hybrid_system, temperature=temperature), composable_states=[lambda_alchemical_state]) zero_endstate = copy.deepcopy(thermodynamic_state) one_endstate = copy.deepcopy(thermodynamic_state) one_endstate.set_alchemical_parameters(1.0, lambda_protocol) new_endstates = create_endstates(zero_endstate, one_endstate)
def make_alchemical_system(self, topology_proposal, current_positions, new_positions): """ Generate an alchemically-modified system at the correct atoms based on the topology proposal. This method generates a hybrid system using the new HybridTopologyFactory. It memoizes so that calling multiple times (within a recent time period) will immediately return a cached object. Parameters ---------- topology_proposal : perses.rjmc.TopologyProposal Unmodified real system corresponding to appropriate leg of transformation. current_positions : np.ndarray of float Positions of "old" system new_positions : np.ndarray of float Positions of "new" system atoms Returns ------- hybrid_factory : perses.annihilation.relative.HybridTopologyFactory a factory object containing the hybrid system """ try: hybrid_factory = self._hybrid_cache[topology_proposal] #If we've retrieved the factory from the cache, update it to include the relevant positions hybrid_factory._old_positions = current_positions hybrid_factory._new_positions = new_positions hybrid_factory._compute_hybrid_positions() except KeyError: try: hybrid_factory = HybridTopologyFactory( topology_proposal, current_positions, new_positions, bond_softening_constant=self._bond_softening_constant, angle_softening_constant=self._angle_softening_constant) self._hybrid_cache[topology_proposal] = hybrid_factory except: hybrid_factory = None return hybrid_factory
def test_create_endstates(): """ test the creation of unsampled endstates """ fe_setup = RelativeFEPSetup(ligand_input=f"{os.getcwd()}/test.smi", old_ligand_index=0, new_ligand_index=1, forcefield_files=['gaff.xml'], phases=['vacuum']) hybrid_factory = HybridTopologyFactory( topology_proposal=fe_setup._vacuum_topology_proposal, current_positions=fe_setup._vacuum_positions_old, new_positions=fe_setup._vacuum_positions_new, neglected_new_angle_terms=fe_setup._vacuum_forward_neglected_angles, neglected_old_angle_terms=fe_setup._vacuum_reverse_neglected_angles, softcore_LJ_v2=True, interpolate_old_and_new_14s=False) zero_state_error, one_state_error = validate_endstate_energies( fe_setup._vacuum_topology_proposal, hybrid_factory, added_energy=fe_setup._vacuum_added_valence_energy, subtracted_energy=fe_setup._vacuum_subtracted_valence_energy, beta=beta, ENERGY_THRESHOLD=ENERGY_THRESHOLD) lambda_alchemical_state = RelativeAlchemicalState.from_system( hybrid_factory.hybrid_system) lambda_protocol = LambdaProtocol(functions='default') lambda_alchemical_state.set_alchemical_parameters(0.0, lambda_protocol) thermodynamic_state = CompoundThermodynamicState( ThermodynamicState(hybrid_factory.hybrid_system, temperature=temperature), composable_states=[lambda_alchemical_state]) zero_endstate = copy.deepcopy(thermodynamic_state) one_endstate = copy.deepcopy(thermodynamic_state) one_endstate.set_alchemical_parameters(1.0, lambda_protocol) new_endstates = create_endstates(zero_endstate, one_endstate)
def test_position_output(): """ Test that the hybrid returns the correct positions for the new and old systems after construction """ from perses.annihilation.relative import HybridTopologyFactory import numpy as np #generate topology proposal topology_proposal, old_positions, new_positions = utils.generate_solvated_hybrid_test_topology( ) factory = HybridTopologyFactory(topology_proposal, old_positions, new_positions) old_positions_factory = factory.old_positions(factory.hybrid_positions) new_positions_factory = factory.new_positions(factory.hybrid_positions) assert np.all( np.isclose(old_positions.in_units_of(unit.nanometers), old_positions_factory.in_units_of(unit.nanometers))) assert np.all( np.isclose(new_positions.in_units_of(unit.nanometers), new_positions_factory.in_units_of(unit.nanometers)))
def test_setup_hybrid_system(): alanine_topology, alanine_positions, leucine_topology, leucine_positions, atom_map = build_two_residues( ) alanine_system = forcefield.createSystem(alanine_topology) leucine_system = forcefield.createSystem(leucine_topology) atom_map = {value: key for key, value in atom_map.items()} hybrid = HybridTopologyFactory(leucine_system, alanine_system, leucine_topology, alanine_topology, leucine_positions, alanine_positions, atom_map, softening=0.0) [ system, topology, positions, sys2_indices_in_system, sys1_indices_in_system ] = hybrid.createPerturbedSystem() compute_alchemical_correction(leucine_system, alanine_system, system, leucine_positions, positions, positions, alanine_positions)
def run_hybrid_endpoint_overlap(topology_proposal, current_positions, new_positions): """ Test that the variance of the perturbation from lambda={0,1} to the corresponding nonalchemical endpoint is not too large. Parameters ---------- topology_proposal : perses.rjmc.TopologyProposal TopologyProposal object describing the transformation current_positions : np.array, unit-bearing Positions of the initial system new_positions : np.array, unit-bearing Positions of the new system Returns ------- hybrid_endpoint_results : list list of [df, ddf, N_eff] for 1 and 0 """ #create the hybrid system: #hybrid_factory = HybridTopologyFactory(topology_proposal, current_positions, new_positions, use_dispersion_correction=True) hybrid_factory = HybridTopologyFactory( topology_proposal, current_positions, new_positions, use_dispersion_correction=False) # DEBUG #get the relevant thermodynamic states: nonalchemical_zero_thermodynamic_state, nonalchemical_one_thermodynamic_state, lambda_zero_thermodynamic_state, lambda_one_thermodynamic_state = utils.generate_endpoint_thermodynamic_states( hybrid_factory.hybrid_system, topology_proposal) nonalchemical_thermodynamic_states = [ nonalchemical_zero_thermodynamic_state, nonalchemical_one_thermodynamic_state ] alchemical_thermodynamic_states = [ lambda_zero_thermodynamic_state, lambda_one_thermodynamic_state ] #create an MCMCMove, BAOAB with default parameters (but don't restart if we encounter a NaN) mc_move = mcmc.LangevinDynamicsMove(n_restart_attempts=0, n_steps=100) initial_sampler_state = SamplerState( hybrid_factory.hybrid_positions, box_vectors=hybrid_factory.hybrid_system.getDefaultPeriodicBoxVectors( )) hybrid_endpoint_results = [] all_results = [] for lambda_state in (0, 1): result, non, hybrid = run_endpoint_perturbation( alchemical_thermodynamic_states[lambda_state], nonalchemical_thermodynamic_states[lambda_state], initial_sampler_state, mc_move, 100, hybrid_factory, lambda_index=lambda_state) all_results.append(non) all_results.append(hybrid) print('lambda {} : {}'.format(lambda_state, result)) hybrid_endpoint_results.append(result) calculate_cross_variance(all_results) return hybrid_endpoint_results
def check_alchemical_hybrid_elimination_bar(topology_proposal, old_positions, new_positions, ncmc_nsteps=50, n_iterations=50, NSIGMA_MAX=6.0, geometry=False): """ Check that the hybrid topology, where both endpoints are identical, returns a free energy within NSIGMA_MAX of 0. Parameters ---------- topology_proposal positions ncmc_nsteps NSIGMA_MAX Returns ------- """ #TODO this is a test #this code is out of date #make the hybrid topology factory: factory = HybridTopologyFactory(topology_proposal, old_positions, new_positions) platform = openmm.Platform.getPlatformByName("CUDA") hybrid_system = factory.hybrid_system hybrid_topology = factory.hybrid_topology initial_hybrid_positions = factory.hybrid_positions #alchemical functions 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' } w_f = np.zeros(n_iterations) w_r = np.zeros(n_iterations) #make the alchemical integrators: forward_integrator = NCMCGHMCAlchemicalIntegrator(temperature, hybrid_system, functions, nsteps=ncmc_nsteps, direction='insert') forward_context = openmm.Context(hybrid_system, forward_integrator, platform) print("Minimizing for forward protocol...") forward_context.setPositions(initial_hybrid_positions) for parm in functions.keys(): forward_context.setParameter(parm, 0.0) openmm.LocalEnergyMinimizer.minimize(forward_context, maxIterations=10) initial_state = forward_context.getState(getPositions=True, getEnergy=True) print("The initial energy after minimization is %s" % str(initial_state.getPotentialEnergy())) initial_forward_positions = initial_state.getPositions(asNumpy=True) equil_positions = simulate_hybrid(hybrid_system, functions, 0.0, initial_forward_positions) print("Beginning forward protocols") #first, do forward protocol (lambda=0 -> 1) for i in range(n_iterations): equil_positions = simulate_hybrid(hybrid_system, functions, 0.0, equil_positions) forward_context.setPositions(equil_positions) forward_integrator.step(ncmc_nsteps) w_f[i] = -1.0 * forward_integrator.getLogAcceptanceProbability( forward_context) bar.update(i) del forward_context, forward_integrator reverse_integrator = NCMCGHMCAlchemicalIntegrator(temperature, hybrid_system, functions, nsteps=ncmc_nsteps, direction='delete') print("Minimizing for reverse protocol...") reverse_context = openmm.Context(hybrid_system, reverse_integrator, platform) reverse_context.setPositions(initial_hybrid_positions) for parm in functions.keys(): reverse_context.setParameter(parm, 1.0) openmm.LocalEnergyMinimizer.minimize(reverse_context, maxIterations=10) initial_state = reverse_context.getState(getPositions=True, getEnergy=True) print("The initial energy after minimization is %s" % str(initial_state.getPotentialEnergy())) initial_reverse_positions = initial_state.getPositions(asNumpy=True) equil_positions = simulate_hybrid(hybrid_system, functions, 1.0, initial_reverse_positions, nsteps=1000) #now, reverse protocol print("Beginning reverse protocols...") for i in range(n_iterations): equil_positions = simulate_hybrid(hybrid_system, functions, 1.0, equil_positions) reverse_context.setPositions(equil_positions) reverse_integrator.step(ncmc_nsteps) w_r[i] = -1.0 * reverse_integrator.getLogAcceptanceProbability( reverse_context) bar.update(i) del reverse_context, reverse_integrator from pymbar import BAR [df, ddf] = BAR(w_f, w_r) print("df = %12.6f +- %12.5f kT" % (df, ddf))
def generate_dipeptide_top_pos_sys(topology, new_res, system, positions, system_generator, conduct_geometry_prop=True, conduct_htf_prop=False): """generate point mutation engine, geometry_engine, and conduct topology proposal, geometry propsal, and hybrid factory generation""" from perses.tests.utils import validate_endstate_energies if conduct_htf_prop: assert conduct_geometry_prop, f"the htf prop can only be conducted if there is a geometry proposal" #create the point mutation engine from perses.rjmc.topology_proposal import PointMutationEngine point_mutation_engine = PointMutationEngine( wildtype_topology=topology, system_generator=system_generator, chain_id= '1', #denote the chain id allowed to mutate (it's always a string variable) max_point_mutants=1, residues_allowed_to_mutate=['2'], #the residue ids allowed to mutate allowed_mutations=[ ('2', new_res) ], #the residue ids allowed to mutate with the three-letter code allowed to change aggregate=True) #always allow aggregation #create a top proposal print(f"making topology proposal") topology_proposal = point_mutation_engine.propose( current_system=system, current_topology=topology) if not conduct_geometry_prop: return topology_proposal if conduct_geometry_prop: #create a geometry engine print(f"generating geometry engine") from perses.rjmc.geometry import FFAllAngleGeometryEngine 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=True) #make a geometry proposal forward print( f"making geometry proposal from {list(topology.residues())[1].name} to {new_res}" ) forward_new_positions, logp_proposal = geometry_engine.propose( topology_proposal, positions, beta) logp_reverse = geometry_engine.logp_reverse(topology_proposal, forward_new_positions, positions, beta) if not conduct_htf_prop: return (topology_proposal, forward_new_positions, logp_proposal, logp_reverse) if conduct_htf_prop: #create a hybrid topology factory from perses.annihilation.relative import 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) if not topology_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 topology_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( forward_htf._topology_proposal, forward_htf, added_valence_energy, subtracted_valence_energy, beta=1.0 / (kB * temperature), ENERGY_THRESHOLD=ENERGY_THRESHOLD, platform=openmm.Platform.getPlatformByName('Reference')) print(f"zero state error : {zero_state_error}") print(f"one state error : {one_state_error}") return forward_htf
def __init__( self, receptor_filename, ligand_filename, mutation_chain_id, mutation_residue_id, proposed_residue, phase='complex', conduct_endstate_validation=False, ligand_index=0, forcefield_files=[ 'amber14/protein.ff14SB.xml', 'amber14/tip3p.xml' ], barostat=openmm.MonteCarloBarostat(1.0 * unit.atmosphere, temperature, 50), forcefield_kwargs={ 'removeCMMotion': False, 'ewaldErrorTolerance': 1e-4, 'nonbondedMethod': app.PME, 'constraints': app.HBonds, 'hydrogenMass': 4 * unit.amus }, small_molecule_forcefields='gaff-2.11', **kwargs): """ arguments receptor_filename : str path to receptor; .pdb ligand_filename : str path to ligand of interest; .sdf or .pdb mutation_chain_id : str name of the chain to be mutated mutation_residue_id : str residue id to change proposed_residue : str three letter code of the residue to mutate to phase : str, default complex if phase == vacuum, then the complex will not be solvated with water; else, it will be solvated with tip3p conduct_endstate_validation : bool, default True whether to conduct an endstate validation of the hybrid topology factory ligand_index : int, default 0 which ligand to use forcefield_files : list of str, default ['amber14/protein.ff14SB.xml', 'amber14/tip3p.xml'] forcefield files for proteins and solvent barostat : openmm.MonteCarloBarostat, default openmm.MonteCarloBarostat(1.0 * unit.atmosphere, 300 * unit.kelvin, 50) barostat to use forcefield_kwargs : dict, default {'removeCMMotion': False, 'ewaldErrorTolerance': 1e-4, 'nonbondedMethod': app.NoCutoff, 'constraints' : app.HBonds, 'hydrogenMass' : 4 * unit.amus} forcefield kwargs for system parametrization small_molecule_forcefields : str, default 'gaff-2.11' the forcefield string for small molecule parametrization TODO : allow argument for separate apo structure if it exists separately allow argument for specator ligands besides the 'ligand_filename' """ from openforcefield.topology import Molecule from openmmforcefields.generators import SystemGenerator # first thing to do is make a complex and apo... pdbfile = open(receptor_filename, 'r') pdb = app.PDBFile(pdbfile) pdbfile.close() receptor_positions, receptor_topology, receptor_md_topology = pdb.positions, pdb.topology, md.Topology.from_openmm( pdb.topology) receptor_topology = receptor_md_topology.to_openmm() receptor_n_atoms = receptor_md_topology.n_atoms molecules = [] ligand_mol = createOEMolFromSDF(ligand_filename, index=ligand_index) ligand_mol = generate_unique_atom_names(ligand_mol) molecules.append( Molecule.from_openeye(ligand_mol, allow_undefined_stereo=False)) ligand_positions, ligand_topology = extractPositionsFromOEMol( ligand_mol), forcefield_generators.generateTopologyFromOEMol( ligand_mol) ligand_md_topology = md.Topology.from_openmm(ligand_topology) ligand_n_atoms = ligand_md_topology.n_atoms #now create a complex complex_md_topology = receptor_md_topology.join(ligand_md_topology) complex_topology = complex_md_topology.to_openmm() complex_positions = unit.Quantity(np.zeros( [receptor_n_atoms + ligand_n_atoms, 3]), unit=unit.nanometers) complex_positions[:receptor_n_atoms, :] = receptor_positions complex_positions[receptor_n_atoms:, :] = ligand_positions #now for a system_generator self.system_generator = SystemGenerator( forcefields=forcefield_files, barostat=barostat, forcefield_kwargs=forcefield_kwargs, small_molecule_forcefield=small_molecule_forcefields, molecules=molecules, cache=None) #create complex and apo inputs... complex_topology, complex_positions, complex_system = self._solvate( complex_topology, complex_positions, 'tip3p', phase=phase) apo_topology, apo_positions, apo_system = self._solvate( receptor_topology, receptor_positions, 'tip3p', phase='phase') 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=True) #run pipeline... htfs = [] for (top, pos, sys) in zip([complex_topology, apo_topology], [complex_positions, apo_positions], [complex_system, apo_system]): point_mutation_engine = PointMutationEngine( wildtype_topology=top, system_generator=self.system_generator, chain_id= mutation_chain_id, #denote the chain id allowed to mutate (it's always a string variable) max_point_mutants=1, residues_allowed_to_mutate=[ mutation_residue_id ], #the residue ids allowed to mutate allowed_mutations=[ (mutation_residue_id, proposed_residue) ], #the residue ids allowed to mutate with the three-letter code allowed to change aggregate=True) #always allow aggregation topology_proposal = point_mutation_engine.propose(sys, top) new_positions, logp_proposal = geometry_engine.propose( topology_proposal, pos, beta) logp_reverse = geometry_engine.logp_reverse( topology_proposal, new_positions, pos, beta) forward_htf = HybridTopologyFactory( topology_proposal=topology_proposal, current_positions=pos, new_positions=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) if not topology_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 topology_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 if conduct_endstate_validation: zero_state_error, one_state_error = validate_endstate_energies( forward_htf._topology_proposal, forward_htf, added_valence_energy, subtracted_valence_energy, beta=beta, ENERGY_THRESHOLD=ENERGY_THRESHOLD) else: pass htfs.append(forward_htf) self.complex_htf = htfs[0] self.apo_htf = htfs[1]
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 generate_top_pos_sys(topology, new_res, system, positions, system_generator): """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") point_mutation_engine = PointMutationEngine( wildtype_topology=topology, system_generator=system_generator, chain_id= '1', #denote the chain id allowed to mutate (it's always a string variable) max_point_mutants=1, residues_allowed_to_mutate=['2'], #the residue ids allowed to mutate allowed_mutations=[ ('2', new_res) ], #the residue ids allowed to mutate with the three-letter code allowed to change aggregate=True) #always allow aggregation #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, local_map_stereo_sidechain, new_oemol_sidechain, old_oemol_sidechain = point_mutation_engine.propose( current_system=system, current_topology=topology) #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, local_map_stereo_sidechain, old_oemol_sidechain, new_oemol_sidechain
def sMC_setup(): """ function to setup local sMC """ from pkg_resources import resource_filename smiles_filename = resource_filename("perses", os.path.join("data", "test.smi")) fe_setup = RelativeFEPSetup(ligand_input=smiles_filename, old_ligand_index=0, new_ligand_index=1, forcefield_files=[], small_molecule_forcefield='gaff-2.11', phases=['vacuum']) hybrid_factory = HybridTopologyFactory( topology_proposal=fe_setup._vacuum_topology_proposal, current_positions=fe_setup._vacuum_positions_old, new_positions=fe_setup._vacuum_positions_new, neglected_new_angle_terms=fe_setup._vacuum_forward_neglected_angles, neglected_old_angle_terms=fe_setup._vacuum_reverse_neglected_angles, softcore_LJ_v2=True, interpolate_old_and_new_14s=False) zero_state_error, one_state_error = validate_endstate_energies( fe_setup._vacuum_topology_proposal, hybrid_factory, added_energy=fe_setup._vacuum_added_valence_energy, subtracted_energy=fe_setup._vacuum_subtracted_valence_energy, beta=beta, platform=openmm.Platform.getPlatformByName('Reference'), ENERGY_THRESHOLD=ENERGY_THRESHOLD) ne_fep = SequentialMonteCarlo(factory=hybrid_factory, lambda_protocol=lambda_protocol, temperature=temperature, trajectory_directory=trajectory_directory, trajectory_prefix=trajectory_prefix, atom_selection=atom_selection, timestep=timestep, eq_splitting_string=eq_splitting_string, neq_splitting_string=neq_splitting_string, collision_rate=collision_rate, ncmc_save_interval=ncmc_save_interval, external_parallelism=None, internal_parallelism=None) assert ne_fep.external_parallelism is False and ne_fep.internal_parallelism is True, f"all parallelism should be None" assert ne_fep.workers == 0, f"local annealing definition only allows 0 workers" assert ne_fep.parallelism_parameters == { 'library': None, 'num_processes': None }, f"the parallelism_parameters are not supported" #get reduced energies ne_fep.minimize_sampler_states() ne_fep.equilibrate(n_equilibration_iterations=10, n_steps_per_equilibration=1, endstates=[0, 1], decorrelate=True, timer=True, minimize=False) assert all( sum([ ne_fep._eq_dict[state][i][-1] for i in range(len(ne_fep._eq_dict[state])) ]) == 10 for state in [0, 1]), f"there should be 10 snapshots per endstate" assert all( len(ne_fep._eq_dict[f"{state}_reduced_potentials"]) == 10 for state in [0, 1]), f"there should be 10 reduced potentials per endstate" assert all( len(ne_fep._eq_dict[f"{state}_decorrelated"]) <= 10 for state in [0, 1] ), f"the decorrelated indices must be less than or equal to the total number of snapshots" #now to check for decorrelation in ne_fep._eq_files_dict... _filenames_0, _filenames_1 = [ ne_fep._eq_dict[0][i][0] for i in range(len(ne_fep._eq_dict[0])) ], [ne_fep._eq_dict[1][i][0] for i in range(len(ne_fep._eq_dict[1]))] decorrelated_0 = [ item for sublist in [ne_fep._eq_files_dict[0][filename] for filename in _filenames_0] for item in sublist ] decorrelated_1 = [ item for sublist in [ne_fep._eq_files_dict[1][filename] for filename in _filenames_1] for item in sublist ] decorrelated_0_files = [ subitem for ssublist in [ item for sublist in [list(ne_fep._eq_files_dict[0].values())] for item in sublist ] for subitem in ssublist ] decorrelated_1_files = [ subitem for ssublist in [ item for sublist in [list(ne_fep._eq_files_dict[1].values())] for item in sublist ] for subitem in ssublist ] assert decorrelated_0 == sorted( decorrelated_0_files ), f"there is a discrepancy between the decorrelated 0 equilibrium states and the decorrelated equilibria saved to disk" assert decorrelated_1 == sorted( decorrelated_1_files ), f"there is a discrepancy between the decorrelated 1 equilibrium states and the decorrelated equilibria saved to disk" return ne_fep
def HybridTopologyFactory_energies( current_mol='toluene', proposed_mol='1,2-bis(trifluoromethyl) benzene'): """ Test whether the difference in the nonalchemical zero and alchemical zero states is the forward valence energy. Also test for the one states. """ from perses.tests.utils import generate_solvated_hybrid_test_topology, generate_endpoint_thermodynamic_states import openmmtools.cache as cache #Just test the solvated system top_proposal, old_positions, _ = generate_solvated_hybrid_test_topology( current_mol_name=current_mol, proposed_mol_name=proposed_mol) #remove the dispersion correction top_proposal._old_system.getForce(3).setUseDispersionCorrection(False) top_proposal._new_system.getForce(3).setUseDispersionCorrection(False) # run geometry engine to generate old and new positions _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) _new_positions, _lp = _geometry_engine.propose(top_proposal, old_positions, beta) _lp_rev = _geometry_engine.logp_reverse(top_proposal, _new_positions, old_positions, beta) # make the hybrid system, reset the CustomNonbondedForce cutoff HTF = HybridTopologyFactory(top_proposal, old_positions, _new_positions) hybrid_system = HTF.hybrid_system nonalch_zero, nonalch_one, alch_zero, alch_one = generate_endpoint_thermodynamic_states( hybrid_system, top_proposal) # compute reduced energies #for the nonalchemical systems... attrib_list = [(nonalch_zero, old_positions, top_proposal._old_system.getDefaultPeriodicBoxVectors()), (alch_zero, HTF._hybrid_positions, hybrid_system.getDefaultPeriodicBoxVectors()), (alch_one, HTF._hybrid_positions, hybrid_system.getDefaultPeriodicBoxVectors()), (nonalch_one, _new_positions, top_proposal._new_system.getDefaultPeriodicBoxVectors())] rp_list = [] for (state, pos, box_vectors) in attrib_list: context, integrator = cache.global_context_cache.get_context(state) samplerstate = SamplerState(positions=pos, box_vectors=box_vectors) samplerstate.apply_to_context(context) rp = state.reduced_potential(context) rp_list.append(rp) #valence energy definitions forward_added_valence_energy = _geometry_engine.forward_final_context_reduced_potential - _geometry_engine.forward_atoms_with_positions_reduced_potential reverse_subtracted_valence_energy = _geometry_engine.reverse_final_context_reduced_potential - _geometry_engine.reverse_atoms_with_positions_reduced_potential nonalch_zero_rp, alch_zero_rp, alch_one_rp, nonalch_one_rp = rp_list[ 0], rp_list[1], rp_list[2], rp_list[3] # print(f"Difference between zeros: {nonalch_zero_rp - alch_zero_rp}; forward added: {forward_added_valence_energy}") # print(f"Difference between ones: {nonalch_zero_rp - alch_zero_rp}; forward added: {forward_added_valence_energy}") assert abs( nonalch_zero_rp - alch_zero_rp + forward_added_valence_energy ) < ENERGY_THRESHOLD, f"The zero state alchemical and nonalchemical energy absolute difference {abs(nonalch_zero_rp - alch_zero_rp + forward_added_valence_energy)} is greater than the threshold of {ENERGY_THRESHOLD}." assert abs( nonalch_one_rp - alch_one_rp + reverse_subtracted_valence_energy ) < ENERGY_THRESHOLD, f"The one state alchemical and nonalchemical energy absolute difference {abs(nonalch_one_rp - alch_one_rp + reverse_subtracted_valence_energy)} is greater than the threshold of {ENERGY_THRESHOLD}." print( f"Abs difference in zero alchemical vs nonalchemical systems: {abs(nonalch_zero_rp - alch_zero_rp + forward_added_valence_energy)}" ) print( f"Abs difference in one alchemical vs nonalchemical systems: {abs(nonalch_one_rp - alch_one_rp + reverse_subtracted_valence_energy)}" )
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 run_setup(setup_options, serialize_systems=True, build_samplers=True): """ Run the setup pipeline and return the relevant setup objects based on a yaml input file. Parameters ---------- setup_options : dict result of loading yaml input file Returns ------- setup_dict: dict {'topology_proposals': top_prop, 'hybrid_topology_factories': htf, 'hybrid_samplers': hss} - 'topology_proposals': """ phases = setup_options['phases'] known_phases = ['complex', 'solvent', 'vacuum'] for phase in phases: assert ( phase in known_phases ), f"Unknown phase, {phase} provided. run_setup() can be used with {known_phases}" if 'use_given_geometries' not in list(setup_options.keys()): use_given_geometries = False else: assert type(setup_options['use_given_geometries']) == type(True) use_given_geometries = setup_options['use_given_geometries'] if 'complex' in phases: _logger.info(f"\tPulling receptor (as pdb or mol2)...") # We'll need the protein PDB file (without missing atoms) try: protein_pdb_filename = setup_options['protein_pdb'] assert protein_pdb_filename is not None receptor_mol2 = None except KeyError: try: receptor_mol2 = setup_options['receptor_mol2'] assert receptor_mol2 is not None protein_pdb_filename = None except KeyError as e: print( "Either protein_pdb or receptor_mol2 must be specified if running a complex simulation" ) raise e else: protein_pdb_filename = None receptor_mol2 = None # And a ligand file containing the pair of ligands between which we will transform ligand_file = setup_options['ligand_file'] _logger.info(f"\tdetected ligand file: {ligand_file}") # get the indices of ligands out of the file: old_ligand_index = setup_options['old_ligand_index'] new_ligand_index = setup_options['new_ligand_index'] _logger.info( f"\told ligand index: {old_ligand_index}; new ligand index: {new_ligand_index}" ) _logger.info(f"\tsetting up forcefield files...") forcefield_files = setup_options['forcefield_files'] if "timestep" in setup_options: if isinstance(setup_options['timestep'], float): timestep = setup_options['timestep'] * unit.femtoseconds else: timestep = setup_options['timestep'] _logger.info(f"\ttimestep: {timestep}.") else: timestep = 1.0 * unit.femtoseconds _logger.info(f"\tno timestep detected: setting default as 1.0fs.") if "neq_splitting" in setup_options: neq_splitting = setup_options['neq_splitting'] _logger.info(f"\tneq_splitting: {neq_splitting}") try: eq_splitting = setup_options['eq_splitting'] _logger.info(f"\teq_splitting: {eq_splitting}") except KeyError as e: print( "If you specify a nonequilibrium splitting string, you must also specify an equilibrium one." ) raise e else: eq_splitting = "V R O R V" neq_splitting = "V R O R V" _logger.info( f"\tno splitting strings specified: defaulting to neq: {neq_splitting}, eq: {eq_splitting}." ) if "measure_shadow_work" in setup_options: measure_shadow_work = setup_options['measure_shadow_work'] _logger.info(f"\tmeasuring shadow work: {measure_shadow_work}.") else: measure_shadow_work = False _logger.info( f"\tno measure_shadow_work specified: defaulting to False.") if isinstance(setup_options['pressure'], float): pressure = setup_options['pressure'] * unit.atmosphere else: pressure = setup_options['pressure'] if isinstance(setup_options['temperature'], float): temperature = setup_options['temperature'] * unit.kelvin else: temperature = setup_options['temperature'] if isinstance(setup_options['solvent_padding'], float): solvent_padding_angstroms = setup_options[ 'solvent_padding'] * unit.angstrom else: solvent_padding_angstroms = setup_options['solvent_padding'] if isinstance(setup_options['ionic_strength'], float): ionic_strength = setup_options['ionic_strength'] * unit.molar else: ionic_strength = setup_options['ionic_strength'] _logger.info(f"\tsetting pressure: {pressure}.") _logger.info(f"\tsetting temperature: {temperature}.") _logger.info(f"\tsetting solvent padding: {solvent_padding_angstroms}A.") _logger.info(f"\tsetting ionic strength: {ionic_strength}M.") setup_pickle_file = setup_options[ 'save_setup_pickle_as'] if 'save_setup_pickle_as' in list( setup_options) else None _logger.info(f"\tsetup pickle file: {setup_pickle_file}") trajectory_directory = setup_options['trajectory_directory'] _logger.info(f"\ttrajectory directory: {trajectory_directory}") try: atom_map_file = setup_options['atom_map'] with open(atom_map_file, 'r') as f: atom_map = { int(x.split()[0]): int(x.split()[1]) for x in f.readlines() } _logger.info(f"\tsucceeded parsing atom map.") except Exception: atom_map = None _logger.info(f"\tno atom map specified: default to None.") if 'topology_proposal' not in list(setup_options.keys( )) or setup_options['topology_proposal'] is None: _logger.info( f"\tno topology_proposal specified; proceeding to RelativeFEPSetup...\n\n\n" ) if 'set_solvent_box_dims_to_complex' in list(setup_options.keys( )) and setup_options['set_solvent_box_dims_to_complex']: set_solvent_box_dims_to_complex = True else: set_solvent_box_dims_to_complex = False _logger.info( f'Box dimensions: {setup_options["complex_box_dimensions"]} and {setup_options["solvent_box_dimensions"]}' ) fe_setup = RelativeFEPSetup( ligand_file, old_ligand_index, new_ligand_index, forcefield_files, phases=phases, protein_pdb_filename=protein_pdb_filename, receptor_mol2_filename=receptor_mol2, pressure=pressure, temperature=temperature, solvent_padding=solvent_padding_angstroms, spectator_filenames=setup_options['spectators'], map_strength=setup_options['map_strength'], atom_expr=setup_options['atom_expr'], bond_expr=setup_options['bond_expr'], atom_map=atom_map, neglect_angles=setup_options['neglect_angles'], anneal_14s=setup_options['anneal_1,4s'], small_molecule_forcefield=setup_options[ 'small_molecule_forcefield'], small_molecule_parameters_cache=setup_options[ 'small_molecule_parameters_cache'], trajectory_directory=trajectory_directory, trajectory_prefix=setup_options['trajectory_prefix'], nonbonded_method=setup_options['nonbonded_method'], complex_box_dimensions=setup_options['complex_box_dimensions'], solvent_box_dimensions=setup_options['solvent_box_dimensions'], ionic_strength=ionic_strength, remove_constraints=setup_options['remove_constraints'], use_given_geometries=use_given_geometries) _logger.info(f"\twriting pickle output...") if setup_pickle_file is not None: with open( os.path.join(os.getcwd(), trajectory_directory, setup_pickle_file), 'wb') as f: try: pickle.dump(fe_setup, f) _logger.info(f"\tsuccessfully dumped pickle.") except Exception as e: print(e) print("\tUnable to save setup object as a pickle") _logger.info( f"\tsetup is complete. Writing proposals and positions for each phase to top_prop dict..." ) else: _logger.info( f"\tsetup is complete. Omitted writing proposals and positions for each phase to top_prop dict..." ) top_prop = dict() for phase in phases: top_prop[f'{phase}_topology_proposal'] = getattr( fe_setup, f'{phase}_topology_proposal') top_prop[f'{phase}_geometry_engine'] = getattr( fe_setup, f'_{phase}_geometry_engine') top_prop[f'{phase}_old_positions'] = getattr( fe_setup, f'{phase}_old_positions') top_prop[f'{phase}_new_positions'] = getattr( fe_setup, f'{phase}_new_positions') top_prop[f'{phase}_added_valence_energy'] = getattr( fe_setup, f'_{phase}_added_valence_energy') top_prop[f'{phase}_subtracted_valence_energy'] = getattr( fe_setup, f'_{phase}_subtracted_valence_energy') top_prop[f'{phase}_logp_proposal'] = getattr( fe_setup, f'_{phase}_logp_proposal') top_prop[f'{phase}_logp_reverse'] = getattr( fe_setup, f'_{phase}_logp_reverse') top_prop[f'{phase}_forward_neglected_angles'] = getattr( fe_setup, f'_{phase}_forward_neglected_angles') top_prop[f'{phase}_reverse_neglected_angles'] = getattr( fe_setup, f'_{phase}_reverse_neglected_angles') top_prop['ligand_oemol_old'] = fe_setup._ligand_oemol_old top_prop['ligand_oemol_new'] = fe_setup._ligand_oemol_new top_prop[ 'non_offset_new_to_old_atom_map'] = fe_setup.non_offset_new_to_old_atom_map _logger.info(f"\twriting atom_mapping.png") atom_map_outfile = os.path.join(os.getcwd(), trajectory_directory, 'atom_mapping.png') if 'render_atom_map' in list( setup_options.keys()) and setup_options['render_atom_map']: render_atom_mapping(atom_map_outfile, fe_setup._ligand_oemol_old, fe_setup._ligand_oemol_new, fe_setup.non_offset_new_to_old_atom_map) else: _logger.info(f"\tloading topology proposal from yaml setup options...") top_prop = np.load(setup_options['topology_proposal']).item() n_steps_per_move_application = setup_options[ 'n_steps_per_move_application'] _logger.info( f"\t steps per move application: {n_steps_per_move_application}") trajectory_directory = setup_options['trajectory_directory'] trajectory_prefix = setup_options['trajectory_prefix'] _logger.info(f"\ttrajectory prefix: {trajectory_prefix}") if 'atom_selection' in setup_options: atom_selection = setup_options['atom_selection'] _logger.info(f"\tatom selection detected: {atom_selection}") else: _logger.info(f"\tno atom selection detected: default to all.") atom_selection = 'all' if setup_options['fe_type'] == 'neq': _logger.info(f"\tInstantiating nonequilibrium switching FEP") n_equilibrium_steps_per_iteration = setup_options[ 'n_equilibrium_steps_per_iteration'] ncmc_save_interval = setup_options['ncmc_save_interval'] write_ncmc_configuration = setup_options['write_ncmc_configuration'] if setup_options['LSF']: _internal_parallelism = { 'library': ('dask', 'LSF'), 'num_processes': setup_options['processes'] } else: _internal_parallelism = None ne_fep = dict() for phase in phases: _logger.info(f"\t\tphase: {phase}") hybrid_factory = HybridTopologyFactory( top_prop['%s_topology_proposal' % phase], top_prop['%s_old_positions' % phase], top_prop['%s_new_positions' % phase], neglected_new_angle_terms=top_prop[ f"{phase}_forward_neglected_angles"], neglected_old_angle_terms=top_prop[ f"{phase}_reverse_neglected_angles"], softcore_LJ_v2=setup_options['softcore_v2'], interpolate_old_and_new_14s=setup_options['anneal_1,4s']) if build_samplers: ne_fep[phase] = SequentialMonteCarlo( factory=hybrid_factory, lambda_protocol=setup_options['lambda_protocol'], temperature=temperature, trajectory_directory=trajectory_directory, trajectory_prefix=f"{trajectory_prefix}_{phase}", atom_selection=atom_selection, timestep=timestep, eq_splitting_string=eq_splitting, neq_splitting_string=neq_splitting, collision_rate=setup_options['ncmc_collision_rate_ps'], ncmc_save_interval=ncmc_save_interval, internal_parallelism=_internal_parallelism) print("Nonequilibrium switching driver class constructed") return {'topology_proposals': top_prop, 'ne_fep': ne_fep} else: _logger.info(f"\tno nonequilibrium detected.") htf = dict() hss = dict() _logger.info(f"\tcataloging HybridTopologyFactories...") for phase in phases: _logger.info(f"\t\tphase: {phase}:") #TODO write a SAMSFEP class that mirrors NonequilibriumSwitchingFEP _logger.info( f"\t\twriting HybridTopologyFactory for phase {phase}...") htf[phase] = HybridTopologyFactory( top_prop['%s_topology_proposal' % phase], top_prop['%s_old_positions' % phase], top_prop['%s_new_positions' % phase], neglected_new_angle_terms=top_prop[ f"{phase}_forward_neglected_angles"], neglected_old_angle_terms=top_prop[ f"{phase}_reverse_neglected_angles"], softcore_LJ_v2=setup_options['softcore_v2'], interpolate_old_and_new_14s=setup_options['anneal_1,4s']) for phase in phases: # Define necessary vars to check energy bookkeeping _top_prop = top_prop['%s_topology_proposal' % phase] _htf = htf[phase] _forward_added_valence_energy = top_prop['%s_added_valence_energy' % phase] _reverse_subtracted_valence_energy = top_prop[ '%s_subtracted_valence_energy' % phase] if not use_given_geometries: zero_state_error, one_state_error = validate_endstate_energies( _top_prop, _htf, _forward_added_valence_energy, _reverse_subtracted_valence_energy, beta=1.0 / (kB * temperature), ENERGY_THRESHOLD=ENERGY_THRESHOLD ) #, trajectory_directory=f'{xml_directory}{phase}') _logger.info(f"\t\terror in zero state: {zero_state_error}") _logger.info(f"\t\terror in one state: {one_state_error}") else: _logger.info( f"'use_given_geometries' was passed to setup; skipping endstate validation" ) #TODO expose more of these options in input if build_samplers: n_states = setup_options['n_states'] _logger.info(f"\tn_states: {n_states}") if 'n_replicas' not in setup_options: n_replicas = n_states else: n_replicas = setup_options['n_replicas'] checkpoint_interval = setup_options['checkpoint_interval'] # generating lambda protocol lambda_protocol = LambdaProtocol( functions=setup_options['protocol-type']) _logger.info( f'Using lambda protocol : {setup_options["protocol-type"]}' ) if atom_selection: selection_indices = htf[phase].hybrid_topology.select( atom_selection) else: selection_indices = None storage_name = str(trajectory_directory) + '/' + str( trajectory_prefix) + '-' + str(phase) + '.nc' _logger.info(f'\tstorage_name: {storage_name}') _logger.info(f'\tselection_indices {selection_indices}') _logger.info(f'\tcheckpoint interval {checkpoint_interval}') reporter = MultiStateReporter( storage_name, analysis_particle_indices=selection_indices, checkpoint_interval=checkpoint_interval) if phase == 'vacuum': endstates = False else: endstates = True if setup_options['fe_type'] == 'fah': _logger.info('SETUP FOR FAH DONE') return { 'topology_proposals': top_prop, 'hybrid_topology_factories': htf } if setup_options['fe_type'] == 'sams': hss[phase] = HybridSAMSSampler( mcmc_moves=mcmc.LangevinSplittingDynamicsMove( timestep=timestep, collision_rate=1.0 / unit.picosecond, n_steps=n_steps_per_move_application, reassign_velocities=False, n_restart_attempts=20, constraint_tolerance=1e-06), hybrid_factory=htf[phase], online_analysis_interval=setup_options['offline-freq'], online_analysis_minimum_iterations=10, flatness_criteria=setup_options['flatness-criteria'], gamma0=setup_options['gamma0']) hss[phase].setup(n_states=n_states, n_replicas=n_replicas, temperature=temperature, storage_file=reporter, lambda_protocol=lambda_protocol, endstates=endstates) elif setup_options['fe_type'] == 'repex': hss[phase] = HybridRepexSampler( mcmc_moves=mcmc.LangevinSplittingDynamicsMove( timestep=timestep, collision_rate=1.0 / unit.picosecond, n_steps=n_steps_per_move_application, reassign_velocities=False, n_restart_attempts=20, constraint_tolerance=1e-06), hybrid_factory=htf[phase], online_analysis_interval=setup_options['offline-freq']) hss[phase].setup(n_states=n_states, temperature=temperature, storage_file=reporter, lambda_protocol=lambda_protocol, endstates=endstates) else: _logger.info(f"omitting sampler construction") if serialize_systems: # save the systems and the states pass _logger.info('WRITING OUT XML FILES') #old_thermodynamic_state, new_thermodynamic_state, hybrid_thermodynamic_state, _ = generate_endpoint_thermodynamic_states(htf[phase].hybrid_system, _top_prop) xml_directory = f'{setup_options["trajectory_directory"]}/xml/' if not os.path.exists(xml_directory): os.makedirs(xml_directory) from perses.utils import data _logger.info('WRITING OUT XML FILES') _logger.info(f'Saving the hybrid, old and new system to disk') data.serialize( htf[phase].hybrid_system, f'{setup_options["trajectory_directory"]}/xml/{phase}-hybrid-system.gz' ) data.serialize( htf[phase]._old_system, f'{setup_options["trajectory_directory"]}/xml/{phase}-old-system.gz' ) data.serialize( htf[phase]._new_system, f'{setup_options["trajectory_directory"]}/xml/{phase}-new-system.gz' ) return { 'topology_proposals': top_prop, 'hybrid_topology_factories': htf, 'hybrid_samplers': hss }
def __init__( self, protein_filename, mutation_chain_id, mutation_residue_id, proposed_residue, phase='complex', conduct_endstate_validation=True, ligand_file=None, ligand_index=0, water_model='tip3p', ionic_strength=0.15 * unit.molar, forcefield_files=[ 'amber14/protein.ff14SB.xml', 'amber14/tip3p.xml' ], barostat=openmm.MonteCarloBarostat(1.0 * unit.atmosphere, temperature, 50), forcefield_kwargs={ 'removeCMMotion': False, 'ewaldErrorTolerance': 0.00025, 'constraints': app.HBonds, 'hydrogenMass': 4 * unit.amus }, periodic_forcefield_kwargs={'nonbondedMethod': app.PME}, nonperiodic_forcefield_kwargs=None, small_molecule_forcefields='gaff-2.11', complex_box_dimensions=None, apo_box_dimensions=None, **kwargs): """ arguments protein_filename : str path to protein (to mutate); .pdb mutation_chain_id : str name of the chain to be mutated mutation_residue_id : str residue id to change proposed_residue : str three letter code of the residue to mutate to phase : str, default complex if phase == vacuum, then the complex will not be solvated with water; else, it will be solvated with tip3p conduct_endstate_validation : bool, default True whether to conduct an endstate validation of the hybrid topology factory ligand_file : str, default None path to ligand of interest (i.e. small molecule or protein); .sdf or .pdb ligand_index : int, default 0 which ligand to use water_model : str, default 'tip3p' solvent model to use for solvation ionic_strength : float * unit.molar, default 0.15 * unit.molar the total concentration of ions (both positive and negative) to add using Modeller. This does not include ions that are added to neutralize the system. Note that only monovalent ions are currently supported. forcefield_files : list of str, default ['amber14/protein.ff14SB.xml', 'amber14/tip3p.xml'] forcefield files for proteins and solvent barostat : openmm.MonteCarloBarostat, default openmm.MonteCarloBarostat(1.0 * unit.atmosphere, 300 * unit.kelvin, 50) barostat to use forcefield_kwargs : dict, default {'removeCMMotion': False, 'ewaldErrorTolerance': 1e-4, 'constraints' : app.HBonds, 'hydrogenMass' : 4 * unit.amus} forcefield kwargs for system parametrization periodic_forcefield_kwargs : dict, default {'nonbondedMethod': app.PME} periodic forcefield kwargs for system parametrization nonperiodic_forcefield_kwargs : dict, default None non-periodic forcefield kwargs for system parametrization small_molecule_forcefields : str, default 'gaff-2.11' the forcefield string for small molecule parametrization complex_box_dimensions : Vec3, default None define box dimensions of complex phase; if none, padding is 1nm apo_box_dimensions : Vec3, default None define box dimensions of apo phase phase; if None, padding is 1nm TODO : allow argument for spectator ligands besides the 'ligand_file' """ # First thing to do is load the apo protein to mutate... protein_pdbfile = open(protein_filename, 'r') protein_pdb = app.PDBFile(protein_pdbfile) protein_pdbfile.close() protein_positions, protein_topology, protein_md_topology = protein_pdb.positions, protein_pdb.topology, md.Topology.from_openmm( protein_pdb.topology) protein_topology = protein_md_topology.to_openmm() protein_n_atoms = protein_md_topology.n_atoms # Load the ligand, if present molecules = [] if ligand_file: if ligand_file.endswith('.sdf'): # small molecule ligand_mol = createOEMolFromSDF(ligand_file, index=ligand_index) ligand_mol = generate_unique_atom_names(ligand_mol) molecules.append( Molecule.from_openeye(ligand_mol, allow_undefined_stereo=False)) ligand_positions, ligand_topology = extractPositionsFromOEMol( ligand_mol ), forcefield_generators.generateTopologyFromOEMol(ligand_mol) ligand_md_topology = md.Topology.from_openmm(ligand_topology) ligand_n_atoms = ligand_md_topology.n_atoms elif ligand_file.endswith('pdb'): # protein ligand_pdbfile = open(ligand_file, 'r') ligand_pdb = app.PDBFile(ligand_pdbfile) ligand_pdbfile.close() ligand_positions, ligand_topology, ligand_md_topology = ligand_pdb.positions, ligand_pdb.topology, md.Topology.from_openmm( ligand_pdb.topology) ligand_n_atoms = ligand_md_topology.n_atoms else: _logger.warning( f'ligand filetype not recognised. Please provide a path to a .pdb or .sdf file' ) return # Now create a complex complex_md_topology = protein_md_topology.join(ligand_md_topology) complex_topology = complex_md_topology.to_openmm() complex_positions = unit.Quantity(np.zeros( [protein_n_atoms + ligand_n_atoms, 3]), unit=unit.nanometers) complex_positions[:protein_n_atoms, :] = protein_positions complex_positions[protein_n_atoms:, :] = ligand_positions # Now for a system_generator self.system_generator = SystemGenerator( forcefields=forcefield_files, barostat=barostat, forcefield_kwargs=forcefield_kwargs, periodic_forcefield_kwargs=periodic_forcefield_kwargs, nonperiodic_forcefield_kwargs=nonperiodic_forcefield_kwargs, small_molecule_forcefield=small_molecule_forcefields, molecules=molecules, cache=None) # Solvate apo and complex... apo_input = list( self._solvate(protein_topology, protein_positions, water_model, phase, ionic_strength, apo_box_dimensions)) inputs = [apo_input] if ligand_file: inputs.append( self._solvate(complex_topology, complex_positions, water_model, phase, ionic_strength, complex_box_dimensions)) 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=True) # Run pipeline... htfs = [] for (top, pos, sys) in inputs: point_mutation_engine = PointMutationEngine( wildtype_topology=top, system_generator=self.system_generator, chain_id= mutation_chain_id, # Denote the chain id allowed to mutate (it's always a string variable) max_point_mutants=1, residues_allowed_to_mutate=[ mutation_residue_id ], # The residue ids allowed to mutate allowed_mutations=[ (mutation_residue_id, proposed_residue) ], # The residue ids allowed to mutate with the three-letter code allowed to change aggregate=True) # Always allow aggregation topology_proposal = point_mutation_engine.propose(sys, top) # Only validate energy bookkeeping if the WT and proposed residues do not involve rings old_res = [ res for res in top.residues() if res.id == mutation_residue_id ][0] validate_bool = False if old_res.name in ring_amino_acids or proposed_residue in ring_amino_acids else True new_positions, logp_proposal = geometry_engine.propose( topology_proposal, pos, beta, validate_energy_bookkeeping=validate_bool) logp_reverse = geometry_engine.logp_reverse( topology_proposal, new_positions, pos, beta, validate_energy_bookkeeping=validate_bool) forward_htf = HybridTopologyFactory( topology_proposal=topology_proposal, current_positions=pos, new_positions=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) if not topology_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})" else: added_valence_energy = geometry_engine.forward_final_context_reduced_potential - geometry_engine.forward_atoms_with_positions_reduced_potential if not topology_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 if conduct_endstate_validation: zero_state_error, one_state_error = validate_endstate_energies( forward_htf._topology_proposal, forward_htf, added_valence_energy, subtracted_valence_energy, beta=beta, ENERGY_THRESHOLD=ENERGY_THRESHOLD) if zero_state_error > ENERGY_THRESHOLD: _logger.warning( f"Reduced potential difference of the nonalchemical and alchemical Lambda = 0 state is above the threshold ({ENERGY_THRESHOLD}): {zero_state_error}" ) if one_state_error > ENERGY_THRESHOLD: _logger.warning( f"Reduced potential difference of the nonalchemical and alchemical Lambda = 1 state is above the threshold ({ENERGY_THRESHOLD}): {one_state_error}" ) else: pass htfs.append(forward_htf) self.apo_htf = htfs[0] self.complex_htf = htfs[1] if ligand_file else None