def _process_phase(i, phase): all_forward = [] all_reverse = [] for gen_id in range(_max_gen(RUN)): fe, err, f_works, r_works = bootstrap_BAR( RUN, phase, gen_id, n_bootstrap) d[f"{phase}_fe_GEN{gen_id}"] = fe d[f"{phase}_dfe_GEN{gen_id}"] = err all_forward.extend(f_works) all_reverse.extend(r_works) sns.kdeplot(all_forward, shade=True, color="cornflowerblue", ax=axes[i]) sns.rugplot( all_forward, ax=axes[i], color="cornflowerblue", alpha=0.5, label=f"forward : N={len(f_works)}", ) sns.rugplot( all_forward, ax=axes[i], color="darkblue", label=f"forward (gen0) : N={len(f_works)}", ) sns.rugplot( [-x for x in all_reverse], ax=axes[i], color="mediumvioletred", label=f"reverse (gen0) : N={len(r_works)}", ) sns.kdeplot([-x for x in all_reverse], shade=True, color="hotpink", ax=axes[i]) sns.rugplot( [-x for x in all_reverse], ax=axes[i], color="hotpink", alpha=0.5, label=f"reverse : N={len(r_works)}", ) axes[i].set_title(phase) # TODO add bootstrapping here d[f"{phase}_fes"] = BAR(np.asarray(all_forward), np.asarray(all_reverse))
def _get_bar_free_energy(works: np.ndarray) -> PointEstimate: """ Compute the BAR free energy Parameters ---------- works : (N,) ndarray 1-D array of records containing fields "forward" and "reverse" Returns ------- PointEstimate BAR free energy point estimate and standard error """ from pymbar import BAR delta_f, ddelta_f = BAR(works["forward"], works["reverse"]) return PointEstimate(point=delta_f, stderr=ddelta_f)
def _bootstrap( gens: List[GenAnalysis], n_bootstrap: int, clones_per_gen: int, gen_number: int, ) -> List[float]: fes = [] for _ in range(n_bootstrap): random_indices = np.random.choice(clones_per_gen, gen_number) subset_f = [ gen.works[i].forward for i in random_indices for gen in gens ] subset_r = [ gen.works[i].reverse for i in random_indices for gen in gens ] fe, _ = BAR(np.asarray(subset_f), np.asarray(subset_r)) fes.append(fe * KT_KCALMOL) return fes
def check_harmonic_oscillator_ncmc(ncmc_nsteps=50, ncmc_integrator="VV"): """ Test NCMC switching of a 3D harmonic oscillator. In this test, the oscillator center is dragged in space, and we check the computed free energy difference with BAR, which should be 0. """ # Parameters for 3D harmonic oscillator mass = 39.948 * unit.amu # mass of particle (argon) sigma = 5.0 * unit.angstrom # standard deviation of harmonic oscillator collision_rate = 5.0 / unit.picosecond # collision rate temperature = 300.0 * unit.kelvin # temperature platform_name = 'Reference' # platform anme NSIGMA_MAX = 6.0 # number of standard errors away from analytical solution tolerated before Exception is thrown # Compute derived quantities. kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse energy K = kT / sigma**2 # spring constant tau = 2 * math.pi * unit.sqrt(mass / K) # time constant timestep = tau / 20.0 platform = openmm.Platform.getPlatformByName(platform_name) # Create a 3D harmonic oscillator with context parameter controlling center of oscillator. system = openmm.System() system.addParticle(mass) energy_expression = '(K/2.0) * ((x-x0)^2 + y^2 + z^2);' force = openmm.CustomExternalForce(energy_expression) force.addGlobalParameter('K', K.in_unit_system(unit.md_unit_system)) force.addGlobalParameter('x0', 0.0) force.addParticle(0, []) system.addForce(force) # Set the positions at the origin. positions = unit.Quantity(np.zeros([1, 3], np.float32), unit.angstroms) functions = {'x0': 'lambda'} # drag spring center x0 from perses.annihilation.ncmc_integrator import NCMCVVAlchemicalIntegrator, NCMCGHMCAlchemicalIntegrator if ncmc_integrator == "VV": ncmc_insert = NCMCVVAlchemicalIntegrator( temperature, system, functions, direction='insert', nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 ncmc_delete = NCMCVVAlchemicalIntegrator( temperature, system, functions, direction='delete', nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 elif ncmc_integrator == "GHMC": ncmc_insert = NCMCGHMCAlchemicalIntegrator( temperature, system, functions, direction='insert', collision_rate=9.1 / unit.picoseconds, nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 ncmc_delete = NCMCGHMCAlchemicalIntegrator( temperature, system, functions, direction='delete', collision_rate=9.1 / unit.picoseconds, nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 else: raise Exception( "%s not recognized as integrator name. Options are VV and GHMC" % ncmc_integrator) # Run NCMC switching trials where the spring center is switched with lambda: 0 -> 1 over a finite number of steps. w_f = collect_switching_data(system, positions, functions, temperature, collision_rate, timestep, platform, ncmc_integrator=ncmc_insert, ncmc_nsteps=ncmc_nsteps, direction='insert') w_r = collect_switching_data(system, positions, functions, temperature, collision_rate, timestep, platform, ncmc_integrator=ncmc_delete, ncmc_nsteps=ncmc_nsteps, direction='delete') from pymbar import BAR [df, ddf] = BAR(w_f, w_r, method='self-consistent-iteration') print('%8.3f +- %.3f 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' % ( ncmc_nsteps, df, ddf, NSIGMA_MAX) msg += '\n' msg += 'w_f = %s\n' % str(w_f) msg += 'w_r = %s\n' % str(w_r) raise Exception(msg)
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 ------- """ #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) with progressbar.ProgressBar(max_value=n_iterations) as bar: 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...") with progressbar.ProgressBar(max_value=n_iterations) as bar: 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))
for k in range(K): stdevs[k, k] = 0 print(stdevs) print("==============================================") print(" Testing computeBAR ") print("==============================================") nonzero_indices = numpy.array(list(range(K)))[Nk_ne_zero] Knon = len(nonzero_indices) for i in range(Knon - 1): k = nonzero_indices[i] k1 = nonzero_indices[i + 1] w_F = u_kln[k, k1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # forward work w_R = u_kln[k1, k, 0:N_k[k1]] - u_kln[k1, k1, 0:N_k[k1]] # reverse work results = BAR(w_F, w_R) 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):
for k in range(K): stdevs[k, k] = 0 print stdevs print "==============================================" print " Testing computeBAR " print "==============================================" nonzero_indices = numpy.array(range(K))[Nk_ne_zero] Knon = len(nonzero_indices) for i in range(Knon - 1): k = nonzero_indices[i] k1 = nonzero_indices[i + 1] w_F = u_kln[k, k1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # forward work w_R = u_kln[k1, k, 0:N_k[k1]] - u_kln[k1, k1, 0:N_k[k1]] # reverse work (df_bar, ddf_bar) = BAR(w_F, w_R) 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
def check_hybrid_round_trip_elimination(topology_proposal, positions, ncmc_nsteps=50, NSIGMA_MAX=6.0): """ Test the hybrid system by switching between lambda = 1 and lambda = 0, then using BAR to compute the free energy difference. As the test is designed so that both endpoints are the same, the free energy difference should be zero. 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 """ functions = { 'lambda_sterics': 'lambda', 'lambda_electrostatics': 'lambda', 'lambda_bonds': 'lambda', 'lambda_angles': 'lambda', 'lambda_torsions': 'lambda' } # Initialize engine from perses.annihilation import NCMCGHMCAlchemicalIntegrator from perses.annihilation.new_relative import HybridTopologyFactory #The current and "proposed" positions are the same, since the molecule is not changed. factory = HybridTopologyFactory(topology_proposal, positions, positions) forward_integrator = NCMCGHMCAlchemicalIntegrator(temperature, factory.hybrid_system, functions, nsteps=ncmc_nsteps, direction='insert') reverse_integrator = NCMCGHMCAlchemicalIntegrator(temperature, factory.hybrid_system, functions, nsteps=ncmc_nsteps, direction='delete') platform = openmm.Platform.getPlatformByName("Reference") forward_context = openmm.Context(factory.hybrid_system, forward_integrator, platform) reverse_context = openmm.Context(factory.hybrid_system, reverse_integrator, platform) # 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_f = np.zeros([niterations], np.float64) for iteration in range(nequil): positions = simulate_hybrid(factory.hybrid_system, functions, 0.0, factory.hybrid_positions) #do forward switching: for iteration in range(niterations): # Equilibrate positions = simulate_hybrid(factory.hybrid_system, functions, 0.0, factory.hybrid_positions) # Check that positions are not NaN if (np.any(np.isnan(positions / unit.angstroms))): raise Exception("Positions became NaN during equilibration") # Hybrid NCMC forward_integrator.reset() forward_context.setPositions(positions) forward_integrator.step(ncmc_nsteps) logP_work = forward_integrator.getTotalWork(forward_context) # 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_f[iteration] = logP_work logP_work_n_r = np.zeros([niterations], np.float64) for iteration in range(nequil): positions = simulate_hybrid(factory.hybrid_system, functions, 1.0, factory.hybrid_positions) #do forward switching: for iteration in range(niterations): # Equilibrate positions = simulate_hybrid(factory.hybrid_system, functions, 1.0, factory.hybrid_positions) # Check that positions are not NaN if (np.any(np.isnan(positions / unit.angstroms))): raise Exception("Positions became NaN during equilibration") # Hybrid NCMC reverse_integrator.reset() reverse_context.setPositions(positions) reverse_integrator.step(ncmc_nsteps) logP_work = reverse_integrator.getTotalWork(forward_context) # 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_r[iteration] = logP_work work_f = -logP_work_n_f work_r = -logP_work_n_r from pymbar import BAR [df, ddf] = BAR(work_f, work_r) 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(work_f) + '\n' msg += str(work_r) + '\n' raise Exception(msg)
def check_alchemical_hybrid_elimination_bar(topology_proposal, positions, ncmc_nsteps=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 ------- """ from perses.annihilation import NCMCGHMCAlchemicalIntegrator from perses.annihilation.new_relative import HybridTopologyFactory #make the hybrid topology factory: factory = HybridTopologyFactory(topology_proposal, positions, positions) platform = openmm.Platform.getPlatformByName("Reference") hybrid_system = factory.hybrid_system hybrid_topology = factory.hybrid_topology initial_hybrid_positions = factory.hybrid_positions n_iterations = 50 #number of times to do NCMC protocol #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') #reverse_integrator = NCMCGHMCAlchemicalIntegrator(temperature, hybrid_system, functions, nsteps=ncmc_nsteps, direction='delete') #first, do forward protocol (lambda=0 -> 1) for i in range(n_iterations): forward_integrator = NCMCGHMCAlchemicalIntegrator(temperature, hybrid_system, functions, nsteps=ncmc_nsteps, direction='insert') equil_positions = simulate_hybrid(hybrid_system, functions, 0.0, initial_hybrid_positions) context = openmm.Context(hybrid_system, forward_integrator, platform) context.setPositions(equil_positions) forward_integrator.step(ncmc_nsteps) w_f[i] = -1.0 * forward_integrator.getLogAcceptanceProbability(context) print(i) del context, forward_integrator #now, reverse protocol for i in range(n_iterations): reverse_integrator = NCMCGHMCAlchemicalIntegrator(temperature, hybrid_system, functions, nsteps=ncmc_nsteps, direction='delete') equil_positions = simulate_hybrid(hybrid_system, functions, 1.0, initial_hybrid_positions) context = openmm.Context(hybrid_system, reverse_integrator, platform) context.setPositions(equil_positions) reverse_integrator.step(ncmc_nsteps) w_r[i] = -1.0 * reverse_integrator.getLogAcceptanceProbability(context) print(i) del context, reverse_integrator from pymbar import BAR [df, ddf] = BAR(w_f, w_r) 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(w_f) + '\n' msg += str(w_r) + '\n' raise Exception(msg)
def DoBAR(fwds, revs, label, verbose): """ BAR to combine fwd and rev data of dGs. Here, don't multiply dGs_R by -1 since BAR calls for reverse work value. Parameters ---------- fwds: dictionary of forward work values for each window revs: dictionary of reverse work values for each window label: string label of what it is (only for printing output) Returns ------- dgs: 1D list of accumulated list of energy values. Ex. if each step was 2, then dgs would be [0,2,4...] gsdlist: 1D list of accompanying stdevs to the dgs list """ fwd_ss = {} # subsampled version of fwds rev_ss = {} # subsampled version of revs dg_bar = np.zeros([len(fwds)], np.float64) # allocate storage: dG steps gsd_bar = np.zeros([len(fwds)], np.float64) # allocate storage: dG stdev steps dgs = np.zeros([len(fwds)], np.float64) # allocate storage: dG accumulated gsdlist = np.zeros([len(fwds)], np.float64) # allocate storage: dG stdev accum #corr_time = np.zeros([len(fwds)], np.float64) corr_time = {} for key, value in fwds.items( ): # this notation changes in python3: http://tinyurl.com/j3uq3me # compute correlation time g = timeseries.statisticalInefficiency(value) corr_time[key] = [g] # compute indices of UNcorrelated timeseries, then extract those samples indices = timeseries.subsampleCorrelatedData(value, g) fwd_ss[key] = value[indices] for key, value in revs.items( ): # this notation changes in python3: http://tinyurl.com/j3uq3me # compute correlation time g = timeseries.statisticalInefficiency(value) corr_time[key].append(g) # compute indices of UNcorrelated timeseries, then extract those samples indices = timeseries.subsampleCorrelatedData(value, g) rev_ss[key] = value[indices] bar = {} # then apply BAR estimator to get dG for each step for kF, kR in zip(sorted(fwd_ss.keys()), sorted(list(rev_ss.keys()), reverse=True)): dg_bar[kF], gsd_bar[kF] = BAR(fwd_ss[kF], rev_ss[kR]) bar[kF] = [np.sum(dg_bar), dg_bar[kF], gsd_bar[kF]] # calculate the net dG standard deviation = sqrt[ sum(s_i^2) ] gsd = (np.sum(np.power(gsd_bar, 2)))**0.5 net = 0. netsd = 0. for i, g in enumerate(dg_bar): # accumulate net dGs into running sums (plot this) dgs[i] = dg_bar[i] + net net = dgs[i] # combine the stdevs: s = sqrt(s1^2 + s2^2 + ...) gsdlist[i] = ((gsd_bar[i])**2. + (netsd)**2.)**0.5 netsd = gsdlist[i] if verbose == True: print('\n\n#####---Correlation Times for dG_{}--#####'.format(label)) print('Window'.rjust(3), 'F'.rjust(5), 'R'.rjust(9)) for k, v in corr_time.items(): print("{:3d} {:10.3f} {:10.3f}".format(k, v[0], v[1])) print("\n\n#####---BAR estimator for dG_{}---#####".format(label)) print('Window'.rjust(3), 'dG'.rjust(5), 'ddG'.rjust(11), "Uncert.".rjust(11)) print("---------------------------------------------------------") for k, v in bar.items(): str = '{:3d} {:10.4f} {:10.4f} +- {:3.4f}'.format( k, v[0], v[1], v[2]) print(str) print(("\nNet dG_{} energy difference = {:.4f} +- {:.4f} kcal/mol".format( label, np.sum(dg_bar), gsd))) return dgs, gsdlist
for k in range(K): stdevs[k,k] = 0 print(stdevs) print("==============================================") print(" Testing computeBAR ") print("==============================================") nonzero_indices = numpy.array(list(range(K)))[Nk_ne_zero] Knon = len(nonzero_indices) for i in range(Knon-1): k = nonzero_indices[i] k1 = nonzero_indices[i+1] w_F = u_kln[k, k1, 0:N_k[k]] - u_kln[k, k, 0:N_k[k]] # forward work w_R = u_kln[k1, k, 0:N_k[k1]] - u_kln[k1, k1, 0:N_k[k1]] # reverse work results = BAR(w_F, w_R, return_dict=True) 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