Umbrella sampling example in Jupyter¶
This notebook mirrors the Umbrella Sampling workflow in the OpenMM Cookbook tutorial. It applies harmonic biases to the distance between two alanine dipeptide atoms, records multiple windows, and plots the reconstructed free-energy relation ((F(x) = -k_B T \ln p(x))).
Step 1¶
In [1]:
from pathlib import Path
import os
import matplotlib.pyplot as plt
import numpy as np
from openmm import app, unit
import openmm as mm
Step 2¶
In [2]:
COURSE_DIR = Path(os.environ.get('COURSE_DIR', str(Path.home() / 'Concepcion26'))).expanduser()
DATA_DIR = COURSE_DIR / 'data'
PDB_FILE = DATA_DIR / 'alanine-dipeptide.pdb'
ATOM_PAIR = (0, 1)
Step 3¶
In [ ]:
def build_simulation():
pdb = app.PDBFile(str(PDB_FILE))
forcefield = app.ForceField('amber14-all.xml', 'amber14/tip3pfb.xml')
modeller = app.Modeller(pdb.topology, pdb.positions)
modeller.addHydrogens(forcefield)
system = forcefield.createSystem(
modeller.topology,
nonbondedMethod=app.NoCutoff,
constraints=app.HBonds,
)
bias = mm.CustomBondForce('0.5*k*(r-r0)^2')
bias.addPerBondParameter('r0')
bias.addGlobalParameter('k', 5000.0)
atom1, atom2 = ATOM_PAIR
bias.addBond(atom1, atom2, [0.2])
system.addForce(bias)
integrator = mm.LangevinIntegrator(
300 * unit.kelvin,
1.0 / unit.picosecond,
2.0 * unit.femtoseconds,
)
simulation = app.Simulation(modeller.topology, system, integrator)
simulation.context.setPositions(modeller.positions)
return simulation, bias
Step 4¶
In [ ]:
def distance_from_state(state):
positions = state.getPositions(asNumpy=True)
atom1, atom2 = ATOM_PAIR
delta = positions[atom1] - positions[atom2]
return np.linalg.norm(delta.value_in_unit(unit.nanometer))
Step 5¶
In [ ]:
simulation, bias = build_simulation()
steps_per_window = 400
sample_interval = 40
windows = np.linspace(0.2, 0.35, 4)
records = []
for target in windows:
bias.setBondParameters(0, ATOM_PAIR[0], ATOM_PAIR[1], [target])
simulation.context.setVelocitiesToTemperature(300 * unit.kelvin)
simulation.minimizeEnergy()
for step in range(steps_per_window):
simulation.step(1)
if (step + 1) % sample_interval == 0:
state = simulation.context.getState(getPositions=True, enforcePeriodicBox=False)
records.append((target, distance_from_state(state)))
records = np.array(records)
Step 6¶
In [ ]:
hist, bins = np.histogram(records[:, 1], bins=25)
bin_centers = 0.5 * (bins[:-1] + bins[1:])
kT = unit.BOLTZMANN_CONSTANT_kB * unit.AVOGADRO_CONSTANT_NA * 300 * unit.kelvin
free_energy = -kT * np.log(hist / hist.sum() + 1e-9)
free_energy = free_energy.value_in_unit(unit.kilojoules_per_mole)
plt.plot(bin_centers, free_energy, '-o')
plt.xlabel('Distance (nm)')
plt.ylabel('Free energy (kJ/mol)')
plt.title('Umbrella sampling rough profile')
plt.grid(True)
plt.show()
Step 7¶
In [ ]:
import csv
output_file = DATA_DIR / 'umbrella_samples.csv'
with output_file.open('w', newline='') as handle:
writer = csv.writer(handle)
writer.writerow(['target_nm', 'sampled_nm'])
writer.writerows(records)
print(f'Saved {len(records)} samples to {output_file}')
The histogram above reproduces the cookbook’s plotting step. Export your data to docs/data/umbrella_samples.csv and run the documented WHAM commands from the Bash section to rebuild the final free-energy curve.