From 3a4fe22f3063c29ff5e056131dbf08a9921de9ca Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Tue, 20 Jan 2026 12:41:32 +0100 Subject: [PATCH 01/21] [TASK] version to beta: tests at BESSY II succeded some parts of ophyd async need to be improved --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4e120e5..f47b902 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "accml_lib" -version = "0.3.0a1" +version = "0.3.0b1" description = "python accelerator middle layer: library path" authors = [ {name = "Waheedullah Sulaiman Khail", email = " Date: Tue, 20 Jan 2026 18:29:04 +0100 Subject: [PATCH 02/21] [TASK] measurement execution engine: make task async --- .../core/interfaces/utils/measurement_execution_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py b/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py index 61906a4..6db81bf 100644 --- a/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py +++ b/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py @@ -7,7 +7,7 @@ class MeasurementExecutionEngine(metaclass=ABCMeta): @abstractmethod - def execute( + async def execute( self, commands_collection: Sequence[TransactionCommand], *args, **kwargs ) -> str: """ From 86d1ca9e236e5d974c55cb59ff8fbbcde058606b Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Wed, 21 Jan 2026 20:16:40 +0100 Subject: [PATCH 03/21] [TASK] design: initial version --- docs/design.rst | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/design.rst diff --git a/docs/design.rst b/docs/design.rst new file mode 100644 index 0000000..ba6480b --- /dev/null +++ b/docs/design.rst @@ -0,0 +1,115 @@ +The design of accml and accml\_lib +================================== + +The design of `accml` and `accml_lib`is based on the following +observations: + +* Different components of the full software stack have + different views of the same "reality". +* We need to execute measurements, analyse them, store and + exchange measurement results and so on: here a structured + communication comes to our help. +* Many measurements are done "relative" to the current state. + So we want to explore the current teritory. + +Handling views +-------------- + +* particle accelerators are simulated by tools that calculate + the difference to an ideal orbit or to be more precise a + real world particle with respect to the movement of an + ideal particle in an idealised accelerator +* in the real world however we need to deal with particles, their + position and behavior in the real world. + +We address this reality by the concept of `views`: both calculations +and operating the real world particle accelerator have the same +target: provide a machine with best possible performance. But they +see or view the same thing from a different angle. + +These views have to be combined. Therefore, we need to take care +that the actual situation the system is in is as much the same as +possible in both views: actually we need to take care to connect +them and keep them on the same state: so simple snippets of +information need to be exchanged between the two. +Furthermore, we need to realise that the building blocks of the +simulation and the real world devices do not match exactly to each +other: they do often but not always and even then they need different +values. + +The problem is tackled by: + + * each message of them needs an "building block" identifier and + and a property identifier. Like business letters would be + addressed to a person and contain a sign. Now note that + the letters typically contain a "your sign", "our-sign. + + Here the liaison manager comes it: in large collaboration + these people connect the participants: whom to talk to for + certain types of problems to get it solved. + + The same concept applies here, but it needs to be structured + so that simple computer programs can handle it. + + * the values need to be translated between the different views. + Same in an internationally collaboration not all speak the + same language. So you need a translation service to get communiques + or letters translated. The same applies here. A translation service + will provide you a translation service given that you know the + parameters. + +A general overview of these views is given in :cite:`accml:icaleps2025`. +The patterns mentiond above are explained in detail in :cite:`dt:europlop25`. + + +Structured communication +------------------------ + +We need to update values of the simulation engine or the real +machine, execute measurements or analyse them. So communications +are based on commands: + +* its simplest is the "ReadCommand": + + .. code-block:: python + + ReadCommand(id="quad", property="set_current") + + We express that we want to read something. The command is then + handled over to the appropriate backend. Details on that will + be given later. + +* for changing a value we use a "Command": + + .. code-block:: python + + Command(id="quad", property="set_current", value="245", behaviour_on_error=None) + + Here we formulate that the quadrupole current will be set to 245 Ampere. + What shall happen in case of an error we leave undefined. + + +A general overview is given in :cite:`accml:icaleps2025`. +The patterns mentioned above are explained in detail in :cite:`dt:europlop25`. + +Out of these building blocks we can build a whole structured communication. Instead +of specifying detectors we just specify what should be read. Based on commands we +build: + +* transaction commands: these shall be executed at the same time +* a command sequence: these (transaction) commands shall be executed step by step. + +A command execution engine takes care to execute these commands. A measurement execution +engine -- an enrichment of a command execution engine -- will additionally hand over +produced data to a storage. + + +Build up of the command engine +------------------------------ + + + +.. rubric:: References + +.. bibliography:: refs.bib + :style: unsrt \ No newline at end of file From deea30e4d397254fc5bc5e5a7a46349fc3c3079f Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 12:29:47 +0100 Subject: [PATCH 04/21] [TASK] sphinx build files, install option added to pyproject --- docs/Makefile | 20 +++++++++ docs/conf.py | 55 +++++++++++++++++++++++ docs/design.rst | 115 ------------------------------------------------ docs/index.rst | 30 +++++++++++++ docs/make.bat | 35 +++++++++++++++ pyproject.toml | 12 ++++- 6 files changed, 151 insertions(+), 116 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/conf.py delete mode 100644 docs/design.rst create mode 100644 docs/index.rst create mode 100644 docs/make.bat diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..62f1ad5 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,55 @@ +import os +import sys +# so accml_lib is found +sys.path.insert(0, os.path.abspath('../../')) + +project = 'accml_lib' +copyright = '2026, Helmholtz Zentrum Berlin' +author = 'Pierre Schnizer, Waheedullah Sulaiman Khail' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx_autodoc_typehints', + 'sphinx.ext.autosummary', + "sphinx.ext.todo", + "sphinxcontrib.bibtex", +] +bibtex_bibfiles = ["refs.bib"] + +# Optional formatting settings: +bibtex_default_style = "unsrt" # or "alpha", "plain", etc. +bibtex_reference_style = "author_year" # controls :cite: rendering style + +autosummary_generate = True +autodoc_typehints = 'description' +html_theme = 'sphinx_rtd_theme' + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# optional: mock imports if your package has heavy optional deps +# autodoc_mock_imports = ['numpy', 'scipy', "lat2db", "acclerator-toolbox"] + + +# Intersphinx configuration: cross-reference external docs +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable", None), + "scipy": ("https://docs.scipy.org/doc/scipy", None), +} + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_static_path = ['_static'] + +# -- Options for todo extension ---------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/todo.html#configuration + +todo_include_todos = True diff --git a/docs/design.rst b/docs/design.rst deleted file mode 100644 index ba6480b..0000000 --- a/docs/design.rst +++ /dev/null @@ -1,115 +0,0 @@ -The design of accml and accml\_lib -================================== - -The design of `accml` and `accml_lib`is based on the following -observations: - -* Different components of the full software stack have - different views of the same "reality". -* We need to execute measurements, analyse them, store and - exchange measurement results and so on: here a structured - communication comes to our help. -* Many measurements are done "relative" to the current state. - So we want to explore the current teritory. - -Handling views --------------- - -* particle accelerators are simulated by tools that calculate - the difference to an ideal orbit or to be more precise a - real world particle with respect to the movement of an - ideal particle in an idealised accelerator -* in the real world however we need to deal with particles, their - position and behavior in the real world. - -We address this reality by the concept of `views`: both calculations -and operating the real world particle accelerator have the same -target: provide a machine with best possible performance. But they -see or view the same thing from a different angle. - -These views have to be combined. Therefore, we need to take care -that the actual situation the system is in is as much the same as -possible in both views: actually we need to take care to connect -them and keep them on the same state: so simple snippets of -information need to be exchanged between the two. -Furthermore, we need to realise that the building blocks of the -simulation and the real world devices do not match exactly to each -other: they do often but not always and even then they need different -values. - -The problem is tackled by: - - * each message of them needs an "building block" identifier and - and a property identifier. Like business letters would be - addressed to a person and contain a sign. Now note that - the letters typically contain a "your sign", "our-sign. - - Here the liaison manager comes it: in large collaboration - these people connect the participants: whom to talk to for - certain types of problems to get it solved. - - The same concept applies here, but it needs to be structured - so that simple computer programs can handle it. - - * the values need to be translated between the different views. - Same in an internationally collaboration not all speak the - same language. So you need a translation service to get communiques - or letters translated. The same applies here. A translation service - will provide you a translation service given that you know the - parameters. - -A general overview of these views is given in :cite:`accml:icaleps2025`. -The patterns mentiond above are explained in detail in :cite:`dt:europlop25`. - - -Structured communication ------------------------- - -We need to update values of the simulation engine or the real -machine, execute measurements or analyse them. So communications -are based on commands: - -* its simplest is the "ReadCommand": - - .. code-block:: python - - ReadCommand(id="quad", property="set_current") - - We express that we want to read something. The command is then - handled over to the appropriate backend. Details on that will - be given later. - -* for changing a value we use a "Command": - - .. code-block:: python - - Command(id="quad", property="set_current", value="245", behaviour_on_error=None) - - Here we formulate that the quadrupole current will be set to 245 Ampere. - What shall happen in case of an error we leave undefined. - - -A general overview is given in :cite:`accml:icaleps2025`. -The patterns mentioned above are explained in detail in :cite:`dt:europlop25`. - -Out of these building blocks we can build a whole structured communication. Instead -of specifying detectors we just specify what should be read. Based on commands we -build: - -* transaction commands: these shall be executed at the same time -* a command sequence: these (transaction) commands shall be executed step by step. - -A command execution engine takes care to execute these commands. A measurement execution -engine -- an enrichment of a command execution engine -- will additionally hand over -produced data to a storage. - - -Build up of the command engine ------------------------------- - - - -.. rubric:: References - -.. bibliography:: refs.bib - :style: unsrt \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..2d793b4 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,30 @@ +accml\_lib: documentation +========================= + +accml contains all the modules which are used by +client code or twin. + +Its general concepts are explained in +the design document of `accml`. + +API +--- + +.. toctree:: + :maxdepth: 4 + + src/accml_lib + + +Further readings: +----------------- + +* For client code / applications look to + + design + +Modules of accml\_lib +--------------------- + + src/modules + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/pyproject.toml b/pyproject.toml index f47b902..edc1f8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,8 +60,18 @@ transitions = {version ="*", optional = true} # for testing pytest-asyncio = {version = "*", optional = true} +# for documentation +sphinx = {version = ">=5", optional = true} +sphinx-rtd-theme = {version = "*", optional = true} +sphinx-autodoc-typehints = {version = "*", optional = true} +sphinxcontrib-napoleon = {version = "*", optional = true} +sphinxcontrib-bibtex = {version = "*", optional = true} + + [tool.poetry.extras] bluesky-epics = ["ophyd-async", "aioca", "p4p", "epics", "bluesky", "databroker"] bluesky-tango = ["ophyd-async", "pytango", "bluesky", "databroker"] pyat-simulator = ["accelerator-toolbox", "transitions"] -testing = ["pytest-asyncio", "accelerator-toolbox", "transitions"] \ No newline at end of file +testing = ["pytest-asyncio", "accelerator-toolbox", "transitions"] + +docs = ["sphinx", "sphinx-rtd-theme", "sphinx-autodoc-typehints", "sphinxcontrib-napoleon", "sphinxcontrib-bibtex"] \ No newline at end of file From 29145b3ff3010558fbb904b25342b29aa2dd9201 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 12:31:26 +0100 Subject: [PATCH 05/21] [TASK] improved documentation strings --- src/accml_lib/core/__init__.py | 18 ++++++++++++++++++ src/accml_lib/core/bl/command_rewritter.py | 3 --- src/accml_lib/custom/__init__.py | 13 +++++++++++++ .../custom/pyat_simulator/__init__.py | 4 ++++ .../custom/pyat_simulator/simulator_backend.py | 8 +++++--- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/accml_lib/core/__init__.py b/src/accml_lib/core/__init__.py index e69de29..83b6aae 100644 --- a/src/accml_lib/core/__init__.py +++ b/src/accml_lib/core/__init__.py @@ -0,0 +1,18 @@ +"""accm\_lib core packages + +The `core` contains the modules which +are to be independent of any facility +and form the basis of `àccml\_lib`. + +It contains the following main parts: + +* models: data models used within the package +* bl: business logic or modules that provide + basic functionality +* config: configuration data + +Interfaces are used to export the interfaces +used within this package. +""" + +__all__ = ["model", "config", "bl", "interfaces"] \ No newline at end of file diff --git a/src/accml_lib/core/bl/command_rewritter.py b/src/accml_lib/core/bl/command_rewritter.py index d534080..0612bc4 100644 --- a/src/accml_lib/core/bl/command_rewritter.py +++ b/src/accml_lib/core/bl/command_rewritter.py @@ -2,9 +2,6 @@ Please note: here we have to map (lattice_name, property) -> (device_name, property) - -Todo: - Split up content in different modules """ from typing import Sequence diff --git a/src/accml_lib/custom/__init__.py b/src/accml_lib/custom/__init__.py index e69de29..6e839e4 100644 --- a/src/accml_lib/custom/__init__.py +++ b/src/accml_lib/custom/__init__.py @@ -0,0 +1,13 @@ +"""Simulation engine and accelerator machine backends + +Todo: + add bessyii_on_tango to __all__ as soon as it + is checked that it works (again) + +Furthermore commonly shared configuration data can be put +into the config_data directory here. Up to now only data +for BESSY II are stored here + + +""" +__all__ = ["pyat_simulator", "bessyii"] \ No newline at end of file diff --git a/src/accml_lib/custom/pyat_simulator/__init__.py b/src/accml_lib/custom/pyat_simulator/__init__.py index e69de29..56e6934 100644 --- a/src/accml_lib/custom/pyat_simulator/__init__.py +++ b/src/accml_lib/custom/pyat_simulator/__init__.py @@ -0,0 +1,4 @@ +"""Accelerator backend based on pyat + +""" +__all__ = ["simulator_backend", "element_proxies", "accelerator_simulator"] \ No newline at end of file diff --git a/src/accml_lib/custom/pyat_simulator/simulator_backend.py b/src/accml_lib/custom/pyat_simulator/simulator_backend.py index 1b2d1db..2d75f1e 100644 --- a/src/accml_lib/custom/pyat_simulator/simulator_backend.py +++ b/src/accml_lib/custom/pyat_simulator/simulator_backend.py @@ -41,9 +41,7 @@ class SimulationStateModel: class SimulatorBackend(BackendRW): - """ - Todo: - where to break async / sync or threaded approach? + """Simulation backend based on pyAT I assume today that the calculation engine works the following way: @@ -57,6 +55,10 @@ class SimulatorBackend(BackendRW): calculation results are protected by a lock, so no more sets are made while calculation is running nor calculation results are delivered ahead of time. + + Todo: + where to break async / sync or threaded approach? + """ def __init__(self, *, acc: AcceleratorSimulatorInterface, name: str, logger=logger): From e9976b903942f2a300007b9c8c65afafaa0996db Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 12:36:14 +0100 Subject: [PATCH 06/21] [TASK] added dunder all --- src/accml_lib/__init__.py | 1 + src/accml_lib/core/bl/__init__.py | 8 ++++++++ src/accml_lib/core/model/__init__.py | 1 + src/accml_lib/core/model/utils/__init__.py | 1 + src/accml_lib/custom/bessyii/__init__.py | 6 ++++++ 5 files changed, 17 insertions(+) diff --git a/src/accml_lib/__init__.py b/src/accml_lib/__init__.py index e69de29..359fccd 100644 --- a/src/accml_lib/__init__.py +++ b/src/accml_lib/__init__.py @@ -0,0 +1 @@ +__all__ = ["core", "custom"] \ No newline at end of file diff --git a/src/accml_lib/core/bl/__init__.py b/src/accml_lib/core/bl/__init__.py index e69de29..f7a490f 100644 --- a/src/accml_lib/core/bl/__init__.py +++ b/src/accml_lib/core/bl/__init__.py @@ -0,0 +1,8 @@ +__all__ = [ + "yellow_pages", + "liaison_manager", + "translator_service", + "command_rewritter", + "unit_conversion", + "delta_backend" +] \ No newline at end of file diff --git a/src/accml_lib/core/model/__init__.py b/src/accml_lib/core/model/__init__.py index e69de29..feeea61 100644 --- a/src/accml_lib/core/model/__init__.py +++ b/src/accml_lib/core/model/__init__.py @@ -0,0 +1 @@ +__all__ = ["utils", "output", "config"] diff --git a/src/accml_lib/core/model/utils/__init__.py b/src/accml_lib/core/model/utils/__init__.py index e69de29..2101377 100644 --- a/src/accml_lib/core/model/utils/__init__.py +++ b/src/accml_lib/core/model/utils/__init__.py @@ -0,0 +1 @@ +__all__ = ["identifiers", "command"] \ No newline at end of file diff --git a/src/accml_lib/custom/bessyii/__init__.py b/src/accml_lib/custom/bessyii/__init__.py index e69de29..29c45f4 100644 --- a/src/accml_lib/custom/bessyii/__init__.py +++ b/src/accml_lib/custom/bessyii/__init__.py @@ -0,0 +1,6 @@ +__all__ = [ + "liasion_translator_setup", + "bessyii_pyat_lattice", + "pyat_simulator_backend", + "setup" +] \ No newline at end of file From 15718c38697b0e3c4f2325c6b554e5b2cfc4070f Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 12:45:50 +0100 Subject: [PATCH 07/21] [TASK] CI for accml_lib docs --- .github/workflows/docs.yml | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..3f75b83 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,42 @@ +name: Build and deploy accml_lib docs + +on: + push: + branches: [ main ] # build on pushes to main (adjust as needed) + pull_request: + branches: [ main ] + +jobs: + build-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: true + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.11' # choose your supported version + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python3 -m pip install \ + 'accml[bluesky-epics]' accml \ + 'accml/external-repositories/accml_lib[bluesky-epics,pyat-simulator]' + + - name: Build docs + working-directory: docs + run: | + sphinx-apidoc -o docs/src accml_lib || "echo: failed to create auto docs!" + make html + + - name: Deploy to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_build/html + # optional: cname: docs.example.com From 688aed4ecddaa2dce7a26ed29815dfbf5894c382 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 12:51:04 +0100 Subject: [PATCH 08/21] [FIX] install only accml lib here, install packages required for docs --- .github/workflows/docs.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3f75b83..3ec1091 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,8 +25,7 @@ jobs: run: | python -m pip install --upgrade pip python3 -m pip install \ - 'accml[bluesky-epics]' accml \ - 'accml/external-repositories/accml_lib[bluesky-epics,pyat-simulator]' + 'accml_lib[bluesky-epics,pyat-simulator,docs]' - name: Build docs working-directory: docs From 268164477f1abdddf28ca161c1931ae32ecf7cfa Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 12:55:21 +0100 Subject: [PATCH 09/21] [FIX] installation path for accml_lib --- .github/workflows/docs.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3ec1091..17d8544 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,8 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python3 -m pip install \ - 'accml_lib[bluesky-epics,pyat-simulator,docs]' + python3 -m pip install -e ./[bluesky-epics,docs,pyat-simulator] - name: Build docs working-directory: docs From 85e4a404797f5d8814f0269edc8cef3a2b350f03 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 13:00:50 +0100 Subject: [PATCH 10/21] [FIX] call to sphinx apidoc --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 17d8544..a80965a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,7 @@ jobs: - name: Build docs working-directory: docs run: | - sphinx-apidoc -o docs/src accml_lib || "echo: failed to create auto docs!" + python3 -m sphinx.ext.apidoc ./ -o docs/src/ || echo "failed to create auto/API docs!" make html - name: Deploy to gh-pages From edaf7a9b5db76169b36cab1b73ed15b668676459 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 13:06:05 +0100 Subject: [PATCH 11/21] [FIX] need to enter docs directory for building doc --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a80965a..b674e91 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,7 +30,7 @@ jobs: working-directory: docs run: | python3 -m sphinx.ext.apidoc ./ -o docs/src/ || echo "failed to create auto/API docs!" - make html + (cd docs/ && make html) - name: Deploy to gh-pages uses: peaceiris/actions-gh-pages@v4 From 2c51d4a00a472775b11ae234bdc1ababbe0cad5c Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 14:05:34 +0100 Subject: [PATCH 12/21] [FIX] use python directly and sphinx commands to build the documentation --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b674e91..67288dd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,8 +29,8 @@ jobs: - name: Build docs working-directory: docs run: | - python3 -m sphinx.ext.apidoc ./ -o docs/src/ || echo "failed to create auto/API docs!" - (cd docs/ && make html) + python3 -m sphinx.ext.apidoc -o docs/src/ ./ || echo "failed to create auto/API docs!" + python3 -m sphinx.cmd.build --conf-dir docs/ docs/ docs/_build/ - name: Deploy to gh-pages uses: peaceiris/actions-gh-pages@v4 From 65e60d74d4ce849abe0dd6ffbd02b7ce92f9769e Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 14:06:18 +0100 Subject: [PATCH 13/21] [FIX] all details will be in accml --- docs/index.rst | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2d793b4..0dd7279 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,17 +14,3 @@ API :maxdepth: 4 src/accml_lib - - -Further readings: ------------------ - -* For client code / applications look to - - design - -Modules of accml\_lib ---------------------- - - src/modules - From 0cabf1a3cdf7c3c0ef4b85f8cb7ccde7579d4d70 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 14:06:37 +0100 Subject: [PATCH 14/21] [TASK] improved installation instructions --- README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e0e115..84db25b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# accml: Accelerator middle layer +# accml_lib: Particle accelerator middle layer: library part `accml` is a software stack designed to facilitate implementing tools characterising (high) energy charged accelerator. @@ -10,6 +10,9 @@ These tools typically address: For details of its concept see [design.md](https://github.com/python-accelerator-middle-layer/accml/design.md). +[![Docs](https://github.com/python-accelerator-middle-layer/accml_lib/workflows/Build%20and%20Deploy%20Docs/badge.svg)](https://.github.io//) + + ## 🚀 Installation and Running Instructions ### 1. Clone the Repository @@ -25,9 +28,31 @@ git checkout dev/main git submodule update --init --recursive ``` ### 3. Install the Package + +Please note: typically, especially as a user, you would install +accml, which in turn will install accml_lib. So typically +you want to look to https://github.com/python-accelerator-middle-layer/accml +and install everything there + +#### 3.1 Installing only accml_lib for an EPICS facility + +For an EPICS facility install + ```bash -python3 -m pip install -e . +python3 -m pip install -e \ + ./[bluesky-epics,pyat-simulator] ``` + +#### 3.1 Installing only accml_lib for a TANGO facility + +**NB** this installation is not yet tested. In case of +experiencing trouble please drop us a line or share your +experience in case of success. +```bash +python3 -m pip install -e \ + ./[bluesky-tango,pyat-simulator] +``` + ### 4. Run the Virtual Accelerator (Test bench) --EPICS VERSION ```bash apptainer run oras://registry.hzdr.de/digital-twins-for-accelerators/containers/pyat-softioc-digital-twin:v0-1-2-bessy.2475331 From 5d65fb80c8728c78d36a7bcb070118287ea00db4 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 14:16:27 +0100 Subject: [PATCH 15/21] [TRY] build doc in docs directory --- .github/workflows/docs.yml | 7 +++++-- .gitignore | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 67288dd..a592cef 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,8 +29,11 @@ jobs: - name: Build docs working-directory: docs run: | - python3 -m sphinx.ext.apidoc -o docs/src/ ./ || echo "failed to create auto/API docs!" - python3 -m sphinx.cmd.build --conf-dir docs/ docs/ docs/_build/ + find . -type d + cd docs/ + python3 -m sphinx.ext.apidoc -o src/ ../src/accml_lib/ || echo "failed to create auto/API docs!" + python3 -m sphinx.cmd.build --conf-dir ./ ./ _build/html/ + cd .. - name: Deploy to gh-pages uses: peaceiris/actions-gh-pages@v4 diff --git a/.gitignore b/.gitignore index 9e6e80f..563cf63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ # emacs autosave files *~ +# doc build files +doc/_build/ +# put api auto generated doc here +doc/src/ + # files for running fixtures fixtures/ From bb7764733f92f634eae4ca0536025559f65e1d8c Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 14:21:46 +0100 Subject: [PATCH 16/21] [FIX] already in docs directory --- .github/workflows/docs.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a592cef..ab9c86b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,11 +29,8 @@ jobs: - name: Build docs working-directory: docs run: | - find . -type d - cd docs/ python3 -m sphinx.ext.apidoc -o src/ ../src/accml_lib/ || echo "failed to create auto/API docs!" python3 -m sphinx.cmd.build --conf-dir ./ ./ _build/html/ - cd .. - name: Deploy to gh-pages uses: peaceiris/actions-gh-pages@v4 From 9106f205e4efda94d03129058dd0677bc5350bae Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 14:34:28 +0100 Subject: [PATCH 17/21] [FIX] doc page badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84db25b..2670103 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ These tools typically address: For details of its concept see [design.md](https://github.com/python-accelerator-middle-layer/accml/design.md). -[![Docs](https://github.com/python-accelerator-middle-layer/accml_lib/workflows/Build%20and%20Deploy%20Docs/badge.svg)](https://.github.io//) +Additional [![Documentation](https://github.com/python-accelerator-middle-layer/accml_lib/actions/workflows/docs.yml/badge.svg)](https://python-accelerator-middle-layer.github.io/accml_lib/) ## 🚀 Installation and Running Instructions From 469ea193b4672723f42a2a6f8bbf0c0700033428 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 15:38:47 +0100 Subject: [PATCH 18/21] [TASK] move test from accml to here as it only tests accml_lib functionality --- .../test_liasion_translator_setup.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/test_core/test_liasion_translator_setup.py diff --git a/tests/test_core/test_liasion_translator_setup.py b/tests/test_core/test_liasion_translator_setup.py new file mode 100644 index 0000000..b429e21 --- /dev/null +++ b/tests/test_core/test_liasion_translator_setup.py @@ -0,0 +1,95 @@ +import pytest +from pathlib import Path +import shutil + +import accml_lib.custom.bessyii.liasion_translator_setup as lts +from accml_lib.core.bl.unit_conversion import EnergyDependentLinearUnitConversion +from accml_lib.core.model.utils.identifiers import ConversionID, LatticeElementPropertyID, DevicePropertyID + + +@pytest.fixture(scope="module") +def config_dir(tmp_path_factory): + """Create a temporary copy of the real config data.""" + tmp_dir = tmp_path_factory.mktemp("config_data") + + base_config_path = ( + Path(__file__).resolve().parents[2] + / "src" + / "accml_lib" + / "custom" + / "config_data" + / "bessyii" + ) + + shutil.copy(base_config_path / "magnets.yaml", tmp_dir / "magnets.yaml") + shutil.copy(base_config_path / "power_converters.yaml", tmp_dir / "power_converters.yaml") + + return tmp_dir + + +def test_build_managers_with_real_data(config_dir): + """End-to-end test of building YellowPages, LiaisonManager, and TranslatorService.""" + yp, lm, tm = lts.build_managers(config_dir) + + # --- YellowPages checks --- + assert yp is not None + assert hasattr(yp, "quadrupole_names") + assert len(yp.quadrupole_names()) > 0 + + # --- LiaisonManager checks --- + assert lm is not None + assert hasattr(lm, "forward_lut") + assert hasattr(lm, "inverse_lut") + assert all(isinstance(k, LatticeElementPropertyID) for k in lm.forward_lut) + assert all(isinstance(v, DevicePropertyID) for v in lm.forward_lut.values()) + + # --- TranslatorService checks (behavioral, not internal attr) --- + assert tm is not None + # find one known mapping and ensure it can retrieve conversion + sample_magnet = yp.quadrupole_names()[0] + conv_id = ConversionID( + lattice_property_id=LatticeElementPropertyID(element_name=sample_magnet, property="main_strength"), + device_property_id=DevicePropertyID(device_name=lm.forward_lut[ + LatticeElementPropertyID(element_name=sample_magnet, property="main_strength") + ].device_name, property="set_current"), + ) + + # Call behaviorally + conversion_obj = tm.get(conv_id) + assert isinstance(conversion_obj, EnergyDependentLinearUnitConversion) + assert hasattr(conversion_obj, "slope") + assert hasattr(conversion_obj, "intercept") + + +def test_build_managers_detects_duplicate_names(monkeypatch, config_dir): + """Ensure duplicate magnet names raise an AssertionError.""" + # Monkeypatch ConfigService.get_magnets to return duplicates + cs = lts.ConfigService( + magnet_path=config_dir / "magnets.yaml", + pc_path=config_dir / "power_converters.yaml", + ) + cs.load() + magnets = cs.get_magnets() + magnets.append(magnets[0]) # duplicate + + monkeypatch.setattr(cs, "get_magnets", lambda: magnets) + monkeypatch.setattr(lts, "ConfigService", lambda *a, **k: cs) + + with pytest.raises(AssertionError): + lts.build_managers(config_dir) + + +def test_load_managers_cache(monkeypatch): + """Ensure @lru_cache prevents multiple builds.""" + count = {"n": 0} + + def fake_build(path): + count["n"] += 1 + return ("yp", "lm", "tm") + + monkeypatch.setattr(lts, "build_managers", fake_build) + + result1 = lts.load_managers() + result2 = lts.load_managers() + assert result1 == result2 + assert count["n"] == 1 From 503cd0b9f132873b6c9b282636093b0b730cb76a Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Thu, 22 Jan 2026 16:47:09 +0100 Subject: [PATCH 19/21] [TASK] addded dunder all, blackified it --- src/accml_lib/core/bl/command_rewritter.py | 58 +++++++++++++++------ src/accml_lib/core/bl/liaison_manager.py | 8 ++- src/accml_lib/core/bl/translator_service.py | 3 ++ src/accml_lib/core/bl/unit_conversion.py | 3 ++ src/accml_lib/core/bl/yellow_pages.py | 9 ++-- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/accml_lib/core/bl/command_rewritter.py b/src/accml_lib/core/bl/command_rewritter.py index 0612bc4..8d6cd5b 100644 --- a/src/accml_lib/core/bl/command_rewritter.py +++ b/src/accml_lib/core/bl/command_rewritter.py @@ -11,7 +11,11 @@ from ...core.interfaces.utils.liaison_manager import LiaisonManagerBase from ...core.interfaces.utils.translator_service import TranslatorServiceBase from ...core.model.utils.command import Command -from ...core.model.utils.identifiers import DevicePropertyID, LatticeElementPropertyID, ConversionID +from ...core.model.utils.identifiers import ( + DevicePropertyID, + LatticeElementPropertyID, + ConversionID, +) class CommandRewriter(CommandRewriterBase): @@ -25,7 +29,11 @@ class CommandRewriter(CommandRewriterBase): to convert the command values between representations. """ - def __init__(self, liaison_manager: LiaisonManagerBase, translation_service: TranslatorServiceBase): + def __init__( + self, + liaison_manager: LiaisonManagerBase, + translation_service: TranslatorServiceBase, + ): """ Initialize the CommandRewriter with the required services. @@ -46,17 +54,24 @@ def inverse(self, cmd: Command) -> Sequence[Command]: Returns: A sequence of commands corresponding to the inverse translations. """ - dev_prop_id = DevicePropertyID( - device_name=cmd.id, property=cmd.property - ) + dev_prop_id = DevicePropertyID(device_name=cmd.id, property=cmd.property) rcmd = self.inverse_read_command(cmd) - lat_prop_ids = [LatticeElementPropertyID(element_name=r.id, property=r.property) for r in rcmd] - - return [self.inverse_translate_one(cmd, dev_prop_id, lat_prop_id) for lat_prop_id in lat_prop_ids] - - def inverse_translate_one(self, cmd: Command, dev_prop_id: DevicePropertyID, - lat_prop_id: LatticeElementPropertyID - ) -> Command: + lat_prop_ids = [ + LatticeElementPropertyID(element_name=r.id, property=r.property) + for r in rcmd + ] + + return [ + self.inverse_translate_one(cmd, dev_prop_id, lat_prop_id) + for lat_prop_id in lat_prop_ids + ] + + def inverse_translate_one( + self, + cmd: Command, + dev_prop_id: DevicePropertyID, + lat_prop_id: LatticeElementPropertyID, + ) -> Command: """ Perform a single inverse translation. @@ -69,11 +84,15 @@ def inverse_translate_one(self, cmd: Command, dev_prop_id: DevicePropertyID, A new Command with the value converted to the lattice state. """ translation_object = self.translator_service.get( - ConversionID(lattice_property_id=lat_prop_id, device_property_id=dev_prop_id) + ConversionID( + lattice_property_id=lat_prop_id, device_property_id=dev_prop_id + ) ) if dev_prop_id.device_name is None: - raise ValueError("Device name cannot be None in device property identifier.") + raise ValueError( + "Device name cannot be None in device property identifier." + ) ncmd = Command( id=lat_prop_id.element_name, @@ -91,7 +110,9 @@ def forward(self, cmd: Command) -> Command: dev_prop_id = DevicePropertyID(device_name=rcmd.id, property=rcmd.property) translation_object = self.translator_service.get( - ConversionID(lattice_property_id=lat_prop_id, device_property_id=dev_prop_id) + ConversionID( + lattice_property_id=lat_prop_id, device_property_id=dev_prop_id + ) ) ncmd = Command( id=dev_prop_id.device_name, @@ -113,4 +134,9 @@ def inverse_read_command(self, command: ReadCommand) -> Sequence[ReadCommand]: device_name=command.id, property=command.property ) lat_prop_ids = self.liaison_manager.inverse(dev_prop_id) - return [ReadCommand(id=lp.element_name, property=lp.property) for lp in lat_prop_ids] \ No newline at end of file + return [ + ReadCommand(id=lp.element_name, property=lp.property) for lp in lat_prop_ids + ] + + +__all__ = ["CommandRewriter"] diff --git a/src/accml_lib/core/bl/liaison_manager.py b/src/accml_lib/core/bl/liaison_manager.py index 66e8611..e0acf94 100644 --- a/src/accml_lib/core/bl/liaison_manager.py +++ b/src/accml_lib/core/bl/liaison_manager.py @@ -2,7 +2,10 @@ from typing import Mapping, Sequence from accml_lib.core.interfaces.utils.liaison_manager import LiaisonManagerBase -from accml_lib.core.model.utils.identifiers import LatticeElementPropertyID, DevicePropertyID +from accml_lib.core.model.utils.identifiers import ( + LatticeElementPropertyID, + DevicePropertyID, +) logger = logging.getLogger("accml") @@ -45,3 +48,6 @@ def inverse(self, id_: DevicePropertyID) -> LatticeElementPropertyID: f"{self.__class__.__name__} id {id_} not found in lookup table: {ke}" ) raise ke + + +__all__ = ["LiaisonManager"] diff --git a/src/accml_lib/core/bl/translator_service.py b/src/accml_lib/core/bl/translator_service.py index fe5e88d..62a4964 100644 --- a/src/accml_lib/core/bl/translator_service.py +++ b/src/accml_lib/core/bl/translator_service.py @@ -41,3 +41,6 @@ def objects_for_device(self, id_: ConversionID): for key, to in self.lut.items() if id_.device_property_id.device_name == key.device_property_id.device_name } + + +__all__ = ["TranslatorService"] diff --git a/src/accml_lib/core/bl/unit_conversion.py b/src/accml_lib/core/bl/unit_conversion.py index 3937e0f..d14810e 100644 --- a/src/accml_lib/core/bl/unit_conversion.py +++ b/src/accml_lib/core/bl/unit_conversion.py @@ -66,3 +66,6 @@ def inverse(self, state: float) -> float: state, ) return (state - self.intercept) / self.slope + + +__all__ = ["EnergyDependentLinearUnitConversion", "LinearUnitConversion"] diff --git a/src/accml_lib/core/bl/yellow_pages.py b/src/accml_lib/core/bl/yellow_pages.py index 3c9da34..0578884 100644 --- a/src/accml_lib/core/bl/yellow_pages.py +++ b/src/accml_lib/core/bl/yellow_pages.py @@ -12,9 +12,9 @@ class FamilyName(Enum): class YellowPages(YellowPagesBase): """ - or use: - get(family_name: str) - separate yellow pages for lattice elements and devices + or use: + get(family_name: str) + separate yellow pages for lattice elements and devices """ def __init__(self, d: dict): @@ -30,3 +30,6 @@ def quadrupole_names(self) -> Sequence[str]: def tune_correction_quadrupole_names(self) -> Sequence[str]: return self.get("tune_correction_quadrupoles") + + +__all__ = ["FamilyName", "YellowPages"] From 21195c2bb27db06b954f119a2e9ba168228d80fe Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Tue, 20 Jan 2026 18:29:04 +0100 Subject: [PATCH 20/21] [TASK] measurement execution engine: make task async --- .../core/interfaces/utils/measurement_execution_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py b/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py index 61906a4..6db81bf 100644 --- a/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py +++ b/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py @@ -7,7 +7,7 @@ class MeasurementExecutionEngine(metaclass=ABCMeta): @abstractmethod - def execute( + async def execute( self, commands_collection: Sequence[TransactionCommand], *args, **kwargs ) -> str: """ From 2604b0cae68d41b3017a124aa08fc5b3a4a8ef75 Mon Sep 17 00:00:00 2001 From: Pierre Schnizer Date: Tue, 20 Jan 2026 18:29:04 +0100 Subject: [PATCH 21/21] [TASK] measurement execution engine: make task async --- .../core/interfaces/utils/measurement_execution_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py b/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py index 61906a4..6db81bf 100644 --- a/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py +++ b/src/accml_lib/core/interfaces/utils/measurement_execution_engine.py @@ -7,7 +7,7 @@ class MeasurementExecutionEngine(metaclass=ABCMeta): @abstractmethod - def execute( + async def execute( self, commands_collection: Sequence[TransactionCommand], *args, **kwargs ) -> str: """