Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 91 additions & 0 deletions .github/pages/make_switcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Make switcher.json to allow docs to switch between different versions."""

import json
import logging
from argparse import ArgumentParser
from pathlib import Path
from subprocess import CalledProcessError, check_output


def report_output(stdout: bytes, label: str) -> list[str]:
"""Print and return something received frm stdout."""
ret = stdout.decode().strip().split("\n")
print(f"{label}: {ret}")
return ret


def get_branch_contents(ref: str) -> list[str]:
"""Get the list of directories in a branch."""
stdout = check_output(["git", "ls-tree", "-d", "--name-only", ref])
return report_output(stdout, "Branch contents")


def get_sorted_tags_list() -> list[str]:
"""Get a list of sorted tags in descending order from the repository."""
stdout = check_output(["git", "tag", "-l", "--sort=-v:refname"])
return report_output(stdout, "Tags list")


def get_versions(ref: str, add: str | None) -> list[str]:
"""Generate the file containing the list of all GitHub Pages builds."""
# Get the directories (i.e. builds) from the GitHub Pages branch
try:
builds = set(get_branch_contents(ref))
except CalledProcessError:
builds = set()
logging.warning(f"Cannot get {ref} contents") # noqa: LOG015

# Add and remove from the list of builds
if add:
builds.add(add)

# Get a sorted list of tags
tags = get_sorted_tags_list()

# Make the sorted versions list from main branches and tags
versions: list[str] = []
for version in ["master", "main"] + tags:
if version in builds:
versions.append(version)
builds.remove(version)

# Add in anything that is left to the bottom
versions += sorted(builds)
print(f"Sorted versions: {versions}")
return versions


def write_json(path: Path, repository: str, versions: list[str]):
"""Write the JSON switcher to path."""
org, repo_name = repository.split("/")
struct = [{"version": version, "url": f"https://{org}.github.io/{repo_name}/{version}/"} for version in versions]
text = json.dumps(struct, indent=2)
print(f"JSON switcher:\n{text}")
path.write_text(text, encoding="utf-8")


def main(args=None):
"""Parse args and write switcher."""
parser = ArgumentParser(description="Make a versions.json file from gh-pages directories")
parser.add_argument(
"--add",
help="Add this directory to the list of existing directories",
)
parser.add_argument(
"repository",
help="The GitHub org and repository name: ORG/REPO",
)
parser.add_argument(
"output",
type=Path,
help="Path of write switcher.json to",
)
args = parser.parse_args(args)

# Write the versions file
versions = get_versions("origin/gh-pages", args.add)
write_json(args.output, args.repository, versions)


if __name__ == "__main__":
main()
32 changes: 17 additions & 15 deletions .github/workflows/_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ name: Build Documentation

on:
workflow_call:
secrets:
deploy_key:
required: false

jobs:
build_docs:
Expand All @@ -21,7 +18,7 @@ jobs:
echo "REPOSITORY_NAME=${REPOSITORY_NAME}" >> $GITHUB_ENV

- name: Checkout the code
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
fetch-depth: 0

Expand All @@ -45,16 +42,21 @@ jobs:
name: ${{ env.REPOSITORY_NAME }}-docs
path: docs/build/html/

- name: Deploy documentation to nsls-ii.github.io
if: github.event_name == 'release'
- name: Sanitize ref name for docs version
run: echo "DOCS_VERSION=${GITHUB_REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV

- name: Move to versioned directory
run: mv docs/build/html .github/pages/$DOCS_VERSION

- name: Write switcher.json
run: python .github/pages/make_switcher.py --add $DOCS_VERSION ${{ github.repository }} .github/pages/switcher.json

- name: Publish Docs to gh-pages
if: github.ref_type == 'tag' || github.ref_name == 'main'
# We pin to the SHA, not the tag, for security reasons.
# https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions
uses: peaceiris/actions-gh-pages@bbdfb200618d235585ad98e965f4aafc39b4c501 # v3.7.3
# https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
with:
deploy_key: ${{ secrets.deploy_key }}
publish_branch: master
publish_dir: ./docs/build/html
external_repository: NSLS-II/NSLS-II.github.io
destination_dir: ${{ env.REPOSITORY_NAME }}
keep_files: true # Keep old files.
force_orphan: false # Keep git history.
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .github/pages
keep_files: true
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ jobs:
needs: lint
if: needs.check.outputs.branch-pr == ''
uses: ./.github/workflows/_docs.yml
secrets:
deploy_key: ${{ github.event_name == 'release' && secrets.ACTIONS_DOCUMENTATION_DEPLOY_KEY || '' }}
permissions:
contents: write

pypi:
name: Publish package to PyPI
Expand Down
4 changes: 4 additions & 0 deletions docs/source/reference/plans.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ Plans
.. autofunction:: blop.plans.optimize_step

.. autofunction:: blop.plans.default_acquire

.. autofunction:: blop.plans.acquire_baseline

.. autofunction:: blop.plans.sample_suggestions
60 changes: 60 additions & 0 deletions docs/source/release-history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,66 @@
Release History
===============

v1.0.0b1 (2026-04-17)
---------------------

Features
........
* **Manual suggestions**: A new method for the Agent that allows manual
optimization control (`#235 <https://github.com/NSLS-II/blop/pull/235>`_).
* **Reconfigurable search spaces**: Search spaces can now be modified between optimization steps,
allowing dynamic adjustment of DOF bounds and constraints
(`#268 <https://github.com/NSLS-II/blop/pull/268>`_).
* **Model checkpoints**: Save and restore optimizer state across sessions
(`#233 <https://github.com/NSLS-II/blop/pull/233>`_).
* **Fixed parameters**: Hold specific parameters constant during optimization via
``Agent.fixed_dofs`` (`#252 <https://github.com/NSLS-II/blop/pull/252>`_).
* **Multi-point routing**: When suggesting multiple points, suggestions are now routed to
minimize actuator travel (`#217 <https://github.com/NSLS-II/blop/pull/217>`_).
* **Failed and abandoned suggestions**: Properly handle and track failed
optimization trials (`#272 <https://github.com/NSLS-II/blop/pull/272>`_).
* **Optimization logging callback**: New ``OptimizationCallbackRouter`` and logging callback
for monitoring optimization progress
(`#270 <https://github.com/NSLS-II/blop/pull/270>`_).
* **Optimization step tracking via event-model**: Optimization metadata is now emitted as
Bluesky event-model documents (`#236 <https://github.com/NSLS-II/blop/pull/236>`_).
* **Queueserver support**: New ``QueueserverAgent`` for running optimization through
the Bluesky queueserver (`#212 <https://github.com/NSLS-II/blop/pull/212>`_,
`#264 <https://github.com/NSLS-II/blop/pull/264>`_,
`#266 <https://github.com/NSLS-II/blop/pull/266>`_).
* **Actuator and Sensor types**: Expanded protocol types to support flyable and collectable
devices (`#211 <https://github.com/NSLS-II/blop/pull/211>`_).
* **Python 3.13 support** (`#206 <https://github.com/NSLS-II/blop/pull/206>`_).

Breaking Changes
................
* **Plans moved to top-level package**: Plans are now imported from ``blop.plans`` instead of
``blop.ax.plans`` (`#259 <https://github.com/NSLS-II/blop/pull/259>`_).
* **Simulation code separated**: The simulation module has been extracted into a separate
``blop_sim`` package (`#248 <https://github.com/NSLS-II/blop/pull/248>`_,
`#256 <https://github.com/NSLS-II/blop/pull/256>`_).
* **Deprecated code removed**: Legacy APIs deprecated in earlier releases have been removed
(`#218 <https://github.com/NSLS-II/blop/pull/218>`_).
* **Deprecated bayesian.acquisition removed**: Use BoTorch's built-in constrained acquisition
functions instead (`#207 <https://github.com/NSLS-II/blop/pull/207>`_).

Dependency Changes
..................
* Requires Ax Platform >= 1.2.3 (`#241 <https://github.com/NSLS-II/blop/pull/241>`_).
* Requires BoTorch >= 0.16.0 (`#221 <https://github.com/NSLS-II/blop/pull/221>`_).

Documentation
.............
* New how-to guide for using Tiled as a databroker (`#215 <https://github.com/NSLS-II/blop/pull/215>`_).
* New how-to guide for using ophyd and ophyd-async devices (`#210 <https://github.com/NSLS-II/blop/pull/210>`_).
* New explanation document for the Ax integration (`#227 <https://github.com/NSLS-II/blop/pull/227>`_).
* Updated tutorials for the simple experiment and XRT KB mirrors
(`#222 <https://github.com/NSLS-II/blop/pull/222>`_,
`#223 <https://github.com/NSLS-II/blop/pull/223>`_,
`#224 <https://github.com/NSLS-II/blop/pull/224>`_).

`Full Changelog <https://github.com/NSLS-II/blop/compare/v0.9.0...v1.0.0b1>`__

v0.9.0 (2025-12-08)
-------------------

Expand Down
3 changes: 2 additions & 1 deletion src/blop/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .ax import Agent, ChoiceDOF, DOFConstraint, Objective, OutcomeConstraint, RangeDOF, ScalarizedObjective
from .ax import DOF, Agent, ChoiceDOF, DOFConstraint, Objective, OutcomeConstraint, RangeDOF, ScalarizedObjective
from .plans import acquire_baseline, default_acquire, optimize, optimize_step, sample_suggestions

try:
Expand All @@ -10,6 +10,7 @@
"__version__",
"Agent",
"ChoiceDOF",
"DOF",
"DOFConstraint",
"Objective",
"OutcomeConstraint",
Expand Down
4 changes: 4 additions & 0 deletions src/blop/ax/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,18 +357,22 @@ def unsubscribe(self, callback: CallbackBase) -> None:

@property
def sensors(self) -> Sequence[Sensor]:
"""The sensors used for data acquisition."""
return self._sensors

@property
def actuators(self) -> Sequence[Actuator]:
"""The actuators that control the degrees of freedom."""
return self._actuators

@property
def evaluation_function(self) -> EvaluationFunction:
"""The function used to evaluate acquired data and produce outcomes."""
return self._evaluation_function

@property
def acquisition_plan(self) -> AcquisitionPlan | None:
"""The acquisition plan for acquiring data, or ``None`` if using the default."""
return self._acquisition_plan

def to_optimization_problem(self) -> OptimizationProblem:
Expand Down
3 changes: 3 additions & 0 deletions src/blop/ax/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,17 @@ def from_checkpoint(cls, checkpoint_path: str) -> "AxOptimizer":

@property
def checkpoint_path(self) -> str | None:
"""The file path for saving and restoring optimizer state, or ``None`` if disabled."""
return self._checkpoint_path

@property
def ax_client(self) -> Client:
"""The underlying Ax ``Client`` used for experiment management."""
return self._client

@property
def fixed_parameters(self) -> dict[str, Any] | None:
"""Parameters held fixed during optimization, or ``None`` if all parameters are free."""
return self._fixed_parameters

@fixed_parameters.setter
Expand Down
12 changes: 12 additions & 0 deletions src/blop/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,18 @@ def __call__(

@dataclass(frozen=True)
class BaseOptimizationProblem(Generic[TActuator, TSensor, TPlan]):
"""Base class for optimization problem definitions.

Provides the common structure shared by all optimization problem types.
Users should use the concrete subclasses :class:`OptimizationProblem` or
:class:`QueueserverOptimizationProblem` instead of this class directly.

See Also
--------
OptimizationProblem : Concrete problem type for standard usage.
QueueserverOptimizationProblem : Concrete problem type for queue server usage.
"""

optimizer: Optimizer
actuators: Sequence[TActuator]
sensors: Sequence[TSensor]
Expand Down
Loading