diff --git a/.github/workflows/deploy-book.yml b/.github/workflows/deploy-book.yml index 5e0387a7..b3ca13fb 100644 --- a/.github/workflows/deploy-book.yml +++ b/.github/workflows/deploy-book.yml @@ -21,6 +21,10 @@ jobs: with: python-version: 3.x + - name: Install Graphviz (for API inheritance diagrams) + run: | + sudo apt-get update && sudo apt-get install -y graphviz + - name: Install dependencies run: | pip install -r docs/requirements.txt @@ -36,28 +40,45 @@ jobs: # Build the book - name: Build the book + env: + ABTEM_DOCS_VERSION: stable run: | jupyter book build docs - + - name: Upload book uses: actions/upload-artifact@v7 with: name: html-pages path: ./docs/_build/html - # Push the book's HTML to github-pages + # Push the book's HTML to the root of github-pages, keeping the /dev/ + # docs (published separately by deploy-dev.yml) intact. deploy-book: needs: build-book runs-on: ubuntu-latest + concurrency: + group: gh-pages-push steps: - name: Retrieve release distributions uses: actions/download-artifact@v8 with: name: html-pages - path: ./docs/_build/html + path: ./site + + - name: Check out current gh-pages + uses: actions/checkout@v7 + with: + ref: gh-pages + path: gh-pages-current + + - name: Preserve /dev/ docs in the new snapshot + run: | + if [ -d gh-pages-current/dev ]; then + cp -R gh-pages-current/dev ./site/dev + fi - name: GitHub Pages action uses: peaceiris/actions-gh-pages@v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/_build/html + publish_dir: ./site diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 00000000..9a292c14 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,59 @@ +# Publishes the development documentation (built from main) under /dev/ on +# gh-pages. The stable docs at the site root are published by deploy-book.yml +# on each release and are the default readers see. + +name: deploy-dev-docs + +on: + push: + branches: [main] + paths: + - "docs/**" + - "scripts/**" + - ".github/workflows/deploy-dev.yml" + workflow_dispatch: {} + +# Only the newest dev build per push burst needs to deploy. +concurrency: + group: deploy-dev + cancel-in-progress: true + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Set up Python 3.x + uses: actions/setup-python@v6 + with: + python-version: 3.x + + - name: Install Graphviz (for API inheritance diagrams) + run: | + sudo apt-get update && sudo apt-get install -y graphviz + + - name: Install dependencies + run: | + pip install -r docs/requirements.txt + + - name: Strip orphaned ipywidgets state + run: | + python scripts/check_notebook_widgets.py --fix + + - name: Build the book (dev) + env: + ABTEM_DOCS_VERSION: dev + run: | + jupyter book build docs + + - name: Deploy to /dev/ + uses: peaceiris/actions-gh-pages@v4.1.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_build/html + # destination_dir makes this deploy replace only /dev/, leaving the + # stable site at the root untouched. NEVER add force_orphan here: + # combined with destination_dir it would replace the whole branch + # with only /dev/, wiping the root site. + destination_dir: dev diff --git a/.gitignore b/.gitignore index 34f321ea..226997a5 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,7 @@ instance/ # Sphinx documentation docs/_build/ -**/_autosummary +docs/reference/api/apidocs/ # PyBuilder target/ diff --git a/.readthedocs.yml b/.readthedocs.yml index 15427007..58dfee71 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,6 +4,8 @@ build: os: ubuntu-22.04 tools: python: "3.13" + apt_packages: + - graphviz jobs: pre_build: # Generate the Sphinx configuration for this Jupyter Book so it builds. diff --git a/docs/_config.yml b/docs/_config.yml index 8f10c460..3d0a9337 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -5,7 +5,7 @@ title: "" author: "" copyright: "" logo: images/logo.png -exclude_patterns: ["_templates", "_ext", "_build", "reference/benchmarks"] +exclude_patterns: ["_ext", "_build", "reference/benchmarks"] only_build_toc_files: false # Force re-execution of notebooks on each build. @@ -24,9 +24,9 @@ bibtex_bibfiles: # Information about where the book exists on the web repository: - url: https://github.com/executablebooks/jupyter-book # Online location of your book + url: https://github.com/abTEM/doc # Online location of your book path_to_book: docs # Optional path to your book, relative to the repository root - branch: master # Which branch of the repository should be used when creating links (optional) + branch: main # Which branch of the repository should be used when creating links (optional) # Add GitHub buttons to your book # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository @@ -37,17 +37,22 @@ html: sphinx: extra_extensions: - - 'sphinx.ext.autodoc' - - 'sphinx.ext.napoleon' - 'sphinx.ext.viewcode' - - 'sphinx.ext.autosummary' - - 'sphinx_autodoc_typehints' + - 'sphinx.ext.inheritance_diagram' - 'matplotlib.sphinxext.roles' + - 'autodoc2' local_extensions: abtem_version_footer: _ext + abtem_autodoc2: _ext config: - autosummary_generate: true - templates_path: [ "_templates" ] + autodoc2_render_plugin: "myst" + autodoc2_output_dir: "reference/api/apidocs" + autodoc2_hidden_objects: ["private", "dunder", "inherited"] + autodoc2_skip_module_regexes: [".*\\._.*"] + # Skip the generated index.rst wrapper page (just a toctree + footnote) - + # api.md links straight to the abtem package page instead. + autodoc2_index_template: null suppress_warnings: ["etoc.toctree"] + graphviz_output_format: "svg" html_context: default_mode: light diff --git a/docs/_ext/abtem_autodoc2.py b/docs/_ext/abtem_autodoc2.py new file mode 100644 index 00000000..927e4721 --- /dev/null +++ b/docs/_ext/abtem_autodoc2.py @@ -0,0 +1,24 @@ +"""Point sphinx-autodoc2 at the installed abTEM package. + +The package location differs between environments (editable checkout locally, +site-packages on CI and Read the Docs), so the path is resolved at build time +from the abtem import rather than hardcoded in _config.yml. +""" + +import os + + +def _set_autodoc2_packages(app, config): + import abtem + + pkg_dir = os.path.dirname(os.path.abspath(abtem.__file__)) + # autodoc2 requires the path relative to the Sphinx source dir, POSIX-style + rel_path = os.path.relpath(pkg_dir, app.srcdir).replace(os.sep, "/") + config.autodoc2_packages = [ + {"path": rel_path, "module": "abtem", "auto_mode": True} + ] + + +def setup(app): + app.connect("config-inited", _set_autodoc2_packages) + return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/docs/_ext/abtem_version_footer.py b/docs/_ext/abtem_version_footer.py index e0022a34..dd598497 100644 --- a/docs/_ext/abtem_version_footer.py +++ b/docs/_ext/abtem_version_footer.py @@ -1,7 +1,23 @@ """Show the installed abTEM version (the one the docs were built/tested against) in the page footer, replacing the theme's static copyright notice. + +Also handles the stable/dev docs versions published on GitHub Pages: the +ABTEM_DOCS_VERSION environment variable ("stable" when unset, so local and +Read the Docs builds behave like today) selects between the stable build at +the site root and the development build served under /dev/. Dev builds get a +warning banner linking back to stable and a robots noindex tag; stable builds +get a footer link to the dev docs. """ +import os + +STABLE_URL = "https://abtem.github.io/doc/" +DEV_URL = STABLE_URL + "dev/" + + +def _is_dev(): + return os.environ.get("ABTEM_DOCS_VERSION", "stable") == "dev" + def _set_abtem_version_footer(app, config): try: @@ -12,14 +28,37 @@ def _set_abtem_version_footer(app, config): version = "unknown" theme_options = dict(config.html_theme_options or {}) - theme_options["extra_footer"] = ( + footer = ( "

Tested against " 'abTEM' - f" v{version}.

" + f" v{version}." ) + if _is_dev(): + theme_options["announcement"] = ( + "This is the development documentation, built from " + f'the latest main branch. Switch to the ' + "stable version." + ) + footer += " Development build." + else: + footer += ( + f' Also available: development version.' + ) + footer += "

" + theme_options["extra_footer"] = footer config.html_theme_options = theme_options +def _add_noindex(app, pagename, templatename, context, doctree): + # Keep the dev docs out of search results so readers land on stable. + if _is_dev(): + context["metatags"] = ( + context.get("metatags", "") + + '\n ' + ) + + def setup(app): app.connect("config-inited", _set_abtem_version_footer) + app.connect("html-page-context", _add_noindex) return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/docs/_templates/custom-class-template.rst b/docs/_templates/custom-class-template.rst deleted file mode 100644 index e19b810e..00000000 --- a/docs/_templates/custom-class-template.rst +++ /dev/null @@ -1,32 +0,0 @@ -{{ objname | escape | underline}} - -.. currentmodule:: {{ module }} - -.. autoclass:: {{ fullname }} - :members: - :show-inheritance: - :inherited-members: - - {% block methods %} - .. automethod:: __init__ - - {% if methods %} - .. rubric:: {{ _('Methods') }} - - .. autosummary:: - {% for item in methods %} - ~{{ name }}.{{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block attributes %} - {% if attributes %} - .. rubric:: {{ _('Attributes') }} - - .. autosummary:: - {% for item in attributes %} - ~{{ name }}.{{ item }} - {%- endfor %} - {% endif %} - {% endblock %} \ No newline at end of file diff --git a/docs/_templates/custom-function-template.rst b/docs/_templates/custom-function-template.rst deleted file mode 100644 index e03319b8..00000000 --- a/docs/_templates/custom-function-template.rst +++ /dev/null @@ -1,5 +0,0 @@ -{{ objname | escape | underline}} - -.. currentmodule:: {{ module }} - -.. auto{{ objtype }}:: {{ objname }} diff --git a/docs/_templates/custom-module-template.rst b/docs/_templates/custom-module-template.rst deleted file mode 100644 index bd0db3f3..00000000 --- a/docs/_templates/custom-module-template.rst +++ /dev/null @@ -1,67 +0,0 @@ -{{ objname | escape | underline}} - -.. automodule:: {{ fullname }} - - {% block attributes %} - {% if attributes %} - .. rubric:: Module Attributes - - .. autosummary:: - :toctree: - {% for item in attributes %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block functions %} - {% if functions %} - .. rubric:: {{ _('Functions') }} - - .. autosummary:: - :toctree: - :template: custom-function-template.rst - {% for item in functions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block classes %} - {% if classes %} - .. rubric:: {{ _('Classes') }} - - .. autosummary:: - :toctree: - :template: custom-class-template.rst - {% for item in classes %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block exceptions %} - {% if exceptions %} - .. rubric:: {{ _('Exceptions') }} - - .. autosummary:: - :toctree: - {% for item in exceptions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - -{% block modules %} -{% if modules %} -.. rubric:: Modules - -.. autosummary:: - :toctree: - :template: custom-module-template.rst - :recursive: -{% for item in modules %} - {{ item }} -{%- endfor %} -{% endif %} -{% endblock %} \ No newline at end of file diff --git a/docs/_toc.yml b/docs/_toc.yml index 7bf63de8..a56e9564 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -50,6 +50,8 @@ parts: - caption: Reference chapters: - file: reference/api/api + sections: + - file: reference/api/class_diagrams - file: reference/config # - file: reference/glossary - caption: abTEM diff --git a/docs/doc_requirements.txt b/docs/doc_requirements.txt deleted file mode 100644 index dc9344ff..00000000 --- a/docs/doc_requirements.txt +++ /dev/null @@ -1,18 +0,0 @@ -numpy -numba -pytest -scipy -h5py -matplotlib -ase -imageio -ipywidgets -docutils -jupyter-book<2 -sphinx>=5,<6 -sphinx_rtd_theme -nbsphinx -sphinx-autodoc-typehints -tqdm -psutil -pyfftw diff --git a/docs/getting_started/overview.md b/docs/getting_started/overview.md index fcdde879..59539b94 100644 --- a/docs/getting_started/overview.md +++ b/docs/getting_started/overview.md @@ -57,6 +57,65 @@ representing physical concepts; for example, a plane wave function is represente focused STEM probe wave function is represented by a `Probe` object. This makes the code easy to understand, not only for specialists but anyone familiar with transmission electron microscopy. +The diagram below sketches how the core objects connect to form the six simulation modes shown below; see the +[API reference](reference:api) for the full class hierarchy behind each of them. + +
+ +The abTEM object-composition pipeline + + + + + + + + + + + + + + + + + + multislice(potential) + apply_ctf(CTF).intensity() + diffraction_patterns() + scan(potential, scan, detectors) + + + + + + + + + + + + + + Atoms + Potential + Wave function + PlaneWave, Probe + Exit wave + Measurement + HRTEM + Measurement + SAED, CBED, PED + Measurement + STEM, 4D-STEM + + +
+A PlaneWave or Probe propagated through a Potential yields an exit wave (single-shot +modes) or, via Probe.scan, a scanned measurement directly (STEM modes). +
+
+ To simulate a (basic) high-resolution TEM (HRTEM) image, we need a `PlaneWave` at a given energy (by default in units of $\mathrm{eV}$), a `Potential` with a given real-space sampling (in units of $\mathrm{Å}$), and a `CTF` (contrast transfer function) representing the objective lens with a given aperture (in $\mathrm{mrad}$) and spherical aberration diff --git a/docs/reference/api/api.md b/docs/reference/api/api.md index 0c786f4f..bdf177a6 100644 --- a/docs/reference/api/api.md +++ b/docs/reference/api/api.md @@ -5,28 +5,12 @@ The *ab*TEM API generally follows an object-oriented design with objects representing elements of the underlying physical models. Numpy-style docstrings embedded in the code allow the reference to be automatically generated. -```{eval-rst} -.. autosummary:: - :toctree: _autosummary - :template: custom-module-template.rst - :recursive: - - abtem.antialias - abtem.array - abtem.atoms - abtem.waves - abtem.scan - abtem.detectors - abtem.measurements - abtem.transfer - abtem.transform - abtem.parametrizations - abtem.integrals - abtem.inelastic.phonons - abtem.distributions - abtem.potentials - abtem.tilt - abtem.visualize - abtem.multislice - abtem.prism +See the [class diagrams](reference:api:diagrams) for an overview of how the main user-facing classes in each part +of the API relate to one another. + +```{toctree} +:titlesonly: +:maxdepth: 3 + +apidocs/abtem/abtem ``` diff --git a/docs/reference/api/class_diagrams.md b/docs/reference/api/class_diagrams.md new file mode 100644 index 00000000..51408c66 --- /dev/null +++ b/docs/reference/api/class_diagrams.md @@ -0,0 +1,36 @@ +(reference:api:diagrams)= + +# Class diagrams + +The diagrams below show how the main user-facing classes in each part of the API relate to one another; see the +[full reference](reference:api) for every class and function. + +## Wave functions + +```{inheritance-diagram} abtem.waves.PlaneWave abtem.waves.Probe abtem.waves.Waves +:parts: 1 +``` + +## Potentials + +```{inheritance-diagram} abtem.potentials.iam.Potential abtem.potentials.iam.PotentialArray abtem.potentials.iam.CrystalPotential +:parts: 1 +``` + +## Detectors + +```{inheritance-diagram} abtem.detectors.AnnularDetector abtem.detectors.PixelatedDetector abtem.detectors.SegmentedDetector abtem.detectors.FlexibleAnnularDetector +:parts: 1 +``` + +## Scan patterns + +```{inheritance-diagram} abtem.scan.GridScan abtem.scan.LineScan abtem.scan.CustomScan +:parts: 1 +``` + +## Contrast transfer function + +```{inheritance-diagram} abtem.transfer.CTF abtem.transfer.Aperture abtem.transfer.TemporalEnvelope +:parts: 1 +``` diff --git a/docs/reference/config.md b/docs/reference/config.md index 8d74faaf..ebe9fa1d 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -19,9 +19,10 @@ defaults before they are used to configure the build. ## Access configuration -```{eval-rst} -.. autosummary:: - abtem.config.get +```{autodoc2-summary} +:renderer: myst + +abtem.core.config.get abtem.config.get ``` The *ab*TEM configuration system is usually accessed using the `abtem.config.get` function. You can use `.` for nested @@ -34,9 +35,10 @@ abtem.config.get("dask.chunk-size") # use "." for nested access ## Specify configuration in Python -```{eval-rst} -.. autosummary:: - abtem.config.set +```{autodoc2-summary} +:renderer: myst + +abtem.core.config.set abtem.config.set ``` The configuration is stored within a normal Python dictionary in `abtem.config.config` and can be modified using normal diff --git a/docs/requirements.txt b/docs/requirements.txt index 2ffc9b33..79f55b59 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,8 +1,4 @@ abtem jupyter-book<2 -sphinx_autodoc_typehints -docutils -jupyter-book -sphinx_rtd_theme -nbsphinx sphinx-autodoc-typehints +docutils