|
7 | 7 | "source": [ |
8 | 8 | "# Extracting and visualising a free energy simulation\n", |
9 | 9 | "\n", |
10 | | - "This notebook provides a step-by-step guide to extract and visualise a free energy simulation trajectory from a ``simulation.nc`` file using [openfe-analysis](https://github.com/OpenFreeEnergy/openfe_analysis) and [MDAnalysis](https://github.com/MDAnalysis/mdanalysis). By the end, you should understand how to:\n", |
| 10 | + "This notebook provides a step-by-step guide to extract and visualise a free energy simulation trajectory from a ``simulation.nc`` file using [openfe-analysis](https://github.com/OpenFreeEnergy/openfe_analysis), [MDAnalysis](https://github.com/MDAnalysis/mdanalysis) and [mdtraj](https://github.com/mdtraj/mdtraj). By the end, you should understand how to:\n", |
11 | 11 | "\n", |
12 | 12 | "1. Extract the trajectory of a ``replica`` or ``single lambda state`` from a ``simulation.nc`` file\n", |
13 | | - "2. Centre the ligand in the simulation box using `MDAnalysis`\n", |
| 13 | + "2. For a given hybrid topology trajectory, extract the relevant atom positions for the end states using `MDAnalysis`\n", |
14 | 14 | "3. Write out the trajectorie(s) using `MDAnalysis`\n", |
| 15 | + "4. Centre the ligand in the simulation box using `mdtraj`\n", |
15 | 16 | "\n", |
16 | 17 | "## Downloading the example data\n", |
17 | 18 | "\n", |
|
28 | 29 | "name": "stdout", |
29 | 30 | "output_type": "stream", |
30 | 31 | "text": [ |
31 | | - "--2025-09-05 10:31:50-- https://zenodo.org/records/15375081/files/simulation.nc\n", |
32 | | - "Resolving zenodo.org (zenodo.org)... 2001:1458:d00:25::100:372, 2001:1458:d00:24::100:f6, 2001:1458:d00:61::100:2f3, ...\n", |
| 32 | + "--2025-09-22 13:47:47-- https://zenodo.org/records/15375081/files/simulation.nc\n", |
| 33 | + "Resolving zenodo.org (zenodo.org)... 2001:1458:d00:25::100:372, 2001:1458:d00:61::100:2f3, 2001:1458:d00:24::100:f6, ...\n", |
33 | 34 | "Connecting to zenodo.org (zenodo.org)|2001:1458:d00:25::100:372|:443... connected.\n", |
34 | 35 | "HTTP request sent, awaiting response... 200 OK\n", |
35 | 36 | "Length: 516886878 (493M) [application/octet-stream]\n", |
36 | | - "Saving to: ‘simulation.nc.1’\n", |
| 37 | + "Saving to: ‘simulation.nc.2’\n", |
37 | 38 | "\n", |
38 | | - "simulation.nc.1 100%[===================>] 492.94M 12.2MB/s in 62s \n", |
39 | | - "\n", |
40 | | - "2025-09-05 10:32:53 (7.96 MB/s) - ‘simulation.nc.1’ saved [516886878/516886878]\n", |
41 | | - "\n", |
42 | | - "--2025-09-05 10:32:53-- https://zenodo.org/records/15375081/files/hybrid_system.pdb\n", |
43 | | - "Resolving zenodo.org (zenodo.org)... 2001:1458:d00:24::100:f6, 2001:1458:d00:61::100:2f3, 2001:1458:d00:25::100:372, ...\n", |
44 | | - "Connecting to zenodo.org (zenodo.org)|2001:1458:d00:24::100:f6|:443... connected.\n", |
| 39 | + "simulation.nc.2 7%[> ] 37.40M 6.76MB/s eta 88s ^C\n", |
| 40 | + "--2025-09-22 13:47:55-- https://zenodo.org/records/15375081/files/hybrid_system.pdb\n", |
| 41 | + "Resolving zenodo.org (zenodo.org)... 2001:1458:d00:61::100:2f3, 2001:1458:d00:24::100:f6, 2001:1458:d00:25::100:372, ...\n", |
| 42 | + "Connecting to zenodo.org (zenodo.org)|2001:1458:d00:61::100:2f3|:443... connected.\n", |
45 | 43 | "HTTP request sent, awaiting response... 200 OK\n", |
46 | 44 | "Length: 388547 (379K) [application/octet-stream]\n", |
47 | | - "Saving to: ‘hybrid_system.pdb.1’\n", |
| 45 | + "Saving to: ‘hybrid_system.pdb.2’\n", |
48 | 46 | "\n", |
49 | | - "hybrid_system.pdb.1 100%[===================>] 379.44K 1.65MB/s in 0.2s \n", |
| 47 | + "hybrid_system.pdb.2 100%[===================>] 379.44K 1.29MB/s in 0.3s \n", |
50 | 48 | "\n", |
51 | | - "2025-09-05 10:32:54 (1.65 MB/s) - ‘hybrid_system.pdb.1’ saved [388547/388547]\n", |
| 49 | + "2025-09-22 13:47:56 (1.29 MB/s) - ‘hybrid_system.pdb.2’ saved [388547/388547]\n", |
52 | 50 | "\n" |
53 | 51 | ] |
54 | 52 | } |
|
74 | 72 | }, |
75 | 73 | { |
76 | 74 | "cell_type": "code", |
77 | | - "execution_count": 6, |
| 75 | + "execution_count": 28, |
78 | 76 | "id": "05ba7dc7", |
79 | 77 | "metadata": {}, |
80 | 78 | "outputs": [ |
81 | 79 | { |
82 | 80 | "name": "stderr", |
83 | 81 | "output_type": "stream", |
84 | 82 | "text": [ |
85 | | - "/Users/joshua/mambaforge/envs/openfe_fast_set/lib/python3.12/site-packages/openfe_analysis/utils/multistate.py:41: UserWarning: This is an older NetCDF file that does not yet contain information about the write frequency of positions and velocities. We will assume that positions and velocities were written out at every iteration. \n", |
| 83 | + "/Users/joshua/mambaforge/envs/openfe_dev/lib/python3.12/site-packages/openfe_analysis/utils/multistate.py:41: UserWarning: This is an older NetCDF file that does not yet contain information about the write frequency of positions and velocities. We will assume that positions and velocities were written out at every iteration. \n", |
86 | 84 | " warnings.warn(wmsg)\n" |
87 | 85 | ] |
88 | 86 | }, |
89 | 87 | { |
90 | 88 | "data": { |
91 | 89 | "application/vnd.jupyter.widget-view+json": { |
92 | | - "model_id": "ca7edee8791e482fa36a171ee3edb895", |
| 90 | + "model_id": "e5a30cb71c014c3c9831cf2f3723ea72", |
93 | 91 | "version_major": 2, |
94 | 92 | "version_minor": 0 |
95 | 93 | }, |
|
103 | 101 | ], |
104 | 102 | "source": [ |
105 | 103 | "import MDAnalysis as mda\n", |
| 104 | + "import mdtraj as md\n", |
106 | 105 | "from openfe_analysis import FEReader\n", |
107 | 106 | "import nglview as nv\n", |
| 107 | + "import numpy as np\n", |
108 | 108 | "\n", |
109 | 109 | "u_0 = mda.Universe(\"hybrid_system.pdb\", \"simulation.nc\", format=FEReader, state_id=0)\n", |
110 | 110 | "\n", |
|
125 | 125 | }, |
126 | 126 | { |
127 | 127 | "cell_type": "code", |
128 | | - "execution_count": 7, |
| 128 | + "execution_count": 29, |
129 | 129 | "id": "009a8ac7-0ca9-494c-bbd6-177e752b0d4f", |
130 | 130 | "metadata": {}, |
131 | 131 | "outputs": [ |
132 | 132 | { |
133 | 133 | "name": "stderr", |
134 | 134 | "output_type": "stream", |
135 | 135 | "text": [ |
136 | | - "/Users/joshua/mambaforge/envs/openfe_fast_set/lib/python3.12/site-packages/openfe_analysis/utils/multistate.py:41: UserWarning: This is an older NetCDF file that does not yet contain information about the write frequency of positions and velocities. We will assume that positions and velocities were written out at every iteration. \n", |
137 | | - " warnings.warn(wmsg)\n", |
138 | | - "DEPRECATED: Please use 'center' method\n" |
| 136 | + "/Users/joshua/mambaforge/envs/openfe_dev/lib/python3.12/site-packages/openfe_analysis/utils/multistate.py:41: UserWarning: This is an older NetCDF file that does not yet contain information about the write frequency of positions and velocities. We will assume that positions and velocities were written out at every iteration. \n", |
| 137 | + " warnings.warn(wmsg)\n" |
139 | 138 | ] |
140 | 139 | }, |
141 | 140 | { |
142 | 141 | "data": { |
143 | 142 | "application/vnd.jupyter.widget-view+json": { |
144 | | - "model_id": "18e16cf1a1144b228e6ab22a06d42002", |
| 143 | + "model_id": "e9e8ec05be4c4075ad1b13bc9be88c6c", |
145 | 144 | "version_major": 2, |
146 | 145 | "version_minor": 0 |
147 | 146 | }, |
|
157 | 156 | "u_1 = mda.Universe(\"hybrid_system.pdb\", \"simulation.nc\", format=FEReader, state_id=-1)\n", |
158 | 157 | "\n", |
159 | 158 | "w = nv.show_mdanalysis(u_1)\n", |
160 | | - "w.center_view()\n", |
| 159 | + "w.center()\n", |
161 | 160 | "w" |
162 | 161 | ] |
163 | 162 | }, |
164 | 163 | { |
165 | 164 | "cell_type": "markdown", |
166 | | - "id": "75602d58-6709-4d9b-8a52-9896fdc6a0af", |
| 165 | + "id": "cbc5534f-846c-486c-9b8b-01e1bab8d880", |
167 | 166 | "metadata": {}, |
168 | 167 | "source": [ |
169 | | - "## Centring the Ligand\n", |
| 168 | + "# Extracting the end state positions with `MDAnalysis` \n", |
170 | 169 | "\n", |
171 | | - "You may have noticed in the view above that the ligand seems to have drifted away from the protein, this is a visualisation artifact caused by the use of periodic boundary conditions and the way in which OpenMM tries to ensure that all particle positions are written into a single periodic box. We can fix this however, using transformations in `MDAnalysis` to centre the ligand and wrap the protein around it like so:" |
| 170 | + "The trajectory data stored in the `simulation.nc` file contains the positions of the end-state ligands in their hybrid topology format. This means only atoms that are unique to the end-states have individual positions, with conserved core atoms sharing a single set of positions. As you might have noticed in the visualisation above, this can complicate the analysis and visualisation of the protein-ligand interactions. However, we can identify the atoms relevant to the end states or core atoms using the beta factors in the topology file:\n", |
| 171 | + "\n", |
| 172 | + "- `0.0`: The non-alchemical atoms (protein, solvent, etc)\n", |
| 173 | + "- `0.25`: The unique atoms of state A\n", |
| 174 | + "- `0.5`: The conserved core atoms present in both end states\n", |
| 175 | + "- `0.75`: The unique atoms of state B\n", |
| 176 | + "\n", |
| 177 | + "With this information, we can easily extract the atom positions relevant to `state A` for `lambda=0`:" |
172 | 178 | ] |
173 | 179 | }, |
174 | 180 | { |
175 | 181 | "cell_type": "code", |
176 | | - "execution_count": 8, |
177 | | - "id": "a5f16795-1b9c-4198-8694-6568eaba06c7", |
| 182 | + "execution_count": 30, |
| 183 | + "id": "0ac90329-4a4a-4c76-be44-447c7ef9bb8b", |
178 | 184 | "metadata": {}, |
179 | 185 | "outputs": [ |
| 186 | + { |
| 187 | + "name": "stderr", |
| 188 | + "output_type": "stream", |
| 189 | + "text": [ |
| 190 | + "/Users/joshua/mambaforge/envs/openfe_dev/lib/python3.12/site-packages/MDAnalysis/core/topologyattrs.py:329: DeprecationWarning: The bfactor topology attribute is only provided as an alias to the tempfactor attribute. It will be removed in 3.0. Please use the tempfactor attribute instead.\n", |
| 191 | + " warnings.warn(BFACTOR_WARNING, DeprecationWarning)\n" |
| 192 | + ] |
| 193 | + }, |
180 | 194 | { |
181 | 195 | "data": { |
182 | 196 | "application/vnd.jupyter.widget-view+json": { |
183 | | - "model_id": "2f9fe1601d5a4c22954bdc2dd69ad482", |
| 197 | + "model_id": "8d08590641d041e9bda6b83b21c8d95e", |
184 | 198 | "version_major": 2, |
185 | 199 | "version_minor": 0 |
186 | 200 | }, |
|
193 | 207 | } |
194 | 208 | ], |
195 | 209 | "source": [ |
196 | | - "import MDAnalysis.transformations as trans\n", |
| 210 | + "# get atoms for state A\n", |
| 211 | + "bfactor = 0.25\n", |
| 212 | + "state_atoms = np.array([atom.ix for atom in u_0.atoms if atom.bfactor in (bfactor, 0.5, 0.0)])\n", |
| 213 | + "state = u_0.atoms[state_atoms]\n", |
197 | 214 | "\n", |
198 | | - "ligand = u_0.select_atoms(\"resname UNK\")\n", |
199 | | - "protein = u_0.select_atoms(\"protein\")\n", |
200 | | - "transforms = [\n", |
201 | | - " trans.center_in_box(ligand, wrap=True),\n", |
202 | | - " trans.wrap(protein),\n", |
203 | | - " trans.nojump.NoJump()\n", |
204 | | - "]\n", |
205 | | - "u_0.trajectory.add_transformations(*transforms)\n", |
| 215 | + "w = nv.show_mdanalysis(state)\n", |
206 | 216 | "\n", |
207 | | - "w = nv.show_mdanalysis(u_0)\n", |
208 | 217 | "w" |
209 | 218 | ] |
210 | 219 | }, |
|
213 | 222 | "id": "7026738c-e4fe-461e-85ca-f726e9768a5e", |
214 | 223 | "metadata": {}, |
215 | 224 | "source": [ |
216 | | - "## Saving the trajectory to file\n", |
| 225 | + "## Saving the trajectory to file with `MDAnalysis`\n", |
217 | 226 | "\n", |
218 | | - "We can now use `MDAnalysis` to save our trajectory to a common file format:" |
| 227 | + "We can now use `MDAnalysis` to save the trajectory of the `state A` atoms to a common file format, note that we will also need to write out a new topology file that can be used to load this trajectory:" |
219 | 228 | ] |
220 | 229 | }, |
221 | 230 | { |
222 | 231 | "cell_type": "code", |
223 | | - "execution_count": 9, |
| 232 | + "execution_count": 31, |
224 | 233 | "id": "6e410679", |
225 | 234 | "metadata": {}, |
226 | | - "outputs": [], |
| 235 | + "outputs": [ |
| 236 | + { |
| 237 | + "name": "stderr", |
| 238 | + "output_type": "stream", |
| 239 | + "text": [ |
| 240 | + "/Users/joshua/mambaforge/envs/openfe_dev/lib/python3.12/site-packages/MDAnalysis/coordinates/PDB.py:1154: UserWarning: Found no information for attr: 'formalcharges' Using default value of '0'\n", |
| 241 | + " warnings.warn(\"Found no information for attr: '{}'\"\n" |
| 242 | + ] |
| 243 | + } |
| 244 | + ], |
227 | 245 | "source": [ |
228 | | - "with mda.Writer('out.xtc', n_atoms=len(u_0.atoms)) as w:\n", |
| 246 | + "# write a new PDB topology file for the state A atoms only\n", |
| 247 | + "state.write(\"state_a_topology.pdb\")\n", |
| 248 | + "# write the trajectory to an xtc file\n", |
| 249 | + "with mda.Writer('out.xtc', n_atoms=len(state.atoms)) as w:\n", |
229 | 250 | " for ts in u_0.trajectory:\n", |
230 | | - " w.write(u_0.atoms)" |
| 251 | + " w.write(u_0.atoms[state_atoms])" |
| 252 | + ] |
| 253 | + }, |
| 254 | + { |
| 255 | + "cell_type": "markdown", |
| 256 | + "id": "75602d58-6709-4d9b-8a52-9896fdc6a0af", |
| 257 | + "metadata": {}, |
| 258 | + "source": [ |
| 259 | + "## Centring the Ligand with `mdtraj`\n", |
| 260 | + "\n", |
| 261 | + "You may have noticed in the view above that the ligand seems to have drifted away from the protein, this is a visualisation artifact caused by the use of periodic boundary conditions and the way in which `OpenMM` tries to ensure that all particle positions are written into a single periodic box. We can fix this, however, using `mdtraj` and the [image_molecules](https://mdtraj.org/1.9.3/api/generated/mdtraj.Trajectory.html?highlight=image_molecules#mdtraj.Trajectory.image_molecules) function:" |
| 262 | + ] |
| 263 | + }, |
| 264 | + { |
| 265 | + "cell_type": "code", |
| 266 | + "execution_count": 33, |
| 267 | + "id": "a5f16795-1b9c-4198-8694-6568eaba06c7", |
| 268 | + "metadata": {}, |
| 269 | + "outputs": [ |
| 270 | + { |
| 271 | + "data": { |
| 272 | + "application/vnd.jupyter.widget-view+json": { |
| 273 | + "model_id": "c967de9e5a9a4ac688f2e74e25ede21f", |
| 274 | + "version_major": 2, |
| 275 | + "version_minor": 0 |
| 276 | + }, |
| 277 | + "text/plain": [ |
| 278 | + "NGLWidget(max_frame=500)" |
| 279 | + ] |
| 280 | + }, |
| 281 | + "metadata": {}, |
| 282 | + "output_type": "display_data" |
| 283 | + } |
| 284 | + ], |
| 285 | + "source": [ |
| 286 | + "traj = md.load_xtc(\"out.xtc\", top=\"state_a_topology.pdb\")\n", |
| 287 | + "traj = traj.image_molecules()\n", |
| 288 | + "\n", |
| 289 | + "w = nv.show_mdtraj(traj)\n", |
| 290 | + "\n", |
| 291 | + "w" |
231 | 292 | ] |
232 | 293 | } |
233 | 294 | ], |
|
0 commit comments