diff --git a/.copier-answers.yml b/.copier-answers.yml index d460da2..1ff6234 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -9,7 +9,7 @@ component_type: library description: Accelerator Toolbox Interface for Pytac distribution_name: atip docker: false -docs_type: README +docs_type: sphinx git_platform: github.com github_org: DiamondLightSource package_name: atip diff --git a/.github/workflows/_docs.yml b/.github/workflows/_docs.yml new file mode 100644 index 0000000..a1cafca --- /dev/null +++ b/.github/workflows/_docs.yml @@ -0,0 +1,54 @@ +on: + workflow_call: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Avoid git conflicts when tag and branch pushed at same time + if: github.ref_type == 'tag' + run: sleep 60 + + - name: Checkout + uses: actions/checkout@v4 + with: + # Need this to get version number from last tag + fetch-depth: 0 + + - name: Install system packages + run: sudo apt-get install graphviz + + - name: Install python packages + uses: ./.github/actions/install_requirements + + - name: Build docs + run: tox -e docs + + - name: Remove environment.pickle + run: rm build/html/.doctrees/environment.pickle + + - name: Upload built docs artifact + uses: actions/upload-artifact@v4 + with: + name: docs + path: build + + - 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 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/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: .github/pages + keep_files: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc606ea..1df64d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,11 @@ jobs: secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + docs: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_docs.yml + dist: needs: check if: needs.check.outputs.branch-pr == '' @@ -48,7 +53,7 @@ jobs: release: if: github.ref_type == 'tag' - needs: [dist] + needs: [dist, docs] uses: ./.github/workflows/_release.yml permissions: contents: write diff --git a/.github/workflows/periodic.yml b/.github/workflows/periodic.yml new file mode 100644 index 0000000..e2a0fd1 --- /dev/null +++ b/.github/workflows/periodic.yml @@ -0,0 +1,13 @@ +name: Periodic + +on: + workflow_dispatch: + schedule: + # Run weekly to check URL links still resolve + - cron: "0 8 * * WED" + +jobs: + linkcheck: + uses: ./.github/workflows/_tox.yml + with: + tox: docs build -- -b linkcheck diff --git a/INSTALL.rst b/INSTALL.rst index 73d9434..5e18eac 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -20,11 +20,10 @@ Initial Setup and Installation $ cd $ git clone https://github.com/DiamondLightSource/atip.git -2. Create a pipenv and install the dependencies:: +2. From within a python virtual environment, install the dependencies:: $ cd atip - $ pipenv install --dev - $ pipenv shell + $ pip install -e ./ 3. Run the tests to ensure everything is working correctly:: diff --git a/README.md b/README.md index 1cbd902..38fcd16 100644 --- a/README.md +++ b/README.md @@ -3,30 +3,35 @@ [![PyPI](https://img.shields.io/pypi/v/atip.svg)](https://pypi.org/project/atip) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) -# atip -Accelerator Toolbox Interface for Pytac +# ATIP - Accelerator Toolbox Interface for Pytac -This is where you should write a short paragraph that describes what your module does, -how it does it, and why people should use it. +ATIP is an addition to [Pytac](), +a framework for controlling particle accelerators. ATIP adds a simulator to +Pytac, which can be used and addressed in the same way as a real accelerator. -Source | -:---: | :---: -PyPI | `pip install atip` -Releases | +ATIP enables the easy offline testing of high level accelerator +controls applications, by either of two methods: -This is where you should put some images or code snippets that illustrate -some relevant examples. If it is a library then you might put some -introductory code here: +* By replacing the real accelerator at the point where it is addressed by the + software, in the Pytac lattice object; -```python -from atip import __version__ +* In a standalone application as a "virtual accelerator", publishing the same + control system interface as the live machine. At Diamond Light Source this + has been implemented with EPICS, and run on a different port to the + operational control system. So the only change required to test software is + to configure this EPICS port. -print(f"Hello atip {__version__}") -``` +The python implementation of +[Accelerator Toolbox]() (pyAT) is used for the +simulation. -Or if it is a commandline tool then you might put some example commands here: +Source | +:---: | :---: +PyPI | `pip install atip` +Docker | `docker run ghcr.io/diamondlightsource/atip:latest` +Documentation | +Installation | +Releases | -``` -python -m atip --version -``` + diff --git a/README.rst b/README.rst deleted file mode 100644 index 160d484..0000000 --- a/README.rst +++ /dev/null @@ -1,216 +0,0 @@ -.. image:: https://travis-ci.org/DiamondLightSource/atip.svg?branch=master - :target: https://travis-ci.org/DiamondLightSource/atip -.. image:: https://coveralls.io/repos/github/DiamondLightSource/atip/badge.svg?branch=master - :target: https://coveralls.io/github/DiamondLightSource/atip?branch=master -.. image:: https://readthedocs.org/projects/atip/badge/?version=latest - :target: https://atip.readthedocs.io/en/latest/?badge=latest -.. image:: https://badge.fury.io/py/atip.svg - :target: https://badge.fury.io/py/atip - -============================================== -ATIP - Accelerator Toolbox Interface for Pytac -============================================== - -ATIP is an addition to `Pytac `_, -a framework for controlling particle accelerators. ATIP adds a simulator to -Pytac, which can be used and addressed in the same way as a real accelerator. - -ATIP enables the easy offline testing of high level accelerator -controls applications, by either of two methods: - -* By replacing the real accelerator at the point where it is addressed by the - software, in the Pytac lattice object; - -* In a standalone application as a "virtual accelerator", publishing the same - control system interface as the live machine. At Diamond Light Source this - has been implemented with EPICS, and run on a different port to the - operational control system. So the only change required to test software is - to configure this EPICS port. - -The python implementation of -`Accelerator Toolbox `_ (pyAT) is used for the -simulation. - -For further information on any of ATIP's functions or classes please read the -documentation `here `_. - -Installation: -------------- - -See the ``INSTALL.rst`` document. - -General Use: ------------- - -ATIP produces an "integrated lattice", which is a Pytac lattice object with a -simulation data source added. The simulated data sources are added using the -``load()`` function found in ``load_sim.py``. - -This adds ``pytac.SIM`` data sources on to the lattice and each of the -elements. - -The integrated lattice acts like a normal Pytac lattice; the simulator can be -referenced like the live machine but with the data source specified as -``pytac.SIM`` instead of ``pytac.LIVE``. - -For example, a get request to a BPM would be -``.get_value('x', data_source=pytac.SIM)``. - -The simulated data sources behave exactly like the live machine, except for a -few cases. For example, the simulator has a number of lattice fields that the -live accelerator doesn't have; and the live machine has a few element fields -that the simulator doesn't. - -Example -^^^^^^^ - -Note that you need an AT lattice that is compatible with Pytac. Some are provided -in ``atip/rings/``, otherwise try running the Matlab function -``atip/rings/create_lattice_matfile.m`` with an AT lattice loaded. - -.. code-block:: python - - >>> import pytac - >>> import atip - >>> # Load the DIAD lattice from Pytac. - >>> lat = pytac.load_csv.load('DIAD') - >>> # Load the AT sim into the Pytac lattice. - >>> atip.load_sim.load_from_filepath(lat, 'atip/rings/DIAD.mat') - >>> # Use the sim by default. - >>> lat.set_default_data_source(pytac.SIM) - >>> # The initial beam position is zero. - >>> lat.get_value('x') - array([0., 0., 0., ..., 0., 0., 0.]) - >>> # Get the first horizontal corrector magnet and set its current to 1A. - >>> hcor1 = lat.get_elements('HSTR')[0] - >>> hcor1.set_value('x_kick', 1, units=pytac.ENG) - >>> # Now the x beam position has changed. - >>> lat.get_value('x') - array([0.00240101, 0.00240101, 0.00239875, ..., 0.00240393, 0.00240327, - 0.00240327]) - >>> - -Virtual Accelerator: --------------------- - -Instructions for using ATIP as a virtual accelerator can be found in -``virtac/README.rst``. - -Implementation: ---------------- - -All the accelerator data for the simulator is held in an ``ATSimulator`` -object, which is referenced by the data sources of the lattice and each -element.Each Pytac element has an equivalent pyAT element, held in a -``ATElementDataSource``; when a get request is made, the appropriate data from -that AT element is returned. - -The ``ATSimulator`` object has a queue of pending changes. When a set request -is received by an element, the element puts the changes onto the queue of the -``ATSimulator``. Inside the ``ATSimulator`` a -`Cothread `_ thread checks the -length of the queue. When it sees changes on the queue, the thread -recalculates the physics data of the lattice to ensure that it is up to date. -This means that the emittance and linear optics data held by ``ATSimulator`` -is updated after every batch of changes, and that without excessive calculation -a very recent version of the lattice's physics data is always available. - -API: ----- - -load_sim: - * ``load_from_filepath(pytac_lattice, at_lattice_filepath, callback=None)`` - - loads the AT lattice from the given filepath to the .mat file and then - calls ``load``. - * ``load(pytac_lattice, at_lattice, callback=None)`` - loads the simulator - onto the passed Pytac lattice, callback is a callable that is passed to - ATSimulator during creation to be called on completion of each round of - physics calculations. - -ATElementDataSource: - * ``get_fields()`` - return the fields on the element. - * ``add_field(field)`` - add the given field to this element's data source. - * ``get_value(field)`` - get the value for a given field on the element. - * ``set_value(field, value)`` - set the value for a given field on the - element, appends the change to the queue. - -ATLatticeDataSource: - * ``get_fields()`` - return the fields on the lattice. - * ``get_value(field)`` - get the value for a given field on the lattice. - * ``set_value(field, set_value)`` - set the value for a given field on the - lattice, currently not supported so raises HandleException. - -ATSimulator: - * ``toggle_calculations()`` - pause or unpause the recalculation thread. - * ``wait_for_calculations(timeout=10)`` - wait up to 'timeout' seconds for - the current calculations to conclude, if they do it returns True, if not - False is returned; if 'timeout' is not passed it will wait 10 seconds. - * ``get_at_element(index)`` - return a shallow copy of the specified AT - element from the central AT ring, N.B. An 'index' of 1 returns ring[0]. - * ``get_at_lattice()`` - return a shallow copy of the entire centralised AT - lattice object. - * ``get_s()`` - return the 's position' of every element in the lattice. - * ``get_total_bend_angle()`` - return the total bending angle of all the - dipoles in the lattice. - * ``get_total_absolute_bend_angle()`` - return the total absolute bending - angle of all the dipoles in the lattice. - * ``get_energy()`` - return the energy of the lattice. - * ``get_tune(field)`` - return the specified plane of the lattice's - 'tune'; 'x' or 'y'. - * ``get_chromaticity(field)`` - return the specified plane of the lattice's - 'chromaticity'; 'x' or 'y'. - * ``get_orbit(field)`` - return the specified plane of the lattice's - 'closed orbit'; 'x', 'phase_x', 'y', or 'phase_y'. - * ``get_dispersion()`` - return the 'dispersion' vector for every element - in the lattice. - * ``get_alpha()`` - return the 'alpha' vector at every element in the - lattice. - * ``get_beta()`` - return the 'beta' vector at every element in the - lattice. - * ``get_mu()`` - return 'mu' at every element in the lattice. - * ``get_m44()`` - return the 4x4 transfer matrix for every element in the - lattice. - * ``get_emittance(field)`` - return the specified plane of the lattice's - 'emittance'; 'x' or 'y'. - * ``get_radiation_integrals()`` - return the 5 Synchrotron Integrals for - the lattice. - * ``get_momentum_compaction()`` - return the momentum compaction factor - for the lattice. - * ``get_energy_spread()`` - return the energy spread for the lattice. - * ``get_energy_loss()`` - return the energy loss per turn of the lattice. - * ``get_damping_partition_numbers()`` - return the damping partition - numbers for the lattice's three normal modes. - * ``get_damping_times()`` - return the damping times for the lattice's - three normal modes. - * ``get_linear_dispersion_action()`` - return the Linear Dispersion Action - ("curly H") for the lattice. - * ``get_horizontal_emittance()`` - return the horizontal ('x') emittance - for the lattice calculated from the radiation integrals. - - -Specific Notes: ---------------- - -In order for ATIP to function correctly, the AT and Pytac lattices used must be -directly equivalent, i.e. they must have the same length and elements in the -same positions. - -If local (not pip) installations are used, ATIP, AT, and Pytac must all be -located in the same source directory in order for ATIP to function correctly. - -The methods on ATIP's data sources that take ``handle`` and ``throw`` arguments -do so only to conform with the Pytac ``DataSource`` base class from which they -inherit. Inside ATIP they are not used and can be ignored. - -To interpret which data is to be returned or set, both ``ATElementDataSource`` -and ``ATLatticeDataSource`` use a dictionary of functions corresponding to -fields. In the case where a cell needs to be passed to the data handling -functions, for further specification, functools' ``partial()`` is used. - -The physics data is received from AT all together; to make it easier to manage, -it is split by ATIP and accessed by a number of methods of the ``ATSimulator`` -object. This aims to be more convenient for the user but does result in the -ATSimulator object having a large number of methods. - -A number of functions that perform tasks that are frequent or long-winded are -included in ``utils.py`` to make life easier for the user. diff --git a/docs/_api.rst b/docs/_api.rst new file mode 100644 index 0000000..d782093 --- /dev/null +++ b/docs/_api.rst @@ -0,0 +1,17 @@ +:orphan: + +.. + This page is not included in the TOC tree, but must exist so that the + autosummary pages are generated for atip and all its + subpackages + +API +=== + +.. autosummary:: + :toctree: _api + :template: custom-module-template.rst + :recursive: + + atip + virtac diff --git a/docs/_templates/custom-module-template.rst b/docs/_templates/custom-module-template.rst new file mode 100644 index 0000000..9aeca54 --- /dev/null +++ b/docs/_templates/custom-module-template.rst @@ -0,0 +1,37 @@ +{{ ('``' + fullname + '``') | underline }} + +{%- set filtered_members = [] %} +{%- for item in members %} + {%- if item in functions + classes + exceptions + attributes %} + {% set _ = filtered_members.append(item) %} + {%- endif %} +{%- endfor %} + +.. automodule:: {{ fullname }} + :members: + + {% block modules %} + {% if modules %} + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + {% for item in modules %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block members %} + {% if filtered_members %} + .. rubric:: Members + + .. autosummary:: + :nosignatures: + {% for item in filtered_members %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/docs/atip.rst b/docs/atip.rst deleted file mode 100644 index e424942..0000000 --- a/docs/atip.rst +++ /dev/null @@ -1,39 +0,0 @@ -API Documentation -================= - -.. automodule:: atip - :members: - :undoc-members: - :show-inheritance: - -atip.load_sim module --------------------- - -.. automodule:: atip.load_sim - :members: - :undoc-members: - :show-inheritance: - -atip.sim_data_sources module ----------------------------- - -.. automodule:: atip.sim_data_sources - :members: - :undoc-members: - :show-inheritance: - -atip.simulator module ------------------------- - -.. automodule:: atip.simulator - :members: - :undoc-members: - :show-inheritance: - -atip.utils module ------------------------- - -.. automodule:: atip.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index 3e14985..63ca443 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,189 +1,209 @@ -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -import os -import sys +"""Configuration file for the Sphinx documentation builder. -# -- Path setup -------------------------------------------------------------- +This file only contains a selection of the most common options. For a full +list see the documentation: +https://www.sphinx-doc.org/en/master/usage/configuration.html +""" -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import sys +from pathlib import Path +from subprocess import check_output -# -- Project information ----------------------------------------------------- +import requests -project = "ATIP" -copyright = "2019, Tobyn Nicholls" -author = "Tobyn Nicholls" +import atip -# The short X.Y version -version = "1.0" -# The full version, including alpha/beta/rc tags -release = "1.0" +# -- General configuration ------------------------------------------------ +# General information about the project. +project = "atip" -# -- General configuration --------------------------------------------------- +# The full version, including alpha/beta/rc tags. +release = atip.__version__ -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' +# The short X.Y version. +if "+" in release: + # Not on a tag, use branch name + root = Path(__file__).absolute().parent.parent + git_branch = check_output("git branch --show-current".split(), cwd=root) + version = git_branch.decode().strip() +else: + version = release -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ + # Use this for generating API docs "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", + # and making summary tables at the top of API docs + "sphinx.ext.autosummary", + # This can parse google style docstrings "sphinx.ext.napoleon", + # For linking to external sphinx documentation + "sphinx.ext.intersphinx", + # Add links to source code in API docs "sphinx.ext.viewcode", + # Adds the inheritance-diagram generation directive + "sphinx.ext.inheritance_diagram", + # Add a copy button to each code block + "sphinx_copybutton", + # For the card element + "sphinx_design", + # So we can write markdown files + "myst_parser", +] + +# So we can use the ::: syntax +myst_enable_extensions = ["colon_fence", "html_image"] + +# If true, Sphinx will warn about all references where the target cannot +# be found. +nitpicky = True + +# A list of (type, target) tuples (by default empty) that should be ignored when +# generating warnings in "nitpicky mode". Note that type should include the +# domain name if present. Example entries would be ('py:func', 'int') or +# ('envvar', 'LD_LIBRARY_PATH'). +nitpick_ignore = [ + ("py:class", "NoneType"), + ("py:class", "'str'"), + ("py:class", "'float'"), + ("py:class", "'int'"), + ("py:class", "'bool'"), + ("py:class", "'object'"), + ("py:class", "'id'"), + ("py:class", "typing_extensions.Literal"), + ("py:class", "number"), + ("py:class", "pythonSoftIoc.RecordWrapper"), + ("py:class", "pytac.lattice.Lattice"), + ("py:exc", "pytac.FieldException"), + ("py:exc", "pytac.ControlSystemException"), + ("py:exc", "pytac.HandleException"), + ("py:class", "at.lattice_object.Lattice"), + ("py:class", "numpy._typing._array_like._Buffer"), + ("py:class", "numpy._typing._array_like._SupportsArray"), + ("py:class", "numpy.dtype"), + ("py:class", "numpy._typing._nested_sequence._NestedSequence"), + ("py:class", "at.elements.Element"), + ("py:class", "numpy.typing.NDArray"), + ("py:class", "at.lattice.Lattice"), + ("py:class", "obj"), + ("py:class", "class"), + ("py:class", "at.lattice.lattice_object.Lattice"), + ("py:class", "cothread.Event"), ] -# Include both class and __init__() docstrings. +# Both the class’ and the __init__ method’s docstring are concatenated and +# inserted into the main body of the autoclass directive autoclass_content = "both" +# Order the members by the order they appear in the source code +autodoc_member_order = "bysource" + +# Don't inherit docstrings from baseclasses +autodoc_inherit_docstrings = False + +# Document only what is in __all__ +autosummary_ignore_module_all = False + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" +# Output graphviz directive produced images in a scalable format +graphviz_output_format = "svg" + +# The name of a reST role (builtin or Sphinx extension) to use as the default +# role, that is, for text marked up `like this` +default_role = "any" # The master toctree document. master_doc = "index" -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. +# These patterns also affect html_static_path and html_extra_path exclude_patterns = ["_build"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" +# This means you can link things like `str` and `asyncio` to the relevant +# docs in the python documentation. +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} -# -- Options for HTML output ------------------------------------------------- +# A dictionary of graphviz graph attributes for inheritance diagrams. +inheritance_graph_attrs = {"rankdir": "TB"} -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" +# Ignore localhost links for periodic check that links in docs are valid +linkcheck_ignore = [r"http://localhost:\d+/"] -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} +# Set copy-button to ignore python and bash prompts +# https://sphinx-copybutton.readthedocs.io/en/latest/use.html#using-regexp-prompt-identifiers +copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] +# -- Options for HTML output ------------------------------------------------- -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. # -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "docsdoc" - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', +html_theme = "pydata_sphinx_theme" +github_repo = "atip" +github_user = "DiamondLightSource" +switcher_json = f"https://{github_user}.github.io/{github_repo}/switcher.json" +switcher_exists = requests.get(switcher_json).ok +if not switcher_exists: + print( + "*** Can't read version switcher, is GitHub pages enabled? \n" + " Once Docs CI job has successfully run once, set the " + "Github pages source branch to be 'gh-pages' at:\n" + f" https://github.com/{github_user}/{github_repo}/settings/pages", + file=sys.stderr, + ) + +# Theme options for pydata_sphinx_theme +# We don't check switcher because there are 3 possible states for a repo: +# 1. New project, docs are not published so there is no switcher +# 2. Existing project with latest copier template, switcher exists and works +# 3. Existing project with old copier template that makes broken switcher, +# switcher exists but is broken +# Point 3 makes checking switcher difficult, because the updated copier template +# will fix the switcher at the end of the docs workflow, but never gets a chance +# to complete as the docs build warns and fails. +html_theme_options = { + "logo": { + "text": project, + }, + "use_edit_page_button": True, + "github_url": f"https://github.com/{github_user}/{github_repo}", + "icon_links": [ + { + "name": "PyPI", + "url": f"https://pypi.org/project/{project}", + "icon": "fas fa-cube", + } + ], + "switcher": { + "json_url": switcher_json, + "version_match": version, + }, + "check_switcher": False, + "navbar_end": ["theme-switcher", "icon-links", "version-switcher"], + "navigation_with_keys": False, } -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, "docs.tex", "ATIP Documentation", "Tobyn Nicholls", "manual"), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "docs", "ATIP Documentation", [author], 1)] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "docs", - "ATIP Documentation", - author, - "docs", - "ATIP - Accelerator Toolbox Interface for Pytac.", - "Accelerator Physics", - ), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ["search.html"] - +# A dictionary of values to pass into the template engine’s context for all pages +html_context = { + "github_user": github_user, + "github_repo": github_repo, + "github_version": version, + "doc_path": "docs", +} -# -- Extension configuration ------------------------------------------------- +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = False -# -- Options for intersphinx extension --------------------------------------- +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = False -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - "python": ("https://docs.python.org/", None), - "pytac": ("https://pytac.readthedocs.io/en/latest/", None), - "cothread": ("https://cothread.readthedocs.io/en/latest/", None), -} +# Logo +html_logo = "images/dls-logo.svg" +html_favicon = html_logo diff --git a/docs/explanations.md b/docs/explanations.md new file mode 100644 index 0000000..ae8ee94 --- /dev/null +++ b/docs/explanations.md @@ -0,0 +1,12 @@ +# Explanations + +Explanations of how it works and why it works that way. + +```{toctree} +:maxdepth: 1 +:glob: + +explanations/implementation_details +explanations/feedback_systems +explanations/* +``` diff --git a/docs/explanations/decisions.md b/docs/explanations/decisions.md new file mode 100644 index 0000000..0533b98 --- /dev/null +++ b/docs/explanations/decisions.md @@ -0,0 +1,12 @@ +# Architectural Decision Records + +Architectural decisions are made throughout a project's lifetime. As a way of keeping track of these decisions, we record these decisions in Architecture Decision Records (ADRs) listed below. + +```{toctree} +:glob: true +:maxdepth: 1 + +decisions/* +``` + +For more information on ADRs see this [blog by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). diff --git a/docs/explanations/decisions/0001-record-architecture-decisions.md b/docs/explanations/decisions/0001-record-architecture-decisions.md new file mode 100644 index 0000000..44d234e --- /dev/null +++ b/docs/explanations/decisions/0001-record-architecture-decisions.md @@ -0,0 +1,18 @@ +# 1. Record architecture decisions + +## Status + +Accepted + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. To create new ADRs we will copy and +paste from existing ones. diff --git a/docs/explanations/decisions/0002-switched-to-python-copier-template.md b/docs/explanations/decisions/0002-switched-to-python-copier-template.md new file mode 100644 index 0000000..66fe5d8 --- /dev/null +++ b/docs/explanations/decisions/0002-switched-to-python-copier-template.md @@ -0,0 +1,28 @@ +# 2. Adopt python-copier-template for project structure + +## Status + +Accepted + +## Context + +We should use the following [python-copier-template](https://github.com/DiamondLightSource/python-copier-template). +The template will ensure consistency in developer +environments and package management. + +## Decision + +We have switched to using the template. + +## Consequences + +This module will use a fixed set of tools as developed in `python-copier-template` +and can pull from this template to update the packaging to the latest techniques. + +As such, the developer environment may have changed, the following could be +different: + +- linting +- formatting +- pip venv setup +- CI/CD diff --git a/docs/explanations/decisions/COPYME b/docs/explanations/decisions/COPYME new file mode 100644 index 0000000..b466c79 --- /dev/null +++ b/docs/explanations/decisions/COPYME @@ -0,0 +1,19 @@ +# 3. Short descriptive title + +Date: Today's date + +## Status + +Accepted + +## Context + +Background to allow us to make the decision, to show how we arrived at our conclusions. + +## Decision + +What decision we made. + +## Consequences + +What we will do as a result of this decision. diff --git a/src/virtac/FEEDBACK_SYSTEMS.rst b/docs/explanations/feedback_systems.md similarity index 96% rename from src/virtac/FEEDBACK_SYSTEMS.rst rename to docs/explanations/feedback_systems.md index 375aecc..638ea06 100644 --- a/src/virtac/FEEDBACK_SYSTEMS.rst +++ b/docs/explanations/feedback_systems.md @@ -1,6 +1,5 @@ -================ -Feedback Systems -================ +# Feedback Systems + Currently supported "slow" feedback systems at Diamond are: @@ -12,8 +11,7 @@ Currently supported "slow" feedback systems at Diamond are: In order to support these various feedback systems, the virtual accelerator makes several adjustments and additions to core ATIP functionality. -Mirrored Records: ------------------ +## Mirrored Records: The ability to create mirror records is provided. A mirror record can take value(s) from one or more records as inputs and set its output dependent on @@ -36,8 +34,7 @@ For more information on mirror records see docstrings of the classes in ``mirror_objects.py``, the relevant methods on ``ATIPServer``, and ``generate_mirrored_pvs`` in ``create_csv.py``. -Masks: ------- +## Masks: Masks are wrappers for existing functions to enable them to be addressed using a different syntax than normal. The types of masks are: @@ -54,8 +51,7 @@ a different syntax than normal. The types of masks are: record object, ``.set(value)`` simply calls ``caput(stored_pv, value)``. -Tune feedback -------------- +## Tune feedback As mentioned above, the ``callback_offset`` class allows the tune feedback system to function exactly as it does on the live machine. diff --git a/docs/explanations/implementation_details.md b/docs/explanations/implementation_details.md new file mode 100644 index 0000000..116c007 --- /dev/null +++ b/docs/explanations/implementation_details.md @@ -0,0 +1,53 @@ +# Implementation details + +All the accelerator data for the simulator is held in an ATSimulator object, which is referenced by the data sources of the lattice and each element.Each Pytac element has an equivalent pyAT element, held in a ATElementDataSource; when a get request is made, the appropriate data from that AT element is returned. + +The ATSimulator object has a queue of pending changes. When a set request is received by an element, the element puts the changes onto the queue of the ATSimulator. Inside the ATSimulator a Cothread thread checks the length of the queue. When it sees changes on the queue, the thread recalculates the physics data of the lattice to ensure that it is up to date. This means that the emittance and linear optics data held by ATSimulator is updated after every batch of changes, and that without excessive calculation a very recent version of the lattice's physics data is always available. + +## API: + +load_sim: + + load_from_filepath(pytac_lattice, at_lattice_filepath, callback=None) - loads the AT lattice from the given filepath to the .mat file and then calls load. + load(pytac_lattice, at_lattice, callback=None) - loads the simulator onto the passed Pytac lattice, callback is a callable that is passed to ATSimulator during creation to be called on completion of each round of physics calculations. + +ATElementDataSource: + + get_fields() - return the fields on the element. + add_field(field) - add the given field to this element's data source. + get_value(field) - get the value for a given field on the element. + set_value(field, value) - set the value for a given field on the element, appends the change to the queue. + +ATLatticeDataSource: + + get_fields() - return the fields on the lattice. + get_value(field) - get the value for a given field on the lattice. + set_value(field, set_value) - set the value for a given field on the lattice, currently not supported so raises HandleException. + +ATSimulator: + + toggle_calculations() - pause or unpause the recalculation thread. + wait_for_calculations(timeout=10) - wait up to 'timeout' seconds for the current calculations to conclude, if they do it returns True, if not False is returned; if 'timeout' is not passed it will wait 10 seconds. + get_at_element(index) - return a shallow copy of the specified AT element from the central AT ring, N.B. An 'index' of 1 returns ring[0]. + get_at_lattice() - return a shallow copy of the entire centralised AT lattice object. + get_s() - return the 's position' of every element in the lattice. + get_total_bend_angle() - return the total bending angle of all the dipoles in the lattice. + get_total_absolute_bend_angle() - return the total absolute bending angle of all the dipoles in the lattice. + get_energy() - return the energy of the lattice. + get_tune(field) - return the specified plane of the lattice's 'tune'; 'x' or 'y'. + get_chromaticity(field) - return the specified plane of the lattice's 'chromaticity'; 'x' or 'y'. + get_orbit(field) - return the specified plane of the lattice's 'closed orbit'; 'x', 'phase_x', 'y', or 'phase_y'. + get_dispersion() - return the 'dispersion' vector for every element in the lattice. + get_alpha() - return the 'alpha' vector at every element in the lattice. + get_beta() - return the 'beta' vector at every element in the lattice. + get_mu() - return 'mu' at every element in the lattice. + get_m44() - return the 4x4 transfer matrix for every element in the lattice. + get_emittance(field) - return the specified plane of the lattice's 'emittance'; 'x' or 'y'. + get_radiation_integrals() - return the 5 Synchrotron Integrals for the lattice. + get_momentum_compaction() - return the momentum compaction factor for the lattice. + get_energy_spread() - return the energy spread for the lattice. + get_energy_loss() - return the energy loss per turn of the lattice. + get_damping_partition_numbers() - return the damping partition numbers for the lattice's three normal modes. + get_damping_times() - return the damping times for the lattice's three normal modes. + get_linear_dispersion_action() - return the Linear Dispersion Action ("curly H") for the lattice. + get_horizontal_emittance() - return the horizontal ('x') emittance for the lattice calculated from the radiation integrals. diff --git a/docs/genindex.md b/docs/genindex.md new file mode 100644 index 0000000..73f1191 --- /dev/null +++ b/docs/genindex.md @@ -0,0 +1,3 @@ +# Index + + diff --git a/docs/how-to.md b/docs/how-to.md new file mode 100644 index 0000000..6b16141 --- /dev/null +++ b/docs/how-to.md @@ -0,0 +1,10 @@ +# How-to Guides + +Practical step-by-step guides for the more experienced user. + +```{toctree} +:maxdepth: 1 +:glob: + +how-to/* +``` diff --git a/docs/how-to/contribute.md b/docs/how-to/contribute.md new file mode 100644 index 0000000..6e41979 --- /dev/null +++ b/docs/how-to/contribute.md @@ -0,0 +1,2 @@ +```{include} ../../.github/CONTRIBUTING.md +``` diff --git a/docs/control_structure.png b/docs/images/control_structure.png similarity index 100% rename from docs/control_structure.png rename to docs/images/control_structure.png diff --git a/docs/images/dls-logo.svg b/docs/images/dls-logo.svg new file mode 100644 index 0000000..4fcaa86 --- /dev/null +++ b/docs/images/dls-logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..730b3fd --- /dev/null +++ b/docs/index.md @@ -0,0 +1,56 @@ +--- +html_theme.sidebar_secondary.remove: true +--- + +```{include} ../README.md +:end-before: + +::::{grid} 2 +:gutter: 4 + +:::{grid-item-card} {material-regular}`directions_walk;2em` +```{toctree} +:maxdepth: 2 +tutorials +``` ++++ +Tutorials for installation and typical usage. New users start here. +::: + +:::{grid-item-card} {material-regular}`directions;2em` +```{toctree} +:maxdepth: 2 +how-to +``` ++++ +Practical step-by-step guides for the more experienced user. +::: + +:::{grid-item-card} {material-regular}`info;2em` +```{toctree} +:maxdepth: 2 +explanations +``` ++++ +Explanations of how it works and why it works that way. +::: + +:::{grid-item-card} {material-regular}`menu_book;2em` +```{toctree} +:maxdepth: 2 +reference +``` ++++ +Technical reference material including APIs and release notes. +::: + +:::: diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 956eb2e..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,82 +0,0 @@ -ATIP - Accelerator Toolbox Interface for Pytac -============================================== - -ATIP is an addition to `Pytac `_, -a framework for controlling particle accelerators. ATIP adds a simulator to -Pytac, which can be used and addressed in the same way as a real accelerator. -This enables the easy offline testing of high level accelerator controls -applications. - -ATIP is hosted on Github `here `_. - -The python implementation of -`Accelerator Toolbox `_ (pyAT) is used -for the simulation. - -.. sidebar:: How ATIP fits into the combined control structure. - - .. image:: control_structure.png - :width: 400 - -ATIP allows an AT lattice to be fitted into the simulation data source of a -Pytac lattice. This integrated lattice acts like a normal Pytac lattice, and -enables the AT simulator to react and respond to changes as the real -accelerator would. - -ATIP also makes use of a `Cothread `_ -thread to recalculate and update the stored physics data any time a change is -made to the lattice. - -ATIP can also be run in a standalone application as a "virtual accelerator", -publishing the same control system interface as the live machine. At Diamond -Light Source this has been implemented with EPICS, using -`PythonSoftIOC `_. -This functionality is not documented here but an explanation of how it works -and how to use it may be found in the ``.rst`` files inside ATIP's ``virtac`` -directory. - -Example -------- - -Note that you need an AT lattice that is compatible with Pytac. Some are provided -in ``atip/rings/``, otherwise try running the Matlab function -``atip/rings/create_lattice_matfile.m`` with an AT lattice loaded. - -.. code-block:: python - - >>> import pytac - >>> import atip - >>> # Load the DIAD lattice from Pytac. - >>> lat = pytac.load_csv.load('DIAD') - >>> # Load the AT sim into the Pytac lattice. - >>> atip.load_sim.load_from_filepath(lat, 'atip/rings/DIAD.mat') - >>> # Use the sim by default. - >>> lat.set_default_data_source(pytac.SIM) - >>> # The initial beam position is zero. - >>> lat.get_value('x') - array([0., 0., 0., ..., 0., 0., 0.]) - >>> # Get the first horizontal corrector magnet and set its current to 1A. - >>> hcor1 = lat.get_elements('HSTR')[0] - >>> hcor1.set_value('x_kick', 1, units=pytac.ENG) - >>> # Now the x beam position has changed. - >>> lat.get_value('x') - array([0.00240101, 0.00240101, 0.00239875, ..., 0.00240393, 0.00240327, - 0.00240327]) - >>> - -Contents: -========= - -.. toctree:: - :maxdepth: 2 - - self - atip - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 0000000..2efa224 --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,13 @@ +# Reference + +Technical reference material including APIs and release notes. + +```{toctree} +:maxdepth: 1 +:glob: + +Atip API <_api/atip> +Virtac API <_api/virtac> +genindex +Release Notes +``` diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 6e3b13a..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Still required for readthedocs. -accelerator-toolbox>=0.0.2 -pytac>=0.3.0 -cothread -numpy -scipy -pytest -pytest-cov -testfixtures -coveralls -mock -flake8 -sphinx -sphinx-rtd-theme diff --git a/docs/tutorials.md b/docs/tutorials.md new file mode 100644 index 0000000..b80c28b --- /dev/null +++ b/docs/tutorials.md @@ -0,0 +1,13 @@ +# Tutorials + +Tutorials for installation and typical usage. New users start here. + +```{toctree} +:maxdepth: 1 +:glob: + +tutorials/installation +tutorials/overview +tutorials/atip_example +tutorials/virtac_example +``` diff --git a/docs/tutorials/atip_example.md b/docs/tutorials/atip_example.md new file mode 100644 index 0000000..fa954ff --- /dev/null +++ b/docs/tutorials/atip_example.md @@ -0,0 +1,29 @@ +# ATIP example + +## Simmulating accelerator physics using ATIP as a data source for Pytac + +Note that you need an AT lattice that is compatible with Pytac. Some are provided +in ``atip/rings/``, otherwise try running the Matlab function +``atip/rings/create_lattice_matfile.m`` with an AT lattice loaded. + +:::{code-block} python + +>>> import pytac +>>> import atip +>>> # Load the DIAD lattice from Pytac. +>>> lat = pytac.load_csv.load('DIAD') +>>> # Load the AT sim into the Pytac lattice. +>>> atip.load_sim.load_from_filepath(lat, 'atip/rings/DIAD.mat') +>>> # Use the sim by default. +>>> lat.set_default_data_source(pytac.SIM) +>>> # The initial beam position is zero. +>>> lat.get_value('x') +array([0., 0., 0., ..., 0., 0., 0.]) +>>> # Get the first horizontal corrector magnet and set its current to 1A. +>>> hcor1 = lat.get_elements('HSTR')[0] +>>> hcor1.set_value('x_kick', 1, units=pytac.ENG) +>>> # Now the x beam position has changed. +>>> lat.get_value('x') +array([0.00240101, 0.00240101, 0.00239875, ..., 0.00240393, 0.00240327, + 0.00240327]) +::: diff --git a/docs/tutorials/installation.md b/docs/tutorials/installation.md new file mode 100644 index 0000000..2e1473f --- /dev/null +++ b/docs/tutorials/installation.md @@ -0,0 +1,42 @@ +# Installation + +## Check your version of python + +You will need python 3.10 or later. You can check your version of python by +typing into a terminal: + +``` +$ python3 --version +``` + +## Create a virtual environment + +It is recommended that you install into a “virtual environment” so this +installation will not interfere with any existing Python software: + +``` +$ python3 -m venv /path/to/venv +$ source /path/to/venv/bin/activate +``` + +## Installing the library + +You can now use `pip` to install the library and its dependencies: + +``` +$ python3 -m pip install atip +``` + +If you require a feature that is not currently released you can also install +from github: + +``` +$ python3 -m pip install git+https://github.com/DiamondLightSource/atip.git +``` + +The library should now be installed and the commandline interface on your path. +You can check the version that has been installed by typing: + +``` +$ atip --version +``` diff --git a/docs/tutorials/overview.md b/docs/tutorials/overview.md new file mode 100644 index 0000000..bfe3af1 --- /dev/null +++ b/docs/tutorials/overview.md @@ -0,0 +1,48 @@ +# Overview + +ATIP is an addition to [Pytac]() +a framework for controlling particle accelerators. ATIP adds a simulator to +Pytac, which can be used and addressed in the same way as a real accelerator. +This enables the easy offline testing of high level accelerator controls +applications. + +ATIP is hosted on Github [here]() + +The python implementation of +[Accelerator Toolbox]() (pyAT) is used +for the simulation. + +:::sidebar +How ATIP fits into the combined control structure. +::: + +ATIP allows an AT lattice to be fitted into the simulation data source of a +Pytac lattice. This integrated lattice acts like a normal Pytac lattice, and +enables the AT simulator to react and respond to changes as the real +accelerator would. + +ATIP also makes use of a [Cothread]() +thread to recalculate and update the stored physics data any time a change is +made to the lattice. + +ATIP can also be run in a standalone application as a "virtual accelerator", +publishing the same control system interface as the live machine. At Diamond +Light Source this has been implemented with EPICS, using +[PythonSoftIOC]() + +More documentation on how to use the virtual accelerator can be found [here]() + + +## Helpful tips: + +In order for ATIP to function correctly, the AT and Pytac lattices used must be directly equivalent, i.e. they must have the same length and elements in the same positions. + +If local (not pip) installations are used, ATIP, AT, and Pytac must all be located in the same source directory in order for ATIP to function correctly. + +The methods on ATIP's data sources that take handle and throw arguments do so only to conform with the Pytac DataSource base class from which they inherit. Inside ATIP they are not used and can be ignored. + +To interpret which data is to be returned or set, both ATElementDataSource and ATLatticeDataSource use a dictionary of functions corresponding to fields. In the case where a cell needs to be passed to the data handling functions, for further specification, functools' partial() is used. + +The physics data is received from AT all together; to make it easier to manage, it is split by ATIP and accessed by a number of methods of the ATSimulator object. This aims to be more convenient for the user but does result in the ATSimulator object having a large number of methods. + +A number of functions that perform tasks that are frequent or long-winded are included in utils.py to make life easier for the user. diff --git a/src/virtac/README.rst b/docs/tutorials/virtac_example.md similarity index 63% rename from src/virtac/README.rst rename to docs/tutorials/virtac_example.md index 3ba9cea..91ef7b6 100644 --- a/src/virtac/README.rst +++ b/docs/tutorials/virtac_example.md @@ -1,6 +1,6 @@ -=========================================================== -Running ATIP as a Virtual Accelerator using Python Soft IOC -=========================================================== +# VIRTAC example + +## Running ATIP as a Virtual Accelerator using Python Soft IOC Using `PythonSoftIOC `_, ATIP can emulate machine PVs, so that the ATIP simulator can be addressed in the same @@ -13,44 +13,50 @@ used by convention at Diamond for simulations) to avoid conflict with the same PVs on the live machine. -Start the virtual accelerator ------------------------------ +## Start the virtual accelerator + +Once ATIP has been installed using pip or by running the docker image: -Run the virtac under the development EPICS port:: +Run the virtac under the development EPICS port: - $ export EPICS_CA_SERVER_PORT=6064 - $ export EPICS_CAS_SERVER_PORT=6064 - $ export EPICS_CA_REPEATER_PORT=6065 - $ # at Diamond the above can be set in one go using: . changeports 6064 - $ pipenv run virtac +:::{code-block} bash +$ export EPICS_CA_SERVER_PORT=6064 +$ export EPICS_CAS_SERVER_PORT=6064 +$ export EPICS_CA_REPEATER_PORT=6065 +$ # at Diamond the above can be set in one go using: . changeports 6064 +$ virtac +::: It takes 10 seconds or so to load the interactive console:: - Starting record creation. - ~*~*Woah, we're halfway there, Wo-oah...*~*~ - Finished creating all 2981 records. - Starting iocInit - ############################################################################ - ## EPICS 7.0.6.0 - ## Rev. 7.0.6.99.1.0 - ############################################################################ - iocRun: All initialization complete - Python 3.7.2 (default, Jan 20 2020, 11:03:41) - [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux - Type "help", "copyright", "credits" or "license" for more information. - (InteractiveConsole) - >>> +:::{code-block} bash +Starting record creation. +~*~*Woah, were halfway there, Wo-oah...*~*~ +Finished creating all 2981 records. +Starting iocInit +############################################################################ +## EPICS 7.0.6.0 +## Rev. 7.0.6.99.1.0 +############################################################################ +iocRun: All initialization complete +Python 3.7.2 (default, Jan 20 2020, 11:03:41) +[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux +Type "help", "copyright", "credits" or "license" for more information. +(InteractiveConsole) +>>> +::: Leave the server running and in a new terminal update the EPICS port:: - $ export EPICS_CA_SERVER_PORT=6064 - $ # or: . changeports 6064 +:::{code-block} bash +$ export EPICS_CA_SERVER_PORT=6064 +$ # or: . changeports 6064 +::: In this new terminal you are then free to address the simulator as you would the live machine, either through Pytac or by directly accessing the PVs. -Feedback Records: ------------------ +## Feedback Records: A number of PVs related to feedback systems are supported. These have been added to aid testing of the high level applications at Diamond that control @@ -95,17 +101,20 @@ This is done inside the server console, in the terminal where one you ran For example disabling SOFB on the first BPM:: - >>> server.set_feedback_record(3, 'enabled', 0) +:::{code-block} bash +>>> server.set_feedback_record(3, 'enabled', 0) +::: or reducing the beam current:: - >>> server.set_feedback_record(0, 'beam_current', 280) +:::{code-block} bash +>>> server.set_feedback_record(0, 'beam_current', 280) +::: For further information on working with feedback systems, please refer to ``FEEDBACK_SYSTEMS.rst``. -Ring Mode: ----------- +## Ring Mode: You can run the virtual accelerator in any ring mode that is supported by Pytac; currently 'VMX', 'VMXSP', 'DIAD', and 'I04'. The ring mode can be set by the @@ -119,7 +128,9 @@ If none of these is set then the virtual accelerator will default to 'I04'. For example:: - $ pipenv run virtac I04 - $ export RINGMODE=I04 - $ caput SR-CS-RING-01:MODE 3 - $ # Having none of these set would also start in mode 'I04'. +:::{code-block} bash +$ virtac I04 +$ export RINGMODE=I04 +$ caput SR-CS-RING-01:MODE 3 +$ # Having none of these set would also start in mode 'I04'. +::: diff --git a/pyproject.toml b/pyproject.toml index 75465f0..a9667d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,12 +30,17 @@ requires-python = ">=3.10" dev = [ "copier", "mypy", + "myst-parser", "pipdeptree", "pre-commit", + "pydata-sphinx-theme>=0.12", "pytest", "pytest-cov", "testfixtures", "ruff", + "sphinx-autobuild", + "sphinx-copybutton", + "sphinx-design", "tox-direct", "types-mock", ] @@ -64,7 +69,7 @@ ignore_missing_imports = true # Ignore missing stubs in imported modules [tool.pytest.ini_options] # Run pytest with all our checkers, and don't spam us with massive tracebacks on error addopts = """ - --tb=native -vv + --tb=native -vv --doctest-modules --doctest-glob="*.rst" """ # https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings filterwarnings = "error" @@ -85,7 +90,7 @@ legacy_tox_ini = """ [tox] skipsdist=True -[testenv:{pre-commit,type-checking,tests}] +[testenv:{pre-commit,type-checking,tests,docs}] # Don't create a virtualenv for the command, requires tox-direct plugin direct = True passenv = * @@ -93,11 +98,15 @@ allowlist_externals = pytest pre-commit mypy + sphinx-build + sphinx-autobuild commands = pre-commit: pre-commit run --all-files --show-diff-on-failure {posargs} type-checking: mypy src tests {posargs} tests: pytest --cov=atip --cov-report term --cov-report xml:cov.xml {posargs} + docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html """ +# Add -W flag to sphinx-build if you want to fail on warnings [tool.ruff] src = ["src", "tests"] diff --git a/src/atip/load_sim.py b/src/atip/load_sim.py index 27fabf1..076aaa0 100644 --- a/src/atip/load_sim.py +++ b/src/atip/load_sim.py @@ -21,7 +21,7 @@ def load_from_filepath( pytac_lattice (pytac.lattice.Lattice): An instance of a Pytac lattice. at_lattice_filepath (str): The path to a .mat file from which the Accelerator Toolbox lattice can be loaded. - callback (callable): To be called after completion of each round of + callback (typing.Callable): To be called after completion of each round of physics calculations. disable_emittance (bool): Whether the emittance should be calculated. @@ -44,7 +44,7 @@ def load(pytac_lattice, at_lattice, callback=None, disable_emittance=False): pytac_lattice (pytac.lattice.Lattice): An instance of a Pytac lattice. at_lattice (at.lattice_object.Lattice): An instance of an Accelerator Toolbox lattice object. - callback (callable): To be called after completion of each round of + callback (typing.Callable): To be called after completion of each round of physics calculations. disable_emittance (bool): Whether the emittance should be calculated. diff --git a/src/atip/sim_data_sources.py b/src/atip/sim_data_sources.py index d37a204..cb88528 100644 --- a/src/atip/sim_data_sources.py +++ b/src/atip/sim_data_sources.py @@ -52,7 +52,7 @@ def __init__(self, at_element, index, atsim, fields=None): data source is attached to. index (int): The element's index in the ring, starting from 1. atsim (ATSimulator): An instance of an ATSimulator object. - fields (list, optional): The fields found on this element. + fields (list, typing.Optional): The fields found on this element. Raises: ValueError: if an unsupported field is passed, i.e. a field not in @@ -111,7 +111,7 @@ def add_field(self, field): this data_source. Raises: - FieldException: if the specified field is already present or if it + pytac.FieldException: if the specified field is already present or if it is not supported. """ if field in self._fields: @@ -128,10 +128,10 @@ def get_value(self, field, handle=None, throw=True): Args: field (str): The requested field. - handle (str, optional): Handle is not needed and is only here to + handle (str, typing.Optional): Handle is not needed and is only here to conform with the structure of the DataSource base class. - throw (bool, optional): If the check for completion of outstanding + throw (bool, typing.Optional): If the check for completion of outstanding calculations times out, then: if True, raise a ControlSystemException; if False, log a warning and return the @@ -141,8 +141,8 @@ def get_value(self, field, handle=None, throw=True): float: The value of the specified field on this data source. Raises: - FieldException: if the specified field does not exist. - ControlSystemException: if the calculation completion check fails, + pytac.FieldException: if the specified field does not exist. + pytac.ControlSystemException: if the calculation completion check fails, and throw is True. """ # Wait for any outstanding calculations to conclude, to ensure they are @@ -168,13 +168,13 @@ def set_value(self, field, value, throw=None): Args: field (str): The requested field. value (float): The value to be set. - throw (bool, optional): Throw is not needed and is only here to + throw (bool, typing.Optional): Throw is not needed and is only here to conform with the structure of the DataSource base class. Raises: - HandleException: if the specified field cannot be set to. - FieldException: if the specified field does not exist. + pytac.HandleException: if the specified field cannot be set to. + pytac.FieldException: if the specified field does not exist. """ if field in self._fields: if field in self._set_field_funcs.keys(): @@ -418,10 +418,10 @@ def get_value(self, field, handle=None, throw=True): Args: field (str): The requested field. - handle (str, optional): Handle is not needed and is only here to + handle (str, typing.Optional): Handle is not needed and is only here to conform with the structure of the DataSource base class. - throw (bool, optional): If the check for completion of outstanding + throw (bool, typing.Optional): If the check for completion of outstanding calculations times out, then: if True, raise a ControlSystemException; if False, log a warning and return the @@ -431,8 +431,8 @@ def get_value(self, field, handle=None, throw=True): float: The value of the specified field on this data source. Raises: - FieldException: if the specified field does not exist. - ControlSystemException: if the calculation completion check fails, + pytac.FieldException: if the specified field does not exist. + pytac.ControlSystemException: if the calculation completion check fails, and throw is True. """ # Wait for any outstanding calculations to conclude, to ensure they are @@ -469,12 +469,12 @@ def set_value(self, field, value, throw=None): Args: field (str): The requested field. value (float): The value to be set. - throw (bool, optional): Throw is not needed and is only here to + throw (bool, typing.Optional): Throw is not needed and is only here to conform with the structure of the DataSource base class. Raises: - HandleException: as setting values to Pytac lattice fields is not + pytac.HandleException: as setting values to Pytac lattice fields is not currently supported. """ raise HandleException( diff --git a/src/atip/simulator.py b/src/atip/simulator.py index 950847b..f51f82b 100644 --- a/src/atip/simulator.py +++ b/src/atip/simulator.py @@ -22,7 +22,9 @@ class LatticeData: def calculate_optics( - at_lattice: at.Lattice, refpts: ArrayLike, disable_emittance: bool = False + at_lattice: at.lattice_object.Lattice, + refpts: ArrayLike, + disable_emittance: bool = False, ) -> LatticeData: """Perform the physics calculations on the lattice. @@ -33,7 +35,7 @@ def calculate_optics( Args: at_lattice (at.lattice_object.Lattice): AT lattice definition. - refpts (numpy.array): A boolean array specifying the points at which + refpts (numpy.typing.NDArray): A boolean array specifying the points at which to calculate physics data. disable_emittance (bool): whether to calculate emittance. @@ -79,7 +81,7 @@ class ATSimulator: _at_lat (at.lattice_object.Lattice): The centralised instance of an AT lattice from which the physics data is calculated. - _rp (numpy.array): A boolean array to be used as refpts for the + _rp (numpy.typing.NDArray): A boolean array to be used as refpts for the physics calculations. _disable_emittance (bool): Whether or not to perform the beam envelope based emittance calculations. @@ -107,7 +109,7 @@ def __init__(self, at_lattice, callback=None, disable_emittance=False): Args: at_lattice (at.lattice_object.Lattice): An instance of an AT lattice object. - callback (callable): Optional, if passed it is called on completion + callback (typing.Callable): Optional, if passed it is called on completion of each round of physics calculations. disable_emittance (bool): Whether or not to perform the beam envelope based emittance calculations. @@ -142,7 +144,7 @@ def queue_set(self, func, field, value): """Add a change to the queue, to be applied when the queue is emptied. Args: - func (callable): The function to be called to apply the change. + func (typing.Callable): The function to be called to apply the change. field (str): The field to be changed. value (float): The value to be set. """ @@ -178,7 +180,7 @@ def _recalculate_phys_data(self, callback): thread to warnings. Args: - callback (callable): to be called after each round of calculations, + callback (typing.Callable): to be called after each round of calculations, indicating that they have concluded. Warns: @@ -254,7 +256,7 @@ def wait_for_calculations(self, timeout=10): changes to the AT lattice, i.e. the physics data is fully up to date. Args: - timeout (float, optional): The number of seconds to wait for. + timeout (float, typing.Optional): The number of seconds to wait for. Returns: bool: False if the timeout elapsed before the calculations @@ -342,7 +344,7 @@ def get_tune(self, field=None): float: The x or y tune for the AT lattice. Raises: - FieldException: if the specified field is not valid for tune. + pytac.FieldException: if the specified field is not valid for tune. """ tunes = self._lattice_data.tunes if field is None: @@ -365,7 +367,7 @@ def get_chromaticity(self, field=None): float: The x or y chromaticity for the AT lattice. Raises: - FieldException: if the specified field is not valid for + pytac.FieldException: if the specified field is not valid for chromaticity. """ chrom = self._lattice_data.chrom @@ -388,11 +390,11 @@ def get_orbit(self, field=None): if None return whole orbit vector. Returns: - numpy.array: The x, x phase, y or y phase for the AT lattice as an + numpy.typing.NDArray: The x, x phase, y or y phase for the AT lattice as an array of floats the length of the AT lattice. Raises: - FieldException: if the specified field is not valid for orbit. + pytac.FieldException: if the specified field is not valid for orbit. """ closed_orbit = self._lattice_data.twiss["closed_orbit"] if field is None: @@ -417,11 +419,11 @@ def get_dispersion(self, field=None): None return whole dispersion vector. Returns: - numpy.array: The eta x, eta prime x, eta y or eta prime y for the + numpy.typing.NDArray: The eta x, eta prime x, eta y or eta prime y for the AT lattice as an array of floats the length of the AT lattice. Raises: - FieldException: if the specified field is not valid for dispersion. + pytac.FieldException: if the specified field is not valid for dispersion. """ dispersion = self._lattice_data.twiss["dispersion"] if field is None: @@ -441,7 +443,7 @@ def get_alpha(self): """Return the alpha vector at every element in the AT lattice. Returns: - numpy.array: The alpha vector for each element. + numpy.typing.NDArray: The alpha vector for each element. """ return self._lattice_data.twiss["alpha"][:-1] @@ -449,7 +451,7 @@ def get_beta(self): """Return the beta vector at every element in the AT lattice. Returns: - numpy.array: The beta vector for each element. + numpy.typing.NDArray: The beta vector for each element. """ return self._lattice_data.twiss["beta"][:-1] @@ -457,7 +459,7 @@ def get_mu(self): """Return mu at every element in the AT lattice. Returns: - numpy.array: The mu array for each element. + numpy.typing.NDArray: The mu array for each element. """ return self._lattice_data.twiss["mu"][:-1] @@ -465,7 +467,7 @@ def get_m66(self): """Return the 6x6 transfer matrix for every element in the AT lattice. Returns: - numpy.array: The 6x6 transfer matrix for each element. + numpy.typing.NDArray: The 6x6 transfer matrix for each element. """ return self._lattice_data.twiss["M"][:-1] @@ -485,7 +487,7 @@ def get_emittance(self, field=None): float: The x or y emittance for the AT lattice. Raises: - FieldException: if the specified field is not valid for emittance. + pytac.FieldException: if the specified field is not valid for emittance. """ if not self._disable_emittance: if field is None: @@ -506,7 +508,7 @@ def get_radiation_integrals(self): """Return the 5 Synchrotron Integrals for the AT lattice. Returns: - numpy.array: The 5 radiation integrals. + numpy.typing.NDArray: The 5 radiation integrals. """ return numpy.asarray(self._lattice_data.radint) @@ -542,7 +544,7 @@ def get_damping_partition_numbers(self): """Return the damping partition numbers for the 3 normal modes. Returns: - numpy.array: The damping partition numbers of the AT lattice. + numpy.typing.NDArray: The damping partition numbers of the AT lattice. """ _, I2, _, I4, _ = self._lattice_data.radint Jx = 1 - (I4 / I2) @@ -558,7 +560,7 @@ def get_damping_times(self): Radiation; August 2013; eqn. 68 Returns: - numpy.array: The damping times of the AT lattice. + numpy.typing.NDArray: The damping times of the AT lattice. """ E0 = self.get_energy() U0 = self.get_energy_loss() diff --git a/src/atip/utils.py b/src/atip/utils.py index aa34ee1..eb57037 100644 --- a/src/atip/utils.py +++ b/src/atip/utils.py @@ -38,7 +38,7 @@ def loader(mode="I04", callback=None, disable_emittance=False): Args: mode (str): The lattice operation mode. - callback (callable): Callable to be called after completion of each + callback (typing.Callable): Callable to be called after completion of each round of physics calculations in ATSimulator. disable_emittance (bool): Whether the emittance should be calculated. @@ -67,7 +67,7 @@ def preload_at(at_lat): the elements. returns: - obj: The elems object with the elements loaded onto it by type. + obj (class): The elems object with the elements loaded onto it by type. """ class elems: @@ -114,7 +114,7 @@ def preload(pytac_lat): to get the elements. returns: - obj: The elems object with the elements loaded onto it by family. + obj(class): The elems object with the elements loaded onto it by family. """ class elems: diff --git a/src/virtac/atip_ioc_entry.py b/src/virtac/atip_ioc_entry.py index 35fd8cb..dbc3bb8 100644 --- a/src/virtac/atip_ioc_entry.py +++ b/src/virtac/atip_ioc_entry.py @@ -18,6 +18,7 @@ def parse_arguments(): + """Parse command line arguments sent to virtac""" parser = argparse.ArgumentParser() parser.add_argument("ring_mode", nargs="?", type=str, help="Ring mode name") parser.add_argument( @@ -39,6 +40,7 @@ def parse_arguments(): def main(): + """Main entrypoint for virtac. Executed when running the 'virtac' command""" args = parse_arguments() log_level = logging.DEBUG if args.verbose else logging.INFO logging.basicConfig(level=log_level, format=LOG_FORMAT) diff --git a/src/virtac/atip_server.py b/src/virtac/atip_server.py index 20063b8..d4ee8a6 100644 --- a/src/virtac/atip_server.py +++ b/src/virtac/atip_server.py @@ -66,17 +66,17 @@ def __init__( ): """ Args: - ring_mode (string): The ring mode to create the lattice in. - limits_csv (string): The filepath to the .csv file from which to + ring_mode (str): The ring mode to create the lattice in. + limits_csv (str): The filepath to the .csv file from which to load the pv limits, for more information see create_csv.py. - feedback_csv (string): The filepath to the .csv file from which to + feedback_csv (str): The filepath to the .csv file from which to load the feedback records, for more information see create_csv.py. - mirror_csv (string): The filepath to the .csv file from which to + mirror_csv (str): The filepath to the .csv file from which to load the mirror records, for more information see create_csv.py. - tune_csv (string): The filepath to the .csv file from which to + tune_csv (str): The filepath to the .csv file from which to load the tune feedback records, for more information see create_csv.py. disable_emittance (bool): Whether the emittance should be disabled. @@ -149,7 +149,7 @@ def _create_records(self, limits_csv, disable_emittance): need to be created for them. Args: - limits_csv (string): The filepath to the .csv file from which to + limits_csv (str): The filepath to the .csv file from which to load the pv limits. disable_emittance (bool): Whether the emittance related PVs should be created or not. @@ -297,7 +297,7 @@ def _create_feedback_records(self, feedback_csv, disable_emittance): cases are also created. Args: - feedback_csv (string): The filepath to the .csv file to load the + feedback_csv (str): The filepath to the .csv file to load the records in accordance with. disable_emittance (bool): Whether the emittance related PVs should be created or not. @@ -374,7 +374,7 @@ def _create_mirror_records(self, mirror_csv): passed, see create_csv.py for more information. Args: - mirror_csv (string): The filepath to the .csv file to load the + mirror_csv (str): The filepath to the .csv file to load the records in accordance with. """ csv_reader = csv.DictReader(open(mirror_csv)) @@ -537,11 +537,11 @@ def set_feedback_record(self, index, field, value): Args: index (int): The index of the element on which to set the value; starting from 1, 0 is used to set on the lattice. - field (string): The field to set the value to. + field (str): The field to set the value to. value (number): The value to be set. Raises: - pytac.exceptions.FieldException: If the lattice or element does + pytac.FieldException: If the lattice or element does not have the specified field. """ try: diff --git a/src/virtac/create_csv.py b/src/virtac/create_csv.py index f6c7284..1576439 100644 --- a/src/virtac/create_csv.py +++ b/src/virtac/create_csv.py @@ -13,7 +13,7 @@ def generate_feedback_pvs(all_elements): - # Also get families for tune feedback + """Get feedback pvs. Also get families for tune feedback""" tune_quad_elements = set( all_elements.q1d + all_elements.q2d @@ -54,8 +54,9 @@ def generate_feedback_pvs(all_elements): def generate_bba_pvs(all_elements): - # Data to be written is stored as a list of tuples each with structure: - # element index (int), field (str), pv (str), value (int). + """Data to be written is stored as a list of tuples each with structure: + element index (int), field (str), pv (str), value (int). + """ data = [("index", "field", "pv", "value", "read-only")] # Iterate over the BPMs to construct the PV names. for elem in all_elements.bpm: @@ -76,6 +77,10 @@ def generate_bba_pvs(all_elements): def generate_pv_limits(lattice): """Get the control limits and precision values from the live machine for all normal PVS. + + Args: + lattice (pytac.lattice.Lattice): The pytac lattice being used by the virtual + machine """ data = [("pv", "upper", "lower", "precision")] for element in lattice: @@ -99,13 +104,17 @@ def generate_pv_limits(lattice): def generate_mirrored_pvs(lattice): """Structure of data: - output type: The type of output record to create, only 'aIn', 'longIn', + + output type: + The type of output record to create, only 'aIn', 'longIn', 'Waveform' types are currently supported; if '' then output to an existing in record already created in ATIPServer, 'caput' is also a special case it creates a mask for cothread.catools.caput calling set(value) on this mask will call caput with the output PV and the passed value. - mirror type: The type of mirroring to apply: + + mirror type (The type of mirroring to apply): + - basic: set the value of the input record to the output record. - summate: sum the values of the input records and set the result to the output record. @@ -114,12 +123,18 @@ def generate_mirrored_pvs(lattice): of the input record and set the result to the output record. N.B. the only transformation type currently supported is 'inverse'. - refresh: monitor the in PV and on a change call refresh_record on - the output PV. - in: The PV(s) to be monitored, on change mirror is updated, if multiple + the output PV. + + in: + The PV(s) to be monitored, on change mirror is updated, if multiple then the PVs should be separated by a comma and one space. - out: The single PV to output to, if a 'record type' is spcified then a new + + out: + The single PV to output to, if a 'record type' is spcified then a new record will be created and so must not exist already. - value: The inital value of the output record. + + value: + The inital value of the output record. """ data = [("output type", "mirror type", "in", "out", "value")] # Tune PV aliases. @@ -261,6 +276,7 @@ def write_data_to_file(data, filename, ring_mode): def parse_arguments(): + """The arguments passed to this script to configure how the csv is to be created""" parser = argparse.ArgumentParser( description="Generate CSV file to define the PVs served by the " "virtual accelerator IOC." diff --git a/src/virtac/mirror_objects.py b/src/virtac/mirror_objects.py index 4e93e9a..dfef0b9 100644 --- a/src/virtac/mirror_objects.py +++ b/src/virtac/mirror_objects.py @@ -64,7 +64,7 @@ class transform: def __init__(self, transformation, output_record): """ Args: - transformation (callable): The transformation to be applied. + transformation (typing.Callable): The transformation to be applied. output_record (pythonSoftIoc.RecordWrapper): The record to set the transformed value to. """