# Tutorial for alchemical free energy calculations using using AWH
## Case study: Extracting the oil-water partition coefficient for a small molecule
In this tutorial, we will use the accelerated weight histogram (AWH) method to calculate the partitioning free energy of a chosen small molecule between water and oil (cyclohexane). Partitioning is calculated as the difference in solvation free energies into the two solvents following the thermodynamic cycle. Here, an alchemical reaction coordinate is sampled using AWH, which provides an easy-to-use, computationally efficient, and fast-converging method to calculate PMFs along physical and alchemical reaction coordinates. The tutorial is performed using the coarse-grained Martini force field ([Martini web site](http://www.cgmartini.nl)) for speed, yet the same procedure is directly applicable to atomistic force fields.
## The workflow in a nutshell
* Choose a small molecule
* Insert the small molecule into water and cyclohexane
* Energy-minimize these two systems
* Run an AWH simulation where the small molecule appears and disappears many times
* Construct the PMF profile from the weight histogram and extract ΔG for both solvents
* Calculate the partititioning free energy ΔΔG based on the thermodynamic cycle
## Learning outcomes
* Set up alchemical simulations using GROMACS
* Understand the meaning of the key simulation parameters
* Understand the working principle of AWH
* Understand the outputs of AWH
## Alchemical transitions & Thermodynamic cycles
* Partitioning free energies could be calculated using equilibrium simulations in a two-phase solvent (water & oil), but sampling could be limited in case the solute is very hydrophobic/philic.
* Alternatively, we could use physical reaction coordinates to 'pull' the molecule from one phase to another and extract the PMF, but this typically leads to large hysteresis effects.
* In simulations, we can also perform alchemical transitions and exploit thermodynamic cycles.
* In an alchemical simulation, we make molecules disappear/appear, or change into other molecules.
* Here, we use the cycle below, in which the dim solutes refer to them being decoupled from the system, i.e. they do not interact with the solvent, but retain their internal interactions. Alternatively, this could be drawn as them being in vacuum.
* To estimate $ \Delta G^\mathrm{partitioning}_\mathrm{water-oil} $, we make a round around the circle, sum all the contributions. This must be equal to 0, since we returned to the starting point, and free energy is a state function (independent of the path).
* This provides us with a simple way to calculate partitioning free energies based on the solvation energies into oil and water phases, which can be calculated using an alhemical simulations.
File ~/Library/Python/3.9/lib/python/site-packages/nglview/base.py:10, in _singleton.<locals>.getinstance()
8 def getinstance():
9 if cls not in instances:
---> 10 instances[cls] = cls()
11 return instances[cls]
File ~/Library/Python/3.9/lib/python/site-packages/nglview/color.py:47, in _ColormakerRegistry.__init__(self, *args, **kwargs)
45 try:
46 get_ipython() # only display in notebook
---> 47 self._ipython_display_()
48 except NameError:
49 pass
File ~/Library/Python/3.9/lib/python/site-packages/nglview/color.py:54, in _ColormakerRegistry._ipython_display_(self, **kwargs)
52 if self._ready:
53 return
---> 54 super()._ipython_display_(**kwargs)
AttributeError: 'super' object has no attribute '_ipython_display_'
%% Cell type:markdown id: tags:
First select a small molecule from the [Martini 3 small molecule library](https://github.com/ricalessandri/Martini3-small-molecules/). Then download the corresponding structure (.gro) file by changing the "TOLU" in the command below
## Generate two solvent boxes and insert the small molecule
* Let's first take a look at the gmx solvate tool and its options.
%% Cell type:code id: tags:
``` python
!gmxsolvate-h
```
%% Cell type:markdown id: tags:
Look at the options. We can select the size of the simulation box with "-box". Alternatively, we can tell how many solvent molecules to insert with "-maxsol". Here, we will generate a cubic box with 800 solvent molecules. The radius needs to be adjusted to account for the large size of Martini water beads.
Now we will generate the topology for the system with 1 solute molecule and 800 waters. We have a template file available, into which we will simply change the name of the chosen solute.
Let's see if the box size has converged. With isotropic pressure coupling all box vectors are equal (cubic box), so it is enough to look at the x component ("Box-X"), which can be extracted from the output files with "gmx energy". We then plot it.
%% Cell type:code id: tags:
``` python
!echo"Box-X"|gmxenergy-feq_w.edr-obox_w.xvg
importmatplotlib.pyplotasplt
importnumpyasnp
box=np.loadtxt('box_w.xvg',comments=['#','@'])
plt.plot(box[:,0],box[:,1])
plt.xlabel('Time (ps)')
plt.ylabel('Box edge (nm)')
plt.show()
```
%% Cell type:markdown id: tags:
The system should look well converged, so we can proceed to the AWH simulation.
## AWH simulation
If we have an equilibrated system, we can proceed to the alchemical AWH run. Let's first take a look at the mdp file template. Some key parameters include:
* "free energy" turns on the alchemical calculation
* "couple-lambda0/1" set what is on/off at different lambda values
* "couple-moltype" is the name of the molecule to solvate. This will be changed in the next step
* "couple-intramol" sets whether bonded and intra-molecular non-bonded interactions are coupled.
* "init-lambda-state" sets the state where the simulation starts defined as the element of the vectors below
* "vdw/coul_lambdas" sets the scaling factors for LJ and electrostatic interactions for the different lambda states (here 30). The scaling parameters of the last one (0,0) correspond to a non-interacting molecule (couple-lambda0=none), and this is the initial state (init-lambda-state=30).
* "calc-lambda-neighbors" sets the number of neighbors for which dH will be calculated. 1 for typical BAR, -1 means all neighbors, in line with MBAR and AWH
* "sc_alpha/sigma/power" set the shape of the soft-core potential used to rid issues at lambda values close to 0 or 1
* AWH parameters here!
%% Cell type:code id: tags:
``` python
!catMDPs/awh.mdp
```
%% Cell type:markdown id: tags:
Let's change the name of the molecule to couple in the mdp file to the chosen one.
%% Cell type:code id: tags:
``` python
!sed"s/TOCOUPLE/$MOLNAME/"MDPs/awh.mdp>awh.mdp
!sed"s/TOCOUPLE/{MOLNAME}/"MDPs/awh.mdp>awh.mdp
```
%% Cell type:markdown id: tags:
Now we'll generate the tpr and run the awh simulation.
%% Cell type:code id: tags:
``` python
!gmxgrompp-fawh.mdp-ceq_w.gro-ptopol_w.top-oawh_w
!gmxmdrun-deffnmawh_w-v
```
%% Cell type:markdown id: tags:
## Analyze the AWH run
Here, we run the tool "gmx awh" to extract the PMF and other profiles stored in the energy file. The tool outputs a file every "awh-nstout" steps (see mdp file), so we put them in a folder, find the last file, move it to the parent folder, and delete the others.
%% Cell type:code id: tags:
``` python
# THE LASTFILE DOES NOT WORK (GETS BRACKETS AROUND FILENAME)
!catpmf_w.xvg|awk'/ 0.000/ {print "The solvation free energy for water is " $2} /30.000/ {print "- " $2 " kJ/mol"}'
```
%% Cell type:markdown id: tags:
Now let's visualize the result. Let's first plot the resulting PMF and the coordinate bias. If the PMF does not have higher curvatures than what can be described by the bias generated using Gaussians, these should look identical (the bias is needed to obtain the target distribution = flat). Here, for uncharged molecules, the first 10 lambdas are equal, as they correspond to turning of the charges. The 20 consecutive ones correspond to turning of the Lennard-Jones interactions (van der Waals forces + steric repulsion). The solvation free energy is obtained as PMF($\lambda=1$) - PMF($\lambda=0$).
%% Cell type:code id: tags:
``` python
importmatplotlib.pyplotasplt
importnumpyasnp
pmf=np.loadtxt('pmf_w.xvg',comments=['#','@'])
plt.plot(pmf[:,0],pmf[:,2])
plt.plot(pmf[:,0],pmf[:,1])
plt.legend(['Coordinate bias','PMF'])
plt.xlabel('$\lambda$')
plt.ylabel('PMF (kJ/mol)')
plt.show()
```
%% Cell type:markdown id: tags:
Let's now look at some other AWH outputs. The target distribution was set to be uniform, and depending on the simulation length, we will have better or worse convergence to it. The error in the target distribution can be checked from the output file.
%% Cell type:code id: tags:
``` python
importmatplotlib.pyplotasplt
importnumpyasnp
pmf=np.loadtxt('pmf_w.xvg',comments=['#','@'])
plt.plot(pmf[:,0],pmf[:,4],'b')
plt.plot(pmf[:,0],pmf[:,5],'r')
plt.legend(['Reference value distribution','Target ref value distribution'])
plt.xlabel('$\lambda$')
plt.ylabel('PMF (kJ/mol)')
plt.show()
```
%% Cell type:code id: tags:
``` python
!greperrorpmf_w.xvg
```
%% Cell type:markdown id: tags:
Finally, we'll check the friction metric, which tells how easy it is to diffuse in the alchemical space along $\lambda$.
%% Cell type:code id: tags:
``` python
importmatplotlib.pyplotasplt
importnumpyasnp
pmf=np.loadtxt('pmf_w.xvg',comments=['#','@'])
plt.plot(pmf[:,0],pmf[:,6],'b')
plt.ylabel('Friction metric')
plt.xlabel('$\lambda$')
plt.show()
```
%% Cell type:markdown id: tags:
Now, we will finally extract the free energy of solvating the chosen solute in water.
%% Cell type:code id: tags:
``` python
!grep" 0.000"pmf_w.xvg|awk'{print "Solvation free energy in water is " $2 " kJ/mol"}'
!catpmf_w.xvg|awk'/ 0.000/ {print "The solvation free energy for water is "$2} /30.000/ {print "- "$2 " kJ/mol"}'
```
%% Output
cat: pmf_chex.xvg: No such file or directory
%% Cell type:markdown id: tags:
Now we will repeat the calculation for hexadecane in one block.
# Tutorial for extracting the PMF of a lipid bilayer permeation using AWH
%% Cell type:markdown id: tags:
In this tutorial, we will use the accelerated weight histogram (AWH) method to calculate the potential of mean force of a chosen small molecule along the direction normal to the plane of the membrane, which estimates the free energy profile for bilayer permeation. The AWH method provides an easy-to-use, computationally efficient, and fast-converging method to calculate PMFs along various reaction coordinates. The tutorial is performed using the coarse-grained Martini force field ([Martini web site](http://www.cgmartini.nl)) for speed, yet the same procedure is directly applicable to atomistic force fields.
## The workflow in a nutshell
* Choose a small molecule
* Set up a coarse-grained lipid bilayer
* Insert the small molecule into the system
* Minimize & equilibrate
* Run an AWH simulation where the small molecule permeates the bilayer many times
* Construct the PMF profile from the weight histogram
## Learning outcomes
* Set up biased simulations using the pull code in GROMACS
* Understand the meaning of the key simulation parameters
* Understand the working principle of AWH
* Understand the outputs of AWH
%% Cell type:code id: tags:
``` python
importMDAnalysisasmda
importnglviewasnv
importmatplotlib.pyplotasplt
importnumpyasnp
fromipywidgetsimportwidgets
fromIPython.displayimportdisplay
importwarnings
warnings.filterwarnings("ignore")
importwget
```
%% Cell type:markdown id: tags:
First select a small molecule from the [Martini 3 small molecule library](https://github.com/ricalessandri/Martini3-small-molecules/). Then download the corresponding structure (.gro) file.
Now, think what options to use to construct a lipid bilayer (bilayer.gro) and the corresponding draft for the topology file (topol.top) that contains a chosen composition (avoid charged lipids) in both leaflets. A suitable size is 8 x 8 x 12 nm^3. Use water as a solvent. A model is provided below, but feel free to change the composition.
## Add the permeant to the system (structure + topology)
First, we will include one molecule of the chosen type with a GROMACS command. You can take a look at the command options with the handle -h. Here, we add one molecule (-nmol 1) of the downloaded molecule (-ci). We try this so many times that the molecule certainly gets included (-try 100). The output file is called bilayer_permeant.gro (-o)
Let's visualize the convergence of the bilayer by looking at the time evolution of the bilayer edge length. Due to semi-isotropic pressure coupling, the membrane remains square, so area per lipid can be obtained by diviving the square of this length by the number of lipids per leaflet.
%% Cell type:code id: tags:
``` python
!echo"Box-X"|gmxenergy-feq.edr-obox.xvg
```
%% Cell type:code id: tags:
``` python
energy=np.loadtxt('box.xvg',comments=['#','@'])
plt.plot(energy[:,0],energy[:,1])
plt.xlabel('Time (ps)')
plt.ylabel('Box edge (nm)')
plt.show()
```
%% Cell type:markdown id: tags:
Area per lipid values for phospholipids are typically 0.50-0.70 nm^2. Is your result in the right ballpark?
%% Cell type:markdown id: tags:
## AWH simulation
Next up, we will take a look at the pull and AWH options at the end of the mdp file, starting from "pull = yes".
We have 2 pull groups; the reference group of "LIPIDS" and the one for the permeant "PERMEANT", which will be changed to the correct molecule name in the next step. We then define one pull coordinate based on the distance between the centres of mass of these two groups. It acts along the direction of the z axis, and uses the external AWH bias.
We use a convolved AWH potential, which contains ... The target distribution of our AWH bias is 1-dimensional and cut off at 40 kJ/mol to avoid the sampmling of unlikely conformations. GROWTH?
The coordinate will be sampled between z values of -4.5 and 4.5, which spans the bilayer and reaches some 2 nanometers to the aqueous phase on both sides of the bilayer. As our simulation system was set to be 12 nm tall, periodicity is not an issue. The initial large force constant ensures rapid convergence of the initial XX state.
%% Cell type:code id: tags:
``` python
!catMDPs/awh_template.mdp
```
%% Cell type:markdown id: tags:
Let's change the name of the permant in the mdp file.
Let's generate an index file that contains groups for lipids and solvent. This is needed for both the definition of the pull coordinate as well as for temperature coupling groups.
%% Cell type:code id: tags:
``` python
!gmxselect-feq.gro-onindex.ndx-seq.tpr-select"LIPIDS = not resname W and not resname $MOLNAME; SOLVENT = not LIPIDS; SOLVENT; LIPIDS; $MOLNAME;"
```
%% Cell type:markdown id: tags:
Now we will generate the tpr file and run the AWH simulation.
We will first check how the simulation has sampled different $\lambda$ values as a function of time. It should cover them all multiple times for convergence. Here, the simulation was very short for demonstration purposes, so this might not be the case for each chosen solute.
%% Cell type:code id: tags:
``` python
awh=np.loadtxt('awh.xvg',comments=['#','@'])
plt.plot(awh[:,0]/1000,awh[:,1])
plt.ylabel('$z$ coordinate')
plt.xlabel('Time (nanoseconds)')
plt.show()
```
%% Cell type:markdown id: tags:
Let's also take a look at the histogram of the $z$ coordinate, which better visualizes the sampling.
%% Cell type:code id: tags:
``` python
plt.hist(awh[:,1],50,
histtype='bar',
facecolor='b')
plt.xlabel('$z$ coordinate')
plt.ylabel('Occurrence')
plt.show()
```
%% Cell type:markdown id: tags:
Next, we run the tool "gmx awh" to extract the PMF and other profiles stored in the energy file. The tool outputs a file every "awh-nstout" steps (see mdp file), so we put them in a folder, find the last file, move it to the parent folder, and delete the others.
%% Cell type:code id: tags:
``` python
# This doesn't work yet :(
!mkdirOUTPUT
!gmxawh-fawh.edr-sawh.tpr-more-oOUTPUT/awh
lastfile=!ls-trOUTPUT/*xvg|tail-n1
!echo{lastfile}
!mv{lastfile}pmf.xvg
!rm-rfOUTPUT
```
%% Cell type:markdown id: tags:
Then we take a look at the AWH outputs. First the PMF and the coordinate bias, which should converge to the same profile given that the curvature of the PMF is not too high at some regions, in which case the bias potential cannot capture its shape.
%% Cell type:code id: tags:
``` python
awh=np.loadtxt('pmf.xvg',comments=['#','@'])
plt.plot(awh[:,0],awh[:,1],'r-')
plt.plot(awh[:,0],awh[:,2],'b-')
plt.legend(['PMF','Coordinate bias'])
plt.ylabel('PMF (kJ/mol)')
plt.xlabel('$z$ coordinate (nm)')
plt.show()
```
%% Cell type:markdown id: tags:
We can also check how well we sample the target distribution.
%% Cell type:code id: tags:
``` python
plt.plot(awh[:,0],awh[:,4],'r-')
plt.plot(awh[:,0],awh[:,5],'b-')
plt.legend(['Reference value distribution','Target ref. value distribution'])
plt.ylabel('PMF (kJ/mol)')
plt.xlabel('$z$ coordinate (nm)')
plt.show()
```
%% Cell type:markdown id: tags:
Finally, let's look at the friction metric to see which regions have the highest friction for your permeant.
%% Cell type:code id: tags:
``` python
plt.plot(awh[:,0],awh[:,6],'r-')
plt.ylabel('Friction')
plt.xlabel('$z$ coordinate (nm)')
plt.show()
```
%% Cell type:markdown id: tags:
# Final questions and Conclusions
* Did your simulation converge?
* Where does your solute prefer to locate itself? Where are the free energy barriers?
* From the PMF, how much is the partitioning free energy from the aqueous phase to the bilayer core?
* Does this agree with the water–oil partitioning free energy from the alchemical simulations?
%% Cell type:markdown id: tags:
The MD simulation will be accelerated using multiple CPU cores. `orterun -n $SLURM_CPUS_PER_TASK` will be used to tell the job to use all cores that were requested when this notebook was started.
The run will take a few tens of seconds to a couple of minutes. Feel free to change `$SLURM_CPUS_PER_TASK` to a smaller number. How does it affect the simulation wall-time and performance (ns/day)? These are printed at the end of the output.
Running simulations in parallel using several CPU cores that are each responsible for a subset of the total computational task is the fundamental principle of high-performance computing!