├── .gitignore
├── 01_introduction
├── 01_introduction.ipynb
├── 01_introduction.md
├── 02_molecular_setup.ipynb
├── 02_molecular_setup.md
├── 03_molecular_dynamics.ipynb
├── 03_molecular_dynamics.md
├── 04_writing_nodes.ipynb
├── 04_writing_nodes.md
├── 05_running_nodes.ipynb
├── 05_running_nodes.md
├── README.md
├── assets
│ ├── 02_solvated.png
│ ├── 03_rmsd.png
│ ├── 03_time_series.png
│ ├── 03_view_molecule.png
│ ├── 03_view_molecules.png
│ ├── 03_view_system.png
│ ├── 04_gui.png
│ └── output.zip
├── get_tutorial.py
├── myconfig
│ ├── Process.py
│ └── Protocol.py
├── nodes
│ ├── equilibrate.ipynb
│ ├── equilibrate.py
│ ├── minimise.ipynb
│ ├── minimise.py
│ ├── parameterise.ipynb
│ ├── parameterise.py
│ ├── solvate.ipynb
│ └── solvate.py
└── scripts
│ └── workflow.sh
├── 02_funnel_metad
├── 01_bss-fun-metad-setup.ipynb
├── 01_bss-fun-metad-setup.md
├── 02_bss-fun-metad-analysis.ipynb
├── 02_bss-fun-metad-analysis.md
├── README.md
├── example_nodes
│ ├── equilibrate_no_trj.py
│ ├── fun_metad.py
│ ├── minimise.py
│ ├── parameterise_solvate.py
│ └── submit.sub
├── figures
│ ├── figure1.jpeg
│ ├── figure2.png
│ └── fun-hsp90.png
└── get_tutorial.py
├── 03_steered_md
├── 01_setup_sMD.ipynb
├── 01_setup_sMD.md
├── 02_trajectory_analysis.ipynb
├── 02_trajectory_analysis.md
├── README.md
├── figures
│ ├── COLVAR_all.png
│ ├── COLVAR_failed.png
│ ├── COLVAR_snapshots.png
│ ├── ensemble-md-protocol.png
│ ├── open-close.png
│ ├── phe196.png
│ └── tyr152.png
├── get_tutorial.py
└── scripts
│ ├── sMD_LSF.sh
│ ├── sMD_multiCV.py
│ ├── sMD_simple.py
│ ├── sMD_slurm.sh
│ ├── seededMD.py
│ └── seededMD_LSF.sh
├── 04_fep
├── 01_intro_to_alchemy
│ ├── alchemical_introduction.ipynb
│ ├── alchemical_introduction.md
│ ├── example_output
│ │ ├── mbar_bound_output.txt
│ │ └── mbar_free_output.txt
│ ├── get_tutorial.py
│ ├── images
│ │ ├── MCS.png
│ │ ├── Therm_cycle.png
│ │ ├── overlap_MBAR_example.png
│ │ ├── overlap_MBAR_example_circles.png
│ │ ├── overlap_MBAR_example_less.png
│ │ ├── overlap_MBAR_example_less_circles.png
│ │ └── thermo_cycle_rel_eq.png
│ └── slides
│ │ └── CCPBioSimTraining2022_slides.pdf
├── 02_RBFE
│ ├── 01_setup_rbfe.ipynb
│ ├── 01_setup_rbfe.md
│ ├── 02_analysis_rbfe.ipynb
│ ├── 02_analysis_rbfe.md
│ ├── get_tutorial.py
│ ├── images
│ │ ├── fep_pipeline.png
│ │ └── tyk2_protlig.png
│ └── scripts
│ │ ├── .svn
│ │ ├── entries
│ │ ├── format
│ │ ├── pristine
│ │ │ ├── 01
│ │ │ │ └── 015f3293a2b13c2bb141480cf0a46d2710d7e505.svn-base
│ │ │ ├── 09
│ │ │ │ └── 09b149f7700b981e3a51dbb930632c234a099ab8.svn-base
│ │ │ ├── 4c
│ │ │ │ └── 4cdea129ef2fd591a19b69c88e8b8182b9c293ce.svn-base
│ │ │ ├── e4
│ │ │ │ └── e489348848a63603fd82f847f97faf1e5598002c.svn-base
│ │ │ ├── ec
│ │ │ │ └── ec0594e6d6479b8b830af7315ad23adaed62f79e.svn-base
│ │ │ ├── f3
│ │ │ │ └── f3791f681e50dd384968ce79e03b599e355721aa.svn-base
│ │ │ └── f9
│ │ │ │ └── f9f96d03b0f2df3f8a8ec74555fbfc2374e949d6.svn-base
│ │ ├── wc.db
│ │ └── wc.db-journal
│ │ ├── analysis.py
│ │ ├── extract_execution_model_bash.sh
│ │ ├── fepprep.py
│ │ ├── ligprep.py
│ │ ├── run_all_slurm.sh
│ │ ├── run_analysis_slurm.sh
│ │ ├── run_fepprep_slurm.sh
│ │ ├── run_ligprep_slurm.sh
│ │ └── run_production_slurm.sh
├── 03_ABFE
│ ├── 01_setup_abfe.ipynb
│ ├── 01_setup_abfe.md
│ ├── 02_analysis_abfe.ipynb
│ ├── 02_analysis_abfe.md
│ ├── get_tutorial.py
│ └── images
│ │ ├── MCS.png
│ │ ├── abfe_cycle_details.png
│ │ ├── alt_poses.png
│ │ ├── boresch_dof.png
│ │ ├── boresch_restr_2.png
│ │ ├── convergence_equil.png
│ │ ├── convergence_no_equil.png
│ │ ├── mif_mif180.png
│ │ ├── ngl_1.png
│ │ ├── ngl_2.png
│ │ ├── ngl_new_1.png
│ │ ├── ngl_new_2.png
│ │ ├── ngl_new_3.png
│ │ ├── overlap.png
│ │ └── pmf_evolution.png
├── 04_PFEP
│ ├── 01_setup_pfep.ipynb
│ ├── 01_setup_pfep.md
│ └── images
│ │ └── pfep_tutorial_tcycle.png
├── 05_ATM
│ ├── 01_ATM.ipynb
│ └── get_tutorial.py
└── README.md
├── LICENSE
├── LIVECOMS
├── 01_introduction
│ └── livecoms.tex
├── 02_funnel_metad
│ ├── fun-hsp90-analyses.png
│ ├── funmetad-hsp90.png
│ ├── funmetadfig1.jpeg
│ └── livecoms.tex
├── 03_steered_md
│ ├── COLVAR_all.png
│ ├── ensemble-md-protocol.png
│ ├── livecoms.tex
│ ├── msm-final.png
│ └── open-close.png
├── 04_fep
│ ├── abfe-tutorial.png
│ ├── alignment_visualisation.png
│ ├── introfep.png
│ ├── introfep_updated.png
│ ├── livecoms.tex
│ ├── pfep-tutorial_tcycle.png
│ ├── rbfe-analysis.png
│ └── rbfe-setup.png
├── livecoms.cls
├── main.tex
├── references.bib
└── vancouver-livecoms.bst
├── README.md
├── Style_guide.ipynb
└── releases
├── LiveCoMS_Article_v1.pdf
└── header_v1.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .ipynb_checkpoints
3 |
--------------------------------------------------------------------------------
/01_introduction/04_writing_nodes.md:
--------------------------------------------------------------------------------
1 | Author: Lester Hedges
2 | Email: lester.hedges@bristol.ac.uk
3 |
4 | # Nodes: _Interoperable workflow components_
5 |
6 | The companion notebook for this section can be found [here](https://github.com/michellab/BioSimSpaceTutorials/blob/4844562e7d2cd0b269cead56562ec16a3dfaef7c/01_introduction/04_writing_nodes.ipynb)
7 |
8 | So far we have been working with BioSimSpace in a rather ad hoc fashion. While this intereactive exploration is a great way of learning and prototyping ideas, it is not a good way of producing a reproducible and interoperable script that can be shared with others. For example, we created processes that used specific packages such as AMBER and GROMACS. If a user didn't have these available on their system, then the script simply wouldn't work. We also used hard-coded paths to input files. This means the user would have to edit the paths each time they ran the script with different input, which would quickly become tedious.
9 |
10 | In order to solve this problem, a core concept of BioSimSpace is the interoperable workflow component, or _node_. These are robust and portable Python scripts that typically do a small, well-defined piece of work. All inputs and outputs from the node are validated and the node is written in a such a way that it is _independent_ of the underlying software packages, i.e. the same script can work with a range of different packages. In addition, nodes are aware of the environment in which they are run, so can be used interactively, from the command-line, or within a workflow engine.
11 |
12 | While it is possible to write a node directly as a Python script, we suggest that the best way of writing one is inside of a [Jupyter](http://jupyter.org) notebook. As you've already seen, the interactive notebook environment provides a fantastic way of prototyping and documenting your node and will allow a user to interact with it directly on a remote cloud server, such as [notebook.biosimspace.org](https://notebook.biosimspace.org). The notebook can provide a complete record of your work, inlcuding documentation, visualisation, and graphs. When you are happy with the node, you can download it as a regular Python script (by clicking on `File/Download As/Python` in JupyterHub or `File/Export Notebook As/Export Notebook to Executable Script` in JupyterLab) and run it directly from the command-line on your workstation, laptop, or on a high-performance computing cluster. Any interactive BioSimSpace elements, such as molecular visualisations, will simply be ignored when run this way.
13 |
14 |
15 | ## An example: Minimisation
16 |
17 | In the rest of the notebook you'll learn how to use BioSimSpace to write a robust and interoperable workflow node to perform energy minimisation on a molecular system.
18 |
19 | As always, we'll first need to import BioSimSpace:
20 |
21 |
22 | ```python
23 | import BioSimSpace as BSS
24 | ```
25 |
26 | We begin by creating a `Node` object. This is the core of our molecular workflow component. It defines what it does, what input is needed, and the output that is produced.
27 |
28 |
29 | ```python
30 | node = BSS.Gateway.Node("A node to perform energy minimisation and save the minimised molecular configuration to file.")
31 | ```
32 |
33 | We'll now set the author and license of the node. When nodes are run the the authorship can be queried so that people can get credit for their work. Eventually, BioSimSpace nodes also will also contain built in tracking information to determine how many times they are run.
34 |
35 |
36 | ```python
37 | node.addAuthor(name="Lester Hedges", email="lester.hedges@bristol.ac.uk", affiliation="University of Bristol")
38 | node.setLicense("GPLv3")
39 | ```
40 |
41 | Nodes require inputs. To specify inputs we use the `BSS.Gateway` package, which is used as a bridge between BioSimSpace and the outside world. This will allow us to document the inputs, define their type, and specify any constraints on their allowed values. Here we will need a set of files that define the molecular system, and an integer that indicates the number of minimisation steps to perform.
42 |
43 |
44 | ```python
45 | node.addInput("files", BSS.Gateway.FileSet(
46 | help="A set of molecular input files.")
47 | )
48 |
49 | node.addInput("steps", BSS.Gateway.Integer(
50 | help="The number of minimisation steps.",
51 | minimum=0,
52 | maximum=1000000,
53 | default=10000)
54 | )
55 |
56 | node.addInput("engine", BSS.Gateway.String(
57 | help="The molecular dynamics engine",
58 | allowed=BSS.MD.engines(),
59 | default="auto")
60 | )
61 | ```
62 |
63 | Note that the input requirements `steps` and `engine` have default values, so are optional.
64 |
65 | We now need to define the output of the node. In this case we will return a set of files representing the minimised molecular system.
66 |
67 |
68 | ```python
69 | node.addOutput("minimised", BSS.Gateway.FileSet(help="The minimised molecular system."))
70 | ```
71 |
72 | When working interactively within a Jupyter notebook we need a way to allow users to set the input requirements. The `node.showControls` method will display a graphical user interface (GUI), from which inputs can be set. All of the elements for this GUI are automatically generated by the `addInput` and `addOutput` functions above. As you'll see in the next section, if we were to run the same node from the command-line, we would instead get an automatically generated [argparse](https://docs.python.org/3/library/argparse.html) parser.
73 |
74 | Note that the GUI requires active user input. All input requirements that don't have a default value _must_ be set before the node can proceed. If you try to query the node for one of the user values then an error will be raised. For bounded integer inputs you can use a slider to set the value, or type in the input box and press enter.
75 |
76 | When working interactively you will typically be running on a remote server where you won't have access to the local filesystem. In this case you'll need to upload files for any of the `File` or `FileSet` input requirements. The GUI below will provide buttons that allow you to browse your own filesystem and select files. Since Jupyter has a limit of 5MB for file transfers, we provide support for compressed formats, such as `.zip` or `.tar.gz`. (A single archive can contain a set of files, allowing you to set a single value for a `FileSet` requirement.) We've provided some example input files that can be used in the training notebooks, which are available to download from the links below. These can then be re-uploaded using the GUI.
77 |
78 | AMBER: [ala.crd](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/amber/ala/ala.crd), [ala.top](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/amber/ala/ala.top)
79 |
80 | GROMACS: [kigaki.gro](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/gromacs/kigaki/kigaki.gro), [kigaki.top](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/gromacs/kigaki/kigaki.top)
81 |
82 |
83 | When uploading files the name of the current file(s) will replace the `Upload` button. If you need to change the file, simply click on the button again and choose a new file. Note that both input files should be loaded at the same time.
84 |
85 |
86 | ```python
87 | node.showControls()
88 | ```
89 |
90 | 
91 |
92 |
93 | Once all requirements are set then we can acces the values using the `node.getInput` method. The first time this is called the `node` will automatically validate all of the input and report the user if any errors were found.
94 |
95 | We'll now create a molecular system using the input files uploaded by the user. As in the previous section, we don't need to specify the format of the files, since this is automatically determined by BioSimSpace. (BioSimSpace has support for a wide range of formats and can convert between many formats too.)
96 |
97 |
98 | ```python
99 | system = BSS.IO.readMolecules(node.getInput("files"))
100 | ```
101 |
102 | As learned in the previus notebook, in order to run a minimisation we need to define a protocol. This can be done using the `BSS.Protocol` package. Here we will create a "best practice" minimisation protocol, overriding the number of steps with the input from the user.
103 |
104 |
105 | ```python
106 | protocol = BSS.Protocol.Minimisation(steps=node.getInput("steps"))
107 | ```
108 |
109 | We now have everything that is required to run a minimisation. To do so, we use the `BSS.MD` package to find an appropriate molecular dynamics package on our current environment. What package is found will depend upon both the system and protocol, as well as the hardware that is available to the user. (For example, the user can choose to find packages with GPU support.)
110 |
111 | Note that this is different to the previous section, where we specifically launched AMBER and GROMACS processes ourselves. This is what makes the node interoperable, i.e. it will work regardles of what MD packages are installed. (As long as we find a package that supports minimisation and supports a molecular file format to which we can convert the input system.) By adding the optional `engine` requirement we have also allowed the user to override the `auto` setting if they prefer to use a specific engine.
112 |
113 | (By default, the `run` function automatically starts the process so it will be running as once you execute the cell below.)
114 |
115 |
116 | ```python
117 | process = BSS.MD.run(system, protocol, engine=node.getInput("engine"))
118 | ```
119 |
120 | We now wait for the process to finish, then check whether there were any errors before continuing. If errors were raised, then we raise an exception and print the last 10 lines of stdout and stderr to the user.
121 |
122 |
123 | ```python
124 | process.wait()
125 |
126 | if process.isError():
127 | print(process.stdout(10))
128 | print(process.stdout(10))
129 | raise RuntimeError("The process exited with an error!")
130 | ```
131 |
132 | When the process has finished running we can get the minimised molecular configuration. We will save this to file using the same format as the original system, and set the `minimised` output requirement to the list of file names that were written.
133 |
134 |
135 | ```python
136 | node.setOutput("minimised",
137 | BSS.IO.saveMolecules("minimised", process.getSystem(), system.fileFormat()))
138 | ```
139 |
140 | Finally, we validate that the node completed succesfully. This will check that all output requirements are satisfied and that no errors were raised by the user. Any file outputs will be available for the user to download as a compressed archive.
141 |
142 | Note that the validation will fail until the cell above finishes running.
143 |
144 |
145 | ```python
146 | node.validate()
147 | ```
148 |
149 |
150 |
151 |
152 | output.zip
153 |
154 |
155 |
156 | Once we are satisfied with our node we can choosed to download it as a regular Python script that can be run from the command-line.
157 |
158 | In JupyterHub, click on: `File/Download As/Python`\
159 | In JupyterLab, click on: `File/Export Notebook As/Export Notebook to Executable Script`
160 |
161 | That's it, you've now succesfully executed your first BioSimSpace node!
162 |
--------------------------------------------------------------------------------
/01_introduction/README.md:
--------------------------------------------------------------------------------
1 | # An introduction to BioSimSpace
2 |
3 | This tutorial provides an introduction to [BioSimSpace](https://biosimspace.org/index.html), an interoperable Python framework for biomolecular simulation.
4 |
5 | The tutorial is divided into five sections, each linked below:
6 |
7 | * 01 - [Introduction](https://github.com/OpenBioSim/BioSimSpaceTutorials/blob/main/01_introduction/01_introduction.md)
8 | * 02 - [Molecular setup](https://github.com/OpenBioSim/BioSimSpaceTutorials/blob/main/01_introduction/02_molecular_setup.md)
9 | * 03 - [Molecular dynamics](https://github.com/OpenBioSim/BioSimSpaceTutorials/blob/main/01_introduction/03_molecular_dynamics.md)
10 | * 04 - [Writing nodes](https://github.com/OpenBioSim/BioSimSpaceTutorials/blob/main/01_introduction/04_writing_nodes.md)
11 | * 05 - [Running nodes](https://github.com/OpenBioSim/BioSimSpaceTutorials/blob/main/01_introduction/05_running_nodes.md)
12 |
--------------------------------------------------------------------------------
/01_introduction/assets/02_solvated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/02_solvated.png
--------------------------------------------------------------------------------
/01_introduction/assets/03_rmsd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/03_rmsd.png
--------------------------------------------------------------------------------
/01_introduction/assets/03_time_series.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/03_time_series.png
--------------------------------------------------------------------------------
/01_introduction/assets/03_view_molecule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/03_view_molecule.png
--------------------------------------------------------------------------------
/01_introduction/assets/03_view_molecules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/03_view_molecules.png
--------------------------------------------------------------------------------
/01_introduction/assets/03_view_system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/03_view_system.png
--------------------------------------------------------------------------------
/01_introduction/assets/04_gui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/04_gui.png
--------------------------------------------------------------------------------
/01_introduction/assets/output.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/01_introduction/assets/output.zip
--------------------------------------------------------------------------------
/01_introduction/get_tutorial.py:
--------------------------------------------------------------------------------
1 | import requests, os
2 |
3 | links = {
4 | "01": (
5 | "inputs.tar.bz2",
6 | "https://openbiosim-my.sharepoint.com/:u:/g/personal/director_openbiosim_org/EU3zvc0UmE1CqX0UKKR5hGIBtdF8Jj1-u8hol1XPwAEgKQ?download=1",
7 | ),
8 | }
9 |
10 |
11 | def download():
12 | localfile, url = links["01"]
13 | # Do not download if tarball already found
14 | if os.path.isfile(localfile):
15 | return
16 | print("Downloading %s from openbiosim.org ..." % localfile)
17 | req = requests.get(url, stream=True)
18 | with open(localfile, "wb") as f:
19 | for chunk in req.iter_content(chunk_size=1024):
20 | if chunk:
21 | f.write(chunk)
22 | f.flush()
23 | print("Extracting compressed tarball ...")
24 | os.system("tar -xf %s" % localfile)
25 | # os.system("rm %s" % localfile)
26 |
--------------------------------------------------------------------------------
/01_introduction/myconfig/Process.py:
--------------------------------------------------------------------------------
1 | import BioSimSpace as BSS
2 |
3 |
4 | # Wrap the instantiation of BSS.Process.Amber objects to configure them
5 | # such that coordinates are always wrapped to the minimum image.
6 | def Amber(
7 | system, protocol, exe=None, name="amber", work_dir=None, seed=None, property_map={}
8 | ):
9 | # Create process using the passed parameters.
10 | process = BSS.Process.Amber(
11 | system,
12 | protocol,
13 | exe=exe,
14 | name=name,
15 | work_dir=work_dir,
16 | seed=seed,
17 | property_map=property_map,
18 | )
19 |
20 | # Get the config.
21 | config = process.getConfig()
22 |
23 | # Add coordinate wrapping as the second last line.
24 | config[-1] = " iwrap=1,"
25 | config.append(" /")
26 |
27 | # Set the new config.
28 | process.setConfig(config)
29 |
30 | # Return the process.
31 | return process
32 |
--------------------------------------------------------------------------------
/01_introduction/myconfig/Protocol.py:
--------------------------------------------------------------------------------
1 | import BioSimSpace as BSS
2 |
3 | # Override the equilibration protocol with some custom defaults.
4 |
5 |
6 | def EquilibrationNVT(
7 | runtime=5 * BSS.Units.Time.nanosecond,
8 | report_interval=2500,
9 | restart_interval=250000,
10 | restraint="backbone",
11 | ):
12 | return BSS.Protocol.Equilibration(
13 | runtime=runtime,
14 | report_interval=report_interval,
15 | restart_interval=restart_interval,
16 | restraint=restraint,
17 | )
18 |
19 |
20 | def EquilibrationNPT(
21 | runtime=5 * BSS.Units.Time.nanosecond,
22 | pressure=BSS.Units.Pressure.atm,
23 | report_interval=2500,
24 | restart_interval=250000,
25 | restraint="backbone",
26 | ):
27 | return BSS.Protocol.Equilibration(
28 | runtime=runtime,
29 | pressure=pressure,
30 | report_interval=report_interval,
31 | restart_interval=restart_interval,
32 | restraint=restraint,
33 | )
34 |
--------------------------------------------------------------------------------
/01_introduction/nodes/equilibrate.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "Author: Lester Hedges
\n",
8 | "Email: lester.hedges@bristol.ac.uk\n",
9 | "\n",
10 | "# Equilibration\n",
11 | "\n",
12 | "A node to perform equilibration of a molecular system. Saves the equilibration system to AMBER format files along with a DCD trajectory."
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "import BioSimSpace as BSS"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "node = BSS.Gateway.Node(\n",
31 | " \"A node to perform equilibration. Saves the equlibrated molecular configuration and trajectory to file.\"\n",
32 | ")\n",
33 | "node.addAuthor(\n",
34 | " name=\"Lester Hedges\",\n",
35 | " email=\"lester.hedges@bristol.ac.uk\",\n",
36 | " affiliation=\"University of Bristol\",\n",
37 | ")\n",
38 | "node.setLicense(\"GPLv3\")"
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "metadata": {},
44 | "source": [
45 | "Set the input requirements:"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "metadata": {},
52 | "outputs": [],
53 | "source": [
54 | "node.addInput(\"files\", BSS.Gateway.FileSet(help=\"A set of molecular input files.\"))\n",
55 | "\n",
56 | "node.addInput(\n",
57 | " \"runtime\",\n",
58 | " BSS.Gateway.Time(\n",
59 | " help=\"The run time.\",\n",
60 | " unit=\"nanoseconds\",\n",
61 | " minimum=0.02 * BSS.Units.Time.nanosecond,\n",
62 | " maximum=10 * BSS.Units.Time.nanosecond,\n",
63 | " default=0.02 * BSS.Units.Time.nanosecond,\n",
64 | " ),\n",
65 | ")\n",
66 | "\n",
67 | "node.addInput(\n",
68 | " \"temperature_start\",\n",
69 | " BSS.Gateway.Temperature(\n",
70 | " help=\"The initial temperature.\",\n",
71 | " unit=\"kelvin\",\n",
72 | " minimum=0 * BSS.Units.Temperature.kelvin,\n",
73 | " maximum=1000 * BSS.Units.Temperature.kelvin,\n",
74 | " default=0 * BSS.Units.Temperature.kelvin,\n",
75 | " ),\n",
76 | ")\n",
77 | "\n",
78 | "node.addInput(\n",
79 | " \"temperature_end\",\n",
80 | " BSS.Gateway.Temperature(\n",
81 | " help=\"The final temperature.\",\n",
82 | " unit=\"kelvin\",\n",
83 | " minimum=0 * BSS.Units.Temperature.kelvin,\n",
84 | " maximum=1000 * BSS.Units.Temperature.kelvin,\n",
85 | " default=300 * BSS.Units.Temperature.kelvin,\n",
86 | " ),\n",
87 | ")\n",
88 | "\n",
89 | "node.addInput(\n",
90 | " \"restraint\",\n",
91 | " BSS.Gateway.String(\n",
92 | " help=\"The type of restraint.\",\n",
93 | " allowed=BSS.Protocol.Equilibration.restraints(),\n",
94 | " default=\"none\",\n",
95 | " ),\n",
96 | ")\n",
97 | "\n",
98 | "node.addInput(\n",
99 | " \"report_interval\",\n",
100 | " BSS.Gateway.Integer(\n",
101 | " help=\"The number of integration steps between reporting output.\",\n",
102 | " minimum=100,\n",
103 | " maximum=10000,\n",
104 | " default=100,\n",
105 | " ),\n",
106 | ")\n",
107 | "\n",
108 | "node.addInput(\n",
109 | " \"restart_interval\",\n",
110 | " BSS.Gateway.Integer(\n",
111 | " help=\"The number of integration steps between saving trajectory frames.\",\n",
112 | " minimum=100,\n",
113 | " maximum=10000,\n",
114 | " default=500,\n",
115 | " ),\n",
116 | ")\n",
117 | "\n",
118 | "node.addInput(\n",
119 | " \"engine\",\n",
120 | " BSS.Gateway.String(\n",
121 | " help=\"The molecular dynamics engine.\", allowed=BSS.MD.engines(), default=\"auto\"\n",
122 | " ),\n",
123 | ")"
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "We now need to define the output of the node. In this case we will return a set of files representing the equilibrated molecular system in AMBER format and a single file containing the trajectory frames."
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "metadata": {},
137 | "outputs": [],
138 | "source": [
139 | "node.addOutput(\n",
140 | " \"equilibrated\", BSS.Gateway.FileSet(help=\"The equilibrated molecular system.\")\n",
141 | ")\n",
142 | "node.addOutput(\"trajectory\", BSS.Gateway.File(help=\"The trajectory file.\"))"
143 | ]
144 | },
145 | {
146 | "cell_type": "code",
147 | "execution_count": null,
148 | "metadata": {},
149 | "outputs": [],
150 | "source": [
151 | "node.showControls()"
152 | ]
153 | },
154 | {
155 | "cell_type": "markdown",
156 | "metadata": {},
157 | "source": [
158 | "Generate the molecular system."
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": null,
164 | "metadata": {},
165 | "outputs": [],
166 | "source": [
167 | "system = BSS.IO.readMolecules(node.getInput(\"files\"))"
168 | ]
169 | },
170 | {
171 | "cell_type": "markdown",
172 | "metadata": {},
173 | "source": [
174 | "Set up the equilibration protocol.\n",
175 | "\n",
176 | "(Note that the keyword arguments happen to have the same name as the input requirements. This need not be the case.)"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "metadata": {},
183 | "outputs": [],
184 | "source": [
185 | "protocol = BSS.Protocol.Equilibration(\n",
186 | " runtime=node.getInput(\"runtime\"),\n",
187 | " temperature_start=node.getInput(\"temperature_start\"),\n",
188 | " temperature_end=node.getInput(\"temperature_end\"),\n",
189 | " restraint=node.getInput(\"restraint\"),\n",
190 | ")"
191 | ]
192 | },
193 | {
194 | "cell_type": "markdown",
195 | "metadata": {},
196 | "source": [
197 | "Start the molecular dynamics process."
198 | ]
199 | },
200 | {
201 | "cell_type": "code",
202 | "execution_count": null,
203 | "metadata": {},
204 | "outputs": [],
205 | "source": [
206 | "process = BSS.MD.run(system, protocol, engine=node.getInput(\"engine\"))"
207 | ]
208 | },
209 | {
210 | "cell_type": "markdown",
211 | "metadata": {},
212 | "source": [
213 | "We now wait for the process to finish, then check whether there were any errors before continuing. If errors were raised, then we raise an exception and print the last 10 lines of `stdout` and `stderr` to the user."
214 | ]
215 | },
216 | {
217 | "cell_type": "code",
218 | "execution_count": null,
219 | "metadata": {},
220 | "outputs": [],
221 | "source": [
222 | "process.wait()\n",
223 | "\n",
224 | "if process.isError():\n",
225 | " print(process.stdout(10))\n",
226 | " print(process.stdout(10))\n",
227 | " raise RuntimeError(\"The process exited with an error!\")"
228 | ]
229 | },
230 | {
231 | "cell_type": "markdown",
232 | "metadata": {},
233 | "source": [
234 | "Get the equilibrated molecular system and write to file in the same format as the input, along with a PDB file to use as a topology file for the trajectory in [VMD](https://www.ks.uiuc.edu/Research/vmd/)."
235 | ]
236 | },
237 | {
238 | "cell_type": "code",
239 | "execution_count": null,
240 | "metadata": {},
241 | "outputs": [],
242 | "source": [
243 | "# Get the original file formats associated with the system.\n",
244 | "# This is returned as a comma-separated string.\n",
245 | "formats = system.fileFormat()\n",
246 | "\n",
247 | "# Append PDB format, so we can visualise the trajectory with VMD.\n",
248 | "formats += \",pdb\"\n",
249 | "\n",
250 | "# Write to file and bind to the output.\n",
251 | "node.setOutput(\n",
252 | " \"equilibrated\", BSS.IO.saveMolecules(\"equilibrated\", process.getSystem(), formats)\n",
253 | ")"
254 | ]
255 | },
256 | {
257 | "cell_type": "markdown",
258 | "metadata": {},
259 | "source": [
260 | "Get the trajectory, convert to MDTraj format and save to a DCD file."
261 | ]
262 | },
263 | {
264 | "cell_type": "code",
265 | "execution_count": null,
266 | "metadata": {},
267 | "outputs": [],
268 | "source": [
269 | "# Get the BioSimSpace trajectory wrapper.\n",
270 | "trajectory = process.getTrajectory()\n",
271 | "\n",
272 | "# Extract the MDTraj object.\n",
273 | "traj = trajectory.getTrajectory(format=\"mdtraj\")\n",
274 | "\n",
275 | "# Write to file.\n",
276 | "traj.save(\"equilibrated.dcd\")\n",
277 | "\n",
278 | "# Bind to the output requirement.\n",
279 | "node.setOutput(\"trajectory\", \"equilibrated.dcd\")"
280 | ]
281 | },
282 | {
283 | "cell_type": "markdown",
284 | "metadata": {},
285 | "source": [
286 | "Validate the node."
287 | ]
288 | },
289 | {
290 | "cell_type": "code",
291 | "execution_count": null,
292 | "metadata": {},
293 | "outputs": [],
294 | "source": [
295 | "node.validate()"
296 | ]
297 | }
298 | ],
299 | "metadata": {
300 | "kernelspec": {
301 | "display_name": "Python 3",
302 | "language": "python",
303 | "name": "python3"
304 | },
305 | "language_info": {
306 | "codemirror_mode": {
307 | "name": "ipython",
308 | "version": 3
309 | },
310 | "file_extension": ".py",
311 | "mimetype": "text/x-python",
312 | "name": "python",
313 | "nbconvert_exporter": "python",
314 | "pygments_lexer": "ipython3"
315 | }
316 | },
317 | "nbformat": 4,
318 | "nbformat_minor": 2
319 | }
320 |
--------------------------------------------------------------------------------
/01_introduction/nodes/equilibrate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Author: Lester Hedges
5 | # Email: lester.hedges@bristol.ac.uk
6 | #
7 | # # Equilibration
8 | #
9 | # A node to perform equilibration of a molecular system. Saves the equilibration system to AMBER format files along with a DCD trajectory.
10 |
11 | # In[ ]:
12 |
13 |
14 | import BioSimSpace as BSS
15 |
16 |
17 | # In[ ]:
18 |
19 |
20 | node = BSS.Gateway.Node(
21 | "A node to perform equilibration. Saves the equlibrated molecular configuration and trajectory to file."
22 | )
23 | node.addAuthor(
24 | name="Lester Hedges",
25 | email="lester.hedges@bristol.ac.uk",
26 | affiliation="University of Bristol",
27 | )
28 | node.setLicense("GPLv3")
29 |
30 |
31 | # Set the input requirements:
32 |
33 | # In[ ]:
34 |
35 |
36 | node.addInput("files", BSS.Gateway.FileSet(help="A set of molecular input files."))
37 |
38 | node.addInput(
39 | "runtime",
40 | BSS.Gateway.Time(
41 | help="The run time.",
42 | unit="nanoseconds",
43 | minimum=0.02 * BSS.Units.Time.nanosecond,
44 | maximum=10 * BSS.Units.Time.nanosecond,
45 | default=0.02 * BSS.Units.Time.nanosecond,
46 | ),
47 | )
48 |
49 | node.addInput(
50 | "temperature_start",
51 | BSS.Gateway.Temperature(
52 | help="The initial temperature.",
53 | unit="kelvin",
54 | minimum=0 * BSS.Units.Temperature.kelvin,
55 | maximum=1000 * BSS.Units.Temperature.kelvin,
56 | default=0 * BSS.Units.Temperature.kelvin,
57 | ),
58 | )
59 |
60 | node.addInput(
61 | "temperature_end",
62 | BSS.Gateway.Temperature(
63 | help="The final temperature.",
64 | unit="kelvin",
65 | minimum=0 * BSS.Units.Temperature.kelvin,
66 | maximum=1000 * BSS.Units.Temperature.kelvin,
67 | default=300 * BSS.Units.Temperature.kelvin,
68 | ),
69 | )
70 |
71 | node.addInput(
72 | "restraint",
73 | BSS.Gateway.String(
74 | help="The type of restraint.",
75 | allowed=BSS.Protocol.Equilibration.restraints(),
76 | default="none",
77 | ),
78 | )
79 |
80 | node.addInput(
81 | "report_interval",
82 | BSS.Gateway.Integer(
83 | help="The number of integration steps between reporting output.",
84 | minimum=100,
85 | maximum=10000,
86 | default=100,
87 | ),
88 | )
89 |
90 | node.addInput(
91 | "restart_interval",
92 | BSS.Gateway.Integer(
93 | help="The number of integration steps between saving trajectory frames.",
94 | minimum=100,
95 | maximum=10000,
96 | default=500,
97 | ),
98 | )
99 |
100 | node.addInput(
101 | "engine",
102 | BSS.Gateway.String(
103 | help="The molecular dynamics engine.", allowed=BSS.MD.engines(), default="auto"
104 | ),
105 | )
106 |
107 |
108 | # We now need to define the output of the node. In this case we will return a set of files representing the equilibrated molecular system in AMBER format and a single file containing the trajectory frames.
109 |
110 | # In[ ]:
111 |
112 |
113 | node.addOutput(
114 | "equilibrated", BSS.Gateway.FileSet(help="The equilibrated molecular system.")
115 | )
116 | node.addOutput("trajectory", BSS.Gateway.File(help="The trajectory file."))
117 |
118 |
119 | # In[ ]:
120 |
121 |
122 | node.showControls()
123 |
124 |
125 | # Generate the molecular system.
126 |
127 | # In[ ]:
128 |
129 |
130 | system = BSS.IO.readMolecules(node.getInput("files"))
131 |
132 |
133 | # Set up the equilibration protocol.
134 | #
135 | # (Note that the keyword arguments happen to have the same name as the input requirements. This need not be the case.)
136 |
137 | # In[ ]:
138 |
139 |
140 | protocol = BSS.Protocol.Equilibration(
141 | runtime=node.getInput("runtime"),
142 | temperature_start=node.getInput("temperature_start"),
143 | temperature_end=node.getInput("temperature_end"),
144 | restraint=node.getInput("restraint"),
145 | )
146 |
147 |
148 | # Start the molecular dynamics process.
149 |
150 | # In[ ]:
151 |
152 |
153 | process = BSS.MD.run(system, protocol, engine=node.getInput("engine"))
154 |
155 |
156 | # We now wait for the process to finish, then check whether there were any errors before continuing. If errors were raised, then we raise an exception and print the last 10 lines of `stdout` and `stderr` to the user.
157 |
158 | # In[ ]:
159 |
160 |
161 | process.wait()
162 |
163 | if process.isError():
164 | print(process.stdout(10))
165 | print(process.stdout(10))
166 | raise RuntimeError("The process exited with an error!")
167 |
168 |
169 | # Get the equilibrated molecular system and write to file in the same format as the input, along with a PDB file to use as a topology file for the trajectory in [VMD](https://www.ks.uiuc.edu/Research/vmd/).
170 |
171 | # In[ ]:
172 |
173 |
174 | # Get the original file formats associated with the system.
175 | # This is returned as a comma-separated string.
176 | formats = system.fileFormat()
177 |
178 | # Append PDB format, so we can visualise the trajectory with VMD.
179 | formats += ",pdb"
180 |
181 | # Write to file and bind to the output.
182 | node.setOutput(
183 | "equilibrated", BSS.IO.saveMolecules("equilibrated", process.getSystem(), formats)
184 | )
185 |
186 |
187 | # Get the trajectory, convert to MDTraj format and save to a DCD file.
188 |
189 | # In[ ]:
190 |
191 |
192 | # Get the BioSimSpace trajectory wrapper.
193 | trajectory = process.getTrajectory()
194 |
195 | # Extract the MDTraj object.
196 | traj = trajectory.getTrajectory(format="mdtraj")
197 |
198 | # Write to file.
199 | traj.save("equilibrated.dcd")
200 |
201 | # Bind to the output requirement.
202 | node.setOutput("trajectory", "equilibrated.dcd")
203 |
204 |
205 | # Validate the node.
206 |
207 | # In[ ]:
208 |
209 |
210 | node.validate()
211 |
--------------------------------------------------------------------------------
/01_introduction/nodes/minimise.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Author: Lester Hedges
5 | # Email: lester.hedges@bristol.ac.uk
6 | #
7 | # # Minimisation
8 | #
9 | # In this notebook you'll learn how to use BioSimSpace to write a robust and interoperable workflow node to perform energy minimisation on a molecular system.
10 | #
11 | # First we'll need to import BioSimSpace:
12 |
13 | # In[ ]:
14 |
15 |
16 | import BioSimSpace as BSS
17 |
18 |
19 | # We begin by creating a `Node` object. This is the core of our molecular workflow component. It defines what it does, what input is needed, and the output that is produced.
20 |
21 | # In[ ]:
22 |
23 |
24 | node = BSS.Gateway.Node(
25 | "A node to perform energy minimisation and save the minimised molecular configuration to file."
26 | )
27 |
28 |
29 | # We'll now set the author and license:
30 |
31 | # In[ ]:
32 |
33 |
34 | node.addAuthor(
35 | name="Lester Hedges",
36 | email="lester.hedges@bristol.ac.uk",
37 | affiliation="University of Bristol",
38 | )
39 | node.setLicense("GPLv3")
40 |
41 |
42 | # Nodes require inputs. To specify inputs we use the `Gateway` module, which is used as a bridge between BioSimSpace and the outside world. This will allow us to document the inputs, define their type, and specify any constraints on their allowed values. Here we will need a set of files that define the molecular system, and an integer that indicates the number of minimisation steps to perform.
43 |
44 | # In[ ]:
45 |
46 |
47 | node.addInput("files", BSS.Gateway.FileSet(help="A set of molecular input files."))
48 |
49 | node.addInput(
50 | "steps",
51 | BSS.Gateway.Integer(
52 | help="The number of minimisation steps.",
53 | minimum=0,
54 | maximum=1000000,
55 | default=10000,
56 | ),
57 | )
58 |
59 | node.addInput(
60 | "engine",
61 | BSS.Gateway.String(
62 | help="The molecular dynamics engine.", allowed=BSS.MD.engines(), default="auto"
63 | ),
64 | )
65 |
66 |
67 | # Note that the input requirement `steps` has a default value, so it is optional.
68 | #
69 | # We now need to define the output of the node. In this case we will return a set of files representing the minimised molecular system.
70 |
71 | # In[ ]:
72 |
73 |
74 | node.addOutput("minimised", BSS.Gateway.FileSet(help="The minimised molecular system."))
75 |
76 |
77 | # When working interactively within a Jupyter notebook we need a way to allow users to set the input requirements. The `node.showControls` method will display a graphical user interface (GUI), from which inputs can be set.
78 | #
79 | # Note that the GUI requires active user input. All input requirements that don't have a default value _must_ be set before the node can proceed. If you try to query the node for one of the user values then an error will be raised. For bounded integer inputs you can use a slider to set the value, or type in the input box and press enter.
80 | #
81 | # When working interactively you will typically be running on a remote server where you won't have access to the local filesystem. In this case you'll need to upload files for any of the `File` or `FileSet` input requirements. The GUI below will provide buttons that allow you to browse your own filesystem and select files. Since Jupyter has a limit of 5MB for file transfers, we provide support for compressed formats, such as `.zip` or `.tar.gz`. (A single archive can contain a set of files, allowing you to set a single value for a `FileSet` requirement.) We've provided some example input files that can be used in the training notebooks, which are available to download from the links below. These can then be re-uploaded using the GUI.
82 | #
83 | # AMBER: [ala.crd](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/amber/ala/ala.crd), [ala.top](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/amber/ala/ala.top)
84 | #
85 | # GROMACS: [kigaki.gro](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/gromacs/kigaki/kigaki.gro), [kigaki.top](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/gromacs/kigaki/kigaki.top)
86 | #
87 | # NAMD: [alanin.pdb](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/namd/alanin/alanin.pdb), [alanin.psf](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/namd/alanin/alanin.psf), [alanin.params](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/namd/alanin/alanin.params)
88 | #
89 | #
90 | # When uploading files the name of the current file(s) will replace the `Upload` button. If you need to change the file, simply click on the button again and choose a new file.
91 |
92 | # In[ ]:
93 |
94 |
95 | node.showControls()
96 |
97 |
98 | # Once all requirements are set then we can acces the values using the `node.getInput` method. The first time this is called the `node` will automatically validate all of the input and report the user if any errors were found.
99 | #
100 | # We'll now create a molecular system using the input files uploaded by the user. Note that we don't specify the format of the files, since this is automatically determined by BioSimSpace. (BioSimSpace has support for a wide range of formats and can convert between certain formats too.)
101 |
102 | # In[ ]:
103 |
104 |
105 | system = BSS.IO.readMolecules(node.getInput("files"))
106 |
107 |
108 | # In order to run a minimisation we need to define a protocol. This can be done using the `BioSimSpace.Protocol` module. Here we will create a "best practice" minimisation protocol, overriding the number of steps with the input from the user.
109 |
110 | # In[ ]:
111 |
112 |
113 | protocol = BSS.Protocol.Minimisation(steps=node.getInput("steps"))
114 |
115 |
116 | # We now have everything that is required to run a minimisation. To do so, we use `BioSimSpace.MD` to find an appropriate molecular dynamics package on our current environment. What package is found will depend upon both the system and protocol, as well as the hardware that is available to the user. (For example, the user can choose to find packages with GPU support.)
117 |
118 | # In[ ]:
119 |
120 |
121 | process = BSS.MD.run(system, protocol, engine=node.getInput("engine"))
122 |
123 |
124 | # We now wait for the process to finish, then check whether there were any errors before continuing. If errors were raised, then we raise an exception and print the last 10 lines of `stdout` and `stderr` to the user.
125 |
126 | # In[ ]:
127 |
128 |
129 | process.wait()
130 |
131 | if process.isError():
132 | print(process.stdout(10))
133 | print(process.stdout(10))
134 | raise RuntimeError("The process exited with an error!")
135 |
136 |
137 | # When the process has finished running we can get the minimised molecular configuration. We will save this to file using the same format as the original system, and set the output requirement to the list of file names that were written.
138 |
139 | # In[ ]:
140 |
141 |
142 | node.setOutput(
143 | "minimised",
144 | BSS.IO.saveMolecules("minimised", process.getSystem(), system.fileFormat()),
145 | )
146 |
147 |
148 | # Finally, we validate that the node completed succesfully. This will check that all output requirements are satisfied. Any file outputs will be available for the user to download as a compressed archive.
149 | #
150 | # Note that the validation will fail until the cell above finishes running.
151 |
152 | # In[ ]:
153 |
154 |
155 | node.validate()
156 |
157 |
158 | # Once we are satisfied with our node we can choosed to download it as a regular Python script that can be run from the command-line.
159 | #
160 | # In JupyterHub, click on: `File/Download As/Python`\
161 | # In JupyterLab, click on: `File/Export Notebook As/Export Notebook to Executable Script`
162 |
--------------------------------------------------------------------------------
/01_introduction/nodes/parameterise.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "Author: Lester Hedges
\n",
8 | "Email: lester.hedges@bristol.ac.uk\n",
9 | "\n",
10 | "# Parameterisation\n",
11 | "\n",
12 | "A node to perform parameterisation of a molecule loaded from PDB file. Saves the parameterised molecule in to AMBER format files."
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "import BioSimSpace as BSS"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "node = BSS.Gateway.Node(\n",
31 | " \"A node to perform parameterisation of a molecule loaded from PDB file. Saves the parameterised molecule in to AMBER format files.\"\n",
32 | ")\n",
33 | "node.addAuthor(\n",
34 | " name=\"Lester Hedges\",\n",
35 | " email=\"lester.hedges@bristol.ac.uk\",\n",
36 | " affiliation=\"University of Bristol\",\n",
37 | ")\n",
38 | "node.setLicense(\"GPLv3\")"
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "metadata": {},
44 | "source": [
45 | "Set the input requirements:"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "metadata": {},
52 | "outputs": [],
53 | "source": [
54 | "node.addInput(\n",
55 | " \"pdb\",\n",
56 | " BSS.Gateway.File(\n",
57 | " help=\"A Protein Data Bank (PDB) file containing a single molecule.\"\n",
58 | " ),\n",
59 | ")\n",
60 | "\n",
61 | "node.addInput(\n",
62 | " \"forcefield\",\n",
63 | " BSS.Gateway.String(\n",
64 | " help=\"The force field to parameterise the molecule with.\",\n",
65 | " allowed=BSS.Parameters.forceFields(),\n",
66 | " ),\n",
67 | ")\n",
68 | "\n",
69 | "node.addInput(\n",
70 | " \"water_model\",\n",
71 | " BSS.Gateway.String(\n",
72 | " help=\"The water model to use for ion parameters.\",\n",
73 | " allowed=BSS.Solvent.waterModels(),\n",
74 | " default=\"tip3p\",\n",
75 | " ),\n",
76 | ")\n",
77 | "\n",
78 | "node.addInput(\n",
79 | " \"pdb4amber\",\n",
80 | " BSS.Gateway.Boolean(\n",
81 | " help=\"Whether to pre-process the PDB file using pdb4amber.\", default=False\n",
82 | " ),\n",
83 | ")"
84 | ]
85 | },
86 | {
87 | "cell_type": "markdown",
88 | "metadata": {},
89 | "source": [
90 | "We now need to define the output of the node. In this case we will return a set of files representing the parameterised molecule in AMBER format."
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": null,
96 | "metadata": {},
97 | "outputs": [],
98 | "source": [
99 | "node.addOutput(\"parameterised\", BSS.Gateway.FileSet(help=\"The parameterised molecule.\"))"
100 | ]
101 | },
102 | {
103 | "cell_type": "code",
104 | "execution_count": null,
105 | "metadata": {},
106 | "outputs": [],
107 | "source": [
108 | "node.showControls()"
109 | ]
110 | },
111 | {
112 | "cell_type": "markdown",
113 | "metadata": {},
114 | "source": [
115 | "Load the PDB file and pre-process with `pdb4amber` if requested. Since we assume a single molecule PDB, take the first molecule in the file."
116 | ]
117 | },
118 | {
119 | "cell_type": "code",
120 | "execution_count": null,
121 | "metadata": {},
122 | "outputs": [],
123 | "source": [
124 | "molecule = BSS.IO.readPDB(node.getInput(\"pdb\"), pdb4amber=node.getInput(\"pdb4amber\"))[0]"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "metadata": {},
130 | "source": [
131 | "Perform the parameterisation using the chosen force field and ion water model. Note that we call the generic `BSS.Parameters.parameterise` function so that we can pass the force field name as an argument."
132 | ]
133 | },
134 | {
135 | "cell_type": "code",
136 | "execution_count": null,
137 | "metadata": {},
138 | "outputs": [],
139 | "source": [
140 | "molecule = BSS.Parameters.parameterise(\n",
141 | " molecule, node.getInput(\"forcefield\"), water_model=node.getInput(\"water_model\")\n",
142 | ").getMolecule()"
143 | ]
144 | },
145 | {
146 | "cell_type": "markdown",
147 | "metadata": {},
148 | "source": [
149 | "Now save the parameterise molecule to AMBER format files."
150 | ]
151 | },
152 | {
153 | "cell_type": "code",
154 | "execution_count": null,
155 | "metadata": {},
156 | "outputs": [],
157 | "source": [
158 | "node.setOutput(\n",
159 | " \"parameterised\", BSS.IO.saveMolecules(\"parameterised\", molecule, [\"prm7\", \"rst7\"])\n",
160 | ")"
161 | ]
162 | },
163 | {
164 | "cell_type": "markdown",
165 | "metadata": {},
166 | "source": [
167 | "Validate the node."
168 | ]
169 | },
170 | {
171 | "cell_type": "code",
172 | "execution_count": null,
173 | "metadata": {},
174 | "outputs": [],
175 | "source": [
176 | "node.validate()"
177 | ]
178 | }
179 | ],
180 | "metadata": {
181 | "kernelspec": {
182 | "display_name": "Python 3",
183 | "language": "python",
184 | "name": "python3"
185 | },
186 | "language_info": {
187 | "codemirror_mode": {
188 | "name": "ipython",
189 | "version": 3
190 | },
191 | "file_extension": ".py",
192 | "mimetype": "text/x-python",
193 | "name": "python",
194 | "nbconvert_exporter": "python",
195 | "pygments_lexer": "ipython3"
196 | }
197 | },
198 | "nbformat": 4,
199 | "nbformat_minor": 2
200 | }
201 |
--------------------------------------------------------------------------------
/01_introduction/nodes/parameterise.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Author: Lester Hedges
5 | # Email: lester.hedges@bristol.ac.uk
6 | #
7 | # # Parameterisation
8 | #
9 | # A node to perform parameterisation of a molecule loaded from PDB file. Saves the parameterised molecule in to AMBER format files.
10 |
11 | # In[ ]:
12 |
13 |
14 | import BioSimSpace as BSS
15 |
16 |
17 | # In[ ]:
18 |
19 |
20 | node = BSS.Gateway.Node(
21 | "A node to perform parameterisation of a molecule loaded from PDB file. Saves the parameterised molecule in to AMBER format files."
22 | )
23 | node.addAuthor(
24 | name="Lester Hedges",
25 | email="lester.hedges@bristol.ac.uk",
26 | affiliation="University of Bristol",
27 | )
28 | node.setLicense("GPLv3")
29 |
30 |
31 | # Set the input requirements:
32 |
33 | # In[ ]:
34 |
35 |
36 | node.addInput(
37 | "pdb",
38 | BSS.Gateway.File(
39 | help="A Protein Data Bank (PDB) file containing a single molecule."
40 | ),
41 | )
42 |
43 | node.addInput(
44 | "forcefield",
45 | BSS.Gateway.String(
46 | help="The force field to parameterise the molecule with.",
47 | allowed=BSS.Parameters.forceFields(),
48 | ),
49 | )
50 |
51 | node.addInput(
52 | "water_model",
53 | BSS.Gateway.String(
54 | help="The water model to use for ion parameters.",
55 | allowed=BSS.Solvent.waterModels(),
56 | default="tip3p",
57 | ),
58 | )
59 |
60 | node.addInput(
61 | "pdb4amber",
62 | BSS.Gateway.Boolean(
63 | help="Whether to pre-process the PDB file using pdb4amber.", default=False
64 | ),
65 | )
66 |
67 |
68 | # We now need to define the output of the node. In this case we will return a set of files representing the parameterised molecule in AMBER format.
69 |
70 | # In[ ]:
71 |
72 |
73 | node.addOutput("parameterised", BSS.Gateway.FileSet(help="The parameterised molecule."))
74 |
75 |
76 | # In[ ]:
77 |
78 |
79 | node.showControls()
80 |
81 |
82 | # Load the PDB file and pre-process with `pdb4amber` if requested. Since we assume a single molecule PDB, take the first molecule in the file.
83 |
84 | # In[ ]:
85 |
86 |
87 | molecule = BSS.IO.readPDB(node.getInput("pdb"), pdb4amber=node.getInput("pdb4amber"))[0]
88 |
89 |
90 | # Perform the parameterisation using the chosen force field and ion water model. Note that we call the generic `BSS.Parameters.parameterise` function so that we can pass the force field name as an argument.
91 |
92 | # In[ ]:
93 |
94 |
95 | molecule = BSS.Parameters.parameterise(
96 | molecule, node.getInput("forcefield"), water_model=node.getInput("water_model")
97 | ).getMolecule()
98 |
99 |
100 | # Now save the parameterise molecule to AMBER format files.
101 |
102 | # In[ ]:
103 |
104 |
105 | node.setOutput(
106 | "parameterised", BSS.IO.saveMolecules("parameterised", molecule, ["prm7", "rst7"])
107 | )
108 |
109 |
110 | # Validate the node.
111 |
112 | # In[ ]:
113 |
114 |
115 | node.validate()
116 |
--------------------------------------------------------------------------------
/01_introduction/nodes/solvate.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "Author: Lester Hedges
\n",
8 | "Email: lester.hedges@bristol.ac.uk\n",
9 | "\n",
10 | "# Solvation\n",
11 | "\n",
12 | "A node to solvate a parameterised molecular system. An appropriate box size will be determine from the axis-aligned bounding box of the molecules in the system"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "import BioSimSpace as BSS"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "node = BSS.Gateway.Node(\n",
31 | " \"A node to solvate a parameterised molecular system. An appropriate box size will be determine from the axis-aligned bounding box of the molecules in the system.\"\n",
32 | ")\n",
33 | "node.addAuthor(\n",
34 | " name=\"Lester Hedges\",\n",
35 | " email=\"lester.hedges@bristol.ac.uk\",\n",
36 | " affiliation=\"University of Bristol\",\n",
37 | ")\n",
38 | "node.setLicense(\"GPLv3\")"
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "metadata": {},
44 | "source": [
45 | "Set the input requirements:"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "metadata": {},
52 | "outputs": [],
53 | "source": [
54 | "node.addInput(\"files\", BSS.Gateway.FileSet(help=\"A set of molecular input files.\"))\n",
55 | "\n",
56 | "node.addInput(\n",
57 | " \"water_model\",\n",
58 | " BSS.Gateway.String(\n",
59 | " help=\"The water model.\", allowed=BSS.Solvent.waterModels(), default=\"tip3p\"\n",
60 | " ),\n",
61 | ")\n",
62 | "\n",
63 | "node.addInput(\n",
64 | " \"box\",\n",
65 | " BSS.Gateway.String(\n",
66 | " help=\"The type of simulation box.\", allowed=BSS.Box.boxTypes(), default=\"cubic\"\n",
67 | " ),\n",
68 | ")\n",
69 | "\n",
70 | "node.addInput(\n",
71 | " \"neutralise\",\n",
72 | " BSS.Gateway.Boolean(\n",
73 | " help=\"Whether to neutralise the system. Ions will be added on top of any specified with 'ion_conc'\",\n",
74 | " default=True,\n",
75 | " ),\n",
76 | ")\n",
77 | "\n",
78 | "node.addInput(\n",
79 | " \"ion_conc\",\n",
80 | " BSS.Gateway.Float(help=\"The ion concentration in mol per litre\", default=0),\n",
81 | ")"
82 | ]
83 | },
84 | {
85 | "cell_type": "markdown",
86 | "metadata": {},
87 | "source": [
88 | "We now need to define the output of the node. In this case we will return a set of files representing the parameterised molecule in the file format(s) of the original system."
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": null,
94 | "metadata": {},
95 | "outputs": [],
96 | "source": [
97 | "node.addOutput(\"solvated\", BSS.Gateway.FileSet(help=\"The solvated system.\"))"
98 | ]
99 | },
100 | {
101 | "cell_type": "code",
102 | "execution_count": null,
103 | "metadata": {},
104 | "outputs": [],
105 | "source": [
106 | "node.showControls()"
107 | ]
108 | },
109 | {
110 | "cell_type": "markdown",
111 | "metadata": {},
112 | "source": [
113 | "Load the molecular system."
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": null,
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "system = BSS.IO.readMolecules(node.getInput(\"files\"))"
123 | ]
124 | },
125 | {
126 | "cell_type": "markdown",
127 | "metadata": {},
128 | "source": [
129 | "Now work out the minimum box size for the molecules in the system."
130 | ]
131 | },
132 | {
133 | "cell_type": "code",
134 | "execution_count": null,
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "# Get the minimium and maximum coordinates of the bounding box that\n",
139 | "# minimally encloses the protein.\n",
140 | "box_min, box_max = system.getAxisAlignedBoundingBox()\n",
141 | "\n",
142 | "# Work out the box size from the difference in the coordinates.\n",
143 | "box_size = [y - x for x, y in zip(box_min, box_max)]\n",
144 | "\n",
145 | "# How much to pad each side of the molecule? (Nonbonded cutoff = 10 A)\n",
146 | "padding = 15 * BSS.Units.Length.angstrom\n",
147 | "\n",
148 | "# Work out an appropriate box. This will used in each dimension to ensure\n",
149 | "# that the cutoff constraints are satisfied if the molecule rotates.\n",
150 | "box_length = max(box_size) + 2 * padding"
151 | ]
152 | },
153 | {
154 | "cell_type": "markdown",
155 | "metadata": {},
156 | "source": [
157 | "Get the box parameters for the chosen box type."
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": null,
163 | "metadata": {},
164 | "outputs": [],
165 | "source": [
166 | "box, angles = BSS.Box.generateBoxParameters(node.getInput(\"box\"), box_length)"
167 | ]
168 | },
169 | {
170 | "cell_type": "markdown",
171 | "metadata": {},
172 | "source": [
173 | "Now solvate with the chosen water model and ion concentration."
174 | ]
175 | },
176 | {
177 | "cell_type": "code",
178 | "execution_count": null,
179 | "metadata": {},
180 | "outputs": [],
181 | "source": [
182 | "solvated = BSS.Solvent.solvate(\n",
183 | " node.getInput(\"water_model\"),\n",
184 | " molecule=system,\n",
185 | " box=box,\n",
186 | " angles=angles,\n",
187 | " is_neutral=node.getInput(\"neutralise\"),\n",
188 | " ion_conc=node.getInput(\"ion_conc\"),\n",
189 | ")"
190 | ]
191 | },
192 | {
193 | "cell_type": "markdown",
194 | "metadata": {},
195 | "source": [
196 | "Write the solvated system to file in the same format as the original system."
197 | ]
198 | },
199 | {
200 | "cell_type": "code",
201 | "execution_count": null,
202 | "metadata": {},
203 | "outputs": [],
204 | "source": [
205 | "node.setOutput(\n",
206 | " \"solvated\", BSS.IO.saveMolecules(\"solvated\", solvated, system.fileFormat())\n",
207 | ")"
208 | ]
209 | },
210 | {
211 | "cell_type": "markdown",
212 | "metadata": {},
213 | "source": [
214 | "Validate the node."
215 | ]
216 | },
217 | {
218 | "cell_type": "code",
219 | "execution_count": null,
220 | "metadata": {},
221 | "outputs": [],
222 | "source": [
223 | "node.validate()"
224 | ]
225 | }
226 | ],
227 | "metadata": {
228 | "kernelspec": {
229 | "display_name": "Python 3",
230 | "language": "python",
231 | "name": "python3"
232 | },
233 | "language_info": {
234 | "codemirror_mode": {
235 | "name": "ipython",
236 | "version": 3
237 | },
238 | "file_extension": ".py",
239 | "mimetype": "text/x-python",
240 | "name": "python",
241 | "nbconvert_exporter": "python",
242 | "pygments_lexer": "ipython3"
243 | }
244 | },
245 | "nbformat": 4,
246 | "nbformat_minor": 2
247 | }
248 |
--------------------------------------------------------------------------------
/01_introduction/nodes/solvate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Author: Lester Hedges
5 | # Email: lester.hedges@bristol.ac.uk
6 | #
7 | # # Solvation
8 | #
9 | # A node to solvate a parameterised molecular system. An appropriate box size will be determine from the axis-aligned bounding box of the molecules in the system
10 |
11 | # In[ ]:
12 |
13 |
14 | import BioSimSpace as BSS
15 |
16 |
17 | # In[ ]:
18 |
19 |
20 | node = BSS.Gateway.Node(
21 | "A node to solvate a parameterised molecular system. An appropriate box size will be determine from the axis-aligned bounding box of the molecules in the system."
22 | )
23 | node.addAuthor(
24 | name="Lester Hedges",
25 | email="lester.hedges@bristol.ac.uk",
26 | affiliation="University of Bristol",
27 | )
28 | node.setLicense("GPLv3")
29 |
30 |
31 | # Set the input requirements:
32 |
33 | # In[ ]:
34 |
35 |
36 | node.addInput("files", BSS.Gateway.FileSet(help="A set of molecular input files."))
37 |
38 | node.addInput(
39 | "water_model",
40 | BSS.Gateway.String(
41 | help="The water model.", allowed=BSS.Solvent.waterModels(), default="tip3p"
42 | ),
43 | )
44 |
45 | node.addInput(
46 | "box",
47 | BSS.Gateway.String(
48 | help="The type of simulation box.", allowed=BSS.Box.boxTypes(), default="cubic"
49 | ),
50 | )
51 |
52 | node.addInput(
53 | "neutralise",
54 | BSS.Gateway.Boolean(
55 | help="Whether to neutralise the system. Ions will be added on top of any specified with 'ion_conc'",
56 | default=True,
57 | ),
58 | )
59 |
60 | node.addInput(
61 | "ion_conc",
62 | BSS.Gateway.Float(help="The ion concentration in mol per litre", default=0),
63 | )
64 |
65 |
66 | # We now need to define the output of the node. In this case we will return a set of files representing the parameterised molecule in the file format(s) of the original system.
67 |
68 | # In[ ]:
69 |
70 |
71 | node.addOutput("solvated", BSS.Gateway.FileSet(help="The solvated system."))
72 |
73 |
74 | # In[ ]:
75 |
76 |
77 | node.showControls()
78 |
79 |
80 | # Load the molecular system.
81 |
82 | # In[ ]:
83 |
84 |
85 | system = BSS.IO.readMolecules(node.getInput("files"))
86 |
87 |
88 | # Now work out the minimum box size for the molecules in the system.
89 |
90 | # In[ ]:
91 |
92 |
93 | # Get the minimium and maximum coordinates of the bounding box that
94 | # minimally encloses the protein.
95 | box_min, box_max = system.getAxisAlignedBoundingBox()
96 |
97 | # Work out the box size from the difference in the coordinates.
98 | box_size = [y - x for x, y in zip(box_min, box_max)]
99 |
100 | # How much to pad each side of the molecule? (Nonbonded cutoff = 10 A)
101 | padding = 15 * BSS.Units.Length.angstrom
102 |
103 | # Work out an appropriate box. This will used in each dimension to ensure
104 | # that the cutoff constraints are satisfied if the molecule rotates.
105 | box_length = max(box_size) + 2 * padding
106 |
107 |
108 | # Get the box parameters for the chosen box type.
109 |
110 | # In[ ]:
111 |
112 |
113 | box, angles = BSS.Box.generateBoxParameters(node.getInput("box"), box_length)
114 |
115 |
116 | # Now solvate with the chosen water model and ion concentration.
117 |
118 | # In[ ]:
119 |
120 |
121 | solvated = BSS.Solvent.solvate(
122 | node.getInput("water_model"),
123 | molecule=system,
124 | box=box,
125 | angles=angles,
126 | is_neutral=node.getInput("neutralise"),
127 | ion_conc=node.getInput("ion_conc"),
128 | )
129 |
130 |
131 | # Write the solvated system to file in the same format as the original system.
132 |
133 | # In[ ]:
134 |
135 |
136 | node.setOutput(
137 | "solvated", BSS.IO.saveMolecules("solvated", solvated, system.fileFormat())
138 | )
139 |
140 |
141 | # Validate the node.
142 |
143 | # In[ ]:
144 |
145 |
146 | node.validate()
147 |
--------------------------------------------------------------------------------
/01_introduction/scripts/workflow.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Exit immediately on error.
4 | set -e
5 |
6 | # Remove existing output.
7 | rm -f parameterised.*
8 | rm -f solvated.*
9 | rm -f minimised.*
10 |
11 | echo "Parameterising..."
12 | python nodes/parameterise.py --pdb inputs/methanol.pdb --forcefield gaff
13 |
14 | echo "Solvating..."
15 | python nodes/solvate.py --files parameterised.* --water_model tip3p
16 |
17 | echo "Minimising..."
18 | python nodes/minimise.py --files solvated.* --steps 1000
19 |
20 | echo "Equilibrating..."
21 | python nodes/equilibrate.py --files minimised.* --restraint heavy
22 |
23 | echo "Done!"
24 |
--------------------------------------------------------------------------------
/02_funnel_metad/01_bss-fun-metad-setup.md:
--------------------------------------------------------------------------------
1 | # Part 2 - Setting up the system, visualising the funnel and preparing the fun-metaD simulations
2 |
3 | In this part of the tutorial I will show you how to set up the BioSimSpace system, parameterising the protein and the ligand, as well as defining the simulation box, adding water and ions. Then, we will define a the funnel and visualise it using NGLview. Finally, we will setup the directories for the fun-metaD simulation itself.
4 |
5 | ## Part 2.1 - setup
6 |
7 | First, let's download inputs for this tutorial
8 |
9 |
10 | ```python
11 | from get_tutorial import download
12 | download("01")
13 | ```
14 |
15 | Now import BioSimSpace
16 |
17 |
18 | ```python
19 | import BioSimSpace as BSS
20 | import os
21 | ```
22 |
23 | In order to parameterise the protein, you just need a tleap-ready protein PDB file. We will use Amber ff14sb forcefield.
24 |
25 |
26 | ```python
27 | protein = BSS.IO.readMolecules("inputs_01/protein.pdb")
28 | protein_water = protein.getWaterMolecules()
29 |
30 | protein_search = protein.search("not water")
31 | protein = protein_search.molecules().getResult(0)
32 |
33 | protein = BSS.Parameters.ff14SB(protein, water_model="tip3p").getMolecule()
34 | ```
35 |
36 | Parameterise the ligand using GAFF2.
37 |
38 |
39 | ```python
40 | # parameterise the ligand
41 | ligand = BSS.IO.readMolecules("inputs_01/ligand.mol2").getMolecule(0)
42 | ligand = BSS.Parameters.gaff2(
43 | ligand, net_charge=BSS.Parameters.formalCharge(ligand)
44 | ).getMolecule()
45 | ```
46 |
47 | Now we solvate the protein-ligand system using TIP3P water.
48 |
49 |
50 | ```python
51 | # Find the dimensions of the protein
52 | box_min, box_max = protein.getAxisAlignedBoundingBox()
53 |
54 | # Work out the box size from the difference in the coordinates.
55 | box_size = [y - x for x, y in zip(box_min, box_max)]
56 |
57 | # how much to pad each side of the protein (nonbonded cutoff = 10 A)
58 | padding = 15 * BSS.Units.Length.angstrom
59 |
60 | box_length = max(box_size) + 2 * padding
61 |
62 | cmplx = protein + ligand
63 |
64 | solvated = BSS.Solvent.tip3p(molecule=cmplx.toSystem(), box=3 * [box_length])
65 | ```
66 |
67 | Save the prepared structures.
68 |
69 |
70 | ```python
71 | BSS.IO.saveMolecules("solvated", solvated, ["PDB", "RST7", "PRM7"])
72 | ```
73 |
74 | ## Part 2.2 - visualisation
75 | Before we run any dynamics, let's visualise the funnel defined automatically by BSS.
76 |
77 |
78 | ```python
79 | # Load the system.
80 | system = BSS.IO.readMolecules(
81 | ["solvated.rst7", "solvated.prm7"]
82 | )
83 |
84 | # Create the funnel parameters.
85 | p0, p1 = BSS.Metadynamics.CollectiveVariable.makeFunnel(system)
86 |
87 | # Define the collective variable.
88 | funnel_cv = BSS.Metadynamics.CollectiveVariable.Funnel(p0, p1)
89 |
90 | # Create a view.
91 | view = BSS.Metadynamics.CollectiveVariable.viewFunnel(system, funnel_cv)
92 |
93 | view.molecules([1, 0, -1])
94 | ```
95 |
96 | Lets check what the funnel looks with a larger radius.
97 |
98 |
99 | ```python
100 | # Load the system.
101 | system = BSS.IO.readMolecules(
102 | ["solvated.rst7", "solvated.prm7"]
103 | )
104 |
105 | # Create the funnel parameters.
106 | p0, p1 = BSS.Metadynamics.CollectiveVariable.makeFunnel(system)
107 |
108 | # Define the collective variable.
109 | funnel_cv = BSS.Metadynamics.CollectiveVariable.Funnel(
110 | p0, p1, width=1.5 * BSS.Units.Length.nanometer
111 | )
112 |
113 | # Create a view.
114 | view = BSS.Metadynamics.CollectiveVariable.viewFunnel(system, funnel_cv)
115 |
116 | view.molecules([1, 0, -1])
117 | ```
118 |
119 | In case the funnel was assigned incorrectly, you can open the structure file and use lists of atom IDs to define the p0 and p1 points.
120 |
121 | Here I will load the same structure file with the p0 and p1 points defined by me.
122 |
123 |
124 | ```python
125 | # Load the system.
126 | system = BSS.IO.readMolecules(["solvated.pdb"])
127 |
128 | # Create the funnel parameters.
129 | p0, p1 = [2624], [584, 1307, 1474, 1887]
130 |
131 | # Define the collective variable.
132 | funnel_cv = BSS.Metadynamics.CollectiveVariable.Funnel(p0, p1)
133 |
134 | # Create a view.
135 | view = BSS.Metadynamics.CollectiveVariable.viewFunnel(system, funnel_cv)
136 |
137 | view.molecules([1, 0, -1])
138 | ```
139 |
140 | # Part 2.3 - Equilibrate the system with OpenMM
141 | Load the prepared system first.
142 |
143 |
144 | ```python
145 | solvated = BSS.IO.readMolecules(
146 | ["solvated.rst7", "solvated.prm7"]
147 | )
148 | ```
149 |
150 | The process below will take ~ 5 minutes if run on openbiosim's default Jupyter server. Running the notebook on a GPU powered computer should accelerate the calculations significantly
151 |
152 |
153 | ```python
154 | protocol = BSS.Protocol.Minimisation()
155 | process = BSS.Process.OpenMM(solvated, protocol)
156 | # Start the process in the background.
157 | process.start()
158 |
159 | # Wait for the process to finish.
160 | process.wait()
161 | ```
162 |
163 | Get the minimised system and save the structure.
164 |
165 |
166 | ```python
167 | minimised = process.getSystem()
168 | # BSS.IO.saveMolecules('minimised',minimised, ['PDB','RST7','PRM7']) # if you want to save the output
169 | ```
170 |
171 | Equilibrate (note the very short run time). This will take ca. 3 min on openbiosim's Jupyter server.
172 |
173 |
174 | ```python
175 | protocol = BSS.Protocol.Equilibration(runtime=0.001 * BSS.Units.Time.nanosecond)
176 | process = BSS.Process.OpenMM(minimised, protocol)
177 |
178 | # Start the process in the background.
179 | process.start()
180 |
181 | # Wait for the process to finish.
182 | process.wait()
183 | ```
184 |
185 | Get the equilibrated system and save the structure
186 |
187 |
188 | ```python
189 | equilibrated = process.getSystem()
190 |
191 | # BSS.IO.saveMolecules('equilibrated',equilibrated, ['PDB','RST7','PRM7']) # if you want to save the output
192 | ```
193 |
194 | ## Part 2.4 - Setup and run fun-metaD
195 |
196 | As I showed in the visualisation part, select the p0, p1 points for the funnel definition.
197 |
198 |
199 | ```python
200 | p0, p1 = BSS.Metadynamics.CollectiveVariable.makeFunnel(equilibrated)
201 | ```
202 |
203 | Write the funnel CV for the simulation. Additionally, we will set a lower upper_bound value.
204 |
205 |
206 | ```python
207 | new_upper_bound = BSS.Metadynamics.Bound(value=3.5 * BSS.Units.Length.nanometer)
208 |
209 | funnel_cv = BSS.Metadynamics.CollectiveVariable.Funnel(
210 | p0, p1, upper_bound=new_upper_bound
211 | )
212 | ```
213 |
214 | Write the metadynamics protocol.
215 |
216 |
217 | ```python
218 | protocol = BSS.Protocol.Metadynamics(
219 | funnel_cv,
220 | runtime=0.01 * BSS.Units.Time.nanosecond,
221 | hill_height=1.5 * BSS.Units.Energy.kj_per_mol,
222 | restart_interval=1000,
223 | bias_factor=10,
224 | )
225 | ```
226 |
227 | Run the metadynamics simulation.
228 |
229 |
230 | ```python
231 | process = BSS.Process.OpenMM(
232 | equilibrated, protocol, work_dir="fun-metaD-work_dir"
233 | )
234 |
235 | process.start()
236 |
237 | process.wait()
238 | ```
239 |
240 | Save the final structures.
241 |
242 |
243 | ```python
244 | finished = process.getSystem()
245 |
246 | # BSS.IO.saveMolecules('final', finished, ['PDB','RST7','PRM7']) # if you want to save the output
247 | ```
248 |
249 | ## Closing thoughts
250 |
251 | The purpose of this notebook has been to demonstrate the logic behind using BioSimSpace to setup and run your funnel metadynamics simulations, as well as visualising the suggested funnel restraints.
252 |
253 | In a real use case, you wouldn't do all of these steps in an interactive notebook. The funnel metadynamics simulations usually require 500-2000 ns of simulation time, which takes between 3 to 14 days for moderately sized systems. You want to submit a script that does the setting up, equilibration and production simulations all in one go. With Lester's help, I've prepared a set of nodes and a LSF submit script for doing all of the steps described above. Check out the `example_node` directory.
254 |
--------------------------------------------------------------------------------
/02_funnel_metad/02_bss-fun-metad-analysis.md:
--------------------------------------------------------------------------------
1 | # Part 2 - Analysis
2 |
3 | After running the simulations, let's see what information we can get out of it. The primary thing we are interested is the absolute binding free energy (ABFE) of ligand binding. What funnel metadynamics (fun-metaD) fundamentally does is deposit bias along a set of reaction coordinates (aka collective variables or CVs), forcing the ligand to unbind, explore the solvent phase, and eventually allow it to rebind back.
4 |
5 | Since we can track the amount of bias deposited along these CVs, we can construct a free energy surface by inverting the accumulated bias and projecting it along the CVs. To recover the ABFE, we can look at the projection CV and find the difference in free energy between the bound phase and the unbound/solvent phase.
6 |
7 | It is important to remember that we aren't truly samply the unbound phase, instead we limit the exploration of the ligand to a small volume at the 'tip' of the funnel. Correction for the loss of rotational and translational freedom of the ligand in the unbound phase due to the funnel restraints are
8 | calculated as described in [Rhys *et al* 2020](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7467642/).
9 |
10 | To determine whether the simulation has converged or not, we will have a look at the time evolution of the CVs and the free energy estimate.
11 |
12 | Let's import the inputs for this tutorial
13 |
14 |
15 | ```python
16 | from get_tutorial import download
17 | download("02")
18 | ```
19 |
20 | Now we import the relevant libraries.
21 |
22 |
23 | ```python
24 | import BioSimSpace as BSS
25 | import numpy as np
26 | import matplotlib.pyplot as plt
27 | ```
28 |
29 | We will need the CV definitions that we used in running the fun-metaD simulation to reconstruct the free energy surface using PLUMED. We begin with loading the system.
30 |
31 |
32 | ```python
33 | system = BSS.IO.readMolecules("inputs_02/0_run/fun-metaD/openmm.*7")
34 | ```
35 |
36 | Now we redefine p0 and p1 points.
37 |
38 |
39 | ```python
40 | p0, p1 = BSS.Metadynamics.CollectiveVariable.makeFunnel(system)
41 | ```
42 |
43 | Write the CV definitions as you put them into the BSS script.
44 |
45 |
46 | ```python
47 | new_upper_bound = BSS.Metadynamics.Bound(3.5 * BSS.Units.Length.nanometer)
48 | cv = BSS.Metadynamics.CollectiveVariable.Funnel(p0, p1, upper_bound=new_upper_bound)
49 | ```
50 |
51 | Use the CV definitions to reconstruct the protocol.
52 |
53 |
54 | ```python
55 | protocol = BSS.Protocol.Metadynamics(cv)
56 | ```
57 |
58 | Create a PLUMED process that will run the analysis
59 |
60 |
61 | ```python
62 | plumed = BSS.Process.Plumed(work_dir="inputs_02/0_run/fun-metaD/")
63 | config = plumed.createConfig(system, protocol)
64 | ```
65 |
66 | ### Part 3.1 - CVs vs time
67 |
68 | Now we can plot the CVs across time. The first column, index 0, is the projection CV and the second column, index 1, is the extent CV.
69 |
70 | We can see that the extent CV is more or less diffusive - the ligand is rapidly exploring across the entire range of allowed CV values.
71 |
72 | The projection variable is much less diffusive, where the ligand is freely exploring the unbound phase, from about 2.0 nm to 3.5 nm, but is far slower in the bound phase. This is understandable, the ligand interacts with protein and is slowed down in its exploration.
73 |
74 | One of the most important things in determining whether the fun-metaD simulation has converged is the return of the ligand from the solvent back into the binding site. Looking at the projection CV, we see the ligand at start is around 1.0-1.2 nm from the origin. It unbinds and returns to that value multiple times, especially clearly at around 35-40 ns. At least one rebinding event is needed to construct an accurate FES.
75 |
76 |
77 | ```python
78 | BSS.Notebook.plot(
79 | x=plumed.getTime(time_series=True),
80 | y=plumed.getCollectiveVariable(0, time_series=True),
81 | )
82 | BSS.Notebook.plot(
83 | x=plumed.getTime(time_series=True),
84 | y=plumed.getCollectiveVariable(1, time_series=True),
85 | )
86 | ```
87 |
88 | ### Part 3.2 - 2D free energy surface
89 |
90 | Now let's have a look at the free energy surfaces. Here we plot the 2D surface.
91 |
92 | The 0th index in the `free_nrg` matrix contains the projection values, 1st index - the extent values and the 2nd index has the free energy values, in kJ/mol.
93 |
94 |
95 | ```python
96 | free_nrg = plumed.getFreeEnergy()
97 | BSS.Notebook.plotContour(
98 | free_nrg[0], free_nrg[1], free_nrg[2], xlabel="proj (nm)", ylabel="ext (nm)"
99 | )
100 | ```
101 |
102 | ### Part 3.3 - 1D FES
103 |
104 | We can integrate the FE values for each CV. The smoothness of the FES in the solvent phase (last 0.5 nm of projection CV) is another indicator of convergence.
105 |
106 | Note - the extent CV shouldn't have any free energy below 0.0 nm, but because the bias potential is Gaussian-shaped, the Gaussian 'tails' overspill to non-real values.
107 |
108 |
109 | ```python
110 | # the projection CV vs FE
111 | free_nrg0 = plumed.getFreeEnergy(0, kt=2.49 * BSS.Units.Energy.kt)
112 | BSS.Notebook.plot(free_nrg0[0], free_nrg0[1])
113 |
114 | # the extent CV vs FE
115 | free_nrg1 = plumed.getFreeEnergy(1, kt=2.49 * BSS.Units.Energy.kt)
116 | BSS.Notebook.plot(free_nrg1[0], free_nrg1[1])
117 | ```
118 |
119 | ### Part 3.4 - Absolute binding free energy
120 |
121 | As I mentioned in the beginning of this tutorial, to recover the ABFE, we need to find the difference in free energy between the bound and the unbound/solvent phases. We will use a 1D FES along the projection CV and will add the funnel correction.
122 |
123 |
124 | ```python
125 | funnel_correction = cv.getCorrection().kcal_per_mol()
126 |
127 | # the last 0.5 nm of the projection CV are between indices -75 to -25
128 | free_nrg_floats = [i.kj_per_mol().value() for i in free_nrg0[1][-75:-25]]
129 |
130 | estimate = -(np.mean(free_nrg_floats) / 4.184 + funnel_correction.value())
131 |
132 | print(f"the funnel correction = {funnel_correction}")
133 | print(f"the ABFE estimate = {estimate:.2f} kcal/mol")
134 | ```
135 |
136 | ### Part 3.5 - $\Delta$G vs time
137 |
138 | Finally, we need to look at the time evolution of the free energy estimate. We can get the 1D FES every 1 ns, and since we recorded the CV every 2 ps, we select a 500 step stride.
139 |
140 |
141 | ```python
142 | free_nrg_series = plumed.getFreeEnergy(0, stride=500)
143 | ```
144 |
145 | Now let's find the estimate at every 1 ns of the simulation.
146 |
147 |
148 | ```python
149 | funnel_correction = cv.getCorrection().kcal_per_mol()
150 |
151 | estimates_list = []
152 | for item in range(np.shape(free_nrg_series)[0]):
153 | free_nrg_floats = [i.value() for i in free_nrg_series[item][1][-75:-25]]
154 | estimate = -(np.mean(free_nrg_floats) / 4.184 + funnel_correction.value())
155 | estimates_list.append(estimate)
156 | ```
157 |
158 | One important aspect of fun-metaD, and all metadynamics simulations, is achieving convergence. If the fun-metaD simulation has fully converged i.e. the free energy surface has been fully explored, then the ABFE estimate should plateau at a certain value.
159 |
160 |
161 | ```python
162 | BSS.Notebook.plot(estimates_list)
163 | ```
164 |
165 | ### Part 3.6 - Multiple repeats
166 |
167 | We can see from the plot above that the free energy estimate still fluctuates quite a lot. The estimate never completely plateaus, just oscillates within a 1 kcal/mol range.
168 |
169 | In order to get a good error estimate, you should do multiple independent simulations. I've provided the data for 5 repeats. Let's plot them all together, while also estimating the mean of the 5 repeats at each timepoint.
170 |
171 |
172 | ```python
173 | funnel_correction = cv.getCorrection().kcal_per_mol()
174 |
175 | plt.title("$\Delta$G vs time")
176 | plt.xlabel("time(ns)")
177 | plt.ylabel("$\Delta$G (kcal/mol)")
178 | all_estimates = []
179 |
180 | for run_index in range(0, 5):
181 | plumed = BSS.Process.Plumed(work_dir=f"inputs_02/{run_index}_run/fun-metaD/")
182 | config = plumed.createConfig(system, protocol)
183 |
184 | free_nrg_series = plumed.getFreeEnergy(0, stride=500)
185 |
186 | estimates_list = []
187 | for item in range(np.shape(free_nrg_series)[0]):
188 | free_nrg_floats = [i.value() for i in free_nrg_series[item][1][-75:-25]]
189 | estimate = -(np.mean(free_nrg_floats) / 4.184 + funnel_correction.value())
190 | estimates_list.append(estimate)
191 |
192 | plt.plot(estimates_list)
193 | all_estimates.append(estimates_list)
194 |
195 | all_estimates = np.array(all_estimates)
196 | mean_estimates = []
197 | for i in range(len(all_estimates[0])):
198 | mean_estimate = np.mean(all_estimates[:, i])
199 | mean_estimates.append(mean_estimate)
200 | plt.plot(mean_estimates, linewidth=3, color="black", label="The mean")
201 | exp_affinity = -4.7
202 | plt.axhline(y=exp_affinity, color="red", linestyle="--")
203 | plt.legend()
204 | plt.show()
205 | ```
206 |
207 | ## Closing thoughts
208 |
209 | You can see that each fun-metaD simulation explores the free energy surface very differently. The ABFE estimates diverge immediately and fluctuate wildly. However, the mean of the estimates is quite stable. This indicates that, while each fun-metaD simulation is exploring a different part of the FES at any given point, the full free energy surface is explored quickly.
210 |
--------------------------------------------------------------------------------
/02_funnel_metad/README.md:
--------------------------------------------------------------------------------
1 | # Funnel metadynamics
2 |
3 | Author: Dominykas Lukauskis
4 |
5 | Email: dominykas.lukauskis.19@ucl.ac.uk
6 |
7 | Requirements:
8 | * BioSimSpace
9 | * OpenMM or Gromacs/Amber patched with PLUMED
10 |
11 | This workshop tutorial walks you through how to setup funnel metadynamics (fun-metaD) simulations to estimate the absolute binding free energy (ABFE), how to visualise the funnel restraints and how to analyse the simulations using [BioSimSpace](https://biosimspace.org/index.html).
12 |
13 | The workshop is divided into two notebooks.
14 |
15 | ## 1. Simulation setup and restraint visualisation
16 | For a step-by-step guide on how to visualise the funnel restraints and how to set up the fun-metaD simulations, follow [this Jupyter NB](01_bss-fun-metad-setup.ipynb).
17 |
18 | ## 2. Analysis
19 | For a step-by-step guide on how to analyse fun-metaD simulations go [here](02_bss-fun-metad-analysis.ipynb). This notebook includes a discussion on how to think about convergence in fun-metaD simulations and how to get a robust ABFE estimate.
20 |
--------------------------------------------------------------------------------
/02_funnel_metad/example_nodes/equilibrate_no_trj.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Author: Dominykas Lukauskis
5 | # Email: dominykas.lukauskis.19@ucl.ac.uk
6 | #
7 | # # Equilibration
8 | #
9 | # A node to perform equilibration of a molecular system. Saves the equilibration system to AMBER format files along with a DCD trajectory.
10 |
11 | # In[ ]:
12 |
13 |
14 | import BioSimSpace as BSS
15 | import os
16 |
17 | # set this just in case its missing in the env
18 | os.environ["CUDA_VISIBLE_DEVICES"] = "0"
19 |
20 | # In[ ]:
21 |
22 |
23 | node = BSS.Gateway.Node(
24 | "A node to perform equilibration. Saves the equlibrated molecular configuration to file."
25 | )
26 | node.addAuthor(
27 | name="Dominykas Lukauskis",
28 | email="dominykas.lukauskis.19@ucl.ac.uk",
29 | affiliation="University College London",
30 | )
31 | node.setLicense("GPLv3")
32 |
33 |
34 | # Set the input requirements:
35 |
36 | # In[ ]:
37 |
38 |
39 | node.addInput("files", BSS.Gateway.FileSet(help="A set of molecular input files."))
40 |
41 | node.addInput(
42 | "engine",
43 | BSS.Gateway.String(
44 | help="The molecular dynamics engine.",
45 | allowed=BSS.MD.engines(),
46 | default="OpenMM",
47 | ),
48 | )
49 |
50 | node.addInput(
51 | "runtime",
52 | BSS.Gateway.Time(
53 | help="The run time.",
54 | unit="nanoseconds",
55 | minimum=0.02 * BSS.Units.Time.nanosecond,
56 | maximum=10 * BSS.Units.Time.nanosecond,
57 | default=2 * BSS.Units.Time.nanosecond,
58 | ),
59 | )
60 |
61 | node.addInput(
62 | "temperature_start",
63 | BSS.Gateway.Temperature(
64 | help="The initial temperature.",
65 | unit="kelvin",
66 | minimum=0 * BSS.Units.Temperature.kelvin,
67 | maximum=1000 * BSS.Units.Temperature.kelvin,
68 | default=0 * BSS.Units.Temperature.kelvin,
69 | ),
70 | )
71 |
72 | node.addInput(
73 | "temperature_end",
74 | BSS.Gateway.Temperature(
75 | help="The final temperature.",
76 | unit="kelvin",
77 | minimum=0 * BSS.Units.Temperature.kelvin,
78 | maximum=1000 * BSS.Units.Temperature.kelvin,
79 | default=300 * BSS.Units.Temperature.kelvin,
80 | ),
81 | )
82 |
83 | node.addInput(
84 | "restraint",
85 | BSS.Gateway.String(
86 | help="The type of restraint.",
87 | allowed=BSS.Protocol.Equilibration.restraints(),
88 | default="none",
89 | ),
90 | )
91 |
92 | # We now need to define the output of the node. In this case we will return a set of files representing the equilibrated molecular system in AMBER format and a single file containing the trajectory frames.
93 |
94 | # In[ ]:
95 |
96 |
97 | node.addOutput(
98 | "equilibrated", BSS.Gateway.FileSet(help="The equilibrated molecular system.")
99 | )
100 |
101 |
102 | # In[ ]:
103 |
104 |
105 | node.showControls()
106 |
107 |
108 | # Generate the molecular system.
109 |
110 | # In[ ]:
111 |
112 |
113 | system = BSS.IO.readMolecules(node.getInput("files"))
114 |
115 |
116 | # Set up the equilibration protocol.
117 | #
118 | # (Note that the keyword arguments happen to have the same name as the input requirements. This need not be the case.)
119 |
120 | # In[ ]:
121 |
122 |
123 | protocol = BSS.Protocol.Equilibration(
124 | runtime=node.getInput("runtime"),
125 | temperature_start=node.getInput("temperature_start"),
126 | temperature_end=node.getInput("temperature_end"),
127 | restraint=node.getInput("restraint"),
128 | )
129 |
130 |
131 | # Start the molecular dynamics process.
132 |
133 | # In[ ]:
134 |
135 | engine = node.getInput("engine")
136 |
137 | if engine == "OpenMM":
138 | process = BSS.Process.OpenMM(system, protocol, platform="CUDA")
139 | elif engine == "Amber":
140 | process = BSS.Process.Amber(
141 | system, protocol, exe="/home/model/MD-SOFTWARE/amber18-gnu-cu10/bin/pmemd.cuda"
142 | )
143 | elif engine == "Gromacs":
144 | process = BSS.Process.Gromacs(system, protocol)
145 |
146 | process.start()
147 |
148 |
149 | # We now wait for the process to finish, then check whether there were any errors before continuing. If errors were raised, then we raise an exception and print the last 10 lines of `stdout` and `stderr` to the user.
150 |
151 | # In[ ]:
152 |
153 |
154 | process.wait()
155 |
156 | if process.isError():
157 | print(process.stdout(10))
158 | print(process.stdout(10))
159 | raise RuntimeError("The process exited with an error!")
160 |
161 |
162 | # Get the equilibrated molecular system and write to file in the same format as the input, along with a PDB file to use as a topology file for the trajectory in [VMD](https://www.ks.uiuc.edu/Research/vmd/).
163 |
164 | # In[ ]:
165 |
166 |
167 | # Get the original file formats associated with the system.
168 | # This is returned as a comma-separated string.
169 | formats = system.fileFormat()
170 |
171 | # Append PDB format, so we can visualise the trajectory with VMD.
172 | formats += ",pdb"
173 |
174 | # Write to file and bind to the output.
175 | node.setOutput(
176 | "equilibrated", BSS.IO.saveMolecules("equilibrated", process.getSystem(), formats)
177 | )
178 |
179 |
180 | # Validate the node.
181 |
182 | # In[ ]:
183 |
184 |
185 | node.validate()
186 |
--------------------------------------------------------------------------------
/02_funnel_metad/example_nodes/fun_metad.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # # Funnel metadynamics
5 | #
6 | # This is a node to perform a funnel metadynamics simulation on a solvated protein-ligand complex.
7 |
8 | # In[ ]:
9 |
10 |
11 | import BioSimSpace as BSS
12 |
13 |
14 | # Initialise the node and metadata.
15 |
16 | # In[ ]:
17 |
18 |
19 | node = BSS.Gateway.Node(
20 | "Perform funnel metadynamics on a solvated protein-ligand complex."
21 | )
22 | node.addAuthor(name="Dominykas Lukauskis", email="dominykas.lukauskis.19@ucl.ac.uk")
23 |
24 |
25 | # Add the inputs to the node.
26 |
27 | # In[ ]:
28 |
29 |
30 | node.addInput("files", BSS.Gateway.FileSet(help="A set of molecular input files."))
31 |
32 | node.addInput(
33 | "runtime",
34 | BSS.Gateway.Time(
35 | help="The run time.",
36 | unit="nanoseconds",
37 | minimum=5 * BSS.Units.Time.nanosecond,
38 | maximum=2000 * BSS.Units.Time.nanosecond,
39 | default=1000 * BSS.Units.Time.nanosecond,
40 | ),
41 | )
42 |
43 | node.addInput(
44 | "hill_height",
45 | BSS.Gateway.Energy(
46 | help="The hill height.",
47 | unit="kj per mol",
48 | minimum=1 * BSS.Units.Energy.kj_per_mol,
49 | maximum=10 * BSS.Units.Energy.kj_per_mol,
50 | default=1.5 * BSS.Units.Energy.kj_per_mol,
51 | ),
52 | )
53 |
54 | node.addInput(
55 | "bias_factor",
56 | BSS.Gateway.Float(
57 | help="The bias factor for well-tempered metadynamics.",
58 | minimum=1.0,
59 | maximum=100.0,
60 | default=10.0,
61 | ),
62 | )
63 |
64 | node.addInput(
65 | "engine",
66 | BSS.Gateway.String(
67 | help="The molecular dynamics engine.",
68 | allowed=BSS.Metadynamics.engines(),
69 | default="OpenMM",
70 | ),
71 | )
72 |
73 | node.addInput(
74 | "work_dir",
75 | BSS.Gateway.String(
76 | help="The working directory for the simulation.", default="fun_metad"
77 | ),
78 | )
79 |
80 |
81 | # Add the outputs of the node.
82 |
83 | # In[ ]:
84 |
85 |
86 | node.addOutput(
87 | "final",
88 | BSS.Gateway.FileSet(
89 | help="The final system, in the original format plus a PDB file."
90 | ),
91 | )
92 |
93 |
94 | # Show the GUI so the user can set inputs interactively.
95 |
96 | # In[ ]:
97 |
98 |
99 | node.showControls()
100 |
101 |
102 | # Create the system from the user specified input files.
103 |
104 | # In[ ]:
105 |
106 |
107 | system = BSS.IO.readMolecules(node.getInput("files"))
108 |
109 |
110 | # Work out "best guess" funnel parameters based on the molecules in the system.
111 |
112 | # In[ ]:
113 |
114 |
115 | p0, p1 = BSS.Metadynamics.CollectiveVariable.makeFunnel(system)
116 |
117 |
118 | # Create the funnel collective variable based on the funnel parameters.
119 |
120 | # In[ ]:
121 |
122 |
123 | cv = BSS.Metadynamics.CollectiveVariable.Funnel(
124 | p0, p1, upper_bound=BSS.Metadynamics.Bound(value=3.5 * BSS.Units.Length.nanometer)
125 | )
126 |
127 |
128 | # Create the metadynamics protocol, passing the collective variable and user-defined configuration parameters.
129 |
130 | # In[ ]:
131 |
132 |
133 | protocol = BSS.Protocol.Metadynamics(
134 | cv,
135 | runtime=node.getInput("runtime"),
136 | hill_height=node.getInput("hill_height"),
137 | bias_factor=node.getInput("bias_factor"),
138 | restart_interval=500000,
139 | )
140 |
141 |
142 | # Create a simulation process using the user-defined molecular-dynamics engine. This will automatically start the process.
143 |
144 | # In[ ]:
145 |
146 |
147 | engine = node.getInput("engine")
148 | work_dir = node.getInput("work_dir")
149 |
150 | if engine == "OpenMM":
151 | process = BSS.Process.OpenMM(system, protocol, platform="CUDA", work_dir=work_dir)
152 | elif engine == "Amber":
153 | process = BSS.Process.Amber(
154 | system,
155 | protocol,
156 | exe="/home/model/MD-SOFTWARE/amber18-gnu-cu10/bin/pmemd.cuda",
157 | work_dir=work_dir,
158 | )
159 | elif engine == "Gromacs":
160 | process = BSS.Process.Gromacs(system, protocol, work_dir=work_dir)
161 |
162 | process.start()
163 |
164 |
165 | # Wait for the process to finish and check for any errors.
166 |
167 | # In[ ]:
168 |
169 |
170 | process.wait()
171 |
172 | # Print the last 10 lines of stdout and stderr if there was an error.
173 | if process.isError():
174 | print(process.stdout(10))
175 | print(process.stderr(10))
176 | raise RuntimeError("The funnel metadynamics process exited with an error!")
177 |
178 |
179 | # Get the system from the process, save to file, and bind to the output requirement.
180 |
181 | # In[ ]:
182 |
183 |
184 | final_system = process.getSystem()
185 | formats = system.fileFormat() + ",pdb"
186 | node.setOutput("final", BSS.IO.saveMolecules("final", final_system, formats))
187 |
188 |
189 | # Validate the node.
190 |
191 | # In[ ]:
192 |
193 |
194 | node.validate()
195 |
--------------------------------------------------------------------------------
/02_funnel_metad/example_nodes/minimise.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Author: Lester Hedges
5 | # Email: lester.hedges@bristol.ac.uk
6 | #
7 | # # Minimisation
8 | #
9 | # In this notebook you'll learn how to use BioSimSpace to write a robust and interoperable workflow node to perform energy minimisation on a molecular system.
10 | #
11 | # First we'll need to import BioSimSpace:
12 |
13 | # In[ ]:
14 |
15 |
16 | import BioSimSpace as BSS
17 |
18 |
19 | # We begin by creating a `Node` object. This is the core of our molecular workflow component. It defines what it does, what input is needed, and the output that is produced.
20 |
21 | # In[ ]:
22 |
23 |
24 | node = BSS.Gateway.Node(
25 | "A node to perform energy minimisation and save the minimised molecular configuration to file."
26 | )
27 |
28 |
29 | # We'll now set the author and license:
30 |
31 | # In[ ]:
32 |
33 |
34 | node.addAuthor(
35 | name="Lester Hedges",
36 | email="lester.hedges@bristol.ac.uk",
37 | affiliation="University of Bristol",
38 | )
39 | node.setLicense("GPLv3")
40 |
41 |
42 | # Nodes require inputs. To specify inputs we use the `Gateway` module, which is used as a bridge between BioSimSpace and the outside world. This will allow us to document the inputs, define their type, and specify any constraints on their allowed values. Here we will need a set of files that define the molecular system, and an integer that indicates the number of minimisation steps to perform.
43 |
44 | # In[ ]:
45 |
46 |
47 | node.addInput("files", BSS.Gateway.FileSet(help="A set of molecular input files."))
48 |
49 | node.addInput(
50 | "steps",
51 | BSS.Gateway.Integer(
52 | help="The number of minimisation steps.",
53 | minimum=0,
54 | maximum=1000000,
55 | default=10000,
56 | ),
57 | )
58 |
59 |
60 | # Note that the input requirement `steps` has a default value, so it is optional.
61 | #
62 | # We now need to define the output of the node. In this case we will return a set of files representing the minimised molecular system.
63 |
64 | # In[ ]:
65 |
66 |
67 | node.addOutput("minimised", BSS.Gateway.FileSet(help="The minimised molecular system."))
68 |
69 |
70 | # When working interactively within a Jupyter notebook we need a way to allow users to set the input requirements. The `node.showControls` method will display a graphical user interface (GUI), from which inputs can be set.
71 | #
72 | # Note that the GUI requires active user input. All input requirements that don't have a default value _must_ be set before the node can proceed. If you try to query the node for one of the user values then an error will be raised. For bounded integer inputs you can use a slider to set the value, or type in the input box and press enter.
73 | #
74 | # When working interactively you will typically be running on a remote server where you won't have access to the local filesystem. In this case you'll need to upload files for any of the `File` or `FileSet` input requirements. The GUI below will provide buttons that allow you to browse your own filesystem and select files. Since Jupyter has a limit of 5MB for file transfers, we provide support for compressed formats, such as `.zip` or `.tar.gz`. (A single archive can contain a set of files, allowing you to set a single value for a `FileSet` requirement.) We've provided some example input files that can be used in the training notebooks, which are available to download from the links below. These can then be re-uploaded using the GUI.
75 | #
76 | # AMBER: [ala.crd](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/amber/ala/ala.crd), [ala.top](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/amber/ala/ala.top)
77 | #
78 | # GROMACS: [kigaki.gro](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/gromacs/kigaki/kigaki.gro), [kigaki.top](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/gromacs/kigaki/kigaki.top)
79 | #
80 | # NAMD: [alanin.pdb](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/namd/alanin/alanin.pdb), [alanin.psf](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/namd/alanin/alanin.psf), [alanin.params](https://raw.githubusercontent.com/michellab/BioSimSpace/devel/demo/namd/alanin/alanin.params)
81 | #
82 | #
83 | # When uploading files the name of the current file(s) will replace the `Upload` button. If you need to change the file, simply click on the button again and choose a new file.
84 |
85 | # In[ ]:
86 |
87 |
88 | node.showControls()
89 |
90 |
91 | # Once all requirements are set then we can acces the values using the `node.getInput` method. The first time this is called the `node` will automatically validate all of the input and report the user if any errors were found.
92 | #
93 | # We'll now create a molecular system using the input files uploaded by the user. Note that we don't specify the format of the files, since this is automatically determined by BioSimSpace. (BioSimSpace has support for a wide range of formats and can convert between certain formats too.)
94 |
95 | # In[ ]:
96 |
97 |
98 | system = BSS.IO.readMolecules(node.getInput("files"))
99 |
100 |
101 | # In order to run a minimisation we need to define a protocol. This can be done using the `BioSimSpace.Protocol` module. Here we will create a "best practice" minimisation protocol, overriding the number of steps with the input from the user.
102 |
103 | # In[ ]:
104 |
105 |
106 | protocol = BSS.Protocol.Minimisation(steps=node.getInput("steps"))
107 |
108 |
109 | # We now have everything that is required to run a minimisation. To do so, we use `BioSimSpace.MD` to find an appropriate molecular dynamics package on our current environment. What package is found will depend upon both the system and protocol, as well as the hardware that is available to the user.
110 |
111 | # In[ ]:
112 |
113 |
114 | process = BSS.MD.run(system, protocol, engine="OpenMM", auto_start=False)
115 |
116 | process.start()
117 |
118 |
119 | # We now wait for the process to finish, then check whether there were any errors before continuing. If errors were raised, then we raise an exception and print the last 10 lines of `stdout` and `stderr` to the user.
120 |
121 | # In[ ]:
122 |
123 |
124 | process.wait()
125 |
126 | if process.isError():
127 | print(process.stdout(10))
128 | print(process.stdout(10))
129 | raise RuntimeError("The process exited with an error!")
130 |
131 |
132 | # When the process has finished running we can get the minimised molecular configuration. We will save this to file using the same format as the original system, and set the output requirement to the list of file names that were written.
133 |
134 | # In[ ]:
135 |
136 |
137 | node.setOutput(
138 | "minimised",
139 | BSS.IO.saveMolecules("minimised", process.getSystem(), system.fileFormat()),
140 | )
141 |
142 |
143 | # Finally, we validate that the node completed succesfully. This will check that all output requirements are satisfied. Any file outputs will be available for the user to download as a compressed archive.
144 | #
145 | # Note that the validation will fail until the cell above finishes running.
146 |
147 | # In[ ]:
148 |
149 |
150 | node.validate()
151 |
152 |
153 | # Once we are satisfied with our node we can choosed to download it as a regular Python script that can be run from the command-line.
154 | #
155 | # In JupyterHub, click on: `File/Download As/Python`\
156 | # In JupyterLab, click on: `File/Export Notebook As/Export Notebook to Executable Script`
157 |
--------------------------------------------------------------------------------
/02_funnel_metad/example_nodes/parameterise_solvate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # Author: Dominykas Lukaukis
5 | # Email: dominykas.lukauskis.19@ucl.ac.uk
6 | #
7 | # # Parameterisation
8 | #
9 | # A node to perform parameterisation of a molecule loaded from PDB file. Saves the parameterised molecule in to AMBER format files.
10 |
11 | # In[ ]:
12 |
13 |
14 | import BioSimSpace as BSS
15 |
16 |
17 | # In[ ]:
18 |
19 |
20 | node = BSS.Gateway.Node(
21 | "A node to perform parameterisation and solvation of protein and ligand molecules loaded from PDB/MOL2 files. Saves the parameterised and solvated system to AMBER format files."
22 | )
23 | node.addAuthor(
24 | name="Dominykas Lukauskis",
25 | email="dominykas.lukauskis.19@ucl.ac.uk",
26 | affiliation="University College London",
27 | )
28 | node.setLicense("GPLv3")
29 |
30 |
31 | # Set the input requirements:
32 |
33 | # In[ ]:
34 |
35 |
36 | node.addInput(
37 | "protein_pdb",
38 | BSS.Gateway.File(
39 | help="A Protein Data Bank (PDB) file containing a single molecule."
40 | ),
41 | )
42 |
43 | node.addInput(
44 | "ligand_file",
45 | BSS.Gateway.File(
46 | help="A Protein Data Bank (PDB) or Tripos MOL2 file containing a single organic molecule."
47 | ),
48 | )
49 |
50 | node.addInput(
51 | "protein_forcefield",
52 | BSS.Gateway.String(
53 | help="The protein force field to parameterise the molecule with.",
54 | allowed=BSS.Parameters.forceFields(),
55 | default="ff14SB",
56 | ),
57 | )
58 |
59 | node.addInput(
60 | "ligand_forcefield",
61 | BSS.Gateway.String(
62 | help="The ligand force field to parameterise the molecule with.",
63 | allowed=BSS.Parameters.forceFields(),
64 | default="gaff2",
65 | ),
66 | )
67 |
68 | node.addInput(
69 | "water_model",
70 | BSS.Gateway.String(
71 | help="The water model to use for ion parameters.",
72 | allowed=BSS.Solvent.waterModels(),
73 | default="tip3p",
74 | ),
75 | )
76 |
77 | node.addInput(
78 | "pdb4amber",
79 | BSS.Gateway.Boolean(
80 | help="Whether to pre-process the protein PDB file using pdb4amber.",
81 | default=False,
82 | ),
83 | )
84 |
85 | node.addInput(
86 | "box",
87 | BSS.Gateway.String(
88 | help="The type of simulation box.", allowed=BSS.Box.boxTypes(), default="cubic"
89 | ),
90 | )
91 |
92 | node.addInput(
93 | "neutralise",
94 | BSS.Gateway.Boolean(
95 | help="Whether to neutralise the system. Ions will be added on top of any specified with 'ion_conc'",
96 | default=True,
97 | ),
98 | )
99 |
100 | node.addInput(
101 | "ion_conc",
102 | BSS.Gateway.Float(help="The ion concentration in mol per litre", default=0),
103 | )
104 |
105 |
106 | # We now need to define the output of the node. In this case we will return a set of files representing the parameterised molecule in AMBER format.
107 |
108 | # In[ ]:
109 |
110 |
111 | node.addOutput(
112 | "solvated", BSS.Gateway.FileSet(help="The parameterised and solvated system.")
113 | )
114 |
115 |
116 | # In[ ]:
117 |
118 |
119 | node.showControls()
120 |
121 |
122 | # Load the PDB file and pre-process with `pdb4amber` if requested. Since we assume a single molecule PDB, take the first molecule in the file.
123 |
124 | # In[ ]:
125 |
126 |
127 | protein = BSS.IO.readPDB(
128 | node.getInput("protein_pdb"), pdb4amber=node.getInput("pdb4amber")
129 | )[0]
130 |
131 |
132 | # Perform the parameterisation using the chosen force field and ion water model. Note that we call the generic `BSS.Parameters.parameterise` function so that we can pass the force field name as an argument.
133 |
134 | # In[ ]:
135 |
136 |
137 | protein = BSS.Parameters.parameterise(
138 | protein,
139 | node.getInput("protein_forcefield"),
140 | water_model=node.getInput("water_model"),
141 | ).getMolecule()
142 |
143 | # Load the PDB file and pre-process with `pdb4amber` if requested. Since we assume a single molecule PDB, take the first molecule in the file.
144 |
145 | # In[ ]:
146 |
147 |
148 | ligand = BSS.IO.readPDB(node.getInput("ligand_file"))[0]
149 |
150 |
151 | # Perform the parameterisation using the chosen force field and ion water model. Note that we call the generic `BSS.Parameters.parameterise` function so that we can pass the force field name as an argument.
152 |
153 | # In[ ]:
154 |
155 | ############ net charge assignment? ##############
156 | ligand = BSS.Parameters.parameterise(
157 | ligand,
158 | node.getInput("ligand_forcefield"),
159 | ).getMolecule()
160 |
161 | # Combine the protein and the ligand
162 |
163 | cmplx = protein + ligand
164 |
165 | # Now work out the minimum box size for the molecules in the system.
166 |
167 | # In[ ]:
168 |
169 |
170 | # Get the minimium and maximum coordinates of the bounding box that
171 | # minimally encloses the protein.
172 | box_min, box_max = cmplx.getAxisAlignedBoundingBox()
173 |
174 | # Work out the box size from the difference in the coordinates.
175 | box_size = [y - x for x, y in zip(box_min, box_max)]
176 |
177 | # How much to pad each side of the molecule? (Nonbonded cutoff = 10 A)
178 | padding = 15 * BSS.Units.Length.angstrom
179 |
180 | # Work out an appropriate box. This will used in each dimension to ensure
181 | # that the cutoff constraints are satisfied if the molecule rotates.
182 | box_length = max(box_size) + 2 * padding
183 |
184 |
185 | # Get the box parameters for the chosen box type.
186 |
187 | # In[ ]:
188 |
189 |
190 | box, angles = BSS.Box.generateBoxParameters(node.getInput("box"), box_length)
191 |
192 |
193 | # Now solvate with the chosen water model and ion concentration.
194 |
195 | # In[ ]:
196 |
197 |
198 | solvated = BSS.Solvent.solvate(
199 | node.getInput("water_model"),
200 | molecule=cmplx,
201 | box=box,
202 | angles=angles,
203 | is_neutral=node.getInput("neutralise"),
204 | ion_conc=node.getInput("ion_conc"),
205 | )
206 |
207 |
208 | # Write the solvated system to file in PDB and Amber format.
209 |
210 | # In[ ]:
211 |
212 |
213 | node.setOutput(
214 | "solvated", BSS.IO.saveMolecules("solvated", solvated, ["PDB", "RST7", "PRM7"])
215 | )
216 |
217 |
218 | # Validate the node.
219 |
220 | # In[ ]:
221 |
222 |
223 | node.validate()
224 |
--------------------------------------------------------------------------------
/02_funnel_metad/example_nodes/submit.sub:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # The #BSUB lines are interpreted by LSF
3 | # -------------------------------------
4 | # LSF job name and queue
5 | #BSUB -J bss_fun-metaD
6 | #BSUB -q phase12_slough
7 | # Output log files
8 | #BSUB -o output.%J
9 | #BSUB -e errors.%J
10 | # Numbers of CPUs
11 | #BSUB -n 1
12 | # Resources
13 | #BSUB -R "rusage[mem=1] span[ptile=8] select[hname!=slhgridap073]"
14 | # GPU resources
15 | #BSUB -gpu "num=1:j_exclusive=yes:mode=exclusive_process"
16 |
17 | source /home/model/MD-SOFTWARE/BSSenv.bashrc
18 |
19 | # set the I/Os
20 | python parameterise_solvate.py --protein_pdb protein.pdb --ligand_file ligand.mol2
21 |
22 | python minimise.py --files solvated.prm7 solvated.rst7
23 |
24 | python equilibrate_no_trj.py --files minimised.rst7 minimised.prm7
25 |
26 | python fun_metad.py --files equilibrated.rst7 equilibrated.prm7
27 |
--------------------------------------------------------------------------------
/02_funnel_metad/figures/figure1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/02_funnel_metad/figures/figure1.jpeg
--------------------------------------------------------------------------------
/02_funnel_metad/figures/figure2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/02_funnel_metad/figures/figure2.png
--------------------------------------------------------------------------------
/02_funnel_metad/figures/fun-hsp90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenBioSim/biosimspace_tutorials/ac200df769ba3373c9e0e724020aaaa96e340889/02_funnel_metad/figures/fun-hsp90.png
--------------------------------------------------------------------------------
/02_funnel_metad/get_tutorial.py:
--------------------------------------------------------------------------------
1 | import requests, os
2 |
3 | links = {
4 | "01": (
5 | "inputs_01.tar.bz2",
6 | "https://openbiosim-my.sharepoint.com/:u:/g/personal/director_openbiosim_org/Ed-8fipPSEFMt01VyyZQBBoBpviQb2k5pmzFsdBulaXXUQ?download=1",
7 | ),
8 | "02": (
9 | "inputs_02.tar.bz2",
10 | "https://openbiosim-my.sharepoint.com/:u:/g/personal/director_openbiosim_org/ES8xuCuWe4dNop5fJeH1opEBoKG0qZZqLph5ZwMadKkJoQ?download=1",
11 | ),
12 | }
13 |
14 |
15 | def download(link):
16 | localfile, url = links[link]
17 | # Do not download if tarball already found
18 | if os.path.isfile(localfile):
19 | return
20 | print("Downloading %s from openbiosim.org ..." % localfile)
21 | req = requests.get(url, stream=True)
22 | with open(localfile, "wb") as f:
23 | for chunk in req.iter_content(chunk_size=1024):
24 | if chunk:
25 | f.write(chunk)
26 | f.flush()
27 | print("Extracting compressed tarball ...")
28 | os.system("tar -xf %s" % localfile)
29 | # os.system("rm %s" % localfile)
30 |
--------------------------------------------------------------------------------
/03_steered_md/02_trajectory_analysis.md:
--------------------------------------------------------------------------------
1 | # Steered MD in BioSimSpace
2 |
3 | This notebook covers analysing sMD trajectories after the run has finished.
4 |
5 | **Reading Time:**
6 | ~ 15 mins
7 |
8 | ### Maintainers
9 | - Adele Hardie -- adele.hardie@ed.ac.uk (@AdeleHardie)
10 |
11 | ### Prerequisites
12 |
13 | - Basic Python and bash
14 | - A completed sMD run
15 |
16 | ### Learning Objectives
17 |
18 | - Analyse the steered MD trajectory
19 |
20 | ### Table of Contents
21 | 1. [Introduction](#intro)
22 | 2. [Plot steering output](#plotting)
23 | 2.1 [Successful steering](#success)
24 | 2.2 [Failed steering](#failed)
25 | 3. [Extract snapshots](#snapshots)
26 | 4. [Example application - Markov State Modelling](#msm)
27 |
28 |
29 | ### Further reading for this topic
30 | - [Applying sMD to study allosteric modulation](https://www.nature.com/articles/s42004-023-00926-1)
31 |
32 | **Jupyter Cheat Sheet**
33 | - To run the currently highlighted cell and move focus to the next cell, hold ⇧ Shift and press ⏎ Enter;
34 | - To run the currently highlighted cell and keep focus in the same cell, hold ⇧ Ctrl and press ⏎ Enter;
35 | - To get help for a specific function, place the cursor within the function's brackets, hold ⇧ Shift, and press ⇥ Tab;
36 |
37 | ### Link to documentation:
38 | You can find the full documentation at [biosimspace.org](https://biosimspace.org).
39 |
40 | ## 1. Introduction
41 |
I am code
tags to write code here\n",
111 | "