Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
984ff61
HTML and Markdown now use same cached simulations to speed up docs
aaronleesander Oct 8, 2026
d4817fa
set PyTorch to CPU only in docs
aaronleesander Oct 8, 2026
2522d7a
updated quickstart
aaronleesander Oct 8, 2026
23f5cbb
updated quickstart
aaronleesander Oct 8, 2026
ca75b2e
updated indexing
aaronleesander Oct 9, 2026
7e642ba
label update
aaronleesander Oct 9, 2026
c05c10e
updated analog simulation
aaronleesander Oct 9, 2026
9270ed0
updated shot-based circuit sim
aaronleesander Oct 9, 2026
ea004f4
updated circuit simulation
aaronleesander Oct 9, 2026
69938a1
updated circuit verification
aaronleesander Oct 9, 2026
8a7bdc8
updated examples
aaronleesander Oct 9, 2026
8bf89fc
updated analog digital
aaronleesander Oct 9, 2026
82b7385
added analog-digital to quickstart
aaronleesander Oct 9, 2026
7ccc5c9
updated index names
aaronleesander Oct 9, 2026
e6f033e
updated transmon example
aaronleesander Oct 9, 2026
d523d7b
cleaned up trapped ion example
aaronleesander Oct 9, 2026
1cabf3d
added emulation
aaronleesander Oct 9, 2026
571b0e5
updated language
aaronleesander Oct 9, 2026
9db9898
updated docs
aaronleesander Oct 9, 2026
a7f08a6
condensed front page table
aaronleesander Oct 9, 2026
126b808
fixed some factual statements
aaronleesander Oct 10, 2026
763480e
fixed broken docs references in docstrings
aaronleesander Oct 10, 2026
49d5f8c
added tests to guarantee docs tests run properly
aaronleesander Oct 10, 2026
93528a0
moved noise characterization to emulation and renamed digital twin
aaronleesander Oct 10, 2026
cf7c5ec
Keep local planning files out of the documentation branch
aaronleesander Oct 10, 2026
c5fee87
Merge branch 'main' into docs-update
aaronleesander Oct 10, 2026
6d18364
Fix repository links in included release notes
aaronleesander Oct 10, 2026
c9cd6b8
Bound Read the Docs notebook execution to one Sphinx worker
aaronleesander Oct 10, 2026
d39eee4
shortened surrogate to reduce runtime
aaronleesander Oct 10, 2026
d3f1642
reduced trajectory budget
aaronleesander Oct 10, 2026
f4a95aa
docs plots now executed as part of CI
aaronleesander Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,44 @@ jobs:
permissions:
contents: read

docs-check:
name: 📚 Documentation
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- name: Check documentation
run: uvx nox --non-interactive -s docs-check

docs-execute:
name: 📊 Documentation examples
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- name: Execute documentation examples
run: uvx nox --non-interactive -s docs-execute
- name: Save executed examples and rendered documentation
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: documentation-examples
path: |
docs/_outputs
docs/_build/executed
if-no-files-found: error
retention-days: 14

build-sdist:
name: 🚀 CD
needs: change-detection
Expand All @@ -90,6 +128,8 @@ jobs:
- python-tests
- python-coverage
- python-linter
- docs-check
- docs-execute
- build-sdist
- build-wheel
runs-on: ubuntu-slim
Expand Down
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
# prek install
#

# Saved notebooks are generated from the checked Markdown cells. Formatting their
# copies would invalidate the output manifest and repeat checks of the same code.
exclude: ^docs/_outputs/.*\.ipynb$

ci:
autoupdate_commit_msg: "⬆️🪝 update pre-commit hooks"
autoupdate_schedule: quarterly
Expand Down
20 changes: 13 additions & 7 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
version: 2

formats:
- htmlzip

sphinx:
configuration: docs/conf.py
fail_on_warning: true

build:
os: ubuntu-24.04
tools:
python: "3.14"
jobs:
install:
- uv pip install --python "$READTHEDOCS_VIRTUALENV_PATH/bin/python" --group docs --exact -e .
build:
html:
# Render saved notebook outputs; execute and validate examples in GitHub Actions.
- >-
"$READTHEDOCS_VIRTUALENV_PATH/bin/python" -u -m sphinx
-j 1 -n -T -W --keep-going -b html -d docs/_build/doctrees
docs "$READTHEDOCS_OUTPUT/html"

python:
install:
- method: uv
command: sync
groups:
- docs
extras: all
command: pip
path: .
2 changes: 1 addition & 1 deletion docs/UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
```{include} ../UPGRADING.md

:relative-docs: docs/
```
221 changes: 221 additions & 0 deletions docs/_ext/yaqs_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# Copyright (c) 2025 - 2026 Chair for Design Automation, TUM
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License

"""Keep API re-exports and type references linked to their defining objects."""

from __future__ import annotations

import builtins
import shutil
from html import escape
from pathlib import Path
from typing import TYPE_CHECKING

from docutils import nodes
from sphinx import addnodes
from sphinx.domains.python import ObjectEntry, PythonDomain

if TYPE_CHECKING:
from collections.abc import Iterator

from autoapi._objects import PythonObject
from sphinx.application import Sphinx
from sphinx_markdown_builder.translator import MarkdownTranslator


_EXTERNAL_ALIASES = {
"NDArray": "numpy.typing.NDArray",
"ArrayLike": "numpy.typing.ArrayLike",
"QuantumCircuit": "qiskit.circuit.QuantumCircuit",
"qiskit.QuantumCircuit": "qiskit.circuit.QuantumCircuit",
"DAGCircuit": "qiskit.dagcircuit.DAGCircuit",
"DAGOpNode": "qiskit.dagcircuit.DAGOpNode",
"Parameter": "qiskit.circuit.Parameter",
"scipy.linalg.LinAlgError": "numpy.linalg.LinAlgError",
}


class _APIPythonDomain(PythonDomain):
"""Publish public import aliases alongside their canonical API entries."""

def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
"""Include import aliases, even when a module has the same name.

Yields:
Python inventory entries.
"""
yield from super().get_objects()
for name, entry in self.data.get("yaqs_public_aliases", {}).items():
if name not in self.objects or self.objects[name].objtype != entry.objtype:
yield name, name, entry.objtype, entry.docname, entry.node_id, -1


def _canonical_name(objects: dict[str, PythonObject], name: str) -> str:
"""Follow a re-export, including a method or attribute below that export.

Returns:
The object's name at its defining module.
"""
visited = set()
while True:
parts = name.split(".")
for end in range(len(parts), 0, -1):
obj = objects.get(".".join(parts[:end]))
if obj is not None and obj.imported:
if end < len(parts) and obj.type not in {"class", "exception", "module", "package"}:
continue
if obj.id in visited:
return name
visited.add(obj.id)
original = obj.obj["original_path"]
suffix = ".".join(parts[end:])
name = original + (f".{suffix}" if suffix else "")
break
else:
return name


def _public_imports(
app: Sphinx,
what: str,
name: str,
obj: PythonObject,
skip: bool, # ruff: ignore[boolean-type-hint-positional-argument] - Sphinx calls event handlers positionally.
options: list[str],
) -> None:
"""List explicit public re-exports without repeating their documentation."""
del app, name, options
if skip or what not in {"module", "package"}:
return
exports = obj.obj.get("all") or []
links = [
f"* :py:obj:`{child.name} <{child.obj['original_path']}>`"
for child in obj.children
if child.imported and child.name in exports
]
if links:
obj.docstring += "\n\n.. rubric:: Public imports\n\n" + "\n".join(links) + "\n"


def _resolve_api_references(app: Sphinx, doctree: nodes.document) -> None:
"""Qualify local aliases and use inventory roles for external types."""
objects = getattr(app.env, "autoapi_all_objects", {})
inventory = getattr(app.env, "intersphinx_inventory", {})
for node in doctree.findall(addnodes.pending_xref):
if node.get("refdomain") != "py":
continue
target = node["reftarget"]
if node.get("reftype") in {"class", "obj"} and isinstance(getattr(builtins, target, None), type):
# Avoid resolving builtin ``type`` or ``float`` to a class member.
node["reftarget"] = f"python:{target}"
continue
module = node.get("py:module", "")
classname = node.get("py:class", "")
candidates = [target, f"{module}.{classname}.{target}", f"{module}.{target}"]
if target.startswith("."):
candidates.insert(0, module + target)
# Resolve within the declared scope before searching globally. The
# imported object remains in AutoAPI's model even when it is not rendered.
for candidate in candidates:
if candidate in objects:
target = _canonical_name(objects, candidate) if node.get("reftype") != "mod" else candidate
break
else:
if "." not in target:
matches = {
name
for name, obj in objects.items()
if name.endswith(f".{target}") and obj.display and not obj.imported
}
if len(matches) == 1:
target = matches.pop()

target = _EXTERNAL_ALIASES.get(target, target)
if target.startswith("np."):
target = "numpy." + target[3:]
obj = objects.get(target)
if (
obj is not None and not obj.display and not obj.imported
) or target == "multiprocessing.context.BaseContext":
# Keep known internal types readable without linking to deliberately
# omitted API entries. Python has no inventory entry for BaseContext.
# Unknown YAQS names still pass through to Sphinx's strict check.
node.replace_self(nodes.literal("", node.astext()))
continue
node["reftarget"] = target
if node.get("reftype") == "class" and any(
target in inventory.get(role, {}) for role in ("py:data", "py:attribute", "py:type")
):
# NumPy documents NDArray as data and scalar dtypes as attributes.
# Generic object roles retain the exact type and use those entries.
node["reftype"] = "obj"


def _register_public_aliases(app: Sphinx, env: object) -> None:
"""Keep public re-export names available to downstream intersphinx users."""
del env
objects = getattr(app.env, "autoapi_all_objects", {})
domain = app.env.domains["py"]
canonical_entries = dict(domain.objects)
aliases = {}
for name, obj in objects.items():
if not obj.imported or obj.obj.get("hide"):
continue
canonical = _canonical_name(objects, name)
for target, entry in canonical_entries.items():
if target == canonical or target.startswith(f"{canonical}."):
alias = name + target[len(canonical) :]
aliases[alias] = ObjectEntry(entry.docname, entry.node_id, entry.objtype, aliased=True)
domain.data["yaqs_public_aliases"] = aliases


def _markdown_images(app: Sphinx, doctree: nodes.document, docname: str) -> None:
"""Keep local image links valid when sphinx-llm merges Markdown outputs."""
if app.builder.name not in {"llms-markdown", "markdown"}:
return
output = Path(app.outdir)
if app.tags.has("sphinx_llm_markdown"):
output = output.parent
for node in doctree.findall(nodes.image):
image = app.env.images.get(node["uri"])
if image is None:
continue
destination = output / "_images" / image[1]
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(Path(app.srcdir) / node["uri"], destination)
node["uri"] = "../" * docname.count("/") + f"_images/{image[1]}"


def _visit_markdown_abbreviation(translator: MarkdownTranslator, node: nodes.abbreviation) -> None:
"""Keep abbreviation explanations and signature separators in Markdown."""
translator.add(f'<abbr title="{escape(node.get("explanation", ""), quote=True)}">')


def _depart_markdown_abbreviation(translator: MarkdownTranslator, node: nodes.abbreviation) -> None:
"""Close an abbreviation after the translator has rendered its text."""
del node
translator.add("</abbr>")


def setup(app: Sphinx) -> dict[str, bool]:
"""Install API presentation hooks.

Returns:
Parallel build support declarations.
"""
if "autoapi-skip-member" in app.events.events:
app.connect("autoapi-skip-member", _public_imports)
app.add_domain(_APIPythonDomain, override=True)
app.add_node(
nodes.abbreviation,
override=True,
markdown=(_visit_markdown_abbreviation, _depart_markdown_abbreviation),
)
app.connect("doctree-read", _resolve_api_references)
app.connect("env-updated", _register_public_aliases)
app.connect("doctree-resolved", _markdown_images)
return {"parallel_read_safe": True, "parallel_write_safe": True}
Loading
Loading