Writing tutorials
The Octopus tutorials are written as Jupyter Notebooks and converted to static HTML using Sphinx and myst-nb. Notebooks consist of a mix of markdown cells, in which MyST markdown can be used, and code cells.
Writing a tutorial
Organizing content and naming conventions
All tutorials are located in the octopus repository under
doc/jupyter_tutorials/<index>-<category>. Tutorials are grouped into different
topics (indicated by category). Tutorials should be self-contained as much as
possible and not require any additional files.
Each notebook should have a name <index>-<tutorial name>.ipynb, the <index>
(starting at 1) determines the tutorial order per group of tutorials.
Octopus input/output must not be written into the base directory containing the
notebook, instead a subdirectory <index>-<tutorial name> should be created at
the top of each notebook (when performing multiple calculations, additional
subdirectories inside that base directory can be created and used as needed).
Each notebook should start with a single level-1 heading (a single # in markdown
syntax), which is the tutorial title. The title should be descriptive but also
concise (as it appears in a navigation sidebar with limited space on
the webpages). All sections in the notebook should be (at least) level-2
headings (double ## in markdown syntax).
Editing tutorials
The easiest way to work on tutorials is to open them in Jupyter lab. The most
convenient option to get all required Python dependencies (without also
compiling Octopus as part of the installation process) is using uv: in the
repo root or a tutorial subdirectory run uv run --only-group tutorial-dev jupyter lab. Ensure that octopus (and an MPI implementation) are in your
PATH. You can also use a Docker image with Octopus via podman run -it -v=$(pwd):/io -p 8888:8888 registry.gitlab.com/octopus-code/octopus:main bash
(executed in the repository root) and run jupyter lab via uv inside the
container.
Before committing notebooks make sure to remove all outputs (or use nbstripout as pre-commit hook/git filter). A CI job will check that notebooks do not contain any output. If you accidentally commit output please remove it locally, amend the commit and force-push the changes to avoid increasing the repository size.
Creating input files
Input files required for octopus (inp, coordinates, …) should be created as
part of the notebook using %%writefile magic: the content of the input file is
contained in a code cell with an additional first line %%writefile <path/to/file>. inp files must not be provided as additional separate files.
Benefits are (i) users can easily modify the content of an input file from
within the notebook and (ii) each tutorial only requires a single file making it
easier to share and distribute tutorials guaranteeing complete information.
Use %%writefile for all text input files. Some tutorials may require
additional larger (binary) files. These can be committed to the repository as
additional files if recreating them within a notebook is not possible.
You should generally redirect stdout/stderr to files (using suitable input options) to (i) keep the executed notebook short (and free from too much information with little value) and (ii) have them available for further use e.g. within postopus.
Executing octopus
Depending on context use one of the following two options to execute octopus:
-
For single execution use the syntax
!cd <index>-<tutorial name>[/optionally/subdirectory] && octopusin a dedicated cell.This will temporarily change into the subdirectory containing the
inpfile and execute octopus in there. The working directory of the notebook remains unchanged (which improves stable and consistent execution of notebooks). -
For calling octopus multiple times, e.g. to systematically vary a parameter in the
inpfile, use Python to create the required input files and usesubprocess.run("octopus", cwd="<index>-<tutorial name>[/optionally/subdirectory]").Similar to the
!syntax, this will run octopus in the correct directory without changing the working directory of the notebook. The syntax is easier to understand withing a longer Python block and therefore preferable over!syntax in loops.
Parallelisation
Tutorials should make use of parallelisation via MPI and/or OpenMP if sensible. Tutorials should use at most 8 CPUs (fewer if it improves performance), which can be expected as a lower limit on all reasonably modern hardware.
For MPI always use mpirun <mpi options> octopus when calling octopus (instead
of orterun, prterun, …; some people may not have mpirun but they can
easily set an alias; mpirun is the clearest command, in particular for
inexperienced users).
To control the number of OpenMP threads, set OMP_NUM_THEADS via Python, e.g.
for 2 threads use (note the use of a string on the right-hand side):
import os
os.environ["OMP_NUM_THREADS"] = "2"
After running this command, all subsequent calls will use 2 threads, it can therefore e.g. be placed at the top of a notebook.
Tutorials should ideally not require users to have a dedicated GPU, which is in
particular in notebooks generally not the case. (When tutorials are used for a
workshop where participants get access to GPU machines or slurm, e.g. via a Jupyter hub,
it is straightforward to substitute all mpirun calls with a suitable other
command/other flags in an automated fashion.)
Data analysis
Tutorials should generally make use of Postopus to support data analysis, visualisation and further postprocessing. If a file format is not yet supported please reach out to the Postopus developers and provide a concise example to create the file in question.
Tutorial validation checks
The last section of each notebook is a special validation section. It should
start with the heading Tutorial Validation Checks.
The first tutorial of the Octopus basics section states its purpose:
The purpose of these checks is to automatically inform the developers if tutorials have to be updated, due to changes in Octopus. These tests are not meant to be regression tests, as all functionality is tested in the testsuite.
Please, note that the reference values are determined by the original input files in the tutorials. The tests might fail if you experiment with the input variables, e.g. to test the convergence, or try different algorithms.
The checks in this section should focus on a few key quantities that can flag up significant changes in the tutorial/octopus that affect the tutorial qualitatively, e.g. convergence in a wrong local energy minimum, no convergence at all, missing/additional peaks in a spectrum, etc.
Tolerances for these checks are on purpose not updated automatically and should be chosen suitably wide to try to ensure that these checks will pass on any computer independent of e.g. used compiler flags, while still capturing significant changes. Picking suitable quantities depends on the physics discussed in the individual tutorials.
To read octopus output, you should generally use postopus (if a quantity/file
is not yet supported please open an
issue). Checks use assert
or numpy.testing.assert_allclose for floating-point numbers. Each cell should
only contain a single assertion. The Octopus CI regularly runs all notebooks to
ensure tests are up-to-date and all assertions pass.
Useful MyST syntax
In the markdown cells you can use everything that is supported by MyST markdown. They will typically only render nicely in the HTML version of the notebook but their syntax is easy to read and does not affect readability of the notebook. A few useful commands are shown in the following subsections:
Admonitions
Sphinx supports a number of specially highlighted boxes for information, warnings, notes, etc. To use them wrap the paragraph(s) in triple colons (called colon fence) and add the type to the opening fence. Inside the block you can use full MyST/mardown syntax. To nest multiple blocks vary the number of colons. Here are two examples for source code and how it is rendered (the theming will look different for notebooks).
:::{info}
A simple info block.
:::
is rendered as:
A simple info block.
::::{warning}
An outer block with `monospace` and an info block:
:::{info}
The inner info block.
:::
::::
An outer block with monospace and an info block:
The inner info block.
Equations
For equations use normal LaTeX. Equations can be wrapped in single/double dollar signs and you can use amsmath.
Code blocks
If you need to include code in a markdown cell you can use normal markdown syntax, i.e. wrapping in single backticks for inline code or surrounding by triple backticks for code blocks. The language for syntax highlighting can be specified after the opening triple backticks. If nothing is specified, sphinx will assume Python.
Static images
Static images can be included using normal markdown syntax. The size and a few other parameters can be controlled with MyST syntax (the trailing block in braces):
{w=400px}
Images should be in png or webp format, eps can not be rendered.
You should only include images that cannot dynamically be generated from the Octopus output.
Removing cells in HTML
Cells in Jupyter notebooks can carry additional metadata (use advanced tools in
the right sidebar of jupyter lab to edit the metadata). You can remove or hide cell
input/output in the static html version. E.g. to remove input of a cell add the
following metadata:
"mystnb": {
"remove_code_source": true
}
Conversion to static HTML
To display a static version of the tutorials on the Octopus webpages, all
notebooks are executed and subsequently converted to HTML (using myst-nb) as
part of the CI.
Organization of the HTML pages
A few additional index.md files are required for the static pages. The
top-level file (doc/jupyter_tutorials/index.md) contains instructions on how
to execute tutorials as well as links to the different groups of tutorials.
The directory for each group contains an index.md file with some general
information about this set of tutorials, a hidden toc directive (required for
sphinx to collect all notebooks) and a list of tutorials with their title
(inserted automatically by sphinx) and a one-line description. (Sphinx'
table of contents does not support the additional description, therefore this
duplication is required.)
The following example shows parts of the index.md file for the first
set of tutorials:
# Octopus Basics
This is a series ...
- Lesson 1: {doc}`1-getting_started` — Learn how to run the code
- Lesson 2: {doc}`2-basic_input_options` — Obtain the ground state of the nitrogen atom.
- ...
:::{toctree}
:maxdepth: 1
:glob:
:hidden:
*
:::
When adding a new tutorial, all you have to do is add the new file and summary to the bullet point list.
Building static HTML pages locally
To build static HTML run uv run --only-group sphinx make html inside
doc/jupyter_tutorials. Running this command will not re-execute notebooks
and is therefore quick. Instead it will use the notebooks in whatever state they
currently are. To browse the output open
doc/jupyter_tutorials/_build/html/index.html in a browser (or run a webserver
in doc/jupyter_tutorials/_build/html, e.g. python -m http.server; running a
a webserver is generally not required to inspect the output; a few advanced
options such as download buttons for notebooks may require it.).
Building static HTML pages in the CI
In the CI static pages are build in two steps:
- Each notebook is executed inside a Docker container using an image created for the current commit. Executed notebooks containing outputs are kept as CI artifacts and passed to the second step.
- Sphinx is executed (using the same
uv run --only-group sphinx make html) and the output under_build/htmlis copied to the Octopus webserver.
The CI exports OCT_PARSE_ENV=1 and OCT_SilenceProgressBar=yes to suppress
progress bars in the notebook output.