From 96ea1c0fc2c24fd521571b8d403e258cb2a7aa55 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Tue, 29 Sep 2026 15:18:46 -0400 Subject: [PATCH 1/5] Add a minimal Sphinx docs build with reno release notes The project had no documentation build at all: no `docs/`, no reno, and no docs CI. Everything user-facing lived in the README, whose hand-written "API Reference" section duplicated the module structure by hand and was free to drift from the code. This adds the smallest thing that is actually real: an API reference generated from the existing docstrings, a working reno setup, and a CI job that fails a pull request when either breaks. Guides and notebooks are deliberately out of scope and can be layered on later without redoing any of this. Two properties of this package shaped the setup. First, autodoc needs the package genuinely installed rather than merely on `sys.path`: the import name `sbd` does not match its directory `python/`, and only setuptools knows the `package-dir` mapping, so the docs environment builds and installs the wheel like the test environments do. Second, importing `sbd` is cheap and GPU-free because backends load lazily, so a CPU-only build on a GPU-less runner is enough to document it. Two details differ from the equivalent setup in qiskit-addon-sqd, where a verbatim copy would have been wrong here: - `linkcode_resolve` needs two separate spellings, since the installed directory (`sbd/`) and the in-repository directory (`python/`) differ. Using one token for both would 404 on every "source" link. - The `sbd` page excludes the `sbd_solver` member. It appears in `sbd.__all__` as a submodule while also having a page of its own, which is a duplicate object description and therefore fatal under `-W`. Enabling `-W` surfaced two pre-existing docstring defects, both fixed here: an unmarked indented block in `assemble_rdms` is now a literal block, which also renders the index formulas as intended, and the over-indented alias list in `init` is reflowed to the indentation napoleon expects. Note that reno only sees notes that git tracks, so a newly added note must be staged before it renders. The release-notes page is also cached across incremental builds; `tox -e docs-clean` forces it to be regenerated. Assisted-by: Claude Opus 5 --- .github/workflows/docs.yml | 69 +++++++++ .gitignore | 4 + docs/_static/.gitkeep | 0 docs/_static/images/qiskit-dark-logo.svg | 178 +++++++++++++++++++++ docs/_static/images/qiskit-light-logo.svg | 178 +++++++++++++++++++++ docs/_templates/autosummary/class.rst | 33 ++++ docs/apidocs/index.rst | 10 ++ docs/apidocs/sbd.device_config.rst | 8 + docs/apidocs/sbd.rst | 9 ++ docs/apidocs/sbd.sbd_solver.rst | 8 + docs/conf.py | 180 ++++++++++++++++++++++ docs/index.rst | 56 +++++++ docs/release-notes.rst | 3 + pyproject.toml | 10 ++ python/__init__.py | 10 +- python/sbd_solver.py | 4 +- releasenotes/config.yaml | 5 + releasenotes/notes/.gitkeep | 0 tox.ini | 27 +++- 19 files changed, 785 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/_static/.gitkeep create mode 100644 docs/_static/images/qiskit-dark-logo.svg create mode 100644 docs/_static/images/qiskit-light-logo.svg create mode 100644 docs/_templates/autosummary/class.rst create mode 100644 docs/apidocs/index.rst create mode 100644 docs/apidocs/sbd.device_config.rst create mode 100644 docs/apidocs/sbd.rst create mode 100644 docs/apidocs/sbd.sbd_solver.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/release-notes.rst create mode 100644 releasenotes/config.yaml create mode 100644 releasenotes/notes/.gitkeep diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..286990d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,69 @@ +name: Build Sphinx docs + +on: + workflow_dispatch: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+*" + branches: + - main + - 'stable/**' + pull_request: + branches: + - main + - 'stable/**' +jobs: + build: + name: Build docs + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v7 + with: + # reno reads the tags and their history to assemble the release notes, and + # setup.py needs the vendored upstream headers to compile the extension. + fetch-depth: 0 + submodules: recursive + - uses: actions/setup-python@v7 + with: + python-version: '3.10' + - name: Install dependencies + # autodoc imports the package, so the extension has to compile here -- hence the + # same MPI and BLAS packages the test workflow installs, rather than the lighter + # set a pure-Python docs build would need. + run: | + python -m pip install --upgrade pip + pip install tox + sudo apt-get update + sudo apt-get install -y libopenmpi-dev openmpi-bin libopenblas-dev + - name: Build docs + shell: bash + # CPU only: the runner has no GPU toolchain, and the API reference does not + # depend on which backends were compiled. + env: + SBD_BUILD_BACKEND: cpu + run: | + tox -edocs + - name: Upload docs artifact + # Uploaded even on failure, which pairs with sphinx-build's --keep-going: the + # partial HTML is usually the fastest way to see what went wrong. + if: always() + uses: actions/upload-pages-artifact@v5 + with: + path: docs/_build/html + + deploy: + name: Deploy docs + if: ${{ github.ref == 'refs/heads/main' }} + needs: build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index 58e68a6..ef20cb7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ build/ dist/ *.egg-info/ +# Sphinx documentation +docs/_build/ +docs/stubs/ + # editor / OS noise .DS_Store *.swp diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/_static/images/qiskit-dark-logo.svg b/docs/_static/images/qiskit-dark-logo.svg new file mode 100644 index 0000000..b520890 --- /dev/null +++ b/docs/_static/images/qiskit-dark-logo.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/images/qiskit-light-logo.svg b/docs/_static/images/qiskit-light-logo.svg new file mode 100644 index 0000000..25b27dd --- /dev/null +++ b/docs/_static/images/qiskit-light-logo.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst new file mode 100644 index 0000000..175b08f --- /dev/null +++ b/docs/_templates/autosummary/class.rst @@ -0,0 +1,33 @@ +{# + We show all the class's methods and attributes on the same page. By default, we document + all methods, including those defined by parent classes. +-#} + +{{ objname | escape | underline }} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :no-members: + :no-inherited-members: + :no-special-members: + :show-inheritance: + +{% block attributes_summary %} + {% if attributes %} + .. rubric:: Attributes + {% for item in attributes %} + .. autoattribute:: {{ item }} + {%- endfor %} + {% endif %} +{% endblock -%} + +{% block methods_summary %} + {% set wanted_methods = (methods | reject('==', '__init__') | list) %} + {% if wanted_methods %} + .. rubric:: Methods + {% for item in wanted_methods %} + .. automethod:: {{ item }} + {%- endfor %} + {% endif %} +{% endblock %} diff --git a/docs/apidocs/index.rst b/docs/apidocs/index.rst new file mode 100644 index 0000000..58288ee --- /dev/null +++ b/docs/apidocs/index.rst @@ -0,0 +1,10 @@ +********************************* +``sbd-eigensolver`` API reference +********************************* + +.. toctree:: + :maxdepth: 1 + + sbd + sbd.sbd_solver + sbd.device_config diff --git a/docs/apidocs/sbd.device_config.rst b/docs/apidocs/sbd.device_config.rst new file mode 100644 index 0000000..7c1a7d1 --- /dev/null +++ b/docs/apidocs/sbd.device_config.rst @@ -0,0 +1,8 @@ +================================================ +Device configuration (:mod:`sbd.device_config`) +================================================ + +.. automodule:: sbd.device_config + :members: + :no-inherited-members: + :no-special-members: diff --git a/docs/apidocs/sbd.rst b/docs/apidocs/sbd.rst new file mode 100644 index 0000000..2e256e8 --- /dev/null +++ b/docs/apidocs/sbd.rst @@ -0,0 +1,9 @@ +============================= +SBD bindings (:mod:`sbd`) +============================= + +.. automodule:: sbd + :members: + :exclude-members: sbd_solver + :no-inherited-members: + :no-special-members: diff --git a/docs/apidocs/sbd.sbd_solver.rst b/docs/apidocs/sbd.sbd_solver.rst new file mode 100644 index 0000000..65be11d --- /dev/null +++ b/docs/apidocs/sbd.sbd_solver.rst @@ -0,0 +1,8 @@ +============================================= +SQD-compatible solver (:mod:`sbd.sbd_solver`) +============================================= + +.. automodule:: sbd.sbd_solver + :members: + :no-inherited-members: + :no-special-members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..47eeaa6 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,180 @@ +# This code is a Qiskit project. +# +# (C) Copyright IBM 2026. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Sphinx configuration for the sbd-eigensolver documentation.""" + +import inspect +import os +import re +import sys +from importlib.metadata import version as metadata_version + +# Note there is deliberately no `sys.path` manipulation here. The import name of this +# package (`sbd`) does not match the directory it lives in (`python/`); the mapping is +# declared as `package-dir = {sbd = "python"}` in pyproject.toml and only setuptools +# knows about it. Autodoc therefore needs the package genuinely *installed*, which is +# also what makes the `metadata_version` call below work. + +project = "Selected Basis Diagonalization (SBD)" +project_copyright = "2026, IBM Quantum" +description = "Python bindings for the SBD eigensolver library" +author = "IBM Quantum" +language = "en" +# The *distribution* name ("sbd-eigensolver"), not the import name ("sbd"). +release = metadata_version("sbd-eigensolver") + +html_theme = "qiskit-ecosystem" + +html_theme_options = { + "dark_logo": "images/qiskit-dark-logo.svg", + "light_logo": "images/qiskit-light-logo.svg", + "sidebar_qiskit_ecosystem_member": False, +} +html_static_path = ["_static"] +templates_path = ["_templates"] + +# Sphinx should ignore these patterns when building. +exclude_patterns = [ + "_build", +] + +extensions = [ + "sphinx.ext.napoleon", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.mathjax", + "sphinx.ext.linkcode", + "sphinx.ext.intersphinx", + "sphinx_copybutton", + "reno.sphinxext", + "qiskit_sphinx_theme", +] + +html_last_updated_fmt = "%Y/%m/%d" +html_title = f"{project} {release}" + +# This allows RST files to put `|version|` in their file and +# have it updated with the release set in conf.py. +rst_prolog = f""" +.. |version| replace:: {release} +""" + +# Options for autodoc. These reflect the values from Qiskit SDK and Runtime. +autosummary_generate = True +autosummary_generate_overwrite = False +autoclass_content = "both" +autodoc_typehints = "description" +autodoc_default_options = { + "inherited-members": None, + "show-inheritance": True, +} +napoleon_google_docstring = True +napoleon_numpy_docstring = False + +# This adds numbers to the captions for figures, tables, +# and code blocks. +numfig = True +numfig_format = {"table": "Table %s"} + +add_module_names = False + +modindex_common_prefix = ["sbd."] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable/", None), +} + +# ---------------------------------------------------------------------------------- +# Source code links +# ---------------------------------------------------------------------------------- + +# The package is imported as `sbd` but stored in the repository under `python/`, so the +# two halves of a source link need different spellings: the installed file path is +# split on one, and the URL is built with the other. +_IMPORT_NAME = "sbd" +_REPO_SUBDIR = "python" + + +def determine_github_branch() -> str: + """Determine the GitHub branch name to use for source code links. + + We need to decide whether to use `stable/` vs. `main` for dev builds. + Refer to https://docs.github.com/en/actions/learn-github-actions/variables + for how we determine this with GitHub Actions. + """ + # If CI env vars not set, default to `main`. This is relevant for local builds. + if "GITHUB_REF_NAME" not in os.environ: + return "main" + + # PR workflows set the branch they're merging into. + if base_ref := os.environ.get("GITHUB_BASE_REF"): + return base_ref + + ref_name = os.environ["GITHUB_REF_NAME"] + + # Check if the ref_name is a tag like `1.0.0` or `1.0.0rc1`. If so, we need + # to transform it to a Git branch like `stable/1.0`. + version_without_patch = re.match(r"(\d+\.\d+)", ref_name) + return f"stable/{version_without_patch.group()}" if version_without_patch else ref_name + + +GITHUB_BRANCH = determine_github_branch() + + +def linkcode_resolve(domain, info): + """Point the "source" link of each documented object at GitHub.""" + if domain != "py": + return None + + module_name = info["module"] + module = sys.modules.get(module_name) + if module is None or _IMPORT_NAME not in module_name: + return None + + def is_valid_code_object(obj): + return inspect.isclass(obj) or inspect.ismethod(obj) or inspect.isfunction(obj) + + obj = module + for part in info["fullname"].split("."): + try: + obj = getattr(obj, part) + except AttributeError: + return None + if not is_valid_code_object(obj): + return None + + # Unwrap decorators. This requires they used `functools.wrap()`. + while hasattr(obj, "__wrapped__"): + obj = obj.__wrapped__ + if not is_valid_code_object(obj): + return None + + try: + full_file_name = inspect.getsourcefile(obj) + except TypeError: + return None + if full_file_name is None or f"/{_IMPORT_NAME}/" not in full_file_name: + return None + file_name = full_file_name.split(f"/{_IMPORT_NAME}/")[-1] + + try: + source, lineno = inspect.getsourcelines(obj) + except (OSError, TypeError): + linespec = "" + else: + ending_lineno = lineno + len(source) - 1 + linespec = f"#L{lineno}-L{ending_lineno}" + return ( + "https://github.com/Qiskit/sbd-eigensolver-python/tree/" + f"{GITHUB_BRANCH}/{_REPO_SUBDIR}/{file_name}{linespec}" + ) diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..4dcd859 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,56 @@ +##################################### +Selected Basis Diagonalization (SBD) +##################################### + +``sbd-eigensolver`` provides Python bindings for the SBD (Selected Basis +Diagonalization) library, which finds eigenvalues and eigenvectors of a +second-quantized Hamiltonian projected onto a subspace spanned by a selected set of +determinants. The bindings are MPI-parallel and can run on CPUs or, where a suitable +toolchain is available, on NVIDIA or AMD GPUs. + +The package also exposes a solver compatible with the ``qiskit-addon-sqd`` interface, +so SBD can be used as the diagonalization step of a sample-based quantum +diagonalization (SQD) workflow. See :mod:`sbd.sbd_solver`. + +Getting started +--------------- + +Installation, the environment variables that control which backends are compiled, and +runnable examples are documented in the `README +`__ in the root +of this project's repository. Example scripts and a notebook live in `python/examples +`__. + +A minimal diagonalization looks like this:: + + import sbd + + config = sbd.TPB_SBD() + results = sbd.tpb_diag_from_files("FCIDUMP", "adets.dat", config) + +The backend is initialized automatically on first use; :func:`sbd.init` only needs to +be called to select a device explicitly. + +Contributing +------------ + +The source code is available `on GitHub +`__. + +We use `GitHub issues +`__ for tracking requests and +bugs. + +License +------- + +`Apache License 2.0 +`__ + +.. toctree:: + :hidden: + + Documentation home + API reference + Release notes + GitHub diff --git a/docs/release-notes.rst b/docs/release-notes.rst new file mode 100644 index 0000000..9523d94 --- /dev/null +++ b/docs/release-notes.rst @@ -0,0 +1,3 @@ +.. _release notes: + +.. release-notes:: Release Notes diff --git a/pyproject.toml b/pyproject.toml index ab324c3..a235da2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,16 @@ nbtest = [ "sbd-eigensolver[basetest]", "nbmake>=1.5.0", ] +docs = [ + # The API reference is generated from the docstrings by autodoc, which imports the + # package -- so the docs build needs the compiled extension, and `sbd.sbd_solver` + # additionally needs qiskit-addon-sqd and pyscf importable. The `test` extra + # already pulls both in. + "sbd-eigensolver[test]", + "qiskit-sphinx-theme~=2.1.0", + "sphinx-copybutton", + "reno>=4.1", +] notebook-dependencies = [ "qiskit-addon-sqd>=0.13.1", "pyscf>=2.9", diff --git a/python/__init__.py b/python/__init__.py index c2a683c..33ac1b5 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -321,11 +321,11 @@ def init(device='cpu', comm_backend='mpi'): Args: device: Default compute device — 'cpu', 'gpu', 'gpu-omp', or 'auto'. - 'gpu' is the NVIDIA-only Thrust backend; 'gpu-omp' is OpenMP - target offload and serves NVIDIA and AMD alike. - Aliases: 'gpu-thrust' / 'gpu-nvidia' / 'cuda' (= 'gpu'); - 'gpu-omp-offload' / 'gpu-nvhpc-omp' / 'gpu-nvidia-omp' / - 'gpu-amd-omp' / 'gpu-rocm-omp' / 'rocm' (= 'gpu-omp'). + 'gpu' is the NVIDIA-only Thrust backend; 'gpu-omp' is OpenMP + target offload and serves NVIDIA and AMD alike. + Aliases for 'gpu': 'gpu-thrust', 'gpu-nvidia', 'cuda'. + Aliases for 'gpu-omp': 'gpu-omp-offload', 'gpu-nvhpc-omp', + 'gpu-nvidia-omp', 'gpu-amd-omp', 'gpu-rocm-omp', 'rocm'. comm_backend: Communication backend — 'mpi'. Raises: diff --git a/python/sbd_solver.py b/python/sbd_solver.py index e9fb489..d22b13e 100644 --- a/python/sbd_solver.py +++ b/python/sbd_solver.py @@ -285,9 +285,11 @@ def assemble_rdms(results: dict, norb: int) -> tuple[np.ndarray | None, np.ndarr qiskit-addon-sqd (e.g. ``fermion.py``'s own ``solve_fermion``). SBD's documented layout (sbd-ext docs/user-guide.md, matching the C++ - reference in apps/chemistry_tpb_selected_basis_diagonalization/main.cc): + reference in apps/chemistry_tpb_selected_basis_diagonalization/main.cc):: + one_p_rdm[s][i + L*j] = two_p_rdm[s+2t][i + L*j + L^2*k + L^3*l] = + A Fortran-order reshape implements those flat-index formulas directly (arr_F[i, j] / arr_F[i, j, k, l]); rdm1 needs no further transpose (it is symmetric here regardless), and rdm2's spin-summed block sum diff --git a/releasenotes/config.yaml b/releasenotes/config.yaml new file mode 100644 index 0000000..0513169 --- /dev/null +++ b/releasenotes/config.yaml @@ -0,0 +1,5 @@ +--- +encoding: utf8 +default_branch: main +unreleased_version_title: "Upcoming release" +earliest_version: 1.6.1 diff --git a/releasenotes/notes/.gitkeep b/releasenotes/notes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tox.ini b/tox.ini index dd3ecb9..55f0681 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.4.3 -envlist = py{310,311,312,313,314}{,-notebook}, mpi +envlist = py{310,311,312,313,314}{,-notebook}, mpi, docs isolated_build = True [testenv] @@ -71,6 +71,31 @@ extras = commands = pytest --nbmake --nbmake-timeout=3000 {posargs} python/examples/ +[testenv:docs] +# Unlike a pure-Python project, the docs build cannot skip installing the package: +# autodoc imports `sbd` to read its docstrings, and conf.py reads the version from the +# installed distribution metadata. `package`/`wheel_build_env` and the MPI/BLAS +# `passenv` are therefore inherited from [testenv] -- the extension has to compile +# here just as it does for the tests, and it reuses the same wheel. +extras = + docs +passenv = + {[testenv]passenv} + # Consulted by conf.py's determine_github_branch() to aim the source-code links at + # the right branch. + CI + GITHUB_BASE_REF + GITHUB_REF_NAME +commands = + sphinx-build -j auto -W -T --keep-going -b html {posargs} {toxinidir}/docs/ {toxinidir}/docs/_build/html + +[testenv:docs-clean] +skip_install = true +allowlist_externals = + rm +commands = + rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build/ + [testenv:slow] # The reference cases grow by roughly an order of magnitude in determinant count per # row of the upstream tables, and compute time scales worse than linearly in that, so From b6c23cff7b124c1945384be6904a1d312e7130a8 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Tue, 29 Sep 2026 17:18:17 -0400 Subject: [PATCH 2/5] Add release notes for everything since 1.6.1 PR #43 adds the reno machinery but deliberately ships no notes, so the release-notes page renders empty. Twenty-eight commits have landed since the 1.6.1 tag, several of which change behavior a user has to know about, and none of it is written down anywhere a user will look. Backfill that history as 19 notes: 6 upgrade, 7 features, 6 fixes. The 1.6.1 tag is far behind main, so every commit covered here is unreleased and the notes render under "Upcoming release" until 1.7.0 is tagged; they need no coordination with the version bump in #42. The upgrade notes are the point of the exercise. Multi-iteration SQD results move, because the amplitudes handed back to the SQD loop were being replaced by a uniform array and are now correct. h_comm_size is gone from the config objects, which raises AttributeError on assignment and, less obviously, is silently ignored as an sbd_config key. carryover_type now defaults to 0, SBD_BUILD_BACKEND=both is rejected in favor of all, backends load one per process with OMP_TARGET_OFFLOAD defaulted to MANDATORY, and pybind11 is no longer a runtime dependency. Examples get one combined note rather than one per pull request: they are scripts in the repository, not importable API, so the note records the parameter regrouping, the dropped Hartree-Fock seed and the new enlarge-subspace driver without itemizing seven pull requests. CI and documentation-only changes get no notes. Three further changes are deliberately omitted as invisible to a 1.6.1 user: the importlib.metadata version switch, the vendored upstream bump (the makestring template instantiation preserves the exposed signature), and the internal GPU build-flag cleanups. Assisted-by: Claude Opus 5 --- .../amd-gpu-support-5c81e3a749f206bd.yaml | 14 ++++++++++ ...ackend-introspection-2c98d4e15b07f36a.yaml | 18 ++++++++++++ ...backend-all-not-both-6d3e08f5b27a1c94.yaml | 14 ++++++++++ ...er-type-default-zero-2b7f4a86d1c3e09f.yaml | 14 ++++++++++ ...pybind11-runtime-dep-3e5b8f20a7c496d1.yaml | 8 ++++++ .../examples-reworked-6e03b8a51d97c42f.yaml | 28 +++++++++++++++++++ ...fix-amplitude-labels-9c2f60b81a4e37d5.yaml | 17 +++++++++++ ...h-undefined-behavior-3f8a51c072be946d.yaml | 18 ++++++++++++ ...x-device-config-auto-5e8c31f70a9b26d4.yaml | 10 +++++++ ...abricated-amplitudes-0b7d4e8a26c19f53.yaml | 20 +++++++++++++ .../fix-rocm-detection-2a4b96e0d75c831f.yaml | 15 ++++++++++ ...ix-temp-dir-deadlock-7d19b5e82c034af6.yaml | 11 ++++++++ .../notes/get-device-id-8f25c091e7b43ad6.yaml | 16 +++++++++++ ...lazy-backend-loading-9a17c4be250d3f86.yaml | 23 +++++++++++++++ .../notes/macos-support-7b4f92c05e83a1d6.yaml | 10 +++++++ ...late-sci-result-rdms-1d6a05f8c43b297e.yaml | 17 +++++++++++ .../remove-h-comm-size-4f2a91c7d3b0e85a.yaml | 16 +++++++++++ ...tudes-change-results-8c1d5e07a94f2b63.yaml | 13 +++++++++ ...st-mpi-build-options-4a6d17b9c8e052f3.yaml | 13 +++++++++ 19 files changed, 295 insertions(+) create mode 100644 releasenotes/notes/amd-gpu-support-5c81e3a749f206bd.yaml create mode 100644 releasenotes/notes/backend-introspection-2c98d4e15b07f36a.yaml create mode 100644 releasenotes/notes/build-backend-all-not-both-6d3e08f5b27a1c94.yaml create mode 100644 releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml create mode 100644 releasenotes/notes/drop-pybind11-runtime-dep-3e5b8f20a7c496d1.yaml create mode 100644 releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml create mode 100644 releasenotes/notes/fix-amplitude-labels-9c2f60b81a4e37d5.yaml create mode 100644 releasenotes/notes/fix-bit-length-undefined-behavior-3f8a51c072be946d.yaml create mode 100644 releasenotes/notes/fix-device-config-auto-5e8c31f70a9b26d4.yaml create mode 100644 releasenotes/notes/fix-fabricated-amplitudes-0b7d4e8a26c19f53.yaml create mode 100644 releasenotes/notes/fix-rocm-detection-2a4b96e0d75c831f.yaml create mode 100644 releasenotes/notes/fix-temp-dir-deadlock-7d19b5e82c034af6.yaml create mode 100644 releasenotes/notes/get-device-id-8f25c091e7b43ad6.yaml create mode 100644 releasenotes/notes/lazy-backend-loading-9a17c4be250d3f86.yaml create mode 100644 releasenotes/notes/macos-support-7b4f92c05e83a1d6.yaml create mode 100644 releasenotes/notes/populate-sci-result-rdms-1d6a05f8c43b297e.yaml create mode 100644 releasenotes/notes/remove-h-comm-size-4f2a91c7d3b0e85a.yaml create mode 100644 releasenotes/notes/sqd-amplitudes-change-results-8c1d5e07a94f2b63.yaml create mode 100644 releasenotes/notes/thrust-mpi-build-options-4a6d17b9c8e052f3.yaml diff --git a/releasenotes/notes/amd-gpu-support-5c81e3a749f206bd.yaml b/releasenotes/notes/amd-gpu-support-5c81e3a749f206bd.yaml new file mode 100644 index 0000000..40a1f98 --- /dev/null +++ b/releasenotes/notes/amd-gpu-support-5c81e3a749f206bd.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + AMD GPUs are now supported through the OpenMP target-offload backend. The same + extension source builds under either vendor -- ``nvc++ -mp=gpu`` for NVIDIA, + ``amdclang++ --offload-arch=gfx*`` for AMD -- so the ``'gpu-omp'`` device is + vendor-neutral. The aliases ``'gpu-amd-omp'``, ``'gpu-rocm-omp'`` and + ``'rocm'`` resolve to it, alongside the existing ``'gpu-omp-offload'``, + ``'gpu-nvhpc-omp'`` and ``'gpu-nvidia-omp'``, and + ``sbd.get_backend('gpu-omp').__sbd_offload_target__`` reports the target a + given installation was actually built for. + + The Thrust backend (``'gpu'``) remains NVIDIA-only, since upstream wires it to + ``nvc++ -cuda`` and there is no rocThrust configuration to build. diff --git a/releasenotes/notes/backend-introspection-2c98d4e15b07f36a.yaml b/releasenotes/notes/backend-introspection-2c98d4e15b07f36a.yaml new file mode 100644 index 0000000..2ed35ba --- /dev/null +++ b/releasenotes/notes/backend-introspection-2c98d4e15b07f36a.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + Three functions have been added for inspecting how the compiled backends are + behaving in a given process: :func:`sbd.loaded_backends` lists the backends + actually imported so far, :func:`sbd.backend_load_errors` maps each unusable + backend to the reason it is unusable -- distinguishing "not built" from a + missing shared library, an architecture mismatch or a failed import -- and + :func:`sbd.has_backend_conflict` reports whether this process has loaded a + combination of backends that silently disables GPU offload. + + :func:`sbd.available_backends` now judges each candidate extension statically, + by checking that it is present with no unresolved shared-library dependencies + and a matching architecture. It deliberately does not import it: importing a + backend runs ``MPI_Init``, which on an MPI without PMIx support hangs when the + process was not started under a launcher. A listed backend is therefore + present and structurally sound rather than guaranteed to load, since an + ABI mismatch only surfaces on a real import. diff --git a/releasenotes/notes/build-backend-all-not-both-6d3e08f5b27a1c94.yaml b/releasenotes/notes/build-backend-all-not-both-6d3e08f5b27a1c94.yaml new file mode 100644 index 0000000..49228b9 --- /dev/null +++ b/releasenotes/notes/build-backend-all-not-both-6d3e08f5b27a1c94.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + The ``SBD_BUILD_BACKEND=both`` build option has been replaced by + ``SBD_BUILD_BACKEND=all``; the old spelling is now rejected with an error + rather than silently misinterpreted. The accepted values are ``auto`` (the + default), ``all``, ``cpu``, ``gpu`` (alias ``gpu_thrust``) and + ``gpu_omp_offload``. + + ``auto`` now builds every backend the detected toolchain supports, which on + NVHPC means all three rather than just CPU and Thrust, so one installation can + serve CPU and both GPU paths. Since Thrust is NVIDIA-only, + ``SBD_BUILD_BACKEND=gpu`` now fails on an AMD host instead of quietly building + the CPU backend alone; use ``gpu_omp_offload`` or ``auto`` there. diff --git a/releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml b/releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml new file mode 100644 index 0000000..2b7af97 --- /dev/null +++ b/releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + :func:`sbd.sbd_solver.solve_sci` and + :func:`sbd.sbd_solver.solve_sci_batch` now configure SBD with + ``carryover_type = 0`` instead of ``1``. SBD's carryover is its own iterative + mechanism for choosing the next subspace, whereas an SQD loop selects the next + subspace itself from the returned amplitudes -- the solver discards SBD's + carryover lists entirely, so computing them was pure work, which for + ``carryover_type = 2`` includes building singles-extended determinant lists. + + Energies are bit-identical across carryover types, so this is a speedup rather + than a change in results. Pass ``sbd_config={"carryover_type": 1}`` to restore + the previous setting. diff --git a/releasenotes/notes/drop-pybind11-runtime-dep-3e5b8f20a7c496d1.yaml b/releasenotes/notes/drop-pybind11-runtime-dep-3e5b8f20a7c496d1.yaml new file mode 100644 index 0000000..ad1a22c --- /dev/null +++ b/releasenotes/notes/drop-pybind11-runtime-dep-3e5b8f20a7c496d1.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + ``pybind11`` is no longer a runtime dependency. It is needed only to compile + the extension modules and remains declared under ``[build-system] requires``, + but nothing in the installed package imports it, so every installation had + been pulling it in for nothing. The runtime dependencies are now ``mpi4py`` + and ``numpy``. diff --git a/releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml b/releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml new file mode 100644 index 0000000..5a71c3a --- /dev/null +++ b/releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml @@ -0,0 +1,28 @@ +--- +features: + - | + The example drivers shipped under ``python/examples`` have been substantially + reworked. Their command-line options are now grouped by the layer they act on, + with the SBD solver's options prefixed to distinguish them from the SQD loop's + -- several names previously read as one layer and acted on the other -- and + every former spelling is kept as an alias. Three SQD options that had no + command-line spelling at all, the energy and occupancy convergence tolerances + and the loop's carryover threshold, now reach the SQD loop instead of being + stuck at its defaults. + + The Hartree-Fock occupancy seed has been dropped from the SQD driver. Passing + it replaced postselection with configuration recovery in every run, so at the + first iteration -- where the only occupancies available are mean-field, with + every virtual orbital at exactly zero -- the subspace was largely synthesized + by bit flips against that prior rather than built from sampled + configurations. The driver now postselects at the first iteration and uses + solver-derived occupancies thereafter, which is what published SQD does. + - | + A new example driver, ``run_sqd_enlarge_subspace_sbd.py``, grows its own + subspace between diagonalizations. It drives the SQD loop one iteration at a + time and, after each solve, expands the dominant determinants through + same-spin single excitations using ``qiskit-addon-sqd``'s own excitation + utilities, feeding the result forward as the next round's configurations. It + stops when the expanded set adds nothing new -- the subspace is closed under + single-excitation connectivity -- or when both the energy and the occupancies + stop moving. diff --git a/releasenotes/notes/fix-amplitude-labels-9c2f60b81a4e37d5.yaml b/releasenotes/notes/fix-amplitude-labels-9c2f60b81a4e37d5.yaml new file mode 100644 index 0000000..45d8feb --- /dev/null +++ b/releasenotes/notes/fix-amplitude-labels-9c2f60b81a4e37d5.yaml @@ -0,0 +1,17 @@ +--- +fixes: + - | + Wavefunction amplitudes are now labeled with the determinants that were + actually diagonalized rather than with the CI strings passed in. The two can + differ: the input is sorted into SBD's canonical order and deduplicated before + diagonalization, so amplitudes and labels are now taken from one list by + construction. + + In practice this fixes duplicate CI strings, which the sorting step documents + that it removes. A caller who passed them got a ``RuntimeError`` complaining + that the amplitude count did not match the subspace size, rejecting a run that + had in fact succeeded; such a call now completes, returning the amplitudes for + the distinct determinants. Ordering was not observably wrong before, since the + canonical order happens to coincide with ascending integer order, but nothing + upstream guarantees that -- and if it ever stopped holding, the result would + have been correct amplitudes against the wrong labels, with no error raised. diff --git a/releasenotes/notes/fix-bit-length-undefined-behavior-3f8a51c072be946d.yaml b/releasenotes/notes/fix-bit-length-undefined-behavior-3f8a51c072be946d.yaml new file mode 100644 index 0000000..9b90afb --- /dev/null +++ b/releasenotes/notes/fix-bit-length-undefined-behavior-3f8a51c072be946d.yaml @@ -0,0 +1,18 @@ +--- +fixes: + - | + The determinant word size used by :func:`sbd.sbd_solver.solve_sci` and + :func:`sbd.sbd_solver.solve_sci_batch` no longer defaults to 64, which was + undefined behavior. SBD packs determinant bitstrings into words of + ``bit_length`` bits and builds its masks by shifting a 64-bit word left by + that amount; a shift of 64 is undefined, and in practice the shift count is + masked to zero, collapsing the mask. The affected routine is reached from the + multi-rank determinant redistribution and sorting paths. The default is now + 20, matching upstream's documented default, and the maximum usable value is + 63. + - | + A caller-supplied ``bit_length`` in ``sbd_config`` is now honored end to end. + The value previously reached the C++ engine but not the code that packs the + determinants, which kept using 64, so the two disagreed about how to interpret + the same words and the determinants were silently corrupted. The configuration + is now built before packing, so one value is used throughout. diff --git a/releasenotes/notes/fix-device-config-auto-5e8c31f70a9b26d4.yaml b/releasenotes/notes/fix-device-config-auto-5e8c31f70a9b26d4.yaml new file mode 100644 index 0000000..47885c3 --- /dev/null +++ b/releasenotes/notes/fix-device-config-auto-5e8c31f70a9b26d4.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + ``DeviceConfig.auto()`` now accounts for which backends were actually built, + not only for what hardware is present. It previously returned the Thrust GPU + device whenever any GPU was detected, which selected a backend that cannot + exist on an AMD host, and on a CPU-only build running on a machine with a GPU + it selected a backend that had not been compiled. The available devices are + now intersected with the backends present, so a CPU-only installation resolves + to the CPU and an AMD host resolves to the OpenMP target-offload backend. diff --git a/releasenotes/notes/fix-fabricated-amplitudes-0b7d4e8a26c19f53.yaml b/releasenotes/notes/fix-fabricated-amplitudes-0b7d4e8a26c19f53.yaml new file mode 100644 index 0000000..b337ec5 --- /dev/null +++ b/releasenotes/notes/fix-fabricated-amplitudes-0b7d4e8a26c19f53.yaml @@ -0,0 +1,20 @@ +--- +fixes: + - | + The wavefunction amplitudes returned by :func:`sbd.sbd_solver.solve_sci` and + :func:`sbd.sbd_solver.solve_sci_batch` are no longer replaced by a uniform + array. SBD writes the amplitudes for the full input subspace -- the product of + the alpha and beta determinant lists -- regardless of its carryover settings, + but the solver sized its read against the carryover counts instead. Under the + former default those sizes never matched, and the mismatch was answered by + substituting a uniform array: correctly shaped, correctly normalized, and + carrying no information at all. + + This was silent and load-bearing. ``qiskit-addon-sqd`` selects each + iteration's determinants by amplitude magnitude and weights them by the + squared magnitude, so every iteration after the first was seeded from uniform + weights over an already-truncated list of determinants. The amplitudes are now + read over the full subspace and paired with the determinants that span it, and + a missing dump or a size mismatch raises rather than being papered over, since + either means the run did not do what was asked. Energies and orbital + occupancies were never affected; they come from SBD directly. diff --git a/releasenotes/notes/fix-rocm-detection-2a4b96e0d75c831f.yaml b/releasenotes/notes/fix-rocm-detection-2a4b96e0d75c831f.yaml new file mode 100644 index 0000000..3835a19 --- /dev/null +++ b/releasenotes/notes/fix-rocm-detection-2a4b96e0d75c831f.yaml @@ -0,0 +1,15 @@ +--- +fixes: + - | + AMD GPU detection and reporting have been corrected. The ``rocm-smi`` probe ran + with a two-second timeout, which is shorter than the tool takes to answer on a + populated node -- so device detection flaked and could resolve to the CPU on a + machine with eight GPUs. The probe now allows enough time, and requires a + per-device line in the output rather than treating a successful exit as proof + that a GPU is present, since ``rocm-smi`` exits successfully with none. + - | + ``get_device_info()`` no longer overcounts AMD GPUs. It counted every output + line mentioning a GPU, reporting 40 devices on a node with eight, and now + counts distinct device indices. Relatedly, ``print_device_info()`` no longer + reports the CPU as available while :func:`sbd.available_backends` reports that + nothing was compiled; it now leads with the backends that were actually built. diff --git a/releasenotes/notes/fix-temp-dir-deadlock-7d19b5e82c034af6.yaml b/releasenotes/notes/fix-temp-dir-deadlock-7d19b5e82c034af6.yaml new file mode 100644 index 0000000..0602242 --- /dev/null +++ b/releasenotes/notes/fix-temp-dir-deadlock-7d19b5e82c034af6.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + A ``temp_dir`` that does not yet exist is now created, along with any missing + parents, instead of hanging a multi-rank run. Handing such a path to + :func:`sbd.sbd_solver.solve_sci` raised ``FileNotFoundError`` on rank 0, and it + did so before the broadcast that hands the created directory to the other + ranks -- so the remaining ranks waited in that broadcast forever and the job + hung with no output rather than reporting the error. Directory setup is now + wrapped so that its outcome is broadcast: on failure every rank raises + together. diff --git a/releasenotes/notes/get-device-id-8f25c091e7b43ad6.yaml b/releasenotes/notes/get-device-id-8f25c091e7b43ad6.yaml new file mode 100644 index 0000000..f3a39a9 --- /dev/null +++ b/releasenotes/notes/get-device-id-8f25c091e7b43ad6.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + A new :func:`sbd.get_device_id` function reports the GPU index the calling + rank will use, or ``-1`` when there is none -- a CPU-only build, or no visible + devices. It applies the same ``rank % num_gpus`` rule SBD's own + diagonalization uses, asking the backend rather than recomputing it, so + another library placed on the same card cannot drift out of step with SBD. + The device count comes from the GPU runtime rather than from parsing + ``nvidia-smi`` output. + + The query selects no device and creates no context, so it is safe to call + before any GPU work and before another framework initializes its own backend. + That is what it is for: a framework such as JAX, initialized on a multi-GPU + node without being told which device belongs to this rank, will otherwise + claim a card SBD is about to use. diff --git a/releasenotes/notes/lazy-backend-loading-9a17c4be250d3f86.yaml b/releasenotes/notes/lazy-backend-loading-9a17c4be250d3f86.yaml new file mode 100644 index 0000000..45d9845 --- /dev/null +++ b/releasenotes/notes/lazy-backend-loading-9a17c4be250d3f86.yaml @@ -0,0 +1,23 @@ +--- +upgrade: + - | + Compiled backends are now imported lazily, one per process: only the backend + for the device actually in use is loaded. This is what makes it safe for all + three backends to be installed side by side, and it is not merely an + optimization. When the CPU extension and the OpenMP target-offload extension + are both loaded into one process, the OpenMP runtime is left initialized + host-only, after which offload regions run on the host while device queries + still report a GPU -- so the run looks accelerated, returns the correct + energy, and exits successfully. Code that imports the ``_core_*`` extension + modules directly should import only one; :func:`sbd.has_backend_conflict` + reports the bad combination. + - | + ``OMP_TARGET_OFFLOAD`` is now set to ``MANDATORY`` before the OpenMP + target-offload backend is imported, unless it is already set to something + non-empty. Under the OpenMP default of ``DEFAULT``, a rank with no visible + device falls back to the host and returns a plausible answer with no + indication that nothing was offloaded -- reachable from an empty + ``CUDA_VISIBLE_DEVICES``, a mispinning launcher, or a GPU-less node. + ``MANDATORY`` turns each of those into an immediate error. Set + ``OMP_TARGET_OFFLOAD=DEFAULT`` explicitly to restore host fallback, for + example to smoke-test on a machine with no GPU. diff --git a/releasenotes/notes/macos-support-7b4f92c05e83a1d6.yaml b/releasenotes/notes/macos-support-7b4f92c05e83a1d6.yaml new file mode 100644 index 0000000..13b4142 --- /dev/null +++ b/releasenotes/notes/macos-support-7b4f92c05e83a1d6.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + macOS is now a supported platform, declared in the trove classifiers and + covered by CI on both Linux and macOS. Building on macOS requires an OpenMP + runtime, which Apple's clang does not ship -- install one first, for example + with ``brew install libomp``. Homebrew's prefix is discovered by asking + ``brew --prefix`` rather than assuming ``/opt/homebrew``, so Intel and Apple + silicon Macs are both handled, and the built extensions carry the rpath + entries needed to resolve keg-only libraries at import time. diff --git a/releasenotes/notes/populate-sci-result-rdms-1d6a05f8c43b297e.yaml b/releasenotes/notes/populate-sci-result-rdms-1d6a05f8c43b297e.yaml new file mode 100644 index 0000000..a9d1d73 --- /dev/null +++ b/releasenotes/notes/populate-sci-result-rdms-1d6a05f8c43b297e.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + :func:`sbd.sbd_solver.solve_sci` and + :func:`sbd.sbd_solver.solve_sci_batch` now populate the ``rdm1`` and ``rdm2`` + fields of the returned ``SCIResult`` when RDMs were requested with + ``sbd_config={"do_rdm": 1}``. SBD computed these all along and the bindings + returned them, but the solver never read the keys, so the fields were left at + their ``None`` default even for a caller who had asked for them. The + spin-summed tensors use the index convention that ``SCIResult.rdm1`` and + ``rdm2`` are contracted with elsewhere in ``qiskit-addon-sqd``, verified + against PySCF's own ``make_rdm1``/``make_rdm2`` on all three backends. + + ``do_rdm = 0`` remains the default, and in that case both fields stay ``None`` + exactly as before. The conversion is also available on its own as + :func:`sbd.sbd_solver.assemble_rdms`, for callers working with the raw result + dictionary returned by :func:`sbd.tpb_diag` and :func:`sbd.gdb_diag`. diff --git a/releasenotes/notes/remove-h-comm-size-4f2a91c7d3b0e85a.yaml b/releasenotes/notes/remove-h-comm-size-4f2a91c7d3b0e85a.yaml new file mode 100644 index 0000000..adaa677 --- /dev/null +++ b/releasenotes/notes/remove-h-comm-size-4f2a91c7d3b0e85a.yaml @@ -0,0 +1,16 @@ +--- +upgrade: + - | + The ``h_comm_size`` attribute has been removed from the ``TPB_SBD`` and + ``GDB_SBD`` configuration objects. Upstream SBD declares the field but never + reads it: ``diag()`` shadows it with a local + ``h_comm_size = mpi_size / (task_comm_size * base_comm_size)`` and passes that + to the determinant-basis communicator, so the attribute could only ever read + back as 1 and silently ignored whatever was assigned to it. + + Code that assigns ``sbd_data.h_comm_size`` now raises ``AttributeError``. + Note also that an ``"h_comm_size"`` key in the ``sbd_config`` dictionary + accepted by :func:`sbd.sbd_solver.solve_sci` is now skipped without warning, + since unknown keys are ignored. To change the helper dimension of the MPI + grid, change the number of ranks or one of ``adet_comm_size``, + ``bdet_comm_size`` and ``task_comm_size``. diff --git a/releasenotes/notes/sqd-amplitudes-change-results-8c1d5e07a94f2b63.yaml b/releasenotes/notes/sqd-amplitudes-change-results-8c1d5e07a94f2b63.yaml new file mode 100644 index 0000000..2d68f16 --- /dev/null +++ b/releasenotes/notes/sqd-amplitudes-change-results-8c1d5e07a94f2b63.yaml @@ -0,0 +1,13 @@ +--- +upgrade: + - | + Multi-iteration SQD results computed through :func:`sbd.sbd_solver.solve_sci` + or :func:`sbd.sbd_solver.solve_sci_batch` will differ from those of earlier + releases. The wavefunction amplitudes returned to the SQD loop were + previously replaced by a uniform array in the common case, and + ``qiskit-addon-sqd`` selects each iteration's determinants from those + amplitudes; the amplitudes are now correct, so the subspaces explored after + the first iteration differ. See the corresponding entry under Bug Fixes. + + Single-iteration energies and orbital occupancies are unaffected: those come + from SBD directly and were never derived from the amplitudes. diff --git a/releasenotes/notes/thrust-mpi-build-options-4a6d17b9c8e052f3.yaml b/releasenotes/notes/thrust-mpi-build-options-4a6d17b9c8e052f3.yaml new file mode 100644 index 0000000..c213a01 --- /dev/null +++ b/releasenotes/notes/thrust-mpi-build-options-4a6d17b9c8e052f3.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Two build options have been exposed for running the Thrust backend on an MPI + that cannot address device memory: ``SBD_NON_CUDA_AWARE_MPI=1`` stages every + transfer through host memory, and ``SBD_THRUST_SAFE_MPI_ALLREDUCE=1`` stages + only the allreduce, which is the cheaper of the two. Without one of these, a + GPU-aware MPI is a hard requirement of that backend. + + Both are compile-time defines, so they must be set before installing, and + changing one means rebuilding. Setting them in the environment of an existing + installation does nothing. Expect either to be slower than a GPU-aware MPI, + since staging adds a copy per transfer. From a610a71e3b506de228cc6e526c4ca1dc7566dda1 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Wed, 7 Oct 2026 14:11:08 -0400 Subject: [PATCH 3/5] Add release notes for #44 and #48, and fix a stale path Three commits landed on main after the branch point, two of which changed behavior since 1.6.1 and so need notes of their own. #48 bound tpb::SBD::seed. init was already exposed and init == 1 means a random start vector, but seed was not bound, so every random-start TPB run began from upstream's default with no way to vary it -- which defeats the point of a random start. GDB_SBD has bound seed since before 1.6.1, so this was an asymmetry between the two config structs. Filed as a feature: a new public config attribute. #44 moved the examples from python/examples to examples/tpb. At the 1.6.1 tag MANIFEST.in shipped them under the old path, so anyone holding a reference to it -- a checkout, an unpacked sdist -- needs to update. The note says plainly that they were never importable from the installed package in either layout, since packages/package-dir did not change; the relocation is about the source tree and the sdist. It also covers the INSTALL.md split out of README.md. The same PR taught setup.py to read NVHPC_ROOT, and to probe compilers/bin as well as bin. NVIDIA's modulefile sets NVHPC_ROOT to the version root, one level above the compiler, so the value NVIDIA hands out matched neither of the things 1.6.1 looked for. Filed as a fix rather than a feature: where it also left nvc++ off PATH, the GPU backends were skipped and gpu_omp_offload failed claiming NVHPC was missing on a host where it was installed. Finally, examples-reworked said the drivers ship under python/examples, which #44 made wrong; it now reads examples/tpb. Assisted-by: Claude Opus 5 --- ...es-moved-out-of-package-4b7e0c92a85d61f3.yaml | 14 ++++++++++++++ .../examples-reworked-6e03b8a51d97c42f.yaml | 10 +++++----- ...ix-nvhpc-root-discovery-6d02b5e8c47a913f.yaml | 16 ++++++++++++++++ .../notes/tpb-seed-binding-8c3f91a5e702d64b.yaml | 16 ++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/examples-moved-out-of-package-4b7e0c92a85d61f3.yaml create mode 100644 releasenotes/notes/fix-nvhpc-root-discovery-6d02b5e8c47a913f.yaml create mode 100644 releasenotes/notes/tpb-seed-binding-8c3f91a5e702d64b.yaml diff --git a/releasenotes/notes/examples-moved-out-of-package-4b7e0c92a85d61f3.yaml b/releasenotes/notes/examples-moved-out-of-package-4b7e0c92a85d61f3.yaml new file mode 100644 index 0000000..4cbb901 --- /dev/null +++ b/releasenotes/notes/examples-moved-out-of-package-4b7e0c92a85d61f3.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + The example scripts and notebook have moved from ``python/examples`` to + ``examples/tpb``, placing them beside the package rather than inside it. They + were never importable from the installed ``sbd`` package in either layout, + but they were included in the source distribution under the old path, so a + reference to ``python/examples`` -- in a checkout, an unpacked sdist, or a + bookmark -- needs updating. + + Installation instructions have moved out of ``README.md`` into a separate + ``INSTALL.md``, which is also shipped in the source distribution. The + examples have gained their own ``examples/tpb/README.md`` describing the + drivers and their options. diff --git a/releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml b/releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml index 5a71c3a..6500b29 100644 --- a/releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml +++ b/releasenotes/notes/examples-reworked-6e03b8a51d97c42f.yaml @@ -1,11 +1,11 @@ --- features: - | - The example drivers shipped under ``python/examples`` have been substantially - reworked. Their command-line options are now grouped by the layer they act on, - with the SBD solver's options prefixed to distinguish them from the SQD loop's - -- several names previously read as one layer and acted on the other -- and - every former spelling is kept as an alias. Three SQD options that had no + The example drivers under ``examples/tpb`` have been substantially reworked. + Their command-line options are now grouped by the layer they act on, with the + SBD solver's options prefixed to distinguish them from the SQD loop's -- + several names previously read as one layer and acted on the other -- and every + former spelling is kept as an alias. Three SQD options that had no command-line spelling at all, the energy and occupancy convergence tolerances and the loop's carryover threshold, now reach the SQD loop instead of being stuck at its defaults. diff --git a/releasenotes/notes/fix-nvhpc-root-discovery-6d02b5e8c47a913f.yaml b/releasenotes/notes/fix-nvhpc-root-discovery-6d02b5e8c47a913f.yaml new file mode 100644 index 0000000..627a3f4 --- /dev/null +++ b/releasenotes/notes/fix-nvhpc-root-discovery-6d02b5e8c47a913f.yaml @@ -0,0 +1,16 @@ +--- +fixes: + - | + The build now finds ``nvc++`` from ``NVHPC_ROOT`` as well as ``NVHPC_HOME``, + and under either variable accepts both the compilers directory and the + version root above it. NVIDIA's own modulefile sets ``NVHPC_ROOT`` to the + version root, where the compiler sits one level down in ``compilers/bin``; + previously only ``NVHPC_HOME`` was read and only ``/bin`` was probed, + so a value handed out by NVIDIA matched neither. Where that left ``nvc++`` + off ``PATH`` as well, the GPU backends were skipped and + ``SBD_BUILD_BACKEND=gpu_omp_offload`` failed reporting that NVHPC was + missing, on a host where it was installed and the environment was pointing + at it. + + A variable that is set but yields no compiler now names the paths it tried, + rather than reporting only that ``nvc++`` was not found. diff --git a/releasenotes/notes/tpb-seed-binding-8c3f91a5e702d64b.yaml b/releasenotes/notes/tpb-seed-binding-8c3f91a5e702d64b.yaml new file mode 100644 index 0000000..ce22635 --- /dev/null +++ b/releasenotes/notes/tpb-seed-binding-8c3f91a5e702d64b.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + ``TPB_SBD`` now exposes a ``seed`` attribute, so the random initial vector + selected by ``init = 1`` can be chosen rather than fixed. The field only + applies at ``init = 1``, which the name does not convey on its own; at the + default ``init = 0`` the start vector is deterministic and ``seed`` is + ignored. ``GDB_SBD`` has exposed ``seed`` all along, so this removes an + asymmetry between the two configuration structs. + + Previously ``init = 1`` was reachable but every random-start run began from + the same upstream default seed, with no way to vary it -- which defeats the + purpose of a random start, since you could not check whether a result + depended on where the solver began. Set it through ``sbd_config``, for + example ``sbd_config={"init": 1, "seed": 987654321}``. The attribute is a new + binding, so the extension must be rebuilt for it to appear. From c6eccc22ca2769a4c4a61e2c2e14a995edda0323 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Wed, 7 Oct 2026 16:22:25 -0400 Subject: [PATCH 4/5] Update releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml Co-authored-by: Sophia Wen --- .../notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml b/releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml index 2b7af97..17ef905 100644 --- a/releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml +++ b/releasenotes/notes/carryover-type-default-zero-2b7f4a86d1c3e09f.yaml @@ -10,5 +10,4 @@ upgrade: ``carryover_type = 2`` includes building singles-extended determinant lists. Energies are bit-identical across carryover types, so this is a speedup rather - than a change in results. Pass ``sbd_config={"carryover_type": 1}`` to restore - the previous setting. + than a change in results. From 4a95bcf1d36407263c67a58acc527692c7d43245 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Wed, 7 Oct 2026 16:41:22 -0400 Subject: [PATCH 5/5] Update tox.ini --- tox.ini | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/tox.ini b/tox.ini index 1f2903e..aaa6118 100644 --- a/tox.ini +++ b/tox.ini @@ -96,31 +96,6 @@ allowlist_externals = commands = rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build/ -[testenv:docs] -# Unlike a pure-Python project, the docs build cannot skip installing the package: -# autodoc imports `sbd` to read its docstrings, and conf.py reads the version from the -# installed distribution metadata. `package`/`wheel_build_env` and the MPI/BLAS -# `passenv` are therefore inherited from [testenv] -- the extension has to compile -# here just as it does for the tests, and it reuses the same wheel. -extras = - docs -passenv = - {[testenv]passenv} - # Consulted by conf.py's determine_github_branch() to aim the source-code links at - # the right branch. - CI - GITHUB_BASE_REF - GITHUB_REF_NAME -commands = - sphinx-build -j auto -W -T --keep-going -b html {posargs} {toxinidir}/docs/ {toxinidir}/docs/_build/html - -[testenv:docs-clean] -skip_install = true -allowlist_externals = - rm -commands = - rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build/ - [testenv:slow] # The reference cases grow by roughly an order of magnitude in determinant count per # row of the upstream tables, and compute time scales worse than linearly in that, so