bar_analytical = (f_k_analytical[k1] - f_k_analytical[k]) bar_error = bar_analytical - df_bar print( "BAR estimator for reduced free energy from states %d to %d is %f +/- %f" % (k, k1, df_bar, ddf_bar)) stddev_away("BAR estimator", bar_error, ddf_bar) print("==============================================") print(" Testing computeEXP ") print("==============================================") print("EXP forward free energy") for k in range(K - 1): if N_k[k] != 0: w_F = u_kln[k, k + 1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # forward work results = EXP(w_F) df_exp = results['Delta_f'] ddf_exp = results['dDelta_f'] exp_analytical = (f_k_analytical[k + 1] - f_k_analytical[k]) exp_error = exp_analytical - df_exp print("df from states %d to %d is %f +/- %f" % (k, k + 1, df_exp, ddf_exp)) stddev_away("df", exp_error, ddf_exp) print("EXP reverse free energy") for k in range(1, K): if N_k[k] != 0: w_R = u_kln[k, k - 1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # reverse work (df_exp, ddf_exp) = EXP(w_R) df_exp = -results['Delta_f'] ddf_exp = results['dDelta_f']
def check_alchemical_null_elimination(topology_proposal, positions, ncmc_nsteps=50, NSIGMA_MAX=6.0, geometry=False): """ Test alchemical elimination engine on null transformations, where some atoms are deleted and then reinserted in a cycle. Parameters ---------- topology_proposal : TopologyProposal The topology proposal to test. This must be a null transformation, where topology_proposal.old_system == topology_proposal.new_system ncmc_steps : int, optional, default=50 Number of NCMC switching steps, or 0 for instantaneous switching. NSIGMA_MAX : float, optional, default=6.0 Number of standard errors away from analytical solution tolerated before Exception is thrown geometry : bool, optional, default=None If True, will also use geometry engine in the middle of the null transformation. """ # Initialize engine from perses.annihilation.ncmc_switching import NCMCEngine ncmc_engine = NCMCEngine(temperature=temperature, nsteps=ncmc_nsteps) # Make sure that old system and new system are identical. if not (topology_proposal.old_system == topology_proposal.new_system): raise Exception( "topology_proposal must be a null transformation for this test (old_system == new_system)" ) for (k, v) in topology_proposal.new_to_old_atom_map.items(): if k != v: raise Exception( "topology_proposal must be a null transformation for this test (retailed atoms must map onto themselves)" ) nequil = 5 # number of equilibration iterations niterations = 50 # number of round-trip switching trials logP_insert_n = np.zeros([niterations], np.float64) logP_delete_n = np.zeros([niterations], np.float64) logP_switch_n = np.zeros([niterations], np.float64) for iteration in range(nequil): [positions, velocities] = simulate(topology_proposal.old_system, positions) for iteration in range(niterations): # Equilibrate [positions, velocities] = simulate(topology_proposal.old_system, positions) # Check that positions are not NaN if (np.any(np.isnan(positions / unit.angstroms))): raise Exception("Positions became NaN during equilibration") # Delete atoms [positions, logP_delete, potential_delete] = ncmc_engine.integrate(topology_proposal, positions, direction='delete') # Check that positions are not NaN if (np.any(np.isnan(positions / unit.angstroms))): raise Exception("Positions became NaN on NCMC deletion") # Insert atoms [positions, logP_insert, potential_insert] = ncmc_engine.integrate(topology_proposal, positions, direction='insert') # Check that positions are not NaN if (np.any(np.isnan(positions / unit.angstroms))): raise Exception("Positions became NaN on NCMC insertion") # Compute probability of switching geometries. logP_switch = -(potential_insert - potential_delete) # Compute total probability logP_delete_n[iteration] = logP_delete logP_insert_n[iteration] = logP_insert logP_switch_n[iteration] = logP_switch #print("Iteration %5d : delete %16.8f kT | insert %16.8f kT | geometry switch %16.8f" % (iteration, logP_delete, logP_insert, logP_switch)) # Check free energy difference is withing NSIGMA_MAX standard errors of zero. logP_n = logP_delete_n + logP_insert_n + logP_switch_n work_n = -logP_n from pymbar import EXP [df, ddf] = EXP(work_n) #print("df = %12.6f +- %12.5f kT" % (df, ddf)) if (abs(df) > NSIGMA_MAX * ddf): msg = 'Delta F (%d steps switching) = %f +- %f kT; should be within %f sigma of 0\n' % ( ncmc_nsteps, df, ddf, NSIGMA_MAX) msg += 'delete logP:\n' msg += str(logP_delete_n) + '\n' msg += 'insert logP:\n' msg += str(logP_insert_n) + '\n' msg += 'logP:\n' msg += str(logP_n) + '\n' raise Exception(msg)
def overlap_check(reference_system, positions, receptor_atoms, ligand_atoms, platform_name=None, annihilate_electrostatics=True, annihilate_sterics=False, precision=None, nsteps=50, nsamples=200): """ Test overlap between reference system and alchemical system by running a short simulation. Parameters ---------- reference_system : simtk.openmm.System The reference System object to compare with positions : simtk.unit.Quantity with units compatible with nanometers The positions to assess energetics for. receptor_atoms : list of int The list of receptor atoms. ligand_atoms : list of int The list of ligand atoms to alchemically modify. platform_name : str, optional, default=None The name of the platform to use for benchmarking. annihilate_electrostatics : bool, optional, default=True If True, electrostatics will be annihilated; if False, decoupled. annihilate_sterics : bool, optional, default=False If True, sterics will be annihilated; if False, decoupled. nsteps : int, optional, default=50 Number of molecular dynamics steps between samples. nsamples : int, optional, default=100 Number of samples to collect. """ # Create a fully-interacting alchemical state. factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) alchemical_state = AlchemicalState(0.00, 1.00, 1.00, 1.0) alchemical_system = factory.createPerturbedSystem(alchemical_state) temperature = 300.0 * units.kelvin collision_rate = 5.0 / units.picoseconds timestep = 2.0 * units.femtoseconds kT = (kB * temperature) # Select platform. platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) # Create integrators. reference_integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) alchemical_integrator = openmm.VerletIntegrator(timestep) # Create contexts. if platform: reference_context = openmm.Context(reference_system, reference_integrator, platform) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) else: reference_context = openmm.Context(reference_system, reference_integrator) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) # Collect simulation data. reference_context.setPositions(positions) du_n = np.zeros([nsamples], np.float64) # du_n[n] is the for sample in range(nsamples): # Run dynamics. reference_integrator.step(nsteps) # Get reference energies. reference_state = reference_context.getState(getEnergy=True, getPositions=True) reference_potential = reference_state.getPotentialEnergy() # Get alchemical energies. alchemical_context.setPositions(reference_state.getPositions()) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() du_n[sample] = (alchemical_potential - reference_potential) / kT # Clean up. del reference_context, alchemical_context # Discard data to equilibration and subsample. from pymbar import timeseries [t0, g, Neff] = timeseries.detectEquilibration(du_n) indices = timeseries.subsampleCorrelatedData(du_n, g=g) du_n = du_n[indices] # Compute statistics. from pymbar import EXP [DeltaF, dDeltaF] = EXP(du_n) # Raise an exception if the error is larger than 3kT. MAX_DEVIATION = 3.0 # kT if (dDeltaF > MAX_DEVIATION): report = "DeltaF = %12.3f +- %12.3f kT (%5d samples, g = %6.1f)" % ( DeltaF, dDeltaF, Neff, g) raise Exception(report) return
bar_analytical = (f_k_analytical[k1] - f_k_analytical[k]) bar_error = bar_analytical - df_bar print "BAR estimator for reduced free energy from states %d to %d is %f +/- %f" % ( k, k1, df_bar, ddf_bar) print "BAR estimator differs by %f standard deviations" % numpy.abs( bar_error / ddf_bar) print "==============================================" print " Testing computeEXP " print "==============================================" print "EXP forward free energy" for k in range(K - 1): if N_k[k] != 0: w_F = u_kln[k, k + 1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # forward work (df_exp, ddf_exp) = EXP(w_F) exp_analytical = (f_k_analytical[k + 1] - f_k_analytical[k]) exp_error = exp_analytical - df_exp print "df from states %d to %d is %f +/- %f" % (k, k + 1, df_exp, ddf_exp) print "df differs by %f standard deviations from analytical" % numpy.abs( exp_error / ddf_exp) print "EXP reverse free energy" for k in range(1, K): if N_k[k] != 0: w_R = u_kln[k, k - 1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # reverse work (df_exp, ddf_exp) = EXP(w_R) df_exp = -df_exp exp_analytical = (f_k_analytical[k] - f_k_analytical[k - 1]) exp_error = exp_analytical - df_exp
def check_hybrid_null_elimination(topology_proposal, positions, new_positions, ncmc_nsteps=50, NSIGMA_MAX=6.0, geometry=False): """ Test alchemical elimination engine on null transformations, where some atoms are deleted and then reinserted in a cycle. Parameters ---------- topology_proposal : TopologyProposal The topology proposal to test. This must be a null transformation, where topology_proposal.old_system == topology_proposal.new_system ncmc_steps : int, optional, default=50 Number of NCMC switching steps, or 0 for instantaneous switching. NSIGMA_MAX : float, optional, default=6.0 Number of standard errors away from analytical solution tolerated before Exception is thrown geometry : bool, optional, default=None If True, will also use geometry engine in the middle of the null transformation. """ functions = { 'lambda_sterics': 'lambda', 'lambda_electrostatics': 'lambda', 'lambda_bonds': 'lambda', 'lambda_angles': 'lambda', 'lambda_torsions': 'lambda' } # Initialize engine from perses.annihilation.ncmc_switching import NCMCHybridEngine ncmc_engine = NCMCHybridEngine(temperature=temperature, functions=functions, nsteps=ncmc_nsteps) # Make sure that old system and new system are identical. # if not (topology_proposal.old_system == topology_proposal.new_system): # raise Exception("topology_proposal must be a null transformation for this test (old_system == new_system)") # for (k,v) in topology_proposal.new_to_old_atom_map.items(): # if k != v: # raise Exception("topology_proposal must be a null transformation for this test (retailed atoms must map onto themselves)") nequil = 5 # number of equilibration iterations niterations = 50 # number of round-trip switching trials logP_work_n = np.zeros([niterations], np.float64) for iteration in range(nequil): [positions, velocities] = simulate(topology_proposal.old_system, positions) for iteration in range(niterations): # Equilibrate [positions, velocities] = simulate(topology_proposal.old_system, positions) # Check that positions are not NaN if (np.any(np.isnan(positions / unit.angstroms))): raise Exception("Positions became NaN during equilibration") # Hybrid NCMC from old to new [_, new_old_positions, logP_work, logP_energy] = ncmc_engine.integrate(topology_proposal, positions, positions) # Check that positions are not NaN if (np.any(np.isnan(positions / unit.angstroms))): raise Exception("Positions became NaN on Hybrid NCMC switch") # Store log probability associated with work logP_work_n[iteration] = logP_work #print("Iteration %5d : NCMC work %16.8f kT | NCMC energy %16.8f kT" % (iteration, logP_work, logP_energy)) # Check free energy difference is withing NSIGMA_MAX standard errors of zero. work_n = -logP_work_n from pymbar import EXP [df, ddf] = EXP(work_n) print("df = %12.6f +- %12.5f kT" % (df, ddf)) if (abs(df) > NSIGMA_MAX * ddf): msg = 'Delta F (%d steps switching) = %f +- %f kT; should be within %f sigma of 0\n' % ( ncmc_nsteps, df, ddf, NSIGMA_MAX) msg += 'logP_work_n:\n' msg += str(logP_work_n) + '\n' raise Exception(msg)
df_bar = results['Delta_f'] ddf_bar = results['dDelta_f'] bar_analytical = (f_k_analytical[k1]-f_k_analytical[k]) bar_error = bar_analytical - df_bar print("BAR estimator for reduced free energy from states %d to %d is %f +/- %f" % (k,k1,df_bar,ddf_bar)) stddev_away("BAR estimator",bar_error,ddf_bar) print("==============================================") print(" Testing computeEXP ") print("==============================================") print("EXP forward free energy") for k in range(K-1): if N_k[k] != 0: w_F = u_kln[k, k+1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # forward work results = EXP(w_F, return_dict=True) df_exp = results['Delta_f'] ddf_exp = results['dDelta_f'] exp_analytical = (f_k_analytical[k+1]-f_k_analytical[k]) exp_error = exp_analytical - df_exp print("df from states %d to %d is %f +/- %f" % (k,k+1,df_exp,ddf_exp)) stddev_away("df",exp_error,ddf_exp) print("EXP reverse free energy") for k in range(1,K): if N_k[k] != 0: w_R = u_kln[k, k-1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # reverse work (df_exp,ddf_exp) = EXP(w_R, return_dict=True) df_exp = -results['Delta_f'] ddf_exp = results['dDelta_f'] exp_analytical = (f_k_analytical[k]-f_k_analytical[k-1])