From 8bb0b9b83312bbe6cd7eea0f595cbc23eac44a1a Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Wed, 15 Nov 2023 17:41:42 +0100 Subject: [PATCH 01/29] rebased on master --- atintegrators/atelem.c | 6 +- docs/conf.py | 7 +- docs/p/examples.rst | 9 + docs/p/index.rst | 4 + docs/p/notebooks/observables.ipynb | 864 ++++++++++++++++++++++++ docs/p/notebooks/parameters.ipynb | 923 ++++++++++++++++++++++++++ docs/p/notebooks/test_variables.ipynb | 525 +++++++++++++++ docs/p/notebooks/variables.ipynb | 573 ++++++++++++++++ docs/p/variables_parameters.md | 51 ++ pyat/at/future.py | 2 + pyat/at/lattice/__init__.py | 3 + pyat/at/lattice/axisdef.py | 33 +- pyat/at/lattice/element_variables.py | 135 ++++ pyat/at/lattice/elements.py | 477 ++++++------- pyat/at/lattice/lattice_object.py | 191 ++++-- pyat/at/lattice/utils.py | 85 ++- pyat/at/lattice/variables.py | 622 +++++++++++++++++ 17 files changed, 4182 insertions(+), 328 deletions(-) create mode 100644 docs/p/examples.rst create mode 100644 docs/p/notebooks/observables.ipynb create mode 100644 docs/p/notebooks/parameters.ipynb create mode 100644 docs/p/notebooks/test_variables.ipynb create mode 100644 docs/p/notebooks/variables.ipynb create mode 100644 docs/p/variables_parameters.md create mode 100644 pyat/at/future.py create mode 100644 pyat/at/lattice/element_variables.py create mode 100644 pyat/at/lattice/variables.py diff --git a/atintegrators/atelem.c b/atintegrators/atelem.c index 5822e1abb..6fde04348 100755 --- a/atintegrators/atelem.c +++ b/atintegrators/atelem.c @@ -193,16 +193,18 @@ static long atGetLong(const PyObject *element, const char *name) { const PyObject *attr = PyObject_GetAttrString((PyObject *)element, name); if (!attr) return 0L; + long l = PyLong_AsLong((PyObject *)attr); Py_DECREF(attr); - return PyLong_AsLong((PyObject *)attr); + return l; } static double atGetDouble(const PyObject *element, const char *name) { const PyObject *attr = PyObject_GetAttrString((PyObject *)element, name); if (!attr) return 0.0; + double d = PyFloat_AsDouble((PyObject *)attr); Py_DECREF(attr); - return PyFloat_AsDouble((PyObject *)attr); + return d; } static long atGetOptionalLong(const PyObject *element, const char *name, long default_value) diff --git a/docs/conf.py b/docs/conf.py index ca711bc22..a510e9ab4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,8 +40,9 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.githubpages', 'sphinx.ext.viewcode', - 'myst_parser', + 'myst_nb', 'sphinx_copybutton', + 'sphinx_design', ] intersphinx_mapping = {'python': ('https://docs.python.org/3', None), @@ -56,7 +57,7 @@ # 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. -exclude_patterns = ["README.rst", "**/*.so"] +exclude_patterns = ["README.rst", "**/*.so", "_build/*"] rst_prolog = """ .. role:: pycode(code) :language: python @@ -92,6 +93,8 @@ "deflist" ] myst_heading_anchors = 3 +nb_execution_mode = "auto" +nb_execution_allow_errors = True # -- Options for HTML output ------------------------------------------------- diff --git a/docs/p/examples.rst b/docs/p/examples.rst new file mode 100644 index 000000000..87dd30a9b --- /dev/null +++ b/docs/p/examples.rst @@ -0,0 +1,9 @@ +.. _example-notebooks: + +Example notebooks +================= + +.. toctree:: + :maxdepth: 1 + + notebooks/test_variables diff --git a/docs/p/index.rst b/docs/p/index.rst index 2b2a79e97..68663ead5 100644 --- a/docs/p/index.rst +++ b/docs/p/index.rst @@ -26,6 +26,9 @@ Sub-packages howto/Installation howto/Primer + variables_parameters + notebooks/observables + examples .. toctree:: :maxdepth: 2 @@ -42,6 +45,7 @@ Sub-packages :recursive: at.lattice + at.latticetools at.tracking at.physics at.load diff --git a/docs/p/notebooks/observables.ipynb b/docs/p/notebooks/observables.ipynb new file mode 100644 index 000000000..68297067e --- /dev/null +++ b/docs/p/notebooks/observables.ipynb @@ -0,0 +1,864 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b3c94f57-7a41-4600-af2b-b2a0034ffe31", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Observables\n", + "\n", + "Observables provide a unified way to access a large quantity of figures resulting from various\n", + "computations on lattices. They may be used in parameter scans, matching, response matrices…\n", + "\n", + "AT provides a number of specific observables sharing a common interface, inherited from the\n", + "{py:class}`.Observable` base class. They are:\n", + "- {py:class}`.OrbitObservable`: {math}`x_{co}`…,\n", + "- {py:obj}`.GlobalOpticsObservable`: tunes, damping times…,\n", + "- {py:class}`.LocalOpticsObservable`: {math}`\\beta`, {math}`\\eta`…,\n", + "- {py:class}`.MatrixObservable`: {math}`T_{ij}`…,\n", + "- {py:class}`.TrajectoryObservable`: {math}`x, p_x`…,\n", + "- {py:class}`.EmittanceObservable`: {math}`\\epsilon_x`…,\n", + "- {py:class}`.LatticeObservable`: attributes of lattice elements,\n", + "- {py:class}`.GeometryObservable`\n", + "\n", + "An Observable has optional {py:attr}`~.Observable.target`, {py:attr}`~.Observable.weight` and {py:attr}`~.Observable.bounds` attributes for matching. After evaluation, it has the following main properties:\n", + "- {py:attr}`~.Observable.value`\n", + "- {py:attr}`~.Observable.weighted_value`: `value / weight`\n", + "- {py:attr}`~.Observable.deviation`: `value - target`\n", + "- {py:attr}`~.Observable.weighted_deviation`: `(value - target)/weight`\n", + "- {py:attr}`~.Observable.residual`: `((value - target)/weight)**2`\n", + "\n", + "Custom Observables may be created by providing the adequate evaluation function.\n", + "\n", + "For evaluation, observables must be grouped in an {py:class}`.ObservableList` which optimises the computation, avoiding redundant function calls. {py:class}`.ObservableList` provides the {py:meth}`~.ObservableList.evaluate` method, and the\n", + "{py:attr}`~.ObservableList.values`, {py:attr}`~.ObservableList.deviations`,\n", + "{py:attr}`~.ObservableList.residuals` and {py:attr}`~.ObservableList.sum_residuals` properties, among others.\n", + "\n", + "This example shows how to declare various Observables, how to evaluate them and how to extract and display their values.\n", + "\n", + "## Setup the environment" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c44ec0ec-f0c7-476b-8de5-ac1d8b174dc0", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import at\n", + "import sys\n", + "import numpy as np\n", + "if sys.version_info.minor < 9:\n", + " from importlib_resources import files, as_file\n", + "else:\n", + " from importlib.resources import files, as_file" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6e2a6c6c-7460-45f6-a217-e5a07f7afb8d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from at import Observable, ObservableList, OrbitObservable, GlobalOpticsObservable, LocalOpticsObservable\n", + "from at import MatrixObservable, TrajectoryObservable, EmittanceObservable, LatticeObservable, GeometryObservable" + ] + }, + { + "cell_type": "markdown", + "id": "19a61c12-f62e-4114-9d87-1b3d19233991", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Load a test lattice" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8bccc37e-3070-4b62-bdda-4da603b48a34", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "fname = 'hmba.mat'\n", + "with as_file(files('machine_data') / fname) as path:\n", + " hmba_lattice = at.load_lattice(path)" + ] + }, + { + "cell_type": "markdown", + "id": "3dedb508-981a-4c25-9dc0-5c0b2b25a561", + "metadata": {}, + "source": [ + "## Create Observables\n", + "\n", + "Create an empty ObservableList:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b6b6d2e5-842a-400d-87ff-d34b131921d1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "obs1=ObservableList(hmba_lattice)" + ] + }, + { + "cell_type": "markdown", + "id": "7fe3c40f-8eb2-4fbb-96d7-e20bf1424f63", + "metadata": {}, + "source": [ + "Horizontal closed orbit on all Monitors:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "99e48ce2-1336-4617-9114-cd15e9a3966d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "obs1.append(OrbitObservable(at.Monitor, axis='x'))" + ] + }, + { + "cell_type": "markdown", + "id": "c29bc37a-8d3a-4330-b6c2-94408255e7c2", + "metadata": {}, + "source": [ + "Create a 2{sup}`nd` ObservableList:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "559a78af-bb1f-4555-99f7-be0cd55f75d4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "obs2=ObservableList(hmba_lattice)" + ] + }, + { + "cell_type": "markdown", + "id": "d1216c95-a8e8-4ae5-b61e-b18fe0582d22", + "metadata": {}, + "source": [ + "Vertical $\\beta$ at all monitors, with a target and bounds.\n", + "\n", + "The vertical $\\beta$ is constrained in the interval\n", + "[*target*+*low_bound* *target*+*up_bound*], so here [*-Infinity 7.0*]\n", + "\n", + "The residual will be zero within the interval." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2c237c62-3b4f-4864-bea3-93325a8a7fce", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "obs2.append(LocalOpticsObservable(at.Monitor, 'beta', plane=1, target=7.0, bounds=(-np.inf, 0.0)))" + ] + }, + { + "cell_type": "markdown", + "id": "900683b5-6c61-427c-901d-edb22390738f", + "metadata": { + "tags": [] + }, + "source": [ + "check the concatenation of ObservableLists:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "99f87e5c-c004-423f-af52-7b77ff1f745d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "allobs = obs1 + obs2" + ] + }, + { + "cell_type": "markdown", + "id": "7cee0839-8b64-4c65-bb85-869062ad168a", + "metadata": {}, + "source": [ + "Full transfer matrix to `BPM02`:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "828dd898-810e-4653-b717-fe955bed6bab", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(MatrixObservable(\"BPM_02\"))" + ] + }, + { + "cell_type": "markdown", + "id": "32e2c4af-dd58-4c67-a767-a585bf110fa1", + "metadata": {}, + "source": [ + "Maximum of vertical beta on monitors:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7c763bd9-0299-47e0-9979-fae6ae230979", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "allobs.append(LocalOpticsObservable(at.Monitor, 'beta', plane='v', statfun=np.amax))" + ] + }, + { + "cell_type": "markdown", + "id": "8907cbd5-05d4-4775-a089-9f062b271394", + "metadata": {}, + "source": [ + "First 4 coordinates of the closed orbit at Quadrupoles:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d9ddeb7c-ea88-4d09-8333-753ad4e0d8e4", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(LocalOpticsObservable(at.Quadrupole, 'closed_orbit', plane=slice(4), target=0.0, weight=1.e-6))" + ] + }, + { + "cell_type": "markdown", + "id": "5b3f80a0-f7ea-48c4-bafb-03e89c04e4e0", + "metadata": {}, + "source": [ + "Position along the lattice of all quadrupoles:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "7b3e32e8-5fd6-4d42-a92f-1691bdc330cf", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "allobs.append(LocalOpticsObservable(at.Quadrupole, 's_pos'))" + ] + }, + { + "cell_type": "markdown", + "id": "c630e18c-8e32-499a-b45a-d19f76b441fe", + "metadata": {}, + "source": [ + "Phase advance between elements 33 and 101 in all planes:\n", + "\n", + "First, let's define a custom evaluation function:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "e9c41097-7d89-4f3f-85cd-aff7aa9a84df", + "metadata": {}, + "outputs": [], + "source": [ + "def phase_advance(ring, elemdata):\n", + " mu = elemdata.mu\n", + " return (mu[-1] - mu[0])" + ] + }, + { + "cell_type": "markdown", + "id": "a19eca9f-fa1f-4a5f-8558-d7d19b58042b", + "metadata": {}, + "source": [ + "Then create the Observable. The evaluation function should return one value per refpoint (2 here). Alternatively,\n", + "it may return a single value (the difference, here), but then one must set `summary=True`." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "185b505f-1bea-4c72-baee-3a24d1a5bb8d", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(LocalOpticsObservable([33, 101], phase_advance, use_integer=True, summary=True))" + ] + }, + { + "cell_type": "markdown", + "id": "2f2a5e1a-f45d-4e6d-a27b-5565d784da44", + "metadata": {}, + "source": [ + "Horizontal tune with the integer part:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "7263d336-7e84-47f2-a949-ed79a13635aa", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(GlobalOpticsObservable('tune', plane=0, use_integer=True))" + ] + }, + { + "cell_type": "markdown", + "id": "43050abf-9a4c-4d86-b358-b7b22dfd4c1f", + "metadata": {}, + "source": [ + "Total phase advance at the end of the lattice (all planes):" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "265be4dd-b67c-4188-a3e4-15c1e4eac3e4", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(LocalOpticsObservable(at.End, 'mu', use_integer=True))" + ] + }, + { + "cell_type": "markdown", + "id": "b2d9ed85-db9b-4ab7-9425-53d20b703eae", + "metadata": {}, + "source": [ + "Chromaticity in all planes:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "28949857-7e9c-499c-ba69-e900932683fd", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(GlobalOpticsObservable('chromaticity', plane=None))" + ] + }, + { + "cell_type": "markdown", + "id": "8cbe4399-5670-4fd2-b745-3b1e8f9eb5b1", + "metadata": {}, + "source": [ + "Average of sextupole strengths:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "e271c5ad-e6c8-4c8a-a66b-671711db3832", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(LatticeObservable(at.Sextupole, 'H', statfun=np.mean))" + ] + }, + { + "cell_type": "markdown", + "id": "9ddd860b-c3f9-4c49-839b-64b5f478a9b5", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Strengths of all sextupoles:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d68df47a-4215-489c-8975-9748b592b949", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "allobs.append(LatticeObservable(at.Sextupole, 'PolynomB', index=2))" + ] + }, + { + "cell_type": "markdown", + "id": "7339554d-c12f-49fb-aade-47691bb5bbce", + "metadata": {}, + "source": [ + "Horizontal emittance:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "47ce8596-266c-4944-82d8-f186e34f09b8", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(EmittanceObservable('emittances', plane='x'))" + ] + }, + { + "cell_type": "markdown", + "id": "862e9a69-f018-4350-af40-89ae79e8ef39", + "metadata": {}, + "source": [ + "Ring circumference:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "f6844781-ee09-4ab6-979b-9fea8034bb7d", + "metadata": {}, + "outputs": [], + "source": [ + "def circumference(ring):\n", + " return ring.get_s_pos(len(ring))[0]\n", + "allobs.append(Observable(circumference))" + ] + }, + { + "cell_type": "markdown", + "id": "510ebd1a-eb20-43ad-9b74-d79fee8047a4", + "metadata": {}, + "source": [ + "p{sub}`x` component of the trajectory on all monitors:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "40a0895a-9aac-4509-86dd-9fb1f84a4e84", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(TrajectoryObservable(at.Monitor,axis='px'))" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "fef988a4-1413-4478-a8da-8c865b27d74b", + "metadata": {}, + "outputs": [], + "source": [ + "allobs.append(GeometryObservable(at.Monitor, 'x'))" + ] + }, + { + "cell_type": "markdown", + "id": "48167f3a-24fa-4963-97ec-1e78033f8895", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Evaluation\n", + "\n", + "An input trajectory is required for the trajectory Observable" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "f78c247c-0228-499f-9455-0f39c57ff6fd", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "r_in = np.zeros(6)\n", + "r_in[0] = 0.001\n", + "r_in[2] = 0.001\n", + "allobs.evaluate(hmba_lattice.enable_6d(copy=True), r_in=r_in, dp=0.0, initial=True)" + ] + }, + { + "cell_type": "markdown", + "id": "637f7487-80e1-4ca7-af5f-a490be880c90", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "### Extract a single Observable value\n", + "(phase advance between elements 3 and 101):" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "582b7317-5527-4afd-ad8c-278f0408dd11", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([3.10650512e+00, 2.99742405e+00, 1.40411771e-14])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "allobs[6].value" + ] + }, + { + "cell_type": "markdown", + "id": "f9a17436-c827-47f7-8eb2-85ce9637c734", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "### Get the list of all Observable values:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "edfba8ca-ee8f-4064-9076-d2bc469739a4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([-3.02189723e-09, 4.50695010e-07, 4.08205708e-07, 2.37899777e-08,\n", + " -1.31783789e-08, 2.47230566e-08, -2.95310962e-08, -4.05598220e-07,\n", + " -4.47398212e-07, -2.24850930e-09]),\n", + " array([5.30279703, 7.17604152, 6.55087808, 2.31448878, 3.40498445,\n", + " 3.405044 , 2.3146451 , 6.55106241, 7.17614175, 5.30283837]),\n", + " array([[[-1.08194106e+00, 3.18809568e+00, 0.00000000e+00,\n", + " 0.00000000e+00, 8.22407787e-02, -1.72158979e-05],\n", + " [-6.80522735e-01, 1.08099571e+00, 0.00000000e+00,\n", + " 0.00000000e+00, 4.90131193e-02, -1.02601760e-05],\n", + " [ 0.00000000e+00, 0.00000000e+00, 7.55929650e-01,\n", + " 3.87059271e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [ 0.00000000e+00, 0.00000000e+00, -6.79279293e-01,\n", + " -2.15524755e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [-1.13309009e-08, -1.08615600e-07, 0.00000000e+00,\n", + " 0.00000000e+00, 9.99995907e-01, -2.09334442e-04],\n", + " [ 2.93742206e-03, 6.73567963e-02, 0.00000000e+00,\n", + " 0.00000000e+00, 2.83582558e-04, 9.99999941e-01]]]),\n", + " 7.176141753295557,\n", + " array([[-3.02810319e-09, -1.45845183e-10, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-1.78478316e-09, 2.17266708e-09, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [ 2.06038228e-07, 1.68970687e-07, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [ 4.63461827e-07, 2.65112155e-07, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [ 4.92846847e-07, -2.48829214e-09, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [ 2.39068148e-07, -2.69323468e-07, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [ 2.24947413e-08, -2.55612556e-08, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-2.95803326e-08, -2.51343244e-08, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [ 3.86437699e-08, 4.66071902e-08, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-1.14693229e-08, -5.89624819e-08, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-1.92472337e-07, -1.36142888e-07, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-4.58527547e-07, -2.67107467e-07, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-4.90185628e-07, -2.06357872e-09, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-2.42425126e-07, 2.63615764e-07, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-8.04954315e-10, -1.24373421e-09, 0.00000000e+00,\n", + " 0.00000000e+00],\n", + " [-1.92735224e-09, -1.83198298e-09, 0.00000000e+00,\n", + " 0.00000000e+00]]),\n", + " array([ 2.693952 , 3.4295565 , 5.52309303, 6.52741246, 7.08941246,\n", + " 8.14326589, 10.34278161, 11.93982486, 13.94182609, 15.63285034,\n", + " 18.00333506, 19.05718849, 19.61918849, 20.67257592, 22.71704445,\n", + " 23.36843995]),\n", + " array([3.10650512e+00, 2.99742405e+00, 1.40411771e-14]),\n", + " 0.3815630185798568,\n", + " array([[2.39743115e+00, 5.36820522e+00, 6.85248782e-04]]),\n", + " array([1.79196871e-01, 1.22425546e-01, 1.69458465e-04]),\n", + " -25.369212247139345,\n", + " array([-78.95535579, 77.03724443, -74.18952538, -74.18952538,\n", + " 77.03724443, -78.95535579]),\n", + " 1.3203910509097569e-10,\n", + " 26.374287952316944,\n", + " array([ 0.00000000e+00, -6.94370474e-04, 6.07151649e-04, 2.38468291e-04,\n", + " -6.81824078e-04, -4.78921797e-04, 4.41491589e-04, 7.01582199e-04,\n", + " -6.05543962e-04, -9.78684823e-05]),\n", + " array([ 2.6514 , 6.4783308 , 7.51380991, 10.28830988, 12.71979153,\n", + " 13.62739043, 16.04912109, 18.79203055, 19.81402164, 23.58054559])]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "allobs.values" + ] + }, + { + "cell_type": "markdown", + "id": "22eb2441-39d9-432d-ae3d-fdee153f49cb", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "### Get a pretty output of all Observables.\n", + "\n", + "As no variation was made, *Actual* values are always equal to *Initial* values.\n", + "\n", + "The residual is zero for all Observables for which no *target* was specified" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "6126bf27-37e9-4c20-8997-c82ba9fad5b1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "location Initial Actual Low bound High bound residual \n", + "orbit[x]\n", + " BPM_01 -3.0219e-09 -3.0219e-09 - - 0.0 \n", + " BPM_02 4.50695e-07 4.50695e-07 - - 0.0 \n", + " BPM_03 4.08206e-07 4.08206e-07 - - 0.0 \n", + " BPM_04 2.379e-08 2.379e-08 - - 0.0 \n", + " BPM_05 -1.31784e-08 -1.31784e-08 - - 0.0 \n", + " BPM_06 2.47231e-08 2.47231e-08 - - 0.0 \n", + " BPM_07 -2.95311e-08 -2.95311e-08 - - 0.0 \n", + " BPM_08 -4.05598e-07 -4.05598e-07 - - 0.0 \n", + " BPM_09 -4.47398e-07 -4.47398e-07 - - 0.0 \n", + " BPM_10 -2.24851e-09 -2.24851e-09 - - 0.0 \n", + "beta[y]\n", + " BPM_01 5.3028 5.3028 -inf 7.0 0.0 \n", + " BPM_02 7.17604 7.17604 -inf 7.0 0.0309906 \n", + " BPM_03 6.55088 6.55088 -inf 7.0 0.0 \n", + " BPM_04 2.31449 2.31449 -inf 7.0 0.0 \n", + " BPM_05 3.40498 3.40498 -inf 7.0 0.0 \n", + " BPM_06 3.40504 3.40504 -inf 7.0 0.0 \n", + " BPM_07 2.31465 2.31465 -inf 7.0 0.0 \n", + " BPM_08 6.55106 6.55106 -inf 7.0 0.0 \n", + " BPM_09 7.17614 7.17614 -inf 7.0 0.0310259 \n", + " BPM_10 5.30284 5.30284 -inf 7.0 0.0 \n", + "matrix\n", + " BPM_02 [-1.082 ...] [-1.082 ...] - - [ 0.0 ...] \n", + "amax(beta[y])\n", + " 7.17614 7.17614 - - 0.0 \n", + "closed_orbit[slice(None, 4, None)]\n", + " QF1A [-3.028e-09 ...] [-3.028e-09 ...] [ 0.0 ...] [ 0.0 ...] [ 9.169e-06 ...] \n", + " QD2A [-1.785e-09 ...] [-1.785e-09 ...] [ 0.0 ...] [ 0.0 ...] [ 3.185e-06 ...] \n", + " QD3A [ 2.06e-07 ...] [ 2.06e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.04245 ...] \n", + " QF4A [ 4.635e-07 ...] [ 4.635e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2148 ...] \n", + " QF4B [ 4.928e-07 ...] [ 4.928e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2429 ...] \n", + " QD5B [ 2.391e-07 ...] [ 2.391e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.05715 ...] \n", + " QF6B [ 2.249e-08 ...] [ 2.249e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.000506 ...] \n", + " QF8B [-2.958e-08 ...] [-2.958e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.000875 ...] \n", + " QF8D [ 3.864e-08 ...] [ 3.864e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.001493 ...] \n", + " QF6D [-1.147e-08 ...] [-1.147e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.0001315 ...] \n", + " QD5D [-1.925e-07 ...] [-1.925e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.03705 ...] \n", + " QF4D [-4.585e-07 ...] [-4.585e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2102 ...] \n", + " QF4E [-4.902e-07 ...] [-4.902e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2403 ...] \n", + " QD3E [-2.424e-07 ...] [-2.424e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.05877 ...] \n", + " QD2E [-8.05e-10 ...] [-8.05e-10 ...] [ 0.0 ...] [ 0.0 ...] [ 6.48e-07 ...] \n", + " QF1E [-1.927e-09 ...] [-1.927e-09 ...] [ 0.0 ...] [ 0.0 ...] [ 3.715e-06 ...] \n", + "s_pos\n", + " QF1A 2.69395 2.69395 - - 0.0 \n", + " QD2A 3.42956 3.42956 - - 0.0 \n", + " QD3A 5.52309 5.52309 - - 0.0 \n", + " QF4A 6.52741 6.52741 - - 0.0 \n", + " QF4B 7.08941 7.08941 - - 0.0 \n", + " QD5B 8.14327 8.14327 - - 0.0 \n", + " QF6B 10.3428 10.3428 - - 0.0 \n", + " QF8B 11.9398 11.9398 - - 0.0 \n", + " QF8D 13.9418 13.9418 - - 0.0 \n", + " QF6D 15.6329 15.6329 - - 0.0 \n", + " QD5D 18.0033 18.0033 - - 0.0 \n", + " QF4D 19.0572 19.0572 - - 0.0 \n", + " QF4E 19.6192 19.6192 - - 0.0 \n", + " QD3E 20.6726 20.6726 - - 0.0 \n", + " QD2E 22.717 22.717 - - 0.0 \n", + " QF1E 23.3684 23.3684 - - 0.0 \n", + "phase_advance\n", + " [ 3.107 ...] [ 3.107 ...] - - [ 0.0 ...] \n", + "tune[x]\n", + " 0.381563 0.381563 - - 0.0 \n", + "mu\n", + " End [ 2.397 ...] [ 2.397 ...] - - [ 0.0 ...] \n", + "chromaticity\n", + " [ 0.1792 ...] [ 0.1792 ...] - - [ 0.0 ...] \n", + "mean(H)\n", + " -25.3692 -25.3692 - - 0.0 \n", + "PolynomB[2]\n", + " SD1A -78.9554 -78.9554 - - 0.0 \n", + " SF2A 77.0372 77.0372 - - 0.0 \n", + " SD1B -74.1895 -74.1895 - - 0.0 \n", + " SD1D -74.1895 -74.1895 - - 0.0 \n", + " SF2E 77.0372 77.0372 - - 0.0 \n", + " SD1E -78.9554 -78.9554 - - 0.0 \n", + "emittances[x]\n", + " 1.32039e-10 1.32039e-10 - - 0.0 \n", + "circumference\n", + " 26.3743 26.3743 - - 0.0 \n", + "trajectory[px]\n", + " BPM_01 0.0 0.0 - - 0.0 \n", + " BPM_02 -0.00069437 -0.00069437 - - 0.0 \n", + " BPM_03 0.000607152 0.000607152 - - 0.0 \n", + " BPM_04 0.000238468 0.000238468 - - 0.0 \n", + " BPM_05 -0.000681824 -0.000681824 - - 0.0 \n", + " BPM_06 -0.000478922 -0.000478922 - - 0.0 \n", + " BPM_07 0.000441492 0.000441492 - - 0.0 \n", + " BPM_08 0.000701582 0.000701582 - - 0.0 \n", + " BPM_09 -0.000605544 -0.000605544 - - 0.0 \n", + " BPM_10 -9.78685e-05 -9.78685e-05 - - 0.0 \n", + "geometry[x]\n", + " BPM_01 2.6514 2.6514 - - 0.0 \n", + " BPM_02 6.47833 6.47833 - - 0.0 \n", + " BPM_03 7.51381 7.51381 - - 0.0 \n", + " BPM_04 10.2883 10.2883 - - 0.0 \n", + " BPM_05 12.7198 12.7198 - - 0.0 \n", + " BPM_06 13.6274 13.6274 - - 0.0 \n", + " BPM_07 16.0491 16.0491 - - 0.0 \n", + " BPM_08 18.792 18.792 - - 0.0 \n", + " BPM_09 19.814 19.814 - - 0.0 \n", + " BPM_10 23.5805 23.5805 - - 0.0 \n" + ] + } + ], + "source": [ + "print(allobs)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/p/notebooks/parameters.ipynb b/docs/p/notebooks/parameters.ipynb new file mode 100644 index 000000000..c7e9bbc9c --- /dev/null +++ b/docs/p/notebooks/parameters.ipynb @@ -0,0 +1,923 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "bc479f4a-a609-468f-a430-d71ad22b5cf2", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "import at\n", + "import sys\n", + "from at import Param, ParamArray\n", + "if sys.version_info.minor < 9:\n", + " from importlib_resources import files, as_file\n", + "else:\n", + " from importlib.resources import files, as_file" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3399ebbe-81d6-453f-80d4-fe4832d0543e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "fname = 'hmba.mat'\n", + "with as_file(files('machine_data') / fname) as path:\n", + " ring = at.load_lattice(path)" + ] + }, + { + "cell_type": "markdown", + "id": "ba73f6c0-aa60-4ced-8158-dfddcade4bd9", + "metadata": {}, + "source": [ + "# Parameters\n", + "\n", + "Parameters are objects of class {py:class}`.Param` which can be used instead of numeric values as {py:class}`.Element` attributes.\n", + "\n", + "Parameters are initialised with a **scalar** numeric value. They have an optional name, only used to identify them in printed output:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "27f3abc6-7cfe-4632-82a1-dc17038983a2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Param(2.5, name='total length') Param(1.0, name='param2')\n" + ] + } + ], + "source": [ + "p1=Param(2.5, name='total length')\n", + "p2=Param(1.0)\n", + "print(p1, p2)" + ] + }, + { + "cell_type": "markdown", + "id": "c61cb66b-2e98-4f1b-9d6d-9d896085f6b4", + "metadata": {}, + "source": [ + "The value of a parameter can be read or modified through its {py:attr}`~.variables.Variable.value` property. {py:meth}`~.variables.Variable.set` and {py:meth}`~.variables.Variable.get` methods are also available:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "772a79ce-91ee-47fd-80bf-a7a76e935339", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.5\n", + "Param(2.4, name='total length')\n" + ] + }, + { + "data": { + "text/plain": [ + "2.3" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(p1.value)\n", + "p1.value = 2.4\n", + "print(p1)\n", + "p1.set(2.3)\n", + "p1.get()" + ] + }, + { + "cell_type": "markdown", + "id": "fa523242-3579-4e0f-aca8-2ecd230cd93c", + "metadata": {}, + "source": [ + "Arithmetic combinations of parameters create new read-only parameters of class {py:class}`.ParamBase`, whose value is permanently kept up-to-date:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a229555f-cbb2-4e36-b9d8-0ca3c0c7cf76", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ParamBase(1.2999999999999998, name='calc1')\n", + "ParamBase(1.4, name='calc1')\n" + ] + } + ], + "source": [ + "p3 = p1-p2\n", + "print(p3)\n", + "p2.value = 0.9\n", + "print(p3)" + ] + }, + { + "cell_type": "markdown", + "id": "57eff2ca-87af-4667-84e8-4e86e5b2db74", + "metadata": {}, + "source": [ + "Parameters may be assigned to {py:class}`.Element` attributes, for instance on initialisation:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c49dc3e7-78ea-4e40-9e82-438674b5ba7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drift:\n", + "\tFamName : DR1\n", + "\tLength : Param(0.9, name='param2')\n", + "\tPassMethod : DriftPass\n", + "Quadrupole:\n", + "\tFamName : QF1\n", + "\tLength : ParamBase(1.4, name='calc1')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 0.6]\n", + "\tK : 0.6\n" + ] + } + ], + "source": [ + "dr1 = at.Drift('DR1', p2)\n", + "qf1 = at.Quadrupole('QF1', p3, 0.6)\n", + "print(dr1)\n", + "print(qf1)" + ] + }, + { + "cell_type": "markdown", + "id": "6c0f9595-9abc-4fee-967e-195dc1db0030", + "metadata": {}, + "source": [ + "The {py:class}`.Element` attributes keep their type so that all the processing of elements either in python functions or in C integrators is unchanged:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "63baa52e-c1f1-4d0d-8470-fc80116239ec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9 \n" + ] + } + ], + "source": [ + "print(dr1.Length, type(dr1.Length))" + ] + }, + { + "cell_type": "markdown", + "id": "ca1a092d-2773-41bb-b5ac-65c4005593be", + "metadata": {}, + "source": [ + "## Assigning parameters\n", + "\n", + "### To a single element\n", + "Parameters may be assigned to {py:class}`.Element` attributes in several ways:\n", + "\n", + "**At element creation:**" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "07e9ced4-f048-46ec-b187-3eb60314ee92", + "metadata": {}, + "outputs": [], + "source": [ + "dr2 = at.Drift('DR2', p2)" + ] + }, + { + "cell_type": "markdown", + "id": "a7846d65-d686-4496-94fa-16f18dd564e6", + "metadata": {}, + "source": [ + "**By converting a numeric attribute into a parameter:**" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "24db8abd-6b85-4196-b1ae-a316e0ddd8ea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Param(0.5, name='param3')\n" + ] + } + ], + "source": [ + "qd1 = at.Quadrupole('QD1', 0.5, -0.4)\n", + "p4 = qd1.parametrise('Length')\n", + "print(p4)" + ] + }, + { + "cell_type": "markdown", + "id": "71a08731-9aed-497c-8084-15d6de0e8215", + "metadata": {}, + "source": [ + "**By normal assignment:**" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "946224b5-7c9b-4a5c-a665-fc516e548a15", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QD1\n", + "\tLength : ParamBase(1.4, name='calc1')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [ 0. -0.4]\n", + "\tK : -0.4\n" + ] + } + ], + "source": [ + "qd1.Length = p3\n", + "print(qd1)" + ] + }, + { + "cell_type": "markdown", + "id": "3064a2f2-b913-4982-978c-268b8e76b1c1", + "metadata": {}, + "source": [ + "**With the {py:meth}`~.Element.set_parameter` method:**" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "43c18e94-cd26-4639-98a9-bf93c736115d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QD1\n", + "\tLength : Param(0.5, name='param3')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [ 0. -0.4]\n", + "\tK : -0.4\n" + ] + } + ], + "source": [ + "qd1.set_parameter('Length', p4)\n", + "print(qd1)" + ] + }, + { + "cell_type": "markdown", + "id": "6318ac4e-332e-44cb-b77f-d5efc4d4e799", + "metadata": {}, + "source": [ + "### To selected elements of a {py:class}`.Lattice`\n", + "To act on several elements in a single step, {py:class}`.Lattice` methods similar to {py:class}`.Element` methods are available\n", + "\n", + "**Convert numeric attributes into parameters:**\n", + "\n", + "The attribute of all the selected elements is replaced by a single parameter whose initial value is the average of the original values." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "07b4603f-8089-451a-a53f-fb462729b79e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Param(2.5394599781303304, name='kf1')\n", + "Quadrupole:\n", + "\tFamName : QF1A\n", + "\tLength : 0.311896\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, Param(2.5394599781303304, name='kf1')])\n", + "\tK : Param(2.5394599781303304, name='kf1')\n" + ] + } + ], + "source": [ + "p5 = ring.parametrise('QF1[AE]', 'PolynomB', index=1, name='kf1')\n", + "print(p5)\n", + "print(ring[5])" + ] + }, + { + "cell_type": "markdown", + "id": "b8931812-b5e8-4b0d-9561-bd2b4a1506e4", + "metadata": {}, + "source": [ + "**Use the {py:meth}`~.Lattice.set_parameter` method:**\n", + "\n", + "The attribute of all the selected elements is replaced by the provided parameter," + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "276e2b71-ff91-4f55-a459-cc62d8bcc2d3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QF1E\n", + "\tLength : Param(0.311896, name='lf1')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, Param(2.5394599781303304, name='kf1')])\n", + "\tK : Param(2.5394599781303304, name='kf1')\n" + ] + } + ], + "source": [ + "p6 = Param(0.311896, name='lf1')\n", + "ring.set_parameter('QF1[AE]', 'Length', p6)\n", + "print(ring[117])" + ] + }, + { + "cell_type": "markdown", + "id": "23aeb148-0aaa-49c1-a24f-c04128cf2397", + "metadata": {}, + "source": [ + "### Special case of array attributes\n", + "\n", + "Normal {py:class}`.Element` array attributes are numeric numpy arrays, so they cannot be assigned objects. Attempting to assign a parameter to an item of an array attribute will assign the current parameter value instead of the parameter itself. Since this is not what is usually desired, a warning is emitted:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a62d3508-fd89-45e9-bc16-19b170cc5da9", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/famille/dev/libraries/at/pyat/at/lattice/elements.py:30: UserWarning: \n", + "\n", + "The Parameter 'QD strength' is ignored, instead its value '-0.3' is used.\n", + "To set a parameter in an array, you must first parametrise the array itself.\n", + "\n", + " warn(UserWarning(message))\n" + ] + } + ], + "source": [ + "p5 = Param(-0.3, name='QD strength')\n", + "qd1.PolynomB[1] = p5" + ] + }, + { + "cell_type": "markdown", + "id": "9a257a54-1838-41eb-8d83-33fb9f339272", + "metadata": {}, + "source": [ + "To accept parameters, the array attribute itself must be first replaced by a {py:class}`.ParamArray` object. The easiest way for that is to let the {py:meth}`~.Element.parametrise` or {py:meth}`~.Element.set_parameter` methods take care of it:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "40c2b4c8-2533-4604-b6d3-af100d1da33b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QD1\n", + "\tLength : Param(0.5, name='param3')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, Param(-0.3, name='QD strength')])\n", + "\tK : Param(-0.3, name='QD strength')\n" + ] + } + ], + "source": [ + "qd1.set_parameter('PolynomB', p5, index=1)\n", + "print(qd1)" + ] + }, + { + "cell_type": "markdown", + "id": "48d61183-7827-4807-ba12-25ba46e1fe19", + "metadata": {}, + "source": [ + "Once the array attribute is a {py:class}`.ParamArray`, any of its items may be assigned a parameter. As with scalar attributes, the type of the attribute is unchanged.\n", + "\n", + "Another possiblility is to first convert the array attribute into a {py:class}`.ParamArray`, and then perform a standard assignment:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0be8546f-ed8d-4c97-835f-fa1f3f77def6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QD1\n", + "\tLength : ParamBase(1.4, name='calc1')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, Param(-0.3, name='QD strength')])\n", + "\tK : Param(-0.3, name='QD strength')\n" + ] + } + ], + "source": [ + "qd1 = at.Quadrupole('QD1', p3, -0.4)\n", + "qd1.parametrise('PolynomB')\n", + "qd1.PolynomB[1] = p5\n", + "print(qd1)" + ] + }, + { + "cell_type": "markdown", + "id": "a60a830b-9ade-47ce-89ed-1e4d4a000c4e", + "metadata": {}, + "source": [ + "## Retrieving parameters\n", + "\n", + "Since the values of {py:class}`.Element` attributes keep their original type, they cannot be used to access the underlying parameter. The only way to retrieve it is to use the {py:meth}`~.Element.get_parameter` method. When applied to a non-parameter attribute, the attribute is returned." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "a4bea7d9-59c3-4af3-a96d-f2e4ee4c2c14", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ParamBase(1.4, name='calc1')\n", + "pp is p3: True\n" + ] + } + ], + "source": [ + "pp = qd1.get_parameter('Length')\n", + "print(pp)\n", + "print(\"pp is p3:\", pp is p3)" + ] + }, + { + "cell_type": "markdown", + "id": "2ba193a5-5d84-4d7a-95e3-11dfeb7241fa", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "This also works for items in array attributes:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a4343df3-a2b7-4b4f-9b74-3bdd3e20cfb2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Param(-0.3, name='QD strength')\n", + "pp is p5: True\n" + ] + } + ], + "source": [ + "pp = qd1.get_parameter('PolynomB', index=1)\n", + "print(pp)\n", + "print(\"pp is p5:\", pp is p5)" + ] + }, + { + "cell_type": "markdown", + "id": "585739fc-6a3b-4e6d-8c52-08e43b2ff17e", + "metadata": {}, + "source": [ + "Retrieving the whole array attribute returns a {py:class}`.ParamArray` object. It may contain either numbers or {py:class}`.Param` objects:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3aa4c9dc-2f8c-4742-a39a-13872adc88b8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ParamArray([0.0, Param(-0.3, name='QD strength')])\n" + ] + } + ], + "source": [ + "pp = qd1.get_parameter('PolynomB')\n", + "print(pp)" + ] + }, + { + "cell_type": "markdown", + "id": "fe98da38-b39a-4e22-a009-16495ccfda58", + "metadata": {}, + "source": [ + "## Checking parametrisation\n", + "\n", + "The {py:meth}`~.Element.is_parametrised` method may be applied to:\n", + "- a full element: it returns true is any of its attributes is a parameter,\n", + "- an array attribute: it returns true if any of its items is a parameter,\n", + "- a scalar attribute or an item of an array." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "8c0eab2f-4ed0-4441-9dd6-a2c81ecdb765", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "True\n", + "False\n", + "True\n", + "False\n" + ] + } + ], + "source": [ + "print(qd1.is_parametrised())\n", + "print(qd1.is_parametrised('PolynomB'))\n", + "print(qd1.is_parametrised('PolynomA'))\n", + "print(qd1.is_parametrised('PolynomB', index=1))\n", + "print(qd1.is_parametrised('PolynomB', index=0))" + ] + }, + { + "cell_type": "markdown", + "id": "b62883e5-8dd4-42f2-9675-bdbe4d9be71c", + "metadata": {}, + "source": [ + "## Removing parameters\n", + "Removing the parameters will \"freeze\" the element at its current value. The {py:meth}`~.Element.unparametrise` method is defined for both {py:class}`.Element` and {py:class}`.Lattice`, and may be applied to:\n", + "- a full element: all the parameters are replaced by their value,\n", + "- an array attribute: the whole {py:class}`.ParamArray` is replaced by a numpy array,\n", + "- a scalar attribute or an item of an array.\n", + "\n", + "### In an Element" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "088bfb6e-1ede-4429-8f38-c179be2c0b9d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QD1\n", + "\tLength : ParamBase(1.4, name='calc1')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, Param(-0.3, name='QD strength')])\n", + "\tK : Param(-0.3, name='QD strength')\n", + "Quadrupole:\n", + "\tFamName : QD1\n", + "\tLength : 1.4\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, -0.3])\n", + "\tK : -0.3\n", + "Quadrupole:\n", + "\tFamName : QD1\n", + "\tLength : 1.4\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [ 0. -0.3]\n", + "\tK : -0.3\n" + ] + } + ], + "source": [ + "print(qd1)\n", + "qd1.unparametrise('Length')\n", + "qd1.unparametrise('PolynomB', 1)\n", + "print(qd1)\n", + "qd1.unparametrise()\n", + "print(qd1)" + ] + }, + { + "cell_type": "markdown", + "id": "295b9a0e-23a9-4321-9715-7a6197d2859e", + "metadata": {}, + "source": [ + "### In a Lattice" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "744f6754-e681-4db4-b80d-842eb0bcb124", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QF1A\n", + "\tLength : Param(0.311896, name='lf1')\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, Param(2.5394599781303304, name='kf1')])\n", + "\tK : Param(2.5394599781303304, name='kf1')\n", + "Quadrupole:\n", + "\tFamName : QF1A\n", + "\tLength : 0.311896\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : ParamArray([0.0, 2.5394599781303304])\n", + "\tK : 2.5394599781303304\n", + "Quadrupole:\n", + "\tFamName : QF1E\n", + "\tLength : 0.311896\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.53945998]\n", + "\tK : 2.5394599781303304\n" + ] + } + ], + "source": [ + "print(ring[5])\n", + "ring.unparametrise('QF1[AE]', 'Length')\n", + "ring.unparametrise('QF1[AE]', 'PolynomB', index=1)\n", + "print(ring[5])\n", + "ring.unparametrise('QF1[AE]')\n", + "print(ring[117])" + ] + }, + { + "cell_type": "markdown", + "id": "564674a3-0bf2-48b8-9978-8f2c9cdc4fd0", + "metadata": {}, + "source": [ + "## Parameter history\n", + "Parameter values are kept in an {py:attr}`~.variables.Variable.history` buffer. The properties {py:attr}`~.variables.Variable.initial_value`, {py:attr}`~.variables.Variable.last_value` and {py:attr}`~.variables.Variable.previous_value` are also available:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "7be2ebce-fe54-4060-8481-a344c4ebf25f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2.5, 2.4, 2.3, 2.2]\n", + "2.5\n", + "2.3\n" + ] + } + ], + "source": [ + "p1.value = 2.2\n", + "print(p1.history)\n", + "print(p1.initial_value)\n", + "print(p1.previous_value)" + ] + }, + { + "cell_type": "markdown", + "id": "d1898549-19dd-46a9-8e93-8356dfae6d8a", + "metadata": {}, + "source": [ + "After varying parameters, in matching for instance, the current status can be printed:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "f191ea2a-997e-4547-b674-3e16ca2d629c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Name Initial Final Variation\n", + "\n", + "total length 2.500000e+00 2.200000e+00 -3.000000e-01\n" + ] + } + ], + "source": [ + "print(p1.status())" + ] + }, + { + "cell_type": "markdown", + "id": "0d155699-1005-431f-a00a-74d6406f5c21", + "metadata": {}, + "source": [ + "Parameters may be reset to a previous history value with the {py:meth}`~.variables.Variable.set_initial` and {py:meth}`~.variables.Variable.set_previous` methods. The history is shortened accordingly." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f92600ae-28da-439f-ae7c-6a787162c888", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Param(2.3, name='total length') [2.5, 2.4, 2.3]\n", + "Param(2.5, name='total length') [2.5]\n" + ] + } + ], + "source": [ + "p1.set_previous()\n", + "print(p1, p1.history)\n", + "p1.set_initial()\n", + "print(p1, p1.history)" + ] + }, + { + "cell_type": "markdown", + "id": "a6f096fe-6cf0-4470-9fcb-3c2e4d77b6e5", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Copying and Saving\n", + "\n", + "In a shallow copy of an {py:class}`.Element`, the parameter attributes are shared between the {py:class}`.Element` and its copy.\n", + "\n", + "In a deep copy of an {py:class}`.Element`, a copy of the original parameter is set in the {py:class}`.Element` copy.\n", + "\n", + "When saving a lattice with parametrised elements, as a `.mat`, a `.m` or `.repr` file, all parametrisation is removed, and a \"frozen\" state of the lattice is saved.\n", + "\n", + "In pickle dumps of an {py:class}`.Element`, parameters are preserved." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/p/notebooks/test_variables.ipynb b/docs/p/notebooks/test_variables.ipynb new file mode 100644 index 000000000..88b6fb792 --- /dev/null +++ b/docs/p/notebooks/test_variables.ipynb @@ -0,0 +1,525 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "d6034009-fea2-4195-9571-2a1a2b21acc3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import at\n", + "import at.plot\n", + "import sys\n", + "if sys.version_info.minor < 9:\n", + " from importlib_resources import files, as_file\n", + "else:\n", + " from importlib.resources import files, as_file" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "353791c1-ac4b-4576-9d8d-c7bc8dcd07ef", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from at.future import Variable, VariableList, ElementVariable, match\n", + "from at import LocalOpticsObservable, ObservableList" + ] + }, + { + "cell_type": "markdown", + "id": "cde6c736-f5b3-4d97-8c40-ef6215322401", + "metadata": {}, + "source": [ + "# Correlated variables\n", + "\n", + "In this example of correlation between variables, we vary the length of the two drifts\n", + "surrounding a monitor but keep the sum of their lengths constant.\n", + "\n", + "Using these 2 correlated variables, we will match a constraint on the monitor.\n", + "\n", + "## Load a test lattice" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2a7220ac-f85c-4aee-983f-1308feab346e", + "metadata": {}, + "outputs": [], + "source": [ + "fname = 'hmba.mat'\n", + "with as_file(files('machine_data') / fname) as path:\n", + " hmba_lattice = at.load_lattice(path)" + ] + }, + { + "cell_type": "markdown", + "id": "18d7c32f-5a73-4fc2-a7c4-6f7d435fe2c3", + "metadata": {}, + "source": [ + "Isolate the two drifts" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4df347e2-6439-438d-97b7-25ad25179a57", + "metadata": {}, + "outputs": [], + "source": [ + "dr1 = hmba_lattice[\"DR_01\"][0]\n", + "dr2 = hmba_lattice[\"DR_02\"][0]" + ] + }, + { + "cell_type": "markdown", + "id": "c8df614e-afec-47f1-bbb2-9e9349c91fb9", + "metadata": {}, + "source": [ + "Get the total length to be preserved" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8dbbe584-e434-4067-a8a5-157cc90e8f05", + "metadata": {}, + "outputs": [], + "source": [ + "l1 = dr1.Length\n", + "l2 = dr2.Length\n", + "ltot = l1 + l2" + ] + }, + { + "cell_type": "markdown", + "id": "0fdc04ae-df36-4f1e-b568-3b275739c646", + "metadata": {}, + "source": [ + "Create a constraint {math}`\\beta_y=3.0` on `BPM_01`" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "510a6be4-519a-45ad-93f1-0641c6de5e1f", + "metadata": {}, + "outputs": [], + "source": [ + "obs1 = LocalOpticsObservable('BPM_01', 'beta', plane='v', target=3.0)" + ] + }, + { + "cell_type": "markdown", + "id": "9d398afa-1063-4af6-84c4-46159c1cd622", + "metadata": {}, + "source": [ + "## Method 1: using parameters\n", + "\n", + "We parametrise the lengths of the drifts surrounding the monitor\n", + "\n", + "### Parametrise the two drifts:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7d6fcc63-46be-416e-9754-1fb46eae0189", + "metadata": {}, + "outputs": [], + "source": [ + "param1 = dr1.parametrise('Length')\n", + "dr2.Length = ltot-param1" + ] + }, + { + "cell_type": "markdown", + "id": "d3487819-a2fd-4410-9e37-6a36c3bd86f8", + "metadata": {}, + "source": [ + "### Run the matching" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ae9630ad-cda8-4477-81fd-051ca67b0848", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "1 constraints, 1 variables, using method lm\n", + "\n", + "`xtol` termination condition is satisfied.\n", + "Function evaluations 13, initial cost 2.6515e+00, final cost 9.8608e-32, first-order optimality 3.26e-16.\n", + "\n", + "Constraints:\n", + "\n", + "location Initial Actual Low bound High bound residual \n", + "beta[y]\n", + " BPM_01 5.30283 3.0 3.0 3.0 1.97062e-11 \n", + "\n", + "Variables:\n", + "\n", + " Name Initial Final Variation\n", + "\n", + " param1 2.651400e+00 9.693839e-01 -1.682016e+00\n" + ] + } + ], + "source": [ + "variables = VariableList([param1])\n", + "constraints = ObservableList(hmba_lattice, [obs1])\n", + "match(hmba_lattice, variables, constraints, verbose=1)" + ] + }, + { + "cell_type": "markdown", + "id": "f908b523-b10d-4fc5-bade-99450231e044", + "metadata": {}, + "source": [ + "### Show the modified lattice" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3205df5f-f494-4900-b743-2468eacddf1c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drift:\n", + "\tFamName : DR_01\n", + "\tLength : Param(0.9693838658136734, name='param1')\n", + "\tPassMethod : DriftPass\n", + "Monitor:\n", + "\tFamName : BPM_01\n", + "\tLength : 0.0\n", + "\tPassMethod : IdentityPass\n", + "\tOffset : [0 0]\n", + "\tScale : [1 1]\n", + "\tReading : [0 0]\n", + "\tRotation : [0 0]\n", + "Drift:\n", + "\tFamName : DR_02\n", + "\tLength : ParamBase(1.7245681341863266, name='calc1')\n", + "\tPassMethod : DriftPass\n" + ] + } + ], + "source": [ + "for elem in hmba_lattice.select([2,3,4]):\n", + " print(elem)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6eb16268-0ffe-4511-a8dc-43cb58afd633", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHHCAYAAAAveOlqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOydd3hb1f3/X1qWLe89shOyJwGShoRVQpMwAzSlgbISSqFltGnTMsootL+0jLBHS4FA+VIoZbQUCIRACIQkEBJnELKdHe9t2ZIl3d8fV+fKirUtW5J9Xs+jR7J07r1Htu+97/OZOkVRFCQSiUQikUgkfQZ9rCcgkUgkEolEIulZpACUSCQSiUQi6WNIASiRSCQSiUTSx5ACUCKRSCQSiaSPIQWgRCKRSCQSSR9DCkCJRCKRSCSSPoYUgBKJRCKRSCR9DCkAJRKJRCKRSPoYUgBKJBKJRCKR9DGkAJRIJD3CsmXL0Ol07N+/v1v2f++996LT6UIa+49//INRo0ZhMpnIysrqlvl0lf3796PT6Vi2bFmspyKRSHohUgBKJH2Ub7/9lp/85Cf069cPs9lMSUkJV1xxBd9++22X9vv//t//45133onOJLuBHTt2cM011zBs2DCee+45/va3v8V0Pq+++iqPPvpoTOcgkUj6HjrZC1gi6Xu89dZbzJ8/n5ycHBYuXMiQIUPYv38/zz//PDU1Nbz22mtcfPHFEe07LS2NH/7wh50sV06nk/b2dsxmc8iWunC49957+cMf/kCwS9qzzz7LjTfeyO7duznhhBOiPo9wOf/889m2bVsny6iiKNhsNkwmEwaDITaTk0gkvRZjrCcgCY1ly5bxy1/+kvr6+lhPRZLg7N27lyuvvJKhQ4eyevVq8vPztc9uvfVWTjvtNK688kq2bNnC0KFDo3Zcg8EQF0KmsrISIG5dvwKdTkdycnKspyGRSHop0gUcJlVVVdx4440MHDgQs9lMUVERs2bNYs2aNdTW1nLzzTczcuRIUlJSGDhwILfccgsNDQ1e+/j66685++yzycrKIjs7m1mzZrF58+YYfSNJX+PBBx/EarXyt7/9zUv8AeTl5fHXv/6VlpYWHnjgAe19EV+3Y8cOfvSjH5GRkUFubi633norbW1t2jidTkdLSwsvvfQSOp0OnU7HNddcA/iPAfzggw8444wzSE9PJyMjg1NOOYVXX31V+/zzzz9n3rx52jk3YMAAfvWrX9Ha2hr2dx88eDD33HMPAPn5+eh0Ou69915t7uL18duI79Dxe6xZs4ZFixaRn59PamoqF198MVVVVZ22D/T9zjzzTN577z0OHDig/b4GDx4M+I8B/OSTTzjttNNITU0lKyuLiy66iO+++85rjPh77dmzh2uuuYasrCwyMzO59tprsVqtXmNXrFjBjBkzyMrKIi0tjZEjR3LHHXeE8VuVSCSJiLQAhsmll16K3W7npZdeYujQoVRUVLBy5Upqamo4evQoR48e5aGHHmLMmDEcOHCAG264gaNHj/Lvf/8bgObmZmbPns2FF17I008/jcPh4J577mHWrFkcOnQIk8kU428o6e28++67DB48mNNOO83n56effjqDBw/mvffe6/TZj370IwYPHsySJUtYt24djz/+OHV1dbz88suAmlxx3XXXMWXKFK6//noAhg0b5ncuy5YtY8GCBYwdO5bbb7+drKwsNm3axPLly7n88ssBeOONN7Bardx4443k5uby1Vdf8cQTT3D48GHeeOONsL77o48+yssvv8zbb7/NM888Q1paGhMmTAhrH4Kbb76Z7Oxs7rnnHvbv38+jjz7KTTfdxOuvvx7y97vzzjtpaGjg8OHDPPLII4DqQvfHxx9/zJw5cxg6dCj33nsvra2tPPHEE0yfPp2NGzdq4lHwox/9iCFDhrBkyRI2btzI3//+dwoKCvjLX/4CqHGg559/PhMmTOC+++7DbDazZ88e1qxZE9HvRCKRJBCKJGTq6uoUQFm1alXI2/zrX/9SkpKSlPb2dkVRFOXrr79WAOXgwYPamC1btiiAsnv3bu29F198URkwYICSkpKizJ07V3nooYeUzMzMqH0XSd+kvr5eAZSLLroo4LgLL7xQAZTGxkZFURTlnnvuUQDlwgsv9Br385//XAGUzZs3a++lpqYqV199dad9vvjiiwqglJWVaXNJT09Xpk6dqrS2tnqNdblc2mur1dppX0uWLFF0Op1y4MAB7T0xx2CIcVVVVV7vA8o999zTafygQYO8vo/4HjNnzvSa569+9SvFYDAo9fX1YX2/8847Txk0aFCn45aVlSmA8uKLL2rvTZo0SSkoKFBqamq09zZv3qzo9Xrlqquu6vQdFyxY4LXPiy++WMnNzdV+fuSRR3z+LiQSSe9HuoDDIC0tjbS0NN555x1sNltI2zQ0NJCRkYHRqBpbR44cSW5uLs8//zx2u53W1laef/55Ro8era3e169fz8KFC7npppsoLS3lrLPO4o9//GN3fS1JH6KpqQmA9PT0gOPE542NjV7v/+IXv/D6+eabbwbg/fffD3suK1asoKmpidtuu61TrFvHJJGUlBTtdUtLC9XV1Zx66qkoisKmTZvCPm60uP76673medppp+F0Ojlw4AAQ+vcLlWPHjlFaWso111xDTk6O9v6ECRM455xzfP4NbrjhBq+fTzvtNGpqarS/q4iD/M9//oPL5Qp7ThKJJHGRAjAMjEYjy5Yt46WXXiIrK4vp06dzxx13sGXLFp/jq6uruf/++zVXGKg31lWrVvHKK6+QkpJCWloay5cv54MPPtBE4mOPPcbs2bP57W9/y4gRI7jllluYNWtWj3xHSe9GCDshBP3hTygOHz7c6+dhw4ah1+sjqu23d+9eAMaNGxdw3MGDBzXRk5aWRn5+PmeccQZAp/janmTgwIFeP2dnZwNQV1cHhP79QkUIy5EjR3b6bPTo0VRXV9PS0hLWHC+77DKmT5/OddddR2FhIT/+8Y/517/+JcWgRNIHkAIwTC699FKOHj3Kf//7X2bPns2qVauYPHlyp0DtxsZGzjvvPMaMGeMVWN7a2srChQuZPn0669atY82aNYwbN47zzjtPC2r/7rvvmDp1qtf+pk2b1t1fTdIHyMzMpLi42O+iRbBlyxb69etHRkZGwHHdUc6lI06nk3POOYf33nuP3/3ud7zzzjusWLFCO996Qqg4nU6f7/vLaFbiqLJWsDmmpKSwevVqPv74Yy3z+7LLLuOcc87x+70lEknvQArACEhOTuacc87hrrvu4ssvv+Saa67RMgtBtZ7Mnj2b9PR03n77ba/EjldffZX9+/fz4osvcsopp/C9732PV199lbKyMv7zn//E4utI+hjnn38+ZWVlfPHFFz4///zzz9m/fz/nn39+p892797t9fOePXtwuVxeyQehikKRHLJt2za/Y7Zu3cquXbt4+OGH+d3vfsdFF13EzJkzKSkpCekY4ZCdnd2pzJLdbufYsWMR7S+U7weh/74GDRoEwM6dOzt9tmPHDvLy8khNTQ1zlqDX6zn77LNZunQp27dv509/+hOffPIJn376adj7kkgkiYMUgFFgzJgxmuul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Nrbb8dpfR5bZF0gDUOIYGzd2FAHuL/z8c7t/f+InNrEJC/0zB2Y/+0kjjTzyWl9saRFdNSSS05lt21q7cgCFFLKHPexjn+fm1A0qKpvZzBa2tH/jlHNOv0FVYf16T8+iXxEcHExISIjt4evbufasj48P06dPZ32b7WexWFi/fj1z587t9DNz585tNx5g3bp1tvHDhw8nNja23Zja2lp27tzZ5TL7ggFpAL788sskJyfj5+fH7Nmz2bVrV7fjP/nkE8aOHYufnx+pqal8842bm2gPJmprex7jKU6pmt3HPnaww0OT6ZkiiviarzsKdvfnbSyRuINT9nEVldWspoD+m/qwi13s5ZSuK/25Ul+eR5zm/vvv54033uCdd97hyJEj3HnnnRgMBltV8PXXX9/Og3jPPfewdu1annnmGY4ePcpjjz3G7t27WbFiBSBUNe69917+8pe/8NVXX3Hw4EGuv/564uPjWbp0qSe+IjAADUBNn+fRRx8lLS2NyZMns2TJEkpLO08c1vR5br75Zvbu3cvSpUtZunQph7TG1xKJRCKRSCRWrr76av75z3/yyCOPMGXKFPbt28fatWttRRy5ubkUtREWnzdvHh988AGvv/46kydP5tNPP+WLL76waQAC/O53v+Puu+/mtttuY+bMmdTX17N27VqPaQDCAJSBcbc+j0QikUgkEklbVqxYYfPgncrGjRs7vLZs2TKWLVvW5fIUReGJJ57giSeecNcUXWZAeQA1fZ62Wjr26PO0HQ9Cn6er8QBNTU3tysXr6urc8wUkEolEIpFI+gEDygDsDX2eznjyySfblYuPHz/e9clLJBKJRCKR9BMGlAHYVzz00EPtysXT09PdstyVK1cSFxfHJ5984pblSdzLypUriZs2jU8OH3bPsk6z33rlypXEXXaZW7ZPX1G6oZS4uDgOH3btdxiIv+fKjRsH3Jx7QvsdSje6Lu6+cs2a0277uJOV330nt89pzoAyAHtDn6czfH1925WLBwe73uO2tLSU22+/neLiYm677bYui1YknsH2+5SVcdvXX1NaX+/0spqrmk+739q2fSorXd4+fUVzVTMZz2ZQXFzM11/fRn29c79Dff3AO3ZL6+u5/bnnbHN29rv3J9qeQ489c4zmqmbnl1Vfz+1/+tOA+k37ktL6em7/y1/k9jnNGVAGYG/o8/QFqqpyxx13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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(,\n", + " ,\n", + " )" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hmba_lattice.plot_beta()" + ] + }, + { + "cell_type": "markdown", + "id": "17403824-c7bd-4e36-91b9-4c406e3e64e4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The first BPM is moved to a location where {math}`\\beta_y=3.0`" + ] + }, + { + "cell_type": "markdown", + "id": "c8d6c102-e9b3-4e68-bee0-42409d282a38", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Restore the lattice" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "6d365a60-d172-4dee-a80d-d7796d8571ab", + "metadata": {}, + "outputs": [], + "source": [ + "dr1.Length = l1\n", + "dr2.Length = l2" + ] + }, + { + "cell_type": "markdown", + "id": "ae3b0459-6b13-4ead-905b-235b20cea2d9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Method 2: custom variable\n", + "\n", + "We define a new variable class which will act on the two elements and fulfil the constraint\n", + "\n", + "### Define a variable coupling two drift lengths so that their sum is constant:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "34f10de4-9123-45d4-a9e9-b071836d4f6c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "class ElementShifter(Variable):\n", + " def __init__(self, dr1, dr2, total_length=None, **kwargs):\n", + " \"\"\"Varies the length of the elements *dr1* and *dr2*\n", + " keeping the sum of their lengths equal to *total_length*.\n", + "\n", + " If *total_length* is None, it is set to the initial total length\n", + " \"\"\" \n", + " # store indexes of the 2 variable elements\n", + " self.dr1 = dr1\n", + " self.dr2 = dr2\n", + " # store the initial total length\n", + " if total_length is None:\n", + " total_length = dr1.Length + dr2.Length\n", + " self.length = total_length\n", + " super().__init__(bounds=(0.0, total_length), **kwargs)\n", + "\n", + " def _setfun(self, value, ring=None):\n", + " dr1.Length = value\n", + " dr2.Length = self.length - value\n", + "\n", + " def _getfun(self, ring=None):\n", + " return dr1.Length" + ] + }, + { + "cell_type": "markdown", + "id": "1928d81f-dac5-464e-af96-2809306cac15", + "metadata": {}, + "source": [ + "Create a variable moving the monitor `BPM_01`" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "816e6de7-9ec4-40bc-be97-f63fcf9d189e", + "metadata": {}, + "outputs": [], + "source": [ + "var0 = ElementShifter(dr1, dr2, name='DR_01', total_length=ltot)" + ] + }, + { + "cell_type": "markdown", + "id": "ebbd2f87-8541-4e89-8b30-acf81815549c", + "metadata": {}, + "source": [ + "### Run the matching" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "0874d86a-63ad-40b7-adba-e805b5108265", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "1 constraints, 1 variables, using method trf\n", + "\n", + "`gtol` termination condition is satisfied.\n", + "Function evaluations 8, initial cost 2.6515e+00, final cost 7.1885e-27, first-order optimality 8.52e-14.\n", + "\n", + "Constraints:\n", + "\n", + "location Initial Actual Low bound High bound residual \n", + "beta[y]\n", + " BPM_01 5.30283 3.0 3.0 3.0 1.19332e-16 \n", + "\n", + "Variables:\n", + "\n", + " Name Initial Final Variation\n", + "\n", + " DR_01 2.651400e+00 9.693778e-01 -1.682022e+00\n" + ] + } + ], + "source": [ + "variables = VariableList([var0])\n", + "constraints = ObservableList(hmba_lattice, [obs1])\n", + "match(hmba_lattice, variables, constraints, verbose=1)" + ] + }, + { + "cell_type": "markdown", + "id": "ebef68f5-440c-47d9-be1c-cafffb258950", + "metadata": {}, + "source": [ + "### Show the modified lattice" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "46848c34-339c-41ad-88ba-949e32dba6c3", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drift:\n", + "\tFamName : DR_01\n", + "\tLength : 0.9693778252605573\n", + "\tPassMethod : DriftPass\n", + "Monitor:\n", + "\tFamName : BPM_01\n", + "\tLength : 0.0\n", + "\tPassMethod : IdentityPass\n", + "\tOffset : [0 0]\n", + "\tScale : [1 1]\n", + "\tReading : [0 0]\n", + "\tRotation : [0 0]\n", + "Drift:\n", + "\tFamName : DR_02\n", + "\tLength : 1.7245741747394425\n", + "\tPassMethod : DriftPass\n" + ] + } + ], + "source": [ + "for elem in hmba_lattice.select([2,3,4]):\n", + " print(elem)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a1ecc073-fb8a-4883-ad43-85f37392ccb2", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHHCAYAAAAveOlqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOydd3hb1f3/X1qWLe89shOyJwGShoRVQpMwAzSlgbISSqFltGnTMsootL+0jLBHS4FA+VIoZbQUCIRACIQkEBJnELKdHe9t2ZIl3d8fV+fKirUtW5J9Xs+jR7J07r1Htu+97/OZOkVRFCQSiUQikUgkfQZ9rCcgkUgkEolEIulZpACUSCQSiUQi6WNIASiRSCQSiUTSx5ACUCKRSCQSiaSPIQWgRCKRSCQSSR9DCkCJRCKRSCSSPoYUgBKJRCKRSCR9DCkAJRKJRCKRSPoYUgBKJBKJRCKR9DGkAJRIJD3CsmXL0Ol07N+/v1v2f++996LT6UIa+49//INRo0ZhMpnIysrqlvl0lf3796PT6Vi2bFmspyKRSHohUgBKJH2Ub7/9lp/85Cf069cPs9lMSUkJV1xxBd9++22X9vv//t//45133onOJLuBHTt2cM011zBs2DCee+45/va3v8V0Pq+++iqPPvpoTOcgkUj6HjrZC1gi6Xu89dZbzJ8/n5ycHBYuXMiQIUPYv38/zz//PDU1Nbz22mtcfPHFEe07LS2NH/7wh50sV06nk/b2dsxmc8iWunC49957+cMf/kCwS9qzzz7LjTfeyO7duznhhBOiPo9wOf/889m2bVsny6iiKNhsNkwmEwaDITaTk0gkvRZjrCcgCY1ly5bxy1/+kvr6+lhPRZLg7N27lyuvvJKhQ4eyevVq8vPztc9uvfVWTjvtNK688kq2bNnC0KFDo3Zcg8EQF0KmsrISIG5dvwKdTkdycnKspyGRSHop0gUcJlVVVdx4440MHDgQs9lMUVERs2bNYs2aNdTW1nLzzTczcuRIUlJSGDhwILfccgsNDQ1e+/j66685++yzycrKIjs7m1mzZrF58+YYfSNJX+PBBx/EarXyt7/9zUv8AeTl5fHXv/6VlpYWHnjgAe19EV+3Y8cOfvSjH5GRkUFubi633norbW1t2jidTkdLSwsvvfQSOp0OnU7HNddcA/iPAfzggw8444wzSE9PJyMjg1NOOYVXX31V+/zzzz9n3rx52jk3YMAAfvWrX9Ha2hr2dx88eDD33HMPAPn5+eh0Ou69915t7uL18duI79Dxe6xZs4ZFixaRn59PamoqF198MVVVVZ22D/T9zjzzTN577z0OHDig/b4GDx4M+I8B/OSTTzjttNNITU0lKyuLiy66iO+++85rjPh77dmzh2uuuYasrCwyMzO59tprsVqtXmNXrFjBjBkzyMrKIi0tjZEjR3LHHXeE8VuVSCSJiLQAhsmll16K3W7npZdeYujQoVRUVLBy5Upqamo4evQoR48e5aGHHmLMmDEcOHCAG264gaNHj/Lvf/8bgObmZmbPns2FF17I008/jcPh4J577mHWrFkcOnQIk8kU428o6e28++67DB48mNNOO83n56effjqDBw/mvffe6/TZj370IwYPHsySJUtYt24djz/+OHV1dbz88suAmlxx3XXXMWXKFK6//noAhg0b5ncuy5YtY8GCBYwdO5bbb7+drKwsNm3axPLly7n88ssBeOONN7Bardx4443k5uby1Vdf8cQTT3D48GHeeOONsL77o48+yssvv8zbb7/NM888Q1paGhMmTAhrH4Kbb76Z7Oxs7rnnHvbv38+jjz7KTTfdxOuvvx7y97vzzjtpaGjg8OHDPPLII4DqQvfHxx9/zJw5cxg6dCj33nsvra2tPPHEE0yfPp2NGzdq4lHwox/9iCFDhrBkyRI2btzI3//+dwoKCvjLX/4CqHGg559/PhMmTOC+++7DbDazZ88e1qxZE9HvRCKRJBCKJGTq6uoUQFm1alXI2/zrX/9SkpKSlPb2dkVRFOXrr79WAOXgwYPamC1btiiAsnv3bu29F198URkwYICSkpKizJ07V3nooYeUzMzMqH0XSd+kvr5eAZSLLroo4LgLL7xQAZTGxkZFURTlnnvuUQDlwgsv9Br385//XAGUzZs3a++lpqYqV199dad9vvjiiwqglJWVaXNJT09Xpk6dqrS2tnqNdblc2mur1dppX0uWLFF0Op1y4MAB7T0xx2CIcVVVVV7vA8o999zTafygQYO8vo/4HjNnzvSa569+9SvFYDAo9fX1YX2/8847Txk0aFCn45aVlSmA8uKLL2rvTZo0SSkoKFBqamq09zZv3qzo9Xrlqquu6vQdFyxY4LXPiy++WMnNzdV+fuSRR3z+LiQSSe9HuoDDIC0tjbS0NN555x1sNltI2zQ0NJCRkYHRqBpbR44cSW5uLs8//zx2u53W1laef/55Ro8era3e169fz8KFC7npppsoLS3lrLPO4o9//GN3fS1JH6KpqQmA9PT0gOPE542NjV7v/+IXv/D6+eabbwbg/fffD3suK1asoKmpidtuu61TrFvHJJGUlBTtdUtLC9XV1Zx66qkoisKmTZvCPm60uP76673medppp+F0Ojlw4AAQ+vcLlWPHjlFaWso111xDTk6O9v6ECRM455xzfP4NbrjhBq+fTzvtNGpqarS/q4iD/M9//oPL5Qp7ThKJJHGRAjAMjEYjy5Yt46WXXiIrK4vp06dzxx13sGXLFp/jq6uruf/++zVXGKg31lWrVvHKK6+QkpJCWloay5cv54MPPtBE4mOPPcbs2bP57W9/y4gRI7jllluYNWtWj3xHSe9GCDshBP3hTygOHz7c6+dhw4ah1+sjqu23d+9eAMaNGxdw3MGDBzXRk5aWRn5+PmeccQZAp/janmTgwIFeP2dnZwNQV1cHhP79QkUIy5EjR3b6bPTo0VRXV9PS0hLWHC+77DKmT5/OddddR2FhIT/+8Y/517/+JcWgRNIHkAIwTC699FKOHj3Kf//7X2bPns2qVauYPHlyp0DtxsZGzjvvPMaMGeMVWN7a2srChQuZPn0669atY82aNYwbN47zzjtPC2r/7rvvmDp1qtf+pk2b1t1fTdIHyMzMpLi42O+iRbBlyxb69etHRkZGwHHdUc6lI06nk3POOYf33nuP3/3ud7zzzjusWLFCO996Qqg4nU6f7/vLaFbiqLJWsDmmpKSwevVqPv74Yy3z+7LLLuOcc87x+70lEknvQArACEhOTuacc87hrrvu4ssvv+Saa67RMgtBtZ7Mnj2b9PR03n77ba/EjldffZX9+/fz4osvcsopp/C9732PV199lbKyMv7zn//E4utI+hjnn38+ZWVlfPHFFz4///zzz9m/fz/nn39+p892797t9fOePXtwuVxeyQehikKRHLJt2za/Y7Zu3cquXbt4+OGH+d3vfsdFF13EzJkzKSkpCekY4ZCdnd2pzJLdbufYsWMR7S+U7weh/74GDRoEwM6dOzt9tmPHDvLy8khNTQ1zlqDX6zn77LNZunQp27dv509/+hOffPIJn376adj7kkgkiYMUgFFgzJgxmuul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Nrbb8dpfR5bZF0gDUOIYGzd2FAHuL/z8c7t/f+InNrEJC/0zB2Y/+0kjjTzyWl9saRFdNSSS05lt21q7cgCFFLKHPexjn+fm1A0qKpvZzBa2tH/jlHNOv0FVYf16T8+iXxEcHExISIjt4evbufasj48P06dPZ32b7WexWFi/fj1z587t9DNz585tNx5g3bp1tvHDhw8nNja23Zja2lp27tzZ5TL7ggFpAL788sskJyfj5+fH7Nmz2bVrV7fjP/nkE8aOHYufnx+pqal8842bm2gPJmprex7jKU6pmt3HPnaww0OT6ZkiiviarzsKdvfnbSyRuINT9nEVldWspoD+m/qwi13s5ZSuK/25Ul+eR5zm/vvv54033uCdd97hyJEj3HnnnRgMBltV8PXXX9/Og3jPPfewdu1annnmGY4ePcpjjz3G7t27WbFiBSBUNe69917+8pe/8NVXX3Hw4EGuv/564uPjWbp0qSe+IjAADUBNn+fRRx8lLS2NyZMns2TJEkpLO08c1vR5br75Zvbu3cvSpUtZunQph7TG1xKJRCKRSCRWrr76av75z3/yyCOPMGXKFPbt28fatWttRRy5ubkUtREWnzdvHh988AGvv/46kydP5tNPP+WLL76waQAC/O53v+Puu+/mtttuY+bMmdTX17N27VqPaQDCAJSBcbc+j0QikUgkEklbVqxYYfPgncrGjRs7vLZs2TKWLVvW5fIUReGJJ57giSeecNcUXWZAeQA1fZ62Wjr26PO0HQ9Cn6er8QBNTU3tysXr6urc8wUkEolEIpFI+gEDygDsDX2eznjyySfblYuPHz/e9clLJBKJRCKR9BMGlAHYVzz00EPtysXT09PdstyVK1cSFxfHJ5984pblSdzLypUriZs2jU8OH3bPsk6z33rlypXEXXaZW7ZPX1G6oZS4uDgOH3btdxiIv+fKjRsH3Jx7QvsdSje6Lu6+cs2a0277uJOV330nt89pzoAyAHtDn6czfH1925WLBwe73uO2tLSU22+/neLiYm677bYui1YknsH2+5SVcdvXX1NaX+/0spqrmk+739q2fSorXd4+fUVzVTMZz2ZQXFzM11/fRn29c79Dff3AO3ZL6+u5/bnnbHN29rv3J9qeQ489c4zmqmbnl1Vfz+1/+tOA+k37ktL6em7/y1/k9jnNGVAGYG/o8/QFqqpyxx13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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(,\n", + " ,\n", + " )" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hmba_lattice.plot_beta()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb new file mode 100644 index 000000000..b811ce26b --- /dev/null +++ b/docs/p/notebooks/variables.ipynb @@ -0,0 +1,573 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "bc479f4a-a609-468f-a430-d71ad22b5cf2", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "import at\n", + "import sys\n", + "if sys.version_info.minor < 9:\n", + " from importlib_resources import files, as_file\n", + "else:\n", + " from importlib.resources import files, as_file" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e9955365-2514-4915-a2e4-5a6b26c1beb0", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "fname = 'hmba.mat'\n", + "with as_file(files('machine_data') / fname) as path:\n", + " ring = at.load_lattice(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d993e922-806d-42fc-a793-51e8c5e82995", + "metadata": {}, + "outputs": [], + "source": [ + "from at.future import ElementVariable, RefptsVariable" + ] + }, + { + "cell_type": "markdown", + "id": "ba73f6c0-aa60-4ced-8158-dfddcade4bd9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Variables\n", + "\n", + "Variables are **references** to any scalar quantity. Predefined classes are available\n", + "for accessing any scalar attribute of an element, or any item of an array attribute.\n", + "\n", + "Any other quantity may be accessed by either subclassing the {py:class}`~.variables.Variable`\n", + "abstract base class, or using a {py:class}`~.variables.CustomVariable`.\n", + "\n", + "## {py:class}`~.element_variables.ElementVariable`\n", + "\n", + "An {py:class}`~.element_variables.ElementVariable` refers to a single attribute (or item of an array attribute) of one or several {py:class}`.Element` objects.\n", + "\n", + "We now create a variable pointing to the length of all QF1 magnets of *ring*:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "15063c05-ef7f-43ec-88d3-0109c8ea0592", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lf1: ElementVariable(0.311896, name='lf1')\n", + "0.311896\n" + ] + } + ], + "source": [ + "lf1 = ElementVariable(ring[\"QF1[AE]\"], \"Length\", name=\"lf1\")\n", + "print(f\"lf1: {lf1}\")\n", + "print(lf1.value)" + ] + }, + { + "cell_type": "markdown", + "id": "b1271329-08be-4655-8884-77d7eec67558", + "metadata": {}, + "source": [ + "and another variable pointing to the strength of the same magnets:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "db5c3831-467a-468b-aca7-648b45c90887", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "kf1: ElementVariable(2.5394599781303304, name='kf1')\n", + "2.5394599781303304\n" + ] + } + ], + "source": [ + "kf1 = ElementVariable(ring[\"QF1[AE]\"], \"PolynomB\", index=1, name=\"kf1\")\n", + "print(\"kf1:\", kf1)\n", + "print(kf1.value)" + ] + }, + { + "cell_type": "markdown", + "id": "0a3bf50d-8a18-4372-9d38-d5143da1807c", + "metadata": {}, + "source": [ + "We can check which elements are concerned by the `kf1` variable. The element container is a set, so that no element may appear twice:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5fbe7aa7-3264-4de8-a8d7-439b9b7ace2f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{Quadrupole('QF1A', 0.311896, 2.5394599781303304, FringeQuadEntrance=1, FringeQuadExit=1, NumIntSteps=20),\n", + " Quadrupole('QF1E', 0.311896, 2.5394599781303304, FringeQuadEntrance=1, FringeQuadExit=1, NumIntSteps=20)}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kf1.elements" + ] + }, + { + "cell_type": "markdown", + "id": "648083b2-b16c-44fc-9bf6-f6e79002d8f4", + "metadata": {}, + "source": [ + "`kf1` drives 2 quadrupoles. Let's look at the 1{sup}`st` one:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d686f64a-1855-4b8a-a6f8-63c52ed038bf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QF1A\n", + "\tLength : 0.311896\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.53945998]\n", + "\tK : 2.5394599781303304\n" + ] + } + ], + "source": [ + "print(ring[5])" + ] + }, + { + "cell_type": "markdown", + "id": "5dc6ea71-8d0a-4b8e-b00f-bebd0b09a874", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can now change the strength of both QF1 magnets and check again the 1{sup}`st` one:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d74f4a63-bacf-4ee4-b42a-de75bfbea193", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QF1A\n", + "\tLength : 0.311896\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.5]\n", + "\tK : 2.5\n" + ] + } + ], + "source": [ + "kf1.set(2.5)\n", + "print(ring[5])" + ] + }, + { + "cell_type": "markdown", + "id": "baed19af-fd43-44d3-a5df-965eed43f0eb", + "metadata": {}, + "source": [ + "We can look at the history of `kf1` values" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "413145df-30e5-4601-b05a-df2b042028ff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2.5394599781303304, 2.5]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kf1.history" + ] + }, + { + "cell_type": "markdown", + "id": "c9b693f9-a04e-4cb6-bbd2-37de0733ccb8", + "metadata": {}, + "source": [ + "And revert to the initial or previous values:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "70a1bca4-af2d-49b9-8462-989d47ad1efe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QF1A\n", + "\tLength : 0.311896\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 20\n", + "\tFringeQuadEntrance : 1\n", + "\tFringeQuadExit : 1\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.53945998]\n", + "\tK : 2.5394599781303304\n" + ] + } + ], + "source": [ + "kf1.set_previous()\n", + "print(ring[5])" + ] + }, + { + "cell_type": "markdown", + "id": "c650be51-228a-4ee0-ac73-d741601f992b", + "metadata": {}, + "source": [ + "An {py:class}`~.element_variables.ElementVariable` is linked to Elements. It will not follow any copy of the element, neither shallow nor deep. So if we make a copy of ring:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "314c398c-fbbc-43ce-abcc-76ba48cf3c03", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ring: 2.5394599781303304\n", + "newring: 2.5394599781303304\n" + ] + } + ], + "source": [ + "newring = ring.deepcopy()\n", + "print(f\"ring: {ring[5].PolynomB[1]}\")\n", + "print(f\"newring: {newring[5].PolynomB[1]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "5ea8a7fc-3bb6-4d18-8656-a8f45755c932", + "metadata": {}, + "source": [ + "and modify the `kf1` variable:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8458ab19-3be1-427c-a55d-0ccb029c9f26", + "metadata": {}, + "outputs": [], + "source": [ + "kf1.set(2.6)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f4c1e653-eeaf-4fd5-be08-2a488bd7df9d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ring: 2.6\n", + "newring: 2.5394599781303304\n" + ] + } + ], + "source": [ + "print(f\"ring: {ring[5].PolynomB[1]}\")\n", + "print(f\"newring: {newring[5].PolynomB[1]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "e2c5b959-448a-425f-bf54-f33b8d189330", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The QF1 in newring is not affected.\n", + "\n", + "One can set upper and lower bounds on a variable. Trying to set a value out of the bounds will raise a {py:obj}`ValueError`. The default is (-{py:obj}`numpy.inf`, {py:obj}`numpy.inf`)." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "738f6c51-2968-4d77-8599-261e7998d52b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "lfbound = ElementVariable(ring[\"QF1[AE]\"], \"Length\", bounds=(0.30, 0.35))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "ba4e728e-fa3e-4d71-9dcb-658d240fd61c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "raises-exception" + ] + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "set value must be in (0.3, 0.35)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:235\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 229\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 230\u001b[0m \n\u001b[1;32m 231\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[1;32m 232\u001b[0m \u001b[38;5;124;03m value: New value to be applied on the variable\u001b[39;00m\n\u001b[1;32m 233\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 234\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 235\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 236\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 237\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", + "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" + ] + } + ], + "source": [ + "lfbound.set(0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "ef061e87-74e7-446d-b93c-a5c84269ce0c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Variables also accept a *step* keyword argument. Its value is used as the initial step in matching, and in the {py:meth}`~.variables.Variable.step_up` and {py:meth}`~.variables.Variable.step_down` methods." + ] + }, + { + "cell_type": "markdown", + "id": "7ee77711-5ae6-4e9c-8fa6-78dcbf8d21ca", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## {py:class}`.RefptsVariable`\n", + "\n", + "An {py:class}`.RefptsVariable` is similar to an {py:class}`~.element_variables.ElementVariable` but it is not associated with an {py:class}`~.Element`\n", + "itself, but with its location in a Lattice. So it will act on any lattice with the same elements.\n", + "\n", + "But it needs a *ring* keyword in its *set* and *get* methods, to identify the selected lattice." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "8cd07413-331c-4b93-b160-cb896c23cc1e", + "metadata": {}, + "outputs": [], + "source": [ + "kf2 = RefptsVariable(\"QF1[AE]\", \"PolynomB\", index=1, name=\"kf2\")" + ] + }, + { + "cell_type": "markdown", + "id": "fa01f10a-02d5-49f8-b00d-92651227db35", + "metadata": {}, + "source": [ + "We can now use this variable on the two rings:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "8f7b53ee-1d3a-4591-afa5-ec11fefcef8a", + "metadata": {}, + "outputs": [], + "source": [ + "kf2.set(2.55, ring=ring)\n", + "kf2.set(2.45, ring=newring)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a2c16507-5153-4ff0-a27c-5a292c3b78f4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ring: 2.55\n", + "newring: 2.45\n" + ] + } + ], + "source": [ + "print(f\"ring: {ring[5].PolynomB[1]}\")\n", + "print(f\"newring: {newring[5].PolynomB[1]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "204d24e6-1e9b-4838-a950-3a8e1df5cac4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Custom variables" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/p/variables_parameters.md b/docs/p/variables_parameters.md new file mode 100644 index 000000000..c90b76bac --- /dev/null +++ b/docs/p/variables_parameters.md @@ -0,0 +1,51 @@ +# Variables and Parameters +Variables and parameters provide a unified way of varying any scalar quantity affecting +a lattice. They may be used in parameter scans, matching, response matrices… + +````{grid} 1 1 1 1 +:gutter: 2 +```{grid-item-card} Variables +:shadow: md + +{py:doc}`notebooks/variables` are **references** to any scalar quantity. AT includes two predefined +variable classes referring to scalar attributes of lattice elements: +- an {py:class}`~.element_variables.ElementVariable` is associated to an element object, and acts on + all occurences of this object. But it will not affect any copy, neither shallow + nor deep, of the original object, +- a {py:class}`.RefptsVariable` is not associated to an element object, but to an + element location in a {py:class}`.Lattice`. It acts on any copy of the initial + lattice. A *ring* argument must be provided to the *set* and *get* methods to + identify the lattice. + +Variable referring to other quantities may be created by: +- deriving the {py:class}`~.variables.Variable` base class. Usually this consist in + overloading the abstract methods *_setfun* and *_getfun* +- Using the {py:class}`.CustomVariable` class. +``` +```{grid-item-card} Parameters + +{py:doc}`notebooks/parameters` are objects of class {py:class}`.Param` which can be used instead of numeric +values as {py:class}`.Element` attributes. + +Arithmetic combinations of parameters create new read-only parameters of class +{py:class}`.ParamBase`, whose value is permanently kept up-to-date. This is useful to +introduce correlation between attributes of different elements. +``` +```` + +Variables and parameters share a common interface inherited from the +{py:class}`~.variables.Variable` abstract base class. This includes the +{py:meth}`~.variables.Variable.get` and {py:meth}`~.variables.Variable.set` methods +and the {py:attr}`~.variables.Variable.value` property. + +Variables and parameters can be grouped in {py:class}`~.variables.VariableList` +containers providing the vectorised equivalent {py:meth}`~.variables.VariableList.get` +and {py:meth}`~.variables.VariableList.set` methods. + +```{toctree} +:maxdepth: 2 +:hidden: + +notebooks/variables.ipynb +notebooks/parameters.ipynb +``` \ No newline at end of file diff --git a/pyat/at/future.py b/pyat/at/future.py new file mode 100644 index 000000000..8af008778 --- /dev/null +++ b/pyat/at/future.py @@ -0,0 +1,2 @@ +from .lattice.variables import * +from .lattice.element_variables import * diff --git a/pyat/at/lattice/__init__.py b/pyat/at/lattice/__init__.py index 31c123c5a..475f7e120 100644 --- a/pyat/at/lattice/__init__.py +++ b/pyat/at/lattice/__init__.py @@ -9,11 +9,14 @@ from .axisdef import * from .options import DConstant, random from .particle_object import Particle +# from .variables import * +from .variables import ParamBase, Param, ParamArray, VariableList from .elements import * from .rectangular_bend import * from .idtable_element import InsertionDeviceKickMap from .utils import * from .lattice_object import * +# from .element_variables import * from .cavity_access import * from .variable_elements import * from .deprecated import * diff --git a/pyat/at/lattice/axisdef.py b/pyat/at/lattice/axisdef.py index ae23141db..5a3b2c983 100644 --- a/pyat/at/lattice/axisdef.py +++ b/pyat/at/lattice/axisdef.py @@ -1,6 +1,7 @@ """Helper functions for axis and plane descriptions""" from __future__ import annotations from typing import Optional, Union + # For sys.version_info.minor < 9: from typing import Tuple @@ -16,31 +17,31 @@ ct=dict(index=5, label=r"$\beta c \tau$", unit=" [m]"), ) for xk, xv in [it for it in _axis_def.items()]: - xv['code'] = xk - _axis_def[xv['index']] = xv + xv["code"] = xk + _axis_def[xv["index"]] = xv _axis_def[xk.upper()] = xv -_axis_def['delta'] = _axis_def['dp'] -_axis_def['xp'] = _axis_def['px'] # For backward compatibility -_axis_def['yp'] = _axis_def['py'] # For backward compatibility -_axis_def['s'] = _axis_def['ct'] -_axis_def['S'] = _axis_def['ct'] -_axis_def[None] = dict(index=slice(None), label="", unit="", code=":") +_axis_def["delta"] = _axis_def["dp"] +_axis_def["xp"] = _axis_def["px"] # For backward compatibility +_axis_def["yp"] = _axis_def["py"] # For backward compatibility +_axis_def["s"] = _axis_def["ct"] +_axis_def["S"] = _axis_def["ct"] +_axis_def[None] = dict(index=None, label="", unit="", code=":") _axis_def[Ellipsis] = dict(index=Ellipsis, label="", unit="", code="...") _plane_def = dict( x=dict(index=0, label="x", unit=" [m]"), y=dict(index=1, label="y", unit=" [m]"), - z=dict(index=2, label="z", unit="") + z=dict(index=2, label="z", unit=""), ) for xk, xv in [it for it in _plane_def.items()]: - xv['code'] = xk - _plane_def[xv['index']] = xv + xv["code"] = xk + _plane_def[xv["index"]] = xv _plane_def[xk.upper()] = xv -_plane_def['h'] = _plane_def['x'] -_plane_def['v'] = _plane_def['y'] -_plane_def['H'] = _plane_def['x'] -_plane_def['V'] = _plane_def['y'] -_plane_def[None] = dict(index=slice(None), label="", unit="", code=":") +_plane_def["h"] = _plane_def["x"] +_plane_def["v"] = _plane_def["y"] +_plane_def["H"] = _plane_def["x"] +_plane_def["V"] = _plane_def["y"] +_plane_def[None] = dict(index=None, label="", unit="", code=":") _plane_def[Ellipsis] = dict(index=Ellipsis, label="", unit="", code="...") diff --git a/pyat/at/lattice/element_variables.py b/pyat/at/lattice/element_variables.py new file mode 100644 index 000000000..bbc9c9414 --- /dev/null +++ b/pyat/at/lattice/element_variables.py @@ -0,0 +1,135 @@ +"""Variables are **references** to scalar attributes of lattice elements. There are 2 +kinds of element variables: + +- an :py:class:`ElementVariable` is associated to an element object, and acts on all + occurences of this object. But it will not affect any copy, neither shallow nor deep, + of the original object, +- a :py:class:`RefptsVariable` is not associated to an element object, but to an element + location in a :py:class:`.Lattice`. It acts on any copy of the initial lattice. A + *ring* argument must be provided to the *set* and *get* methods to identify the + lattice, which may be a possibly modified copy of the original lattice +""" +from __future__ import annotations +from collections.abc import Sequence +from typing import Union, Optional +import numpy as np +from .utils import Refpts, getval, setval +from .elements import Element +from .lattice_object import Lattice +from .variables import Variable + +__all__ = ["RefptsVariable", "ElementVariable"] + + +class RefptsVariable(Variable): + r"""A reference to a scalar attribute of :py:class:`.Lattice` elements. + + It can refer to: + + * a scalar attribute or + * an element of an array attribute + + of one or several :py:class:`.Element`\ s of a lattice. + + A :py:class:`RefptsVariable` is not associated to element objets themselves, but + to the location of these elements in a lattice. So a :py:class:`RefptsVariable` + will act equally on any copy of the initial ring. + As a consequence, a *ring* keyword argument (:py:class:`.Lattice` object) must be + supplied for getting or setting the variable. + """ + + def __init__( + self, refpts: Refpts, attrname: str, index: Optional[int] = None, **kwargs + ): + r""" + Parameters: + refpts: Location of variable :py:class:`.Element`\ s + attrname: Attribute name + index: Index in the attribute array. Use :py:obj:`None` for + scalar attributes + + Keyword Args: + name (str): Name of the Variable. Default: ``''`` + bounds (tuple[float, float]): Lower and upper bounds of the + variable value. Default: (-inf, inf) + delta (float): Step. Default: 1.0 + """ + self._getf = getval(attrname, index=index) + self._setf = setval(attrname, index=index) + self.refpts = refpts + super().__init__(**kwargs) + + def _setfun(self, value: float, ring: Lattice = None): + if ring is None: + raise ValueError("Can't set values if ring is None") + for elem in ring.select(self.refpts): + self._setf(elem, value) + + def _getfun(self, ring: Lattice = None) -> float: + if ring is None: + raise ValueError("Can't get values if ring is None") + values = np.array([self._getf(elem) for elem in ring.select(self.refpts)]) + return np.average(values) + + +class ElementVariable(Variable): + r"""A reference to a scalar attribute of :py:class:`.Lattice` elements. + + It can refer to: + + * a scalar attribute or + * an element of an array attribute + + of one or several :py:class:`.Element`\ s of a lattice. + + An :py:class:`ElementVariable` is associated to an element object, and acts on all + occurrences of this object. But it will not affect any copy, neither shallow nor + deep, of the original object. + """ + + def __init__( + self, + elements: Union[Element, Sequence[Element]], + attrname: str, + index: Optional[int] = None, + **kwargs, + ): + r""" + Parameters: + elements: :py:class:`.Element` or Sequence[Element] whose + attribute is varied + attrname: Attribute name + index: Index in the attribute array. Use :py:obj:`None` for + scalar attributes + + Keyword Args: + name (str): Name of the Variable. Default: ``''`` + bounds (tuple[float, float]): Lower and upper bounds of the + variable value. Default: (-inf, inf) + delta (float): Step. Default: 1.0 + """ + # Ensure the uniqueness of elements + if isinstance(elements, Element): + self._elements = {elements} + else: + self._elements = set(elements) + # Check that the attribute is not already a parameter + if any(el.is_parametrised(attrname, index=index) for el in self._elements): + raise TypeError(f"{attrname} attribute is a Variable") + self._getf = getval(attrname, index=index) + self._setf = setval(attrname, index=index) + super().__init__(**kwargs) + self._history.append(self._getfun()) + + def _setfun(self, value: float, **kwargs): + for elem in self._elements: + self._setf(elem, value) + + def _getfun(self, **kwargs) -> float: + values = np.array([self._getf(elem) for elem in self._elements]) + return np.average(values) + + @property + def elements(self): + """Return the set of elements acted upon by the variable""" + return self._elements diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 5d242de3f..5caf060cc 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -12,21 +12,52 @@ from copy import copy, deepcopy from abc import ABC from collections.abc import Generator, Iterable -from typing import Any, Optional +from typing import Union, Optional +from warnings import warn +from .variables import Param, ParamBase, ParamArray +# noinspection PyProtectedMember +from .variables import _nop + + +class WarningArray(numpy.ndarray): + """subclass of ndarray which warns when setting a Param item""" + def __setitem__(self, key, value): + if isinstance(value, ParamBase): + message = f"\n\nThe Parameter '{value.name}' is ignored, instead " \ + f"its value '{value.value}' is used." \ + "\nTo set a parameter in an array, you must first " \ + "parametrise the array itself.\n" + warn(UserWarning(message)) + super().__setitem__(key, value) + + def __repr__(self): + # Simulate a standard ndarray + return repr(self.view(numpy.ndarray)) def _array(value, shape=(-1,), dtype=numpy.float64): # Ensure proper ordering(F) and alignment(A) for "C" access in integrators return numpy.require(value, dtype=dtype, requirements=['F', 'A']).reshape( - shape, order='F') + shape, order='F').view(WarningArray) def _array66(value): return _array(value, shape=(6, 6)) -def _nop(value): - return value +def _float(value): + return float(value) + + +def _int(value): + return int(value) + + +def _array_type(value): + if isinstance(value, ParamBase): + return ParamArray + else: + return numpy.array class LongtMotion(ABC): @@ -240,7 +271,7 @@ class Element(object): """Base class for AT elements""" _BUILD_ATTRIBUTES = ['FamName'] - _conversions = dict(FamName=str, PassMethod=str, Length=float, + _conversions = dict(FamName=str, PassMethod=str, Length=_float, R1=_array66, R2=_array66, T1=lambda v: _array(v, (6,)), T2=lambda v: _array(v, (6,)), @@ -248,9 +279,9 @@ class Element(object): EApertures=lambda v: _array(v, (2,)), KickAngle=lambda v: _array(v, (2,)), PolynomB=_array, PolynomA=_array, - BendingAngle=float, - MaxOrder=int, NumIntSteps=int, - Energy=float, + BendingAngle=_float, + MaxOrder=_int, NumIntSteps=_int, + Energy=_float, ) _entrance_fields = ['T1', 'R1'] @@ -272,27 +303,38 @@ def __init__(self, family_name: str, **kwargs): def __setattr__(self, key, value): try: - super(Element, self).__setattr__( - key, self._conversions.get(key, _nop)(value)) + if isinstance(value, (ParamBase, ParamArray)): + value.set_dtype(self._conversions.get(key, _nop)) + else: + value = self._conversions.get(key, _nop)(value) + super(Element, self).__setattr__(key, value) except Exception as exc: exc.args = ('In element {0}, parameter {1}: {2}'.format( self.FamName, key, exc),) raise + + def __getattribute__(self, key): + attr = super(Element, self).__getattribute__(key) + if isinstance(attr, (ParamBase, ParamArray)): + return attr.value + else: + return attr def __str__(self): first3 = ['FamName', 'Length', 'PassMethod'] + # Get values and parameter objects attrs = dict(self.items()) - keywords = ['\t{0} : {1!s}'.format(k, attrs.pop(k)) for k in first3] - keywords += ['\t{0} : {1!s}'.format(k, v) for k, v in attrs.items()] + keywords = [f"\t{k} : {attrs.pop(k)!s}" for k in first3] + keywords += [f"\t{k} : {v!s}" for k, v in attrs.items()] return '\n'.join((type(self).__name__ + ':', '\n'.join(keywords))) def __repr__(self): - attrs = dict(self.items()) - arguments = [attrs.pop(k, getattr(self, k)) for k in - self._BUILD_ATTRIBUTES] + # Get values only, even for parameters + attrs = dict((k, getattr(self, k)) for k, v in self.items()) + arguments = [attrs.pop(k) for k in self._BUILD_ATTRIBUTES] defelem = self.__class__(*arguments) - keywords = ['{0!r}'.format(arg) for arg in arguments] - keywords += ['{0}={1!r}'.format(k, v) for k, v in sorted(attrs.items()) + keywords = [f"{v!r}" for v in arguments] + keywords += [f"{k}={v!r}" for k, v in sorted(attrs.items()) if not numpy.array_equal(v, getattr(defelem, k, None))] args = re.sub(r'\n\s*', ' ', ', '.join(keywords)) return '{0}({1})'.format(self.__class__.__name__, args) @@ -371,10 +413,10 @@ def deepcopy(self) -> Element: """Return a deep copy of the element""" return deepcopy(self) - def items(self) -> Generator[tuple[str, Any], None, None]: + def items(self) -> Generator[tuple, None, None]: """Iterates through the data members""" - for k, v in vars(self).items(): - yield k, v + # Properties may be added by overloading this method + yield from vars(self).items() def is_compatible(self, other: Element) -> bool: """Checks if another :py:class:`Element` can be merged""" @@ -405,6 +447,137 @@ def is_collective(self) -> bool: """:py:obj:`True` if the element involves collective effects""" return self._get_collective() + def evaluate(self): + attrs = dict(self.items()) + for k, v in attrs.items(): + if isinstance(v, (ParamBase, ParamArray)): + setattr(self, k, v.value) + + def set_parameter(self, attrname: str, value, index: int = None) -> None: + """Set an element's parameter + + This allows setting a parameter into an item of an array attribute. + + Args: + attrname: Attribute name + value: Parameter or value to be set + index: Index into an array attribute. If *value* is a + parameter, the attribute is converted to a + :py:class:`.ParamArray`. + """ + if index is None: + setattr(self, attrname, value) + else: + attr = self.__dict__[attrname] + if isinstance(value, ParamBase) and not isinstance(attr, ParamArray): + # replace the numpy array with a ParamArray + attr = ParamArray(attr) + setattr(self, attrname, attr) + attr[index] = value + + def _get_parameter(self, attrname: str, index: Optional[int] = None): + attr = self.__dict__[attrname] + if index is not None: + attr = attr[index] + return attr + + def get_parameter(self, attrname: str, index: Optional[int] = None): + """Extract a parameter of an element + + Unlike :py:func:`getattr`, :py:func:`get_parameter` returns the + parameter itself instead of its value. It the item is not a parameter, + both functions are equivalent, the value is returned. Properties cannot + be accessed, one must use the associated array item. + + Args: + attrname: Attribute name + index: Index in an array attribute. If :py:obj:`None`, the + whole attribute is set + """ + attr = self._get_parameter(attrname, index=index) + if not isinstance(attr, (ParamBase, ParamArray)): + message = f"\n\n{self.FamName}.{attrname} is not a parameter.\n" + # warn(AtWarning(message)) + raise TypeError(message) + return attr + + def is_parametrised(self, attrname: Optional[str] = None, + index: Optional[int] = None) -> bool: + """Check for the parametrisation of an element + + Args: + attrname: Attribute name. If :py:obj:`None`, return :py:obj:`True` + if any attribute is parametrized + index: Index in an array attribute. If :py:obj:`None`, the + whole attribute is tested for parametrisation + """ + if attrname is None: + for attr in self.__dict__.values(): + if isinstance(attr, (ParamBase, ParamArray)): + return True + return False + else: + attr = self._get_parameter(attrname, index=index) + return isinstance(attr, (ParamBase, ParamArray)) + + def parametrise(self, attrname: str, index: Optional[int] = None, + name: str = '') -> Union[Param, ParamArray]: + """Convert an attribute into a parameter + + The value of the attribute is kept unchanged. If the attribute is + already parametrised, the existing parameter is returned. + + Args: + attrname: Attribute name + index: Index in an array. If :py:obj:`None`, the + whole attribute is parametrised + name: Name of the created parameter + + Returns: + param: A :py:class:`.ParamArray` for an array attribute, + a :py:class:`.Param` for a scalar attribute or an item in an + array attribute + + """ + vini = self._get_parameter(attrname, index=index) + + if isinstance(vini, (ParamBase, ParamArray)): + return vini + + if isinstance(vini, numpy.ndarray): + attr = ParamArray(vini) + else: + attr = Param(vini, name=name) + + if index is None: + setattr(self, attrname, attr) + else: + varr = self.parametrise(attrname) + varr[index] = attr + return attr + + def unparametrise(self, attrname: Optional[str] = None, + index: Optional[int] = None) -> None: + """Freeze the parameter values + + Args: + attrname: Attribute name. If :py:obj:`None`, all the attributes + are frozen + index: Index in an array. If :py:obj:`None`, the whole + attribute is frozen + """ + if attrname is None: + for key, attr in self.__dict__.items(): + if isinstance(attr, (ParamBase, ParamArray)): + setattr(self, key, attr.value) + else: + attr = self._get_parameter(attrname, index=index) + if isinstance(attr, (ParamBase, ParamArray)): + if index is None: + setattr(self, attrname, attr.value) + else: + self._get_parameter(attrname)[index] = attr.value + class LongElement(Element): """Base class for long elements @@ -454,22 +627,8 @@ def popattr(element, attr): return element_list def is_compatible(self, other) -> bool: - def compatible_field(fieldname): - f1 = getattr(self, fieldname, None) - f2 = getattr(other, fieldname, None) - if f1 is None and f2 is None: # no such field - return True - elif f1 is None or f2 is None: # only one - return False - else: # both - return numpy.all(f1 == f2) - - if not (type(other) is type(self) and self.PassMethod == other.PassMethod): - return False - for fname in ("RApertures", "EApertures"): - if not compatible_field(fname): - return False - return True + return type(other) is type(self) and \ + self.PassMethod == other.PassMethod def merge(self, other) -> None: super().merge(other) @@ -488,12 +647,6 @@ class BeamMoments(Element): """Element to compute bunches mean and std""" def __init__(self, family_name: str, **kwargs): - """ - Args: - family_name: Name of the element - - Default PassMethod: ``BeamMomentsPass`` - """ kwargs.setdefault('PassMethod', 'BeamMomentsPass') self._stds = numpy.zeros((6, 1, 1), order='F') self._means = numpy.zeros((6, 1, 1), order='F') @@ -505,118 +658,13 @@ def set_buffers(self, nturns, nbunch): @property def stds(self): - """Beam 6d standard deviation""" return self._stds @property def means(self): - """Beam 6d center of mass""" return self._means -class SliceMoments(Element): - """Element to compute slices mean and std""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['nslice'] - _conversions = dict(Element._conversions, nslice=int) - - def __init__(self, family_name: str, nslice: int, **kwargs): - """ - Args: - family_name: Name of the element - nslice: Number of slices - - Keyword arguments: - startturn: Start turn of the acquisition (Default 0) - endturn: End turn of the acquisition (Default 1) - - Default PassMethod: ``SliceMomentsPass`` - """ - kwargs.setdefault('PassMethod', 'SliceMomentsPass') - self._startturn = kwargs.pop('startturn', 0) - self._endturn = kwargs.pop('endturn', 1) - super(SliceMoments, self).__init__(family_name, nslice=nslice, - **kwargs) - self._nbunch = 1 - self.startturn = self._startturn - self.endturn = self._endturn - self._dturns = self.endturn - self.startturn - self._stds = numpy.zeros((3, nslice, self._dturns), order='F') - self._means = numpy.zeros((3, nslice, self._dturns), order='F') - self._spos = numpy.zeros((nslice, self._dturns), order='F') - self._weights = numpy.zeros((nslice, self._dturns), order='F') - self.set_buffers(self._endturn, 1) - - def set_buffers(self, nturns, nbunch): - self.endturn = min(self.endturn, nturns) - self._dturns = self.endturn - self.startturn - self._nbunch = nbunch - self._stds = numpy.zeros((3, nbunch*self.nslice, self._dturns), - order='F') - self._means = numpy.zeros((3, nbunch*self.nslice, self._dturns), - order='F') - self._spos = numpy.zeros((nbunch*self.nslice, self._dturns), - order='F') - self._weights = numpy.zeros((nbunch*self.nslice, self._dturns), - order='F') - - @property - def stds(self): - """Slices x,y,dp standard deviation""" - return self._stds.reshape((3, self._nbunch, - self.nslice, - self._dturns)) - - @property - def means(self): - """Slices x,y,dp center of mass""" - return self._means.reshape((3, self._nbunch, - self.nslice, - self._dturns)) - - @property - def spos(self): - """Slices s position""" - return self._spos.reshape((self._nbunch, - self.nslice, - self._dturns)) - - @property - def weights(self): - """Slices weights in mA if beam current >0, - otherwise fraction of total number of - particles in the bunch - """ - return self._weights.reshape((self._nbunch, - self.nslice, - self._dturns)) - - @property - def startturn(self): - """Start turn of the acquisition""" - return self._startturn - - @startturn.setter - def startturn(self, value): - if value < 0: - raise ValueError('startturn must be greater or equal to 0') - if value >= self._endturn: - raise ValueError('startturn must be smaller than endturn') - self._startturn = value - - @property - def endturn(self): - """End turn of the acquisition""" - return self._endturn - - @endturn.setter - def endturn(self, value): - if value <= 0: - raise ValueError('endturn must be greater than 0') - if value <= self._startturn: - raise ValueError('endturn must be greater than startturn') - self._endturn = value - - class Aperture(Element): """Aperture element""" _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['Limits'] @@ -722,8 +770,8 @@ def __init__(self, family_name: str, poly_a, poly_b, **kwargs): """ Args: family_name: Name of the element - poly_a: Array of skew multipole components - poly_b: Array of normal multipole components + poly_a: Array of normal multipole components + poly_b: Array of skew multipole components Keyword arguments: MaxOrder: Number of desired multipoles. Default: highest @@ -745,8 +793,10 @@ def lengthen(poly, dl): return poly # Remove MaxOrder, PolynomA and PolynomB - poly_a, len_a, ord_a = getpol(_array(kwargs.pop('PolynomA', poly_a))) - poly_b, len_b, ord_b = getpol(_array(kwargs.pop('PolynomB', poly_b))) + ipola = kwargs.pop("PolynomA", poly_a) + ipolb = kwargs.pop("PolynomB", poly_b) + poly_a, len_a, ord_a = getpol(_array(ipola)) + poly_b, len_b, ord_b = getpol(_array(ipolb)) deforder = max(getattr(self, 'DefaultOrder', 0), ord_a, ord_b) maxorder = kwargs.pop('MaxOrder', deforder) kwargs.setdefault('PassMethod', 'ThinMPolePass') @@ -757,23 +807,27 @@ def lengthen(poly, dl): len_ab = max(self.MaxOrder + 1, len_a, len_b) self.PolynomA = lengthen(poly_a, len_ab - len_a) self.PolynomB = lengthen(poly_b, len_ab - len_b) + if isinstance(ipola, ParamArray): + lista = ipola[:] + list(self.PolynomA)[len(ipola):] + self.PolynomA = ParamArray(lista) + if isinstance(ipolb, ParamArray): + listb = ipolb[:] + list(self.PolynomB)[len(ipolb):] + self.PolynomB = ParamArray(listb) def __setattr__(self, key, value): """Check the compatibility of MaxOrder, PolynomA and PolynomB""" polys = ('PolynomA', 'PolynomB') if key in polys: - value = _array(value) lmin = getattr(self, 'MaxOrder') if not len(value) > lmin: raise ValueError( 'Length of {0} must be larger than {1}'.format(key, lmin)) elif key == 'MaxOrder': - value = int(value) + intval = int(value) lmax = min(len(getattr(self, k)) for k in polys) - if not value < lmax: + if not intval < lmax: raise ValueError( 'MaxOrder must be smaller than {0}'.format(lmax)) - super(ThinMultipole, self).__setattr__(key, value) @@ -789,8 +843,8 @@ def __init__(self, family_name: str, length: float, poly_a, poly_b, Args: family_name: Name of the element length: Element length [m] - poly_a: Array of skew multipole components - poly_b: Array of normal multipole components + poly_a: Array of normal multipole components + poly_b: Array of skew multipole components Keyword arguments: MaxOrder: Number of desired multipoles. Default: highest @@ -823,23 +877,25 @@ def is_compatible(self, other) -> bool: @property def K(self) -> float: """Focusing strength [mˆ-2]""" - return 0.0 if len(self.PolynomB) < 2 else self.PolynomB[1] + arr = self.PolynomB + return 0.0 if len(arr) < 2 else arr[1] # noinspection PyPep8Naming @K.setter def K(self, strength: float): - self.PolynomB[1] = strength + self.set_parameter('PolynomB', strength, index=1) # noinspection PyPep8Naming @property def H(self) -> float: """Sextupolar strength [mˆ-3]""" - return 0.0 if len(self.PolynomB) < 3 else self.PolynomB[2] + arr = self.PolynomB + return 0.0 if len(arr) < 3 else arr[2] # noinspection PyPep8Naming @H.setter def H(self, strength): - self.PolynomB[2] = strength + self.set_parameter('PolynomB', strength, index=2) class Dipole(Radiative, Multipole): @@ -871,14 +927,16 @@ def __init__(self, family_name: str, length: float, family_name: Name of the element length: Element length [m] bending_angle: Bending angle [rd] - k: Focusing strength [m^-2] + poly_a: Array of normal multipole components + poly_b: Array of skew multipole components + k=0: Field index Keyword arguments: EntranceAngle=0.0: entrance angle ExitAngle=0.0: exit angle PolynomB: straight multipoles PolynomA: skew multipoles - MaxOrder=0: Number of desired multipoles + MaxOrder: Number of desired multipoles NumIntSt=10: Number of integration steps FullGap: Magnet full gap FringeInt1: Extension of the entrance fringe field @@ -900,10 +958,9 @@ def __init__(self, family_name: str, length: float, KickAngle: Correction deviation angles (H, V) FieldScaling: Scaling factor applied to the magnetic field - Available PassMethods: :ref:`BndMPoleSymplectic4Pass`, - :ref:`BendLinearPass`, :ref:`ExactSectorBendPass`, - :ref:`ExactRectangularBendPass`, :ref:`ExactRectBendPass`, - BndStrMPoleSymplectic4Pass + Available PassMethods: :ref:`BndMPoleSymplectic4Pass`, :ref:`BendLinearPass`, + :ref:`ExactSectorBendPass`, :ref:`ExactRectangularBendPass`, + :ref:`ExactRectBendPass`, BndStrMPoleSymplectic4Pass Default PassMethod: :ref:`BndMPoleSymplectic4Pass` """ @@ -916,7 +973,7 @@ def __init__(self, family_name: str, length: float, def items(self) -> Generator[tuple, None, None]: yield from super().items() - yield 'K', self.K + yield 'K', vars(self)["PolynomB"][1] def _part(self, fr, sumfr): pp = super(Dipole, self)._part(fr, sumfr) @@ -962,12 +1019,12 @@ def __init__(self, family_name: str, length: float, Args: family_name: Name of the element length: Element length [m] - k: Focusing strength [mˆ-2] + k: strength [mˆ-2] Keyword Arguments: PolynomB: straight multipoles PolynomA: skew multipoles - MaxOrder=1: Number of desired multipoles + MaxOrder: Number of desired multipoles NumIntSteps=10: Number of integration steps FringeQuadEntrance: 0: no fringe field effect (default) @@ -983,14 +1040,15 @@ def __init__(self, family_name: str, length: float, Default PassMethod: ``StrMPoleSymplectic4Pass`` """ - poly_b = kwargs.pop('PolynomB', numpy.array([0, k])) - kwargs.setdefault('PassMethod', 'StrMPoleSymplectic4Pass') + poly_type = _array_type(k) + poly_b = kwargs.pop("PolynomB", poly_type([0.0, k])) + kwargs.setdefault("PassMethod", "StrMPoleSymplectic4Pass") super(Quadrupole, self).__init__(family_name, length, [], poly_b, **kwargs) def items(self) -> Generator[tuple, None, None]: yield from super().items() - yield 'K', self.K + yield 'K', vars(self)["PolynomB"][1] class Sextupole(Multipole): @@ -1018,11 +1076,16 @@ def __init__(self, family_name: str, length: float, Default PassMethod: ``StrMPoleSymplectic4Pass`` """ - poly_b = kwargs.pop('PolynomB', [0, 0, h]) - kwargs.setdefault('PassMethod', 'StrMPoleSymplectic4Pass') + poly_type = _array_type(h) + poly_b = kwargs.pop("PolynomB", poly_type([0.0, 0.0, h])) + kwargs.setdefault("PassMethod", "StrMPoleSymplectic4Pass") super(Sextupole, self).__init__(family_name, length, [], poly_b, **kwargs) + def items(self) -> Generator[tuple, None, None]: + yield from super().items() + yield 'H', vars(self)["PolynomB"][2] + class Octupole(Multipole): """Octupole element, with no changes from multipole at present""" @@ -1094,7 +1157,7 @@ def set_longt_motion(self, enable, new_pass=None, **kwargs): class M66(Element): """Linear (6, 6) transfer matrix""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ["M66"] + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES _conversions = dict(Element._conversions, M66=_array66) def __init__(self, family_name: str, m66=None, **kwargs): @@ -1108,17 +1171,13 @@ def __init__(self, family_name: str, m66=None, **kwargs): if m66 is None: m66 = numpy.identity(6) kwargs.setdefault('PassMethod', 'Matrix66Pass') - kwargs.setdefault("M66", m66) - super(M66, self).__init__(family_name, **kwargs) + super(M66, self).__init__(family_name, M66=m66, **kwargs) class SimpleQuantDiff(_DictLongtMotion, Element): """ Linear tracking element for a simplified quantum diffusion, - radiation damping and energy loss. - - Note: The damping times are needed to compute the correct - kick for the emittance. Radiation damping is NOT applied. + radiation damping and energy loss """ _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES default_pass = {False: 'IdentityPass', True: 'SimpleQuantDiffPass'} @@ -1127,7 +1186,7 @@ def __init__(self, family_name: str, betax: float = 1.0, betay: float = 1.0, emitx: float = 0.0, emity: float = 0.0, espread: float = 0.0, taux: float = 0.0, tauy: float = 0.0, - tauz: float = 0.0, + tauz: float = 0.0, U0: float = 0.0, **kwargs): """ Args: @@ -1142,6 +1201,7 @@ def __init__(self, family_name: str, betax: float = 1.0, taux: Horizontal damping time [turns] tauy: Vertical damping time [turns] tauz: Longitudinal damping time [turns] + U0: Energy Loss [eV] Default PassMethod: ``SimpleQuantDiffPass`` """ @@ -1171,61 +1231,12 @@ def __init__(self, family_name: str, betax: float = 1.0, if espread > 0.0: assert tauz > 0.0, 'if espread is given, tauz must be non zero' + self.U0 = U0 self.betax = betax self.betay = betay super(SimpleQuantDiff, self).__init__(family_name, **kwargs) -class SimpleRadiation(_DictLongtMotion, Radiative, Element): - """Simple radiation damping and energy loss""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES - _conversions = dict(Element._conversions, U0=float, - damp_mat_diag=lambda v: _array(v, shape=(6,))) - - default_pass = {False: 'IdentityPass', True: 'SimpleRadiationPass'} - - def __init__(self, family_name: str, - taux: float = 0.0, tauy: float = 0.0, - tauz: float = 0.0, U0: float = 0.0, - **kwargs): - """ - Args: - family_name: Name of the element - - Optional Args: - taux: Horizontal damping time [turns] - tauy: Vertical damping time [turns] - tauz: Longitudinal damping time [turns] - U0: Energy loss per turn [eV] - - Default PassMethod: ``SimpleRadiationPass`` - """ - assert taux >= 0.0, 'taux must be greater than or equal to 0' - if taux == 0.0: - dampx = 1 - else: - dampx = numpy.exp(-1/taux) - - assert tauy >= 0.0, 'tauy must be greater than or equal to 0' - if tauy == 0.0: - dampy = 1 - else: - dampy = numpy.exp(-1/tauy) - - assert tauz >= 0.0, 'tauz must be greater than or equal to 0' - if tauz == 0.0: - dampz = 1 - else: - dampz = numpy.exp(-1/tauz) - - kwargs.setdefault('PassMethod', self.default_pass[True]) - kwargs.setdefault("U0", U0) - kwargs.setdefault("damp_mat_diag", - numpy.array([dampx, dampx, dampy, dampy, dampz, dampz])) - - super(SimpleRadiation, self).__init__(family_name, **kwargs) - - class Corrector(LongElement): """Corrector element""" _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['KickAngle'] diff --git a/pyat/at/lattice/lattice_object.py b/pyat/at/lattice/lattice_object.py index 45b972617..fc98859d5 100644 --- a/pyat/at/lattice/lattice_object.py +++ b/pyat/at/lattice/lattice_object.py @@ -11,12 +11,9 @@ As an example, see the at.physics.orbit module """ from __future__ import annotations - -__all__ = ['Lattice', 'Filter', 'type_filter', 'params_filter', 'lattice_filter', - 'elem_generator', 'no_filter'] - import sys import copy +import numpy import math from typing import Optional, Union if sys.version_info.minor < 9: @@ -26,18 +23,18 @@ from typing import SupportsIndex from collections.abc import Callable, Iterable, Generator from warnings import warn - -import numpy - -from . import elements as elt -from .elements import Element +from ..constants import clight, e_mass from .particle_object import Particle from .utils import AtError, AtWarning, Refpts -from .utils import get_s_pos, get_elements,get_value_refpts, set_value_refpts # noinspection PyProtectedMember from .utils import get_uint32_index, get_bool_index, _refcount, Uint32Refpts -from .utils import refpts_iterator, checktype, set_shift, set_tilt, get_geometry -from ..constants import clight, e_mass +from .utils import refpts_iterator, checktype, getval +from .utils import get_s_pos, get_elements +from .utils import get_value_refpts, set_value_refpts +from .utils import set_shift, set_tilt, get_geometry +from . import elements as elt +from .variables import Param +from .elements import Element _TWO_PI_ERROR = 1.E-4 Filter = Callable[..., Iterable[Element]] @@ -73,7 +70,10 @@ ) } -# Don't warn on floating-point errors +__all__ = ['Lattice', 'type_filter', 'params_filter', 'lattice_filter', + 'elem_generator', 'no_filter'] + +# Don't warn on floating-pont errors numpy.seterr(divide='ignore', invalid='ignore') @@ -97,7 +97,9 @@ class Lattice(list): '_fillpattern') # noinspection PyUnusedLocal - def __init__(self, *args, iterator: Filter = None, scan: bool = False, **kwargs): + def __init__(self, *args, + iterator: Filter = None, + scan: bool = False, **kwargs): """ Lattice(elements, **params) Lattice(filter, [filter, ...,] iterator=iter,**params) @@ -180,7 +182,7 @@ def __init__(self, *args, iterator: Filter = None, scan: bool = False, **kwargs) energy and periodicity if not yet defined. """ if iterator is None: - (arg1,) = args or [[]] # accept 0 or 1 argument + arg1, = args or [[]] # accept 0 or 1 argument if isinstance(arg1, Lattice): elems = lattice_filter(kwargs, arg1) else: @@ -194,21 +196,25 @@ def __init__(self, *args, iterator: Filter = None, scan: bool = False, **kwargs) for attr in self._excluded_attributes: kwargs.pop(attr, None) # set default values - kwargs.setdefault("name", "") - periodicity = kwargs.setdefault("periodicity", 1) - kwargs.setdefault("particle", kwargs.pop("_particle", Particle())) - kwargs.setdefault("beam_current", kwargs.pop("_beam_current", 0.0)) + kwargs.setdefault('name', '') + periodicity = kwargs.setdefault('periodicity', 1) + kwargs.setdefault('_particle', Particle()) # dummy initialization in case the harmonic number is not there - kwargs.setdefault("_fillpattern", numpy.ones(1)) + kwargs.setdefault('_fillpattern', numpy.ones(1)) # Remove temporary keywords - frequency: Optional[float] = kwargs.pop("_frequency", None) - cell_length: Optional[float] = kwargs.pop("_length", None) - cell_h = kwargs.pop("cell_harmnumber", kwargs.pop("_cell_harmnumber", math.nan)) - ring_h = kwargs.pop("harmonic_number", cell_h * periodicity) - - energy = kwargs.setdefault("energy", kwargs.pop("_energy", None)) - if energy is None: - raise AtError("Lattice energy is not defined") + frequency = kwargs.pop('_frequency', None) + cell_length = kwargs.pop('_length', None) + cell_h = kwargs.pop('_harmnumber', math.nan) + ring_h = kwargs.pop('harmonic_number', periodicity*cell_h) + bcurrent = kwargs.pop('beam_current', 0.0) + kwargs.setdefault('_beam_current', bcurrent) + + if 'energy' in kwargs: + kwargs.pop('_energy', None) + elif '_energy' not in kwargs: + raise AtError('Lattice energy is not defined') + if 'particle' in kwargs: + kwargs.pop('_particle', None) # set attributes self.update(kwargs) @@ -218,12 +224,12 @@ def __init__(self, *args, iterator: Filter = None, scan: bool = False, **kwargs) rev = self.beta * clight / cell_length self._cell_harmnumber = int(round(frequency / rev)) try: - fp = kwargs.pop("_fillpattern", numpy.ones(1)) + fp = kwargs.pop('_fillpattern', numpy.ones(1)) self.set_fillpattern(bunches=fp) except AssertionError: self.set_fillpattern() elif not math.isnan(ring_h): - self._cell_harmnumber = ring_h / periodicity + self.harmonic_number = ring_h def __getitem__(self, key): try: # Integer @@ -321,11 +327,11 @@ def insert(self, idx: SupportsIndex, elem: Element, copy_elements=False): super().insert(idx, elem) def extend(self, elems: Iterable[Element], copy_elements=False): + # noinspection PyUnresolvedReferences r"""This method adds all the elements of `elems` to the end of the lattice. The behavior is the same as for a :py:obj:`list` - Equivalent syntaxes: - + Equivalents syntaxes: >>> ring.extend(elems) >>> ring += elems @@ -343,12 +349,12 @@ def extend(self, elems: Iterable[Element], copy_elements=False): super().extend(elems) def append(self, elem: Element, copy_elements=False): + # noinspection PyUnresolvedReferences r"""This method overwrites the inherited method :py:meth:`list.append()`, its behavior is changed, it accepts only AT lattice elements :py:obj:`Element` as input argument. - Equivalent syntaxes: - + Equivalents syntaxes: >>> ring.append(elem) >>> ring += [elem] @@ -360,22 +366,22 @@ def append(self, elem: Element, copy_elements=False): """ self.extend([elem], copy_elements=copy_elements) - def repeat(self, n: int, copy_elements: bool = True): + def repeat(self, n: int, copy_elements=True): + # noinspection SpellCheckingInspection,PyUnresolvedReferences,PyRedeclaration r"""This method allows to repeat the lattice `n` times. If `n` does not divide `ring.periodicity`, the new ring - periodicity is set to 1, otherwise it is set to + periodicity is set to 1, otherwise it is et to `ring.periodicity /= n`. - Equivalent syntaxes: - + Equivalents syntaxes: >>> newring = ring.repeat(n) >>> newring = ring * n Parameters: - n : number of repetitions - copy_elements: If :py:obj:`True`, deep copies of the lattice are used for - the repetition. Otherwise, the original elements are repeated in the - developed lattice. + n (int): number of repetition + copy_elements(bool): Default :py:obj:`True`. + If :py:obj:`True` deepcopies of the + lattice are used for the repetition Returns: newring (Lattice): the new repeated lattice @@ -394,21 +400,22 @@ def copy_fun(elem, copy): warn(AtWarning('Non-integer number of cells: {}/{}. Periodi' 'city set to 1'.format(self.periodicity, n))) periodicity = 1 - hdict = dict(periodicity=periodicity) try: - hdict.update(harmonic_number=self.cell_harmnumber*n*periodicity) + cell_h = self._cell_harmnumber except AttributeError: - pass + hdict = {} + else: + hdict = dict(_cell_harmnumber=n*cell_h) elems = (copy_fun(el, copy_elements) for _ in range(n) for el in self) return Lattice(elem_generator, elems, iterator=self.attrs_filter, - **hdict) + periodicity=periodicity, **hdict) def concatenate(self, *lattices: Iterable[Element], copy_elements=False, copy=False): + # noinspection PyUnresolvedReferences,SpellCheckingInspection,PyRedeclaration """Concatenate several `Iterable[Element]` with the lattice - Equivalent syntaxes: - + Equivalents syntaxes: >>> newring = ring.concatenate(r1, r2, r3, copy=True) >>> newring = ring + r1 + r2 + r3 @@ -439,6 +446,7 @@ def concatenate(self, *lattices: Iterable[Element], return lattice if copy else None def reverse(self, copy=False): + # noinspection PyUnresolvedReferences r"""Reverse the order of the lattice and swapt the faces of elements. Alignment errors are not swapped @@ -461,19 +469,20 @@ def reverse(self, copy=False): reversed_list = list(elems) self[:] = reversed_list - def develop(self, copy_elements: bool = True) -> Lattice: + def develop(self) -> Lattice: """Develop a periodical lattice by repeating its elements *self.periodicity* times - Parameters: - copy_elements: If :py:obj:`True`, deep copies of the elements are used for - the repetition. Otherwise, the original elements are repeated in the - developed lattice. + The elements of the new lattice are deep copies ot the original + elements, so that they are all independent. Returns: newlattice: The developed lattice """ - return self.repeat(self.periodicity, copy_elements=copy_elements) + elist = (el.deepcopy() for _ in range(self.periodicity) for el in self) + return Lattice(elem_generator, elist, + iterator=self.attrs_filter, periodicity=1, + harmonic_number=self.harmonic_number) @property def attrs(self) -> dict: @@ -1322,13 +1331,18 @@ def reduce_filter(_, itelem): def replace(self, refpts: Refpts, **kwargs) -> Lattice: """Return a shallow copy of the lattice replacing the selected - elements by a deep copy + elements by an unparametrised deep copy Parameters: refpts: element selector """ + def newelem(elem): + newelem = elem.deepcopy() + newelem.unparametrise() + return newelem + check = get_bool_index(self, refpts) - elems = (el.deepcopy() if ok else el for el, ok in zip(self, check)) + elems = (newelem(el) if ok else el for el, ok in zip(self, check)) return Lattice(elem_generator, elems, iterator=self.attrs_filter, **kwargs) @@ -1383,6 +1397,61 @@ def radiation(self) -> bool: self._radiation = radiate return radiate + def set_parameter(self, refpts: Refpts, attrname: str, value, + index: Optional[int] = None) -> None: + """Set a parameter as an attribute of the selected elements + + Args: + refpts: Element selector + attrname: Attribute name + value: Parameter or value to be set + index: Index into an array attribute. If *value* is a + parameter, the attribute is converted to a + :py:class:`.ParamArray`. + """ + for elem in self.select(refpts): + elem.set_parameter(attrname, value, index=index) + + def parametrise(self, refpts: Refpts, attrname: str, + index: Optional[int] = None, name: str = '') -> Param: + """Convert an attribute of the selected elements into a parameter + + A single parameter is created and assigned to all the selected + elements. Its initial value is the mean of the original values. + + Args: + refpts: Element selector + attrname: Attribute name + index: Index into an array attribute. If *value* is a + parameter, the attribute is converted to a + :py:class:`.ParamArray`. + name: Name of the created parameter + + Returns: + param: The created parameter + """ + elems = self[refpts] + getf = getval(attrname, index=index) + vals = numpy.array([getf(elem) for elem in elems]) + attr = Param(numpy.mean(vals), name=name) + for elem in elems: + elem.set_parameter(attrname, attr, index=index) + return attr + + def unparametrise(self, refpts, attrname: Optional[str] = None, + index: Optional[int] = None) -> None: + """Freeze the value of attributes of the selected elements + + Args: + refpts: Element selector + attrname: Attribute name. If :py:obj:`None`, all the attributes + are frozen + index: Index in an array. If :py:obj:`None`, the whole + attribute is frozen + """ + for elem in self.select(refpts): + elem.unparametrise(attrname=attrname, index=index) + def lattice_filter(params, lattice): """Copy lattice parameters and run through all lattice elements @@ -1411,10 +1480,11 @@ def elem_generator(params, elems: Iterable[Element]) -> Iterable[Element]: return elems -no_filter: Filter = elem_generator # provided for backward compatibility +no_filter = elem_generator # provided for backward compatibility -def type_filter(params, elems: Iterable[Element]) -> Generator[Element, None, None]: +def type_filter(params, elems: Iterable[Element]) \ + -> Generator[Element, None, None]: """Run through all elements and check element validity. Analyse elements for radiation state @@ -1424,7 +1494,7 @@ def type_filter(params, elems: Iterable[Element]) -> Generator[Element, None, No Yields: lattice ``Elements`` - """ + """ radiate = False for idx, elem in enumerate(elems): if isinstance(elem, Element): @@ -1437,7 +1507,8 @@ def type_filter(params, elems: Iterable[Element]) -> Generator[Element, None, No params['_radiation'] = radiate -def params_filter(params, elem_filter: Filter, *args) -> Generator[Element, None, None]: +def params_filter(params, elem_filter: Filter, *args) \ + -> Generator[Element, None, None]: """Run through all elements, looking for energy and periodicity. Remove the Energy attribute of non-radiating elements diff --git a/pyat/at/lattice/utils.py b/pyat/at/lattice/utils.py index 28037c878..48473a75a 100644 --- a/pyat/at/lattice/utils.py +++ b/pyat/at/lattice/utils.py @@ -37,6 +37,7 @@ from typing import Union, Tuple, List, Type from enum import Enum from itertools import compress +from operator import attrgetter from fnmatch import fnmatch from .elements import Element, Dipole @@ -58,7 +59,7 @@ 'set_shift', 'set_tilt', 'set_rotation', 'tilt_elem', 'shift_elem', 'rotate_elem', 'get_value_refpts', 'set_value_refpts', 'Refpts', - 'get_geometry'] + 'get_geometry', 'setval', 'getval'] _axis_def = dict( x=dict(index=0, label="x", unit=" [m]"), @@ -113,6 +114,72 @@ def _type_error(refpts, types): "Invalid refpts type {0}. Allowed types: {1}".format(tp, types)) +# setval and getval return pickleable functions: no inner, nested function +# are allowed. So nested functions are replaced be module-level callable +# class instances +class _AttrItemGetter(object): + __slots__ = ["attrname", "index"] + + def __init__(self, attrname: str, index: int): + self.attrname = attrname + self.index = index + + def __call__(self, elem): + return getattr(elem, self.attrname)[self.index] + + +def getval(attrname: str, index: Optional[int] = None) -> Callable: + """Return a callable object which fetches item *index* of + attribute *attrname* of its operand. Examples: + + - After ``f = getval('Length')``, ``f(elem)`` returns ``elem.Length`` + - After ``f = getval('PolynomB, index=1)``, ``f(elem)`` returns + ``elem.PolynomB[1]`` + + """ + if index is None: + return attrgetter(attrname) + else: + return _AttrItemGetter(attrname, index) + + +class _AttrSetter(object): + __slots__ = ["attrname"] + + def __init__(self, attrname: str): + self.attrname = attrname + + def __call__(self, elem, value): + setattr(elem, self.attrname, value) + + +class _AttrItemSetter(object): + __slots__ = ["attrname", "index"] + + def __init__(self, attrname: str, index: int): + self.attrname = attrname + self.index = index + + def __call__(self, elem, value): + getattr(elem, self.attrname)[self.index] = value + + +def setval(attrname: str, index: Optional[int] = None) -> Callable: + """Return a callable object which sets the value of item *index* of + attribute *attrname* of its 1st argument to it 2nd orgument. + + - After ``f = setval('Length')``, ``f(elem, value)`` is equivalent to + ``elem.Length = value`` + - After ``f = setval('PolynomB, index=1)``, ``f(elem, value)`` is + equivalent to ``elem.PolynomB[1] = value`` + + """ + if index is None: + return _AttrSetter(attrname) + else: + return _AttrItemSetter(attrname, index) + + # noinspection PyIncorrectDocstring def axis_descr(*args, key=None) -> Tuple: r"""axis_descr(axis [ ,axis], key=None) @@ -774,13 +841,7 @@ def get_value_refpts(ring: Sequence[Element], refpts: Refpts, Returns: attrvalues: numpy Array of attribute values. """ - if index is None: - def getf(elem): - return getattr(elem, attrname) - else: - def getf(elem): - return getattr(elem, attrname)[index] - + getf = getval(attrname, index=index) return numpy.array([getf(elem) for elem in refpts_iterator(ring, refpts, regex=regex)]) @@ -817,13 +878,7 @@ def set_value_refpts(ring: Sequence[Element], refpts: Refpts, elements are shared with the original lattice. Any further modification will affect both lattices. """ - if index is None: - def setf(elem, value): - setattr(elem, attrname, value) - else: - def setf(elem, value): - getattr(elem, attrname)[index] = value - + setf = setval(attrname, index=index) if increment: attrvalues += get_value_refpts(ring, refpts, attrname, index=index, diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py new file mode 100644 index 000000000..5ca100c1c --- /dev/null +++ b/pyat/at/lattice/variables.py @@ -0,0 +1,622 @@ +r""" +Definition of :py:class:`.Variable` objects used in matching and +response matrices. + +See :ref:`example-notebooks` for examples of matching and response matrices. + +Each :py:class:`Variable` has a scalar value. + +.. rubric:: Class hierarchy + +:py:class:`Variable`\ (name, bounds, delta) + +- :py:class:`.ParamBase`\ (...) + + - :py:class:`.Param`\ (value) +- :py:class:`~.element_variables.ElementVariable`\ (elements, attrname, index, ...) +- :py:class:`~.element_variables.RefptsVariable`\ (refpts, attrname, index, ...) +- :py:class:`CustomVariable`\ (setfun, getfun, ...) + +.. rubric:: Variable methods + +:py:class:`Variable` provides the following methods: + +- :py:meth:`~Variable.get` +- :py:meth:`~Variable.set` +- :py:meth:`~Variable.set_previous` +- :py:meth:`~Variable.set_initial` +- :py:meth:`~Variable.increment` +- :py:meth:`~Variable.step_up` +- :py:meth:`~Variable.step_down` + +.. rubric:: Variable properties + +:py:class:`.Variable` provides the following properties: + +- :py:attr:`~Variable.initial_value` +- :py:attr:`~Variable.last_value` +- :py:attr:`~Variable.previous_value` +- :py:attr:`~Variable.history` + +The :py:class:`Variable` abstract class may be used as a base class to define +custom variables (see examples). Typically, this consist in overloading the abstract +methods *_setfun* and *_getfun* + +.. rubric:: Examples + +Write a subclass of :py:class:`Variable` which varies two drift lengths so +that their sum is constant: + +.. code-block:: python + + class ElementShifter(at.Variable): + '''Varies the length of the elements identified by *ref1* and *ref2* + keeping the sum of their lengths equal to *total_length*. + + If *total_length* is None, it is set to the initial total length + ''' + def __init__(self, drift1, drift2, total_length=None, **kwargs): + # store the 2 variable elements + self.drift1 = drift1 + self.drift2 = drift2 + # store the initial total length + if total_length is None: + total_length = drift1.Length + drift2.Length + self.length = total_length + super().__init__(bounds=(0.0, total_length), **kwargs) + + def _setfun(self, value, **kwargs): + self.drift1.Length = value + self.drift2.Length = self.length - value + + def _getfun(self, **kwargs): + return self.drift1.Length + +And create a variable varying the length of drifts *DR_01* and *DR_01* and +keeping their sum constant: + +.. code-block:: python + + drift1 = hmba_lattice["DR_01"] + drift2 = hmba_lattice["DR_02"] + var2 = ElementShifter(drift1, drift2, name="DR_01") + +""" + +from __future__ import annotations +import numpy as np +import abc +from numbers import Number +from operator import add, sub, mul, truediv, pos, neg +from collections.abc import Iterable, Sequence, Callable + +__all__ = ["Variable", "CustomVariable", "ParamBase", "Param", "ParamArray", + "VariableList"] + + +def _nop(value): + return value + + +def _default_array(value): + return np.require(value, dtype=float, requirements=["F", "A"]) + + +class _Evaluate(abc.ABC): + @abc.abstractmethod + def __call__(self): ... + + +class _Scalar(_Evaluate): + __slots__ = "value" + + def __init__(self, value): + if not isinstance(value, Number): + raise TypeError("'value' must be a Number") + self.value = value + + def __call__(self): + return self.value + + +class _BinaryOp(_Evaluate): + __slots__ = ["oper", "left", "right"] + + @staticmethod + def _set_type(value): + if isinstance(value, Number): + return _Scalar(value) + elif isinstance(value, Variable): + return value + else: + msg = "Param Operation not defined for type {0}".format(type(value)) + raise TypeError(msg) + + def __init__(self, oper, left, right): + self.oper = oper + self.right = self._set_type(right) + self.left = self._set_type(left) + + def __call__(self): + return self.oper(self.left.value, self.right.value) + + +class _UnaryOp(_Evaluate): + __slots__ = ["oper", "param"] + + def __init__(self, oper, param): + self.oper = oper + self.param = param + + def __call__(self): + return self.oper(self.param.value) + + +class Variable(abc.ABC): + """A :py:class:`Variable` abstract base class + + Derived classes must implement the :py:meth:`_getfun` and + :py:meth:`_getfun` methods + """ + + _counter = 0 + _prefix = "var" + + def __init__( + self, + *, + name: str = "", + bounds: tuple[Number, Number] = (-np.inf, np.inf), + delta: Number = 1.0, + ): + """ + Parameters: + name: Name of the Variable + bounds: Lower and upper bounds of the variable value + delta: Initial variation step + """ + self.name = self._setname(name) + self.bounds = bounds + self.delta = delta + self._history = [] + + @classmethod + def _setname(cls, name): + cls._counter += 1 + if name: + return name + else: + return f"{cls._prefix}{cls._counter}" + + # noinspection PyUnusedLocal + def _setfun(self, value: Number, **kwargs): + classname = self.__class__.__name__ + raise TypeError(f"{classname!r} is read-only") + + @abc.abstractmethod + def _getfun(self, **kwargs) -> Number: ... + + @property + def history(self) -> list[Number]: + """History of the values of the variable""" + return self._history + + @property + def initial_value(self) -> Number: + """Initial value of the variable""" + if len(self._history) > 0: + return self._history[0] + else: + raise IndexError(f"{self.name}: No value has been set yet") + + @property + def last_value(self) -> Number: + """Last value of the variable""" + if len(self._history) > 0: + return self._history[-1] + else: + raise IndexError(f"{self.name}: No value has been set yet") + + @property + def previous_value(self) -> Number: + """Value before the last one""" + if len(self._history) > 1: + return self._history[-2] + else: + raise IndexError(f"{self.name}: history too short") + + def set(self, value: Number, **kwargs) -> None: + """Set the variable value + + Args: + value: New value to be applied on the variable + """ + if value < self.bounds[0] or value > self.bounds[1]: + raise ValueError(f"set value must be in {self.bounds}") + self._setfun(value, **kwargs) + self._history.append(value) + + def get(self, initial=False, **kwargs) -> Number: + """Get the actual variable value + + Args: + initial: If :py:obj:`True`, set the variable initial value + + Returns: + value: Value of the variable + """ + value = self._getfun(**kwargs) + if initial: + self._history = [value] + return value + + @property + def value(self): + return self.get() + + @value.setter + def value(self, value: Number): + self.set(value) + + def set_previous(self, **kwargs) -> None: + """Reset to the value before the last one""" + if len(self._history) > 1: + self._history.pop() # Remove the last value + prev = self._history.pop() # retrieve the previous value + self.set(prev, **kwargs) + else: + raise IndexError(f"{self.name}: history too short") + + def set_initial(self, **kwargs) -> None: + """Reset to the initial value""" + if len(self._history) > 0: + iniv = self._history[0] + self._history = [] + self.set(iniv, **kwargs) + else: + raise IndexError(f"{self.name}: No value has been set yet") + + def increment(self, incr: Number, **kwargs) -> None: + """Increment the variable value + + Args: + incr: Increment value + """ + if len(self._history) == 0: + self.get(initial=True, **kwargs) + self.set(self.last_value + incr, **kwargs) + + def _step(self, step: Number, **kwargs) -> None: + self.set(self.initial_value + step, **kwargs) + + def step_up(self, **kwargs) -> None: + """Set to initial_value + delta""" + self._step(self.delta, **kwargs) + + def step_down(self, **kwargs) -> None: + """Set to initial_value - delta""" + self._step(-self.delta, **kwargs) + + @staticmethod + def _header(): + return "\n{:>12s}{:>13s}{:>16s}{:>16s}\n".format( + "Name", "Initial", "Final ", "Variation" + ) + + def _line(self, ring=None): + if ring is not None: + vnow = self.get(ring) + vini = self._history[0] + elif len(self._history) > 0: + vnow = self._history[-1] + vini = self._history[0] + else: + vnow = vini = np.nan + + return "{:>12s}{: 16e}{: 16e}{: 16e}".format( + self.name, vini, vnow, (vnow - vini) + ) + + def status(self, **kwargs): + """Return a string describing the current status of the variable + + Returns: + status: Variable description + """ + return "\n".join((self._header(), self._line(**kwargs))) + + def __add__(self, other): + fun = _BinaryOp(add, self, other) + return ParamBase(fun) + + def __radd__(self, other): + return self.__add__(other) + + def __pos__(self): + return ParamBase(_UnaryOp(pos, self)) + + def __neg__(self): + return ParamBase(_UnaryOp(neg, self)) + + def __sub__(self, other): + fun = _BinaryOp(sub, self, other) + return ParamBase(fun) + + def __rsub__(self, other): + fun = _BinaryOp(sub, other, self) + return ParamBase(fun) + + def __mul__(self, other): + fun = _BinaryOp(mul, self, other) + return ParamBase(fun) + + def __rmul__(self, other): + return self.__mul__(other) + + def __truediv__(self, other): + fun = _BinaryOp(truediv, self, other) + return ParamBase(fun) + + def __rtruediv__(self, other): + fun = _BinaryOp(add, other, self) + return ParamBase(fun) + + def __float__(self): + return float(self.value) + + def __int__(self): + return int(self.value) + + def __str__(self): + return f"{self.__class__.__name__}({self.value}, name={self.name!r})" + + def __repr__(self): + return repr(self.value) + + +class CustomVariable(Variable): + r"""A :py:class:`.Variable` with user-defined get and set functions + + This is a convenience function allowing user-defined *get* and *set* + functions. But subclassing :py:class:`.Variable` should always be preferred + for clarity and efficiency. + + """ + + def __init__( + self, + setfun: Callable, + getfun: Callable, + *args, + name: str = "", + bounds: tuple[Number, Number] = (-np.inf, np.inf), + delta: Number = 1.0, + **kwargs, + ): + """ + Parameters: + getfun: Function for getting the variable value. Called as + :pycode:`getfun(*args, ring=ring, **kwargs) -> Number` + setfun: Function for setting the variable value. Called as + :pycode:`setfun(value: Number, *args, ring=ring, **kwargs): None` + name: Name of the Variable + bounds: Lower and upper bounds of the variable value + delta: Initial variation step + *args: Variable argument list transmitted to both the *getfun* + and *setfun* functions. Such arguments can always be avoided by + using :py:func:`~functools.partial` or callable class objects. + + Keyword Args: + **kwargs: Keyword arguments transmitted to both the *getfun* + and *setfun* functions. Such arguments can always be avoided by + using :py:func:`~functools.partial` or callable class objects. + """ + super().__init__(name=name, bounds=bounds, delta=delta) + self.getfun = getfun + self.setfun = setfun + self.args = args + self.kwargs = kwargs + + def _getfun(self, ring=None) -> Number: + return self.getfun(*self.args, ring=ring, **self.kwargs) + + def _setfun(self, value: Number, ring=None): + self.setfun(value, *self.args, ring=ring, **self.kwargs) + + +class ParamBase(Variable): + """Read-only base class for parameters + + It is used for computed parameters, and should not be instantiated + otherwise. See :py:class:`.Variable` for a description of inherited + methods + """ + + _counter = 0 + _prefix = "calc" + + def __init__( + self, + evaluate: _Evaluate, + *, + name: str = "", + dtype: Callable[[Number], Number] = _nop, + bounds: tuple[float, float] = (-np.inf, np.inf), + delta: float = 1.0, + ): + """ + + Args: + evaluate: Evaluator function + name: Name of the parameter + dtype: data type of the parameter + bounds: Lower and upper bounds of the parameter value + delta: Initial variation step + """ + super(ParamBase, self).__init__(name=name, bounds=bounds, delta=delta) + if not isinstance(evaluate, _Evaluate): + raise TypeError("'Evaluate' must be an _Evaluate object") + self._evaluate = evaluate + self.dtype = dtype + + def _getfun(self, **kwargs): + return self.dtype(self._evaluate()) + + def set_dtype(self, dtype: Callable[[Number], Number]): + """Set the data type. Called when a parameter is assigned to an + :py:class:`.Element` attribute""" + if dtype is not self.dtype: + if self.dtype is _nop: + self.dtype = dtype + else: + raise ValueError("Cannot change the data type of the parameter") + + +class Param(ParamBase): + """Standalone scalar parameter + + See :py:class:`.Variable` for a description of inherited methods + """ + + _counter = 0 + _prefix = "param" + + def __init__( + self, + value: Number, + *, + name: str = "", + dtype: Callable[[Number], Number] = _nop, + bounds: tuple[float, float] = (-np.inf, np.inf), + delta: float = 1.0, + ): + """ + Args: + value: Initial value of the parameter + name: Name of the parameter + dtype: data type of the parameter + bounds: Lower and upper bounds of the parameter value + delta: Initial variation step + """ + super(Param, self).__init__( + _Scalar(value), name=name, dtype=dtype, bounds=bounds, delta=delta + ) + self._history.append(self._evaluate()) + + def _getfun(self, ring=None): + return self._evaluate() + + def _setfun(self, value, ring=None): + self._evaluate = _Scalar(self.dtype(value)) + + def set_dtype(self, dtype: Callable[[Number], Number]): + oldv = self._evaluate() + super(Param, self).set_dtype(dtype) + self._evaluate = _Scalar(dtype(oldv)) + + +class _PArray(np.ndarray): + """Subclass of ndarray which reports to its parent ParamArray""" + + def __new__(cls, value, buildfun): + a = buildfun(value) + obj = a.view(cls) + obj._parent = value + return obj + + def __array_finalize__(self, obj): + if obj is not None: + self._parent = getattr(obj, "_parent", None) + + def __setitem__(self, key, value): + super().__setitem__(key, value) + if self._parent is not None: + self._parent[key] = value + + def __repr__(self): + # Simulate a standard ndarray + return repr(self.view(np.ndarray)) + + +class ParamArray(np.ndarray): + """Simulate a numpy array where items may be parametrised""" + + def __new__(cls, value, buildfun=lambda v: np.array(v, dtype=float, order="F")): + obj = np.array(value, dtype=object, order="F").view(cls) + obj._value = _PArray(obj.view(np.ndarray), buildfun) + return obj + + # noinspection PyUnusedLocal + def __array_finalize__(self, obj): + self._value = None + + def set_dtype(self, buildfun): + """Set the data type. Called when a parameter is assigned to an + :py:class:`.Element` attribute""" + # noinspection PyAttributeOutsideInit + self._value = _PArray(self.view(np.ndarray), buildfun) + + @property + def value(self): + self._value[:] = self + return self._value + + def __repr__(self): + return repr(self.value) + + def __str__(self): + it = np.nditer(self, flags=["refs_ok"], order="C") + contents = ", ".join([str(el) for el in it]) + return f"{self.__class__.__name__}([{contents}])" + + +class VariableList(list): + """Container for :py:class:`Variable` objects + + :py:class:`VariableList` supports all :py:class:`list` methods, like + appending, insertion or concatenation with the "+" operator. + """ + + def get(self, initial=False, **kwargs) -> Sequence[float]: + r"""Get the current :py:class:`Variable`\ s' values + + Args: + initial: If :py:obj:`True`, set the :py:class:`Variable`\ s' + initial value + + Returns: + values: 1D array of values of all variables + """ + return np.array([var.get(initial=initial, **kwargs) for var in self]) + + def set(self, values: Iterable[float], **kwargs) -> None: + r"""Set the :py:class:`Variable`\ s' values + + Args: + values: Iterable of values + """ + for var, val in zip(self, values): + var.set(val, **kwargs) + + def increment(self, increment: Iterable[float], **kwargs) -> None: + r"""Increment the :py:class:`Variable`\ s' values + + Args: + increment: Iterable of values + """ + for var, incr in zip(self, increment): + var.increment(incr, **kwargs) + + # noinspection PyProtectedMember + def status(self, **kwargs) -> str: + """String description of the variables""" + values = "\n".join(var._line(**kwargs) for var in self) + return "\n".join((Variable._header(), values)) + + def __str__(self) -> str: + return self.status() + + @property + def deltas(self) -> Sequence[Number]: + """delta values of the variables""" + return np.array([var.delta for var in self]) From 0e663e0b716a67c140e34685a4ee4b3d1b844b5b Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Sat, 18 Nov 2023 21:11:57 +0100 Subject: [PATCH 02/29] Element array attributes are always ParamArrays --- docs/p/notebooks/parameters.ipynb | 616 +++++++++++++++++------------- pyat/at/lattice/elements.py | 143 +++---- pyat/at/lattice/variables.py | 81 ++-- 3 files changed, 451 insertions(+), 389 deletions(-) diff --git a/docs/p/notebooks/parameters.ipynb b/docs/p/notebooks/parameters.ipynb index c7e9bbc9c..9f290e110 100644 --- a/docs/p/notebooks/parameters.ipynb +++ b/docs/p/notebooks/parameters.ipynb @@ -47,7 +47,13 @@ { "cell_type": "markdown", "id": "ba73f6c0-aa60-4ced-8158-dfddcade4bd9", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "# Parameters\n", "\n", @@ -60,7 +66,13 @@ "cell_type": "code", "execution_count": 3, "id": "27f3abc6-7cfe-4632-82a1-dc17038983a2", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -71,15 +83,21 @@ } ], "source": [ - "p1=Param(2.5, name='total length')\n", - "p2=Param(1.0)\n", - "print(p1, p2)" + "total_length = Param(2.5, name='total length')\n", + "dlength = Param(1.0)\n", + "print(total_length, dlength)" ] }, { "cell_type": "markdown", "id": "c61cb66b-2e98-4f1b-9d6d-9d896085f6b4", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "The value of a parameter can be read or modified through its {py:attr}`~.variables.Variable.value` property. {py:meth}`~.variables.Variable.set` and {py:meth}`~.variables.Variable.get` methods are also available:" ] @@ -88,7 +106,13 @@ "cell_type": "code", "execution_count": 4, "id": "772a79ce-91ee-47fd-80bf-a7a76e935339", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -110,17 +134,23 @@ } ], "source": [ - "print(p1.value)\n", - "p1.value = 2.4\n", - "print(p1)\n", - "p1.set(2.3)\n", - "p1.get()" + "print(total_length.value)\n", + "total_length.value = 2.4\n", + "print(total_length)\n", + "total_length.set(2.3)\n", + "total_length.get()" ] }, { "cell_type": "markdown", "id": "fa523242-3579-4e0f-aca8-2ecd230cd93c", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "Arithmetic combinations of parameters create new read-only parameters of class {py:class}`.ParamBase`, whose value is permanently kept up-to-date:" ] @@ -129,7 +159,13 @@ "cell_type": "code", "execution_count": 5, "id": "a229555f-cbb2-4e36-b9d8-0ca3c0c7cf76", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -141,16 +177,22 @@ } ], "source": [ - "p3 = p1-p2\n", - "print(p3)\n", - "p2.value = 0.9\n", - "print(p3)" + "qlength = total_length - dlength\n", + "print(qlength)\n", + "dlength.value = 0.9\n", + "print(qlength)" ] }, { "cell_type": "markdown", "id": "57eff2ca-87af-4667-84e8-4e86e5b2db74", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "Parameters may be assigned to {py:class}`.Element` attributes, for instance on initialisation:" ] @@ -159,7 +201,13 @@ "cell_type": "code", "execution_count": 6, "id": "c49dc3e7-78ea-4e40-9e82-438674b5ba7e", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -175,15 +223,15 @@ "\tPassMethod : StrMPoleSymplectic4Pass\n", "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 0.6]\n", + "\tPolynomA : [0.0 0.0]\n", + "\tPolynomB : [0.0 0.6]\n", "\tK : 0.6\n" ] } ], "source": [ - "dr1 = at.Drift('DR1', p2)\n", - "qf1 = at.Quadrupole('QF1', p3, 0.6)\n", + "dr1 = at.Drift('DR1', dlength)\n", + "qf1 = at.Quadrupole('QF1', qlength, 0.6)\n", "print(dr1)\n", "print(qf1)" ] @@ -191,7 +239,13 @@ { "cell_type": "markdown", "id": "6c0f9595-9abc-4fee-967e-195dc1db0030", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "The {py:class}`.Element` attributes keep their type so that all the processing of elements either in python functions or in C integrators is unchanged:" ] @@ -200,7 +254,13 @@ "cell_type": "code", "execution_count": 7, "id": "63baa52e-c1f1-4d0d-8470-fc80116239ec", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -217,7 +277,13 @@ { "cell_type": "markdown", "id": "ca1a092d-2773-41bb-b5ac-65c4005593be", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Assigning parameters\n", "\n", @@ -231,16 +297,28 @@ "cell_type": "code", "execution_count": 8, "id": "07e9ced4-f048-46ec-b187-3eb60314ee92", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ - "dr2 = at.Drift('DR2', p2)" + "dr2 = at.Drift('DR2', dlength)" ] }, { "cell_type": "markdown", "id": "a7846d65-d686-4496-94fa-16f18dd564e6", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "**By converting a numeric attribute into a parameter:**" ] @@ -249,7 +327,13 @@ "cell_type": "code", "execution_count": 9, "id": "24db8abd-6b85-4196-b1ae-a316e0ddd8ea", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -261,14 +345,20 @@ ], "source": [ "qd1 = at.Quadrupole('QD1', 0.5, -0.4)\n", - "p4 = qd1.parametrise('Length')\n", - "print(p4)" + "ql = qd1.parametrise('Length')\n", + "print(ql)" ] }, { "cell_type": "markdown", "id": "71a08731-9aed-497c-8084-15d6de0e8215", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "**By normal assignment:**" ] @@ -276,8 +366,14 @@ { "cell_type": "code", "execution_count": 10, - "id": "946224b5-7c9b-4a5c-a665-fc516e548a15", - "metadata": {}, + "id": "90b0b580-16cb-4d4b-a80b-a6262fe3dad5", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -285,25 +381,33 @@ "text": [ "Quadrupole:\n", "\tFamName : QD1\n", - "\tLength : ParamBase(1.4, name='calc1')\n", + "\tLength : Param(0.2, name='param5')\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [ 0. -0.4]\n", - "\tK : -0.4\n" + "\tPolynomA : [0.0 0.0]\n", + "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", + "\tK : Param(-0.5, name='quad strength')\n" ] } ], "source": [ - "qd1.Length = p3\n", + "qstrength = Param(-0.5, name='quad strength')\n", + "qd1.Length = Param(0.2)\n", + "qd1.PolynomB[1] = qstrength\n", "print(qd1)" ] }, { "cell_type": "markdown", "id": "3064a2f2-b913-4982-978c-268b8e76b1c1", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "**With the {py:meth}`~.Element.set_parameter` method:**" ] @@ -312,7 +416,13 @@ "cell_type": "code", "execution_count": 11, "id": "43c18e94-cd26-4639-98a9-bf93c736115d", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -320,25 +430,31 @@ "text": [ "Quadrupole:\n", "\tFamName : QD1\n", - "\tLength : Param(0.5, name='param3')\n", + "\tLength : ParamBase(1.4, name='calc1')\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [ 0. -0.4]\n", - "\tK : -0.4\n" + "\tPolynomA : [0.0 0.0]\n", + "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", + "\tK : Param(-0.5, name='quad strength')\n" ] } ], "source": [ - "qd1.set_parameter('Length', p4)\n", + "qd1.set_parameter('Length', qlength)\n", "print(qd1)" ] }, { "cell_type": "markdown", "id": "6318ac4e-332e-44cb-b77f-d5efc4d4e799", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "### To selected elements of a {py:class}`.Lattice`\n", "To act on several elements in a single step, {py:class}`.Lattice` methods similar to {py:class}`.Element` methods are available\n", @@ -352,7 +468,13 @@ "cell_type": "code", "execution_count": 12, "id": "07b4603f-8089-451a-a53f-fb462729b79e", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -367,22 +489,28 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, Param(2.5394599781303304, name='kf1')])\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 Param(2.5394599781303304, name='kf1')]\n", "\tK : Param(2.5394599781303304, name='kf1')\n" ] } ], "source": [ - "p5 = ring.parametrise('QF1[AE]', 'PolynomB', index=1, name='kf1')\n", - "print(p5)\n", + "kf1 = ring.parametrise('QF1[AE]', 'PolynomB', index=1, name='kf1')\n", + "print(kf1)\n", "print(ring[5])" ] }, { "cell_type": "markdown", "id": "b8931812-b5e8-4b0d-9561-bd2b4a1506e4", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "**Use the {py:meth}`~.Lattice.set_parameter` method:**\n", "\n", @@ -393,7 +521,13 @@ "cell_type": "code", "execution_count": 13, "id": "276e2b71-ff91-4f55-a459-cc62d8bcc2d3", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -407,155 +541,60 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, Param(2.5394599781303304, name='kf1')])\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 Param(2.5394599781303304, name='kf1')]\n", "\tK : Param(2.5394599781303304, name='kf1')\n" ] } ], "source": [ - "p6 = Param(0.311896, name='lf1')\n", - "ring.set_parameter('QF1[AE]', 'Length', p6)\n", + "lf1 = Param(0.311896, name='lf1')\n", + "ring.set_parameter('QF1[AE]', 'Length', lf1)\n", "print(ring[117])" ] }, - { - "cell_type": "markdown", - "id": "23aeb148-0aaa-49c1-a24f-c04128cf2397", - "metadata": {}, - "source": [ - "### Special case of array attributes\n", - "\n", - "Normal {py:class}`.Element` array attributes are numeric numpy arrays, so they cannot be assigned objects. Attempting to assign a parameter to an item of an array attribute will assign the current parameter value instead of the parameter itself. Since this is not what is usually desired, a warning is emitted:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "a62d3508-fd89-45e9-bc16-19b170cc5da9", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/famille/dev/libraries/at/pyat/at/lattice/elements.py:30: UserWarning: \n", - "\n", - "The Parameter 'QD strength' is ignored, instead its value '-0.3' is used.\n", - "To set a parameter in an array, you must first parametrise the array itself.\n", - "\n", - " warn(UserWarning(message))\n" - ] - } - ], - "source": [ - "p5 = Param(-0.3, name='QD strength')\n", - "qd1.PolynomB[1] = p5" - ] - }, - { - "cell_type": "markdown", - "id": "9a257a54-1838-41eb-8d83-33fb9f339272", - "metadata": {}, - "source": [ - "To accept parameters, the array attribute itself must be first replaced by a {py:class}`.ParamArray` object. The easiest way for that is to let the {py:meth}`~.Element.parametrise` or {py:meth}`~.Element.set_parameter` methods take care of it:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "40c2b4c8-2533-4604-b6d3-af100d1da33b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QD1\n", - "\tLength : Param(0.5, name='param3')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, Param(-0.3, name='QD strength')])\n", - "\tK : Param(-0.3, name='QD strength')\n" - ] - } - ], - "source": [ - "qd1.set_parameter('PolynomB', p5, index=1)\n", - "print(qd1)" - ] - }, - { - "cell_type": "markdown", - "id": "48d61183-7827-4807-ba12-25ba46e1fe19", - "metadata": {}, - "source": [ - "Once the array attribute is a {py:class}`.ParamArray`, any of its items may be assigned a parameter. As with scalar attributes, the type of the attribute is unchanged.\n", - "\n", - "Another possiblility is to first convert the array attribute into a {py:class}`.ParamArray`, and then perform a standard assignment:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "0be8546f-ed8d-4c97-835f-fa1f3f77def6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QD1\n", - "\tLength : ParamBase(1.4, name='calc1')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, Param(-0.3, name='QD strength')])\n", - "\tK : Param(-0.3, name='QD strength')\n" - ] - } - ], - "source": [ - "qd1 = at.Quadrupole('QD1', p3, -0.4)\n", - "qd1.parametrise('PolynomB')\n", - "qd1.PolynomB[1] = p5\n", - "print(qd1)" - ] - }, { "cell_type": "markdown", "id": "a60a830b-9ade-47ce-89ed-1e4d4a000c4e", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Retrieving parameters\n", "\n", - "Since the values of {py:class}`.Element` attributes keep their original type, they cannot be used to access the underlying parameter. The only way to retrieve it is to use the {py:meth}`~.Element.get_parameter` method. When applied to a non-parameter attribute, the attribute is returned." + "Since the values of {py:class}`.Element` attributes keep their original type, they cannot be used to access the underlying parameter. The only way to retrieve it is to use the {py:meth}`~.Element.get_parameter` method. a {py:obj}`TypeError` is raised if the attribute is not\n", + "a Parameter." ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "id": "a4bea7d9-59c3-4af3-a96d-f2e4ee4c2c14", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ParamBase(1.4, name='calc1')\n", - "pp is p3: True\n" + "ql is qlength: True\n" ] } ], "source": [ - "pp = qd1.get_parameter('Length')\n", - "print(pp)\n", - "print(\"pp is p3:\", pp is p3)" + "ql = qd1.get_parameter('Length')\n", + "print(ql)\n", + "print(\"ql is qlength:\", ql is qlength)" ] }, { @@ -574,56 +613,41 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 15, "id": "a4343df3-a2b7-4b4f-9b74-3bdd3e20cfb2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Param(-0.3, name='QD strength')\n", - "pp is p5: True\n" - ] - } - ], - "source": [ - "pp = qd1.get_parameter('PolynomB', index=1)\n", - "print(pp)\n", - "print(\"pp is p5:\", pp is p5)" - ] - }, - { - "cell_type": "markdown", - "id": "585739fc-6a3b-4e6d-8c52-08e43b2ff17e", - "metadata": {}, - "source": [ - "Retrieving the whole array attribute returns a {py:class}`.ParamArray` object. It may contain either numbers or {py:class}`.Param` objects:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "3aa4c9dc-2f8c-4742-a39a-13872adc88b8", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ParamArray([0.0, Param(-0.3, name='QD strength')])\n" + "Param(-0.5, name='quad strength')\n", + "qs is qstrength: True\n" ] } ], "source": [ - "pp = qd1.get_parameter('PolynomB')\n", - "print(pp)" + "qs = qd1.get_parameter('PolynomB', index=1)\n", + "print(qs)\n", + "print(\"qs is qstrength:\", qs is qstrength)" ] }, { "cell_type": "markdown", "id": "fe98da38-b39a-4e22-a009-16495ccfda58", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Checking parametrisation\n", "\n", @@ -635,9 +659,15 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 16, "id": "8c0eab2f-4ed0-4441-9dd6-a2c81ecdb765", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -645,16 +675,12 @@ "text": [ "True\n", "True\n", - "False\n", - "True\n", "False\n" ] } ], "source": [ "print(qd1.is_parametrised())\n", - "print(qd1.is_parametrised('PolynomB'))\n", - "print(qd1.is_parametrised('PolynomA'))\n", "print(qd1.is_parametrised('PolynomB', index=1))\n", "print(qd1.is_parametrised('PolynomB', index=0))" ] @@ -662,7 +688,13 @@ { "cell_type": "markdown", "id": "b62883e5-8dd4-42f2-9675-bdbe4d9be71c", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Removing parameters\n", "Removing the parameters will \"freeze\" the element at its current value. The {py:meth}`~.Element.unparametrise` method is defined for both {py:class}`.Element` and {py:class}`.Lattice`, and may be applied to:\n", @@ -675,9 +707,15 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 17, "id": "088bfb6e-1ede-4429-8f38-c179be2c0b9d", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -689,52 +727,63 @@ "\tPassMethod : StrMPoleSymplectic4Pass\n", "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, Param(-0.3, name='QD strength')])\n", - "\tK : Param(-0.3, name='QD strength')\n", + "\tPolynomA : [0.0 0.0]\n", + "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", + "\tK : Param(-0.5, name='quad strength')\n", "Quadrupole:\n", "\tFamName : QD1\n", "\tLength : 1.4\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, -0.3])\n", - "\tK : -0.3\n", + "\tPolynomA : [0.0 0.0]\n", + "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", + "\tK : Param(-0.5, name='quad strength')\n", "Quadrupole:\n", "\tFamName : QD1\n", "\tLength : 1.4\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [ 0. -0.3]\n", - "\tK : -0.3\n" + "\tPolynomA : [0.0 0.0]\n", + "\tPolynomB : [0.0 -0.5]\n", + "\tK : -0.5\n" ] } ], "source": [ "print(qd1)\n", "qd1.unparametrise('Length')\n", - "qd1.unparametrise('PolynomB', 1)\n", "print(qd1)\n", - "qd1.unparametrise()\n", + "qd1.unparametrise('PolynomB', 1)\n", "print(qd1)" ] }, { "cell_type": "markdown", "id": "295b9a0e-23a9-4321-9715-7a6197d2859e", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "### In a Lattice" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 18, "id": "744f6754-e681-4db4-b80d-842eb0bcb124", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -748,19 +797,19 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, Param(2.5394599781303304, name='kf1')])\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 Param(2.5394599781303304, name='kf1')]\n", "\tK : Param(2.5394599781303304, name='kf1')\n", "Quadrupole:\n", "\tFamName : QF1A\n", - "\tLength : 0.311896\n", + "\tLength : Param(0.311896, name='lf1')\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", "\tNumIntSteps : 20\n", "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : ParamArray([0.0, 2.5394599781303304])\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5394599781303304]\n", "\tK : 2.5394599781303304\n", "Quadrupole:\n", "\tFamName : QF1E\n", @@ -770,15 +819,14 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.53945998]\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5394599781303304]\n", "\tK : 2.5394599781303304\n" ] } ], "source": [ "print(ring[5])\n", - "ring.unparametrise('QF1[AE]', 'Length')\n", "ring.unparametrise('QF1[AE]', 'PolynomB', index=1)\n", "print(ring[5])\n", "ring.unparametrise('QF1[AE]')\n", @@ -788,7 +836,13 @@ { "cell_type": "markdown", "id": "564674a3-0bf2-48b8-9978-8f2c9cdc4fd0", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Parameter history\n", "Parameter values are kept in an {py:attr}`~.variables.Variable.history` buffer. The properties {py:attr}`~.variables.Variable.initial_value`, {py:attr}`~.variables.Variable.last_value` and {py:attr}`~.variables.Variable.previous_value` are also available:" @@ -796,40 +850,58 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 19, "id": "7be2ebce-fe54-4060-8481-a344c4ebf25f", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[2.5, 2.4, 2.3, 2.2]\n", - "2.5\n", - "2.3\n" + "[1.0, 0.9, 1.1]\n", + "1.0\n", + "0.9\n" ] } ], "source": [ - "p1.value = 2.2\n", - "print(p1.history)\n", - "print(p1.initial_value)\n", - "print(p1.previous_value)" + "dlength.value = 1.1\n", + "print(dlength.history)\n", + "print(dlength.initial_value)\n", + "print(dlength.previous_value)" ] }, { "cell_type": "markdown", "id": "d1898549-19dd-46a9-8e93-8356dfae6d8a", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "After varying parameters, in matching for instance, the current status can be printed:" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 20, "id": "f191ea2a-997e-4547-b674-3e16ca2d629c", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -838,42 +910,54 @@ "\n", " Name Initial Final Variation\n", "\n", - "total length 2.500000e+00 2.200000e+00 -3.000000e-01\n" + " param2 1.000000e+00 1.100000e+00 1.000000e-01\n" ] } ], "source": [ - "print(p1.status())" + "print(dlength.status())" ] }, { "cell_type": "markdown", "id": "0d155699-1005-431f-a00a-74d6406f5c21", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "Parameters may be reset to a previous history value with the {py:meth}`~.variables.Variable.set_initial` and {py:meth}`~.variables.Variable.set_previous` methods. The history is shortened accordingly." ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 21, "id": "f92600ae-28da-439f-ae7c-6a787162c888", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Param(2.3, name='total length') [2.5, 2.4, 2.3]\n", - "Param(2.5, name='total length') [2.5]\n" + "Param(0.9, name='param2') [1.0, 0.9]\n", + "Param(1.0, name='param2') [1.0]\n" ] } ], "source": [ - "p1.set_previous()\n", - "print(p1, p1.history)\n", - "p1.set_initial()\n", - "print(p1, p1.history)" + "dlength.set_previous()\n", + "print(dlength, dlength.history)\n", + "dlength.set_initial()\n", + "print(dlength, dlength.history)" ] }, { diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 5caf060cc..8cb559627 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -12,37 +12,19 @@ from copy import copy, deepcopy from abc import ABC from collections.abc import Generator, Iterable -from typing import Union, Optional -from warnings import warn +from typing import Optional from .variables import Param, ParamBase, ParamArray # noinspection PyProtectedMember from .variables import _nop -class WarningArray(numpy.ndarray): - """subclass of ndarray which warns when setting a Param item""" - def __setitem__(self, key, value): - if isinstance(value, ParamBase): - message = f"\n\nThe Parameter '{value.name}' is ignored, instead " \ - f"its value '{value.value}' is used." \ - "\nTo set a parameter in an array, you must first " \ - "parametrise the array itself.\n" - warn(UserWarning(message)) - super().__setitem__(key, value) - - def __repr__(self): - # Simulate a standard ndarray - return repr(self.view(numpy.ndarray)) - - def _array(value, shape=(-1,), dtype=numpy.float64): # Ensure proper ordering(F) and alignment(A) for "C" access in integrators - return numpy.require(value, dtype=dtype, requirements=['F', 'A']).reshape( - shape, order='F').view(WarningArray) + return ParamArray(value, shape=shape, dtype=dtype) -def _array66(value): - return _array(value, shape=(6, 6)) +def _array66(value, dtype=numpy.float64): + return _array(value, shape=(6, 6), dtype=dtype) def _float(value): @@ -53,13 +35,6 @@ def _int(value): return int(value) -def _array_type(value): - if isinstance(value, ParamBase): - return ParamArray - else: - return numpy.array - - class LongtMotion(ABC): """Abstract Base class for all Element classes whose instances may modify the particle momentum @@ -302,17 +277,12 @@ def __init__(self, family_name: str, **kwargs): self.update(kwargs) def __setattr__(self, key, value): - try: - if isinstance(value, (ParamBase, ParamArray)): - value.set_dtype(self._conversions.get(key, _nop)) - else: - value = self._conversions.get(key, _nop)(value) - super(Element, self).__setattr__(key, value) - except Exception as exc: - exc.args = ('In element {0}, parameter {1}: {2}'.format( - self.FamName, key, exc),) - raise - + if isinstance(value, ParamBase): + value.set_conversion(self._conversions.get(key, _nop)) + else: + value = self._conversions.get(key, _nop)(value) + super(Element, self).__setattr__(key, value) + def __getattribute__(self, key): attr = super(Element, self).__getattribute__(key) if isinstance(attr, (ParamBase, ParamArray)): @@ -475,7 +445,7 @@ def set_parameter(self, attrname: str, value, index: int = None) -> None: setattr(self, attrname, attr) attr[index] = value - def _get_parameter(self, attrname: str, index: Optional[int] = None): + def _get_attribute(self, attrname: str, index: Optional[int] = None): attr = self.__dict__[attrname] if index is not None: attr = attr[index] @@ -494,8 +464,8 @@ def get_parameter(self, attrname: str, index: Optional[int] = None): index: Index in an array attribute. If :py:obj:`None`, the whole attribute is set """ - attr = self._get_parameter(attrname, index=index) - if not isinstance(attr, (ParamBase, ParamArray)): + attr = self._get_attribute(attrname, index=index) + if not isinstance(attr, ParamBase): message = f"\n\n{self.FamName}.{attrname} is not a parameter.\n" # warn(AtWarning(message)) raise TypeError(message) @@ -512,16 +482,24 @@ def is_parametrised(self, attrname: Optional[str] = None, whole attribute is tested for parametrisation """ if attrname is None: - for attr in self.__dict__.values(): - if isinstance(attr, (ParamBase, ParamArray)): + for attr in self.__dict__: + if self.is_parametrised(attr): return True return False else: - attr = self._get_parameter(attrname, index=index) - return isinstance(attr, (ParamBase, ParamArray)) + attr = self._get_attribute(attrname, index=index) + if isinstance(attr, ParamBase): + return True + elif isinstance(attr, numpy.ndarray): + for item in attr.flat: + if isinstance(item, ParamBase): + return True + return False + else: + return False def parametrise(self, attrname: str, index: Optional[int] = None, - name: str = '') -> Union[Param, ParamArray]: + name: str = '') -> ParamBase: """Convert an attribute into a parameter The value of the attribute is kept unchanged. If the attribute is @@ -539,21 +517,18 @@ def parametrise(self, attrname: str, index: Optional[int] = None, array attribute """ - vini = self._get_parameter(attrname, index=index) + vini = self._get_attribute(attrname, index=index) - if isinstance(vini, (ParamBase, ParamArray)): + if isinstance(vini, ParamBase): return vini - if isinstance(vini, numpy.ndarray): - attr = ParamArray(vini) - else: - attr = Param(vini, name=name) + attr = Param(vini, name=name) # raises TypeError if vini is not a Number if index is None: setattr(self, attrname, attr) else: - varr = self.parametrise(attrname) - varr[index] = attr + varr = self._get_attribute(attrname) + varr[index] = attr # raises IndexError it the attr is not an array return attr def unparametrise(self, attrname: Optional[str] = None, @@ -566,17 +541,25 @@ def unparametrise(self, attrname: Optional[str] = None, index: Index in an array. If :py:obj:`None`, the whole attribute is frozen """ + + def unparam_attr(attrname, attr): + if isinstance(attr, ParamBase): + setattr(self, attrname, attr.value) + elif isinstance(attr, numpy.ndarray): + for i, item in enumerate(attr.flat): + if isinstance(item, ParamBase): + ij = numpy.unravel_index(i, attr.shape) + attr[ij] = item.value + if attrname is None: for key, attr in self.__dict__.items(): - if isinstance(attr, (ParamBase, ParamArray)): - setattr(self, key, attr.value) + unparam_attr(key, attr) else: - attr = self._get_parameter(attrname, index=index) - if isinstance(attr, (ParamBase, ParamArray)): - if index is None: - setattr(self, attrname, attr.value) - else: - self._get_parameter(attrname)[index] = attr.value + if index is None: + unparam_attr(attrname, self._get_attribute(attrname)) + else: + attr = self._get_attribute(attrname) + attr[index] = attr[index].value class LongElement(Element): @@ -792,12 +775,13 @@ def lengthen(poly, dl): else: return poly - # Remove MaxOrder, PolynomA and PolynomB - ipola = kwargs.pop("PolynomA", poly_a) - ipolb = kwargs.pop("PolynomB", poly_b) - poly_a, len_a, ord_a = getpol(_array(ipola)) - poly_b, len_b, ord_b = getpol(_array(ipolb)) + # PolynomA and PolynomB and convert to ParamArray + prmpola = self._conversions["PolynomA"](kwargs.pop("PolynomA", poly_a)) + prmpolb = self._conversions["PolynomB"](kwargs.pop("PolynomB", poly_b)) + poly_a, len_a, ord_a = getpol(prmpola.value) + poly_b, len_b, ord_b = getpol(prmpolb.value) deforder = max(getattr(self, 'DefaultOrder', 0), ord_a, ord_b) + # Remove MaxOrder maxorder = kwargs.pop('MaxOrder', deforder) kwargs.setdefault('PassMethod', 'ThinMPolePass') super(ThinMultipole, self).__init__(family_name, **kwargs) @@ -805,14 +789,8 @@ def lengthen(poly, dl): super(ThinMultipole, self).__setattr__('MaxOrder', maxorder) # Adjust polynom lengths and set them len_ab = max(self.MaxOrder + 1, len_a, len_b) - self.PolynomA = lengthen(poly_a, len_ab - len_a) - self.PolynomB = lengthen(poly_b, len_ab - len_b) - if isinstance(ipola, ParamArray): - lista = ipola[:] + list(self.PolynomA)[len(ipola):] - self.PolynomA = ParamArray(lista) - if isinstance(ipolb, ParamArray): - listb = ipolb[:] + list(self.PolynomB)[len(ipolb):] - self.PolynomB = ParamArray(listb) + self.PolynomA = lengthen(prmpola, len_ab - len_a) + self.PolynomB = lengthen(prmpolb, len_ab - len_b) def __setattr__(self, key, value): """Check the compatibility of MaxOrder, PolynomA and PolynomB""" @@ -964,12 +942,11 @@ def __init__(self, family_name: str, length: float, Default PassMethod: :ref:`BndMPoleSymplectic4Pass` """ - poly_b = kwargs.pop('PolynomB', numpy.array([0, k])) kwargs.setdefault('BendingAngle', bending_angle) kwargs.setdefault('EntranceAngle', 0.0) kwargs.setdefault('ExitAngle', 0.0) kwargs.setdefault('PassMethod', 'BndMPoleSymplectic4Pass') - super(Dipole, self).__init__(family_name, length, [], poly_b, **kwargs) + super(Dipole, self).__init__(family_name, length, [], [0, k], **kwargs) def items(self) -> Generator[tuple, None, None]: yield from super().items() @@ -1040,10 +1017,8 @@ def __init__(self, family_name: str, length: float, Default PassMethod: ``StrMPoleSymplectic4Pass`` """ - poly_type = _array_type(k) - poly_b = kwargs.pop("PolynomB", poly_type([0.0, k])) kwargs.setdefault("PassMethod", "StrMPoleSymplectic4Pass") - super(Quadrupole, self).__init__(family_name, length, [], poly_b, + super(Quadrupole, self).__init__(family_name, length, [], [0.0, k], **kwargs) def items(self) -> Generator[tuple, None, None]: @@ -1076,10 +1051,8 @@ def __init__(self, family_name: str, length: float, Default PassMethod: ``StrMPoleSymplectic4Pass`` """ - poly_type = _array_type(h) - poly_b = kwargs.pop("PolynomB", poly_type([0.0, 0.0, h])) kwargs.setdefault("PassMethod", "StrMPoleSymplectic4Pass") - super(Sextupole, self).__init__(family_name, length, [], poly_b, + super(Sextupole, self).__init__(family_name, length, [], [0.0, 0.0, h], **kwargs) def items(self) -> Generator[tuple, None, None]: diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 5ca100c1c..60daa71e6 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -89,9 +89,16 @@ def _getfun(self, **kwargs): from numbers import Number from operator import add, sub, mul, truediv, pos, neg from collections.abc import Iterable, Sequence, Callable +from typing import Any -__all__ = ["Variable", "CustomVariable", "ParamBase", "Param", "ParamArray", - "VariableList"] +__all__ = [ + "Variable", + "CustomVariable", + "ParamBase", + "Param", + "ParamArray", + "VariableList", +] def _nop(value): @@ -104,7 +111,8 @@ def _default_array(value): class _Evaluate(abc.ABC): @abc.abstractmethod - def __call__(self): ... + def __call__(self): + ... class _Scalar(_Evaluate): @@ -112,7 +120,7 @@ class _Scalar(_Evaluate): def __init__(self, value): if not isinstance(value, Number): - raise TypeError("'value' must be a Number") + raise TypeError("The parameter value must be a scalar") self.value = value def __call__(self): @@ -194,7 +202,8 @@ def _setfun(self, value: Number, **kwargs): raise TypeError(f"{classname!r} is read-only") @abc.abstractmethod - def _getfun(self, **kwargs) -> Number: ... + def _getfun(self, **kwargs) -> Number: + ... @property def history(self) -> list[Number]: @@ -440,7 +449,7 @@ def __init__( evaluate: _Evaluate, *, name: str = "", - dtype: Callable[[Number], Number] = _nop, + conversion: Callable[[Any], Number] = _nop, bounds: tuple[float, float] = (-np.inf, np.inf), delta: float = 1.0, ): @@ -449,7 +458,7 @@ def __init__( Args: evaluate: Evaluator function name: Name of the parameter - dtype: data type of the parameter + conversion: data conversion function bounds: Lower and upper bounds of the parameter value delta: Initial variation step """ @@ -457,17 +466,17 @@ def __init__( if not isinstance(evaluate, _Evaluate): raise TypeError("'Evaluate' must be an _Evaluate object") self._evaluate = evaluate - self.dtype = dtype + self._conversion = conversion def _getfun(self, **kwargs): - return self.dtype(self._evaluate()) + return self._conversion(self._evaluate()) - def set_dtype(self, dtype: Callable[[Number], Number]): + def set_conversion(self, conversion: Callable[[Number], Number]): """Set the data type. Called when a parameter is assigned to an :py:class:`.Element` attribute""" - if dtype is not self.dtype: - if self.dtype is _nop: - self.dtype = dtype + if conversion is not self._conversion: + if self._conversion is _nop: + self._conversion = conversion else: raise ValueError("Cannot change the data type of the parameter") @@ -486,7 +495,7 @@ def __init__( value: Number, *, name: str = "", - dtype: Callable[[Number], Number] = _nop, + conversion: Callable[[Number], Number] = _nop, bounds: tuple[float, float] = (-np.inf, np.inf), delta: float = 1.0, ): @@ -494,12 +503,12 @@ def __init__( Args: value: Initial value of the parameter name: Name of the parameter - dtype: data type of the parameter + conversion: data conversion function bounds: Lower and upper bounds of the parameter value delta: Initial variation step """ super(Param, self).__init__( - _Scalar(value), name=name, dtype=dtype, bounds=bounds, delta=delta + _Scalar(value), name=name, conversion=conversion, bounds=bounds, delta=delta ) self._history.append(self._evaluate()) @@ -507,26 +516,27 @@ def _getfun(self, ring=None): return self._evaluate() def _setfun(self, value, ring=None): - self._evaluate = _Scalar(self.dtype(value)) + self._evaluate = _Scalar(self._conversion(value)) - def set_dtype(self, dtype: Callable[[Number], Number]): + def set_conversion(self, conversion: Callable[[Number], Number]): oldv = self._evaluate() - super(Param, self).set_dtype(dtype) - self._evaluate = _Scalar(dtype(oldv)) + super(Param, self).set_conversion(conversion) + self._evaluate = _Scalar(conversion(oldv)) class _PArray(np.ndarray): """Subclass of ndarray which reports to its parent ParamArray""" - def __new__(cls, value, buildfun): - a = buildfun(value) - obj = a.view(cls) + # This is the array obtained with an element get_attribute. + # It is also the one used when setting an item of an array attribute. + + def __new__(cls, value, dtype=np.float64): + obj = np.array(value, dtype=dtype, order="F").view(cls) obj._parent = value return obj def __array_finalize__(self, obj): - if obj is not None: - self._parent = getattr(obj, "_parent", None) + self._parent = getattr(obj, "_parent", None) def __setitem__(self, key, value): super().__setitem__(key, value) @@ -541,20 +551,15 @@ def __repr__(self): class ParamArray(np.ndarray): """Simulate a numpy array where items may be parametrised""" - def __new__(cls, value, buildfun=lambda v: np.array(v, dtype=float, order="F")): - obj = np.array(value, dtype=object, order="F").view(cls) - obj._value = _PArray(obj.view(np.ndarray), buildfun) + def __new__(cls, value, shape=(-1,), dtype=np.float64): + obj = np.asfortranarray(value, dtype=object).reshape(shape).view(cls) + obj._value = _PArray(obj, dtype=dtype) return obj - # noinspection PyUnusedLocal def __array_finalize__(self, obj): - self._value = None - - def set_dtype(self, buildfun): - """Set the data type. Called when a parameter is assigned to an - :py:class:`.Element` attribute""" - # noinspection PyAttributeOutsideInit - self._value = _PArray(self.view(np.ndarray), buildfun) + val = getattr(obj, "_value", None) + if val is not None: + self._value = _PArray(self, dtype=val.dtype) @property def value(self): @@ -566,8 +571,8 @@ def __repr__(self): def __str__(self): it = np.nditer(self, flags=["refs_ok"], order="C") - contents = ", ".join([str(el) for el in it]) - return f"{self.__class__.__name__}([{contents}])" + contents = " ".join([str(el) for el in it]) + return f"[{contents}]" class VariableList(list): From e910b4419538f5118905cc0653f8cf995afe5bd5 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Tue, 21 Nov 2023 11:03:55 +0100 Subject: [PATCH 03/29] Documentation of Variables --- docs/p/notebooks/test_variables.ipynb | 194 +++----------- docs/p/notebooks/variables.ipynb | 370 +++++++++++++++++++++++++- docs/p/variables_parameters.md | 19 +- 3 files changed, 403 insertions(+), 180 deletions(-) diff --git a/docs/p/notebooks/test_variables.ipynb b/docs/p/notebooks/test_variables.ipynb index 88b6fb792..b5135822d 100644 --- a/docs/p/notebooks/test_variables.ipynb +++ b/docs/p/notebooks/test_variables.ipynb @@ -25,7 +25,19 @@ "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "ename": "ImportError", + "evalue": "cannot import name 'match' from 'at.future' (/Users/laurent/dev/libraries/at/pyat/at/future.py)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[2], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mat\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfuture\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Variable, VariableList, ElementVariable, match\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mat\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LocalOpticsObservable, ObservableList\n", + "\u001b[0;31mImportError\u001b[0m: cannot import name 'match' from 'at.future' (/Users/laurent/dev/libraries/at/pyat/at/future.py)" + ] + } + ], "source": [ "from at.future import Variable, VariableList, ElementVariable, match\n", "from at import LocalOpticsObservable, ObservableList" @@ -48,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "2a7220ac-f85c-4aee-983f-1308feab346e", "metadata": {}, "outputs": [], @@ -68,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "4df347e2-6439-438d-97b7-25ad25179a57", "metadata": {}, "outputs": [], @@ -87,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "8dbbe584-e434-4067-a8a5-157cc90e8f05", "metadata": {}, "outputs": [], @@ -107,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "510a6be4-519a-45ad-93f1-0641c6de5e1f", "metadata": {}, "outputs": [], @@ -129,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "7d6fcc63-46be-416e-9754-1fb46eae0189", "metadata": {}, "outputs": [], @@ -148,34 +160,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "ae9630ad-cda8-4477-81fd-051ca67b0848", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "1 constraints, 1 variables, using method lm\n", - "\n", - "`xtol` termination condition is satisfied.\n", - "Function evaluations 13, initial cost 2.6515e+00, final cost 9.8608e-32, first-order optimality 3.26e-16.\n", - "\n", - "Constraints:\n", - "\n", - "location Initial Actual Low bound High bound residual \n", - "beta[y]\n", - " BPM_01 5.30283 3.0 3.0 3.0 1.97062e-11 \n", - "\n", - "Variables:\n", - "\n", - " Name Initial Final Variation\n", - "\n", - " param1 2.651400e+00 9.693839e-01 -1.682016e+00\n" - ] - } - ], + "outputs": [], "source": [ "variables = VariableList([param1])\n", "constraints = ObservableList(hmba_lattice, [obs1])\n", @@ -192,33 +180,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "3205df5f-f494-4900-b743-2468eacddf1c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Drift:\n", - "\tFamName : DR_01\n", - "\tLength : Param(0.9693838658136734, name='param1')\n", - "\tPassMethod : DriftPass\n", - "Monitor:\n", - "\tFamName : BPM_01\n", - "\tLength : 0.0\n", - "\tPassMethod : IdentityPass\n", - "\tOffset : [0 0]\n", - "\tScale : [1 1]\n", - "\tReading : [0 0]\n", - "\tRotation : [0 0]\n", - "Drift:\n", - "\tFamName : DR_02\n", - "\tLength : ParamBase(1.7245681341863266, name='calc1')\n", - "\tPassMethod : DriftPass\n" - ] - } - ], + "outputs": [], "source": [ "for elem in hmba_lattice.select([2,3,4]):\n", " print(elem)" @@ -226,33 +191,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "6eb16268-0ffe-4511-a8dc-43cb58afd633", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHHCAYAAAAveOlqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOydd3hb1f3/X1qWLe89shOyJwGShoRVQpMwAzSlgbISSqFltGnTMsootL+0jLBHS4FA+VIoZbQUCIRACIQkEBJnELKdHe9t2ZIl3d8fV+fKirUtW5J9Xs+jR7J07r1Htu+97/OZOkVRFCQSiUQikUgkfQZ9rCcgkUgkEolEIulZpACUSCQSiUQi6WNIASiRSCQSiUTSx5ACUCKRSCQSiaSPIQWgRCKRSCQSSR9DCkCJRCKRSCSSPoYUgBKJRCKRSCR9DCkAJRKJRCKRSPoYUgBKJBKJRCKR9DGkAJRIJD3CsmXL0Ol07N+/v1v2f++996LT6UIa+49//INRo0ZhMpnIysrqlvl0lf3796PT6Vi2bFmspyKRSHohUgBKJH2Ub7/9lp/85Cf069cPs9lMSUkJV1xxBd9++22X9vv//t//45133onOJLuBHTt2cM011zBs2DCee+45/va3v8V0Pq+++iqPPvpoTOcgkUj6HjrZC1gi6Xu89dZbzJ8/n5ycHBYuXMiQIUPYv38/zz//PDU1Nbz22mtcfPHFEe07LS2NH/7wh50sV06nk/b2dsxmc8iWunC49957+cMf/kCwS9qzzz7LjTfeyO7duznhhBOiPo9wOf/889m2bVsny6iiKNhsNkwmEwaDITaTk0gkvRZjrCcgCY1ly5bxy1/+kvr6+lhPRZLg7N27lyuvvJKhQ4eyevVq8vPztc9uvfVWTjvtNK688kq2bNnC0KFDo3Zcg8EQF0KmsrISIG5dvwKdTkdycnKspyGRSHop0gUcJlVVVdx4440MHDgQs9lMUVERs2bNYs2aNdTW1nLzzTczcuRIUlJSGDhwILfccgsNDQ1e+/j66685++yzycrKIjs7m1mzZrF58+YYfSNJX+PBBx/EarXyt7/9zUv8AeTl5fHXv/6VlpYWHnjgAe19EV+3Y8cOfvSjH5GRkUFubi633norbW1t2jidTkdLSwsvvfQSOp0OnU7HNddcA/iPAfzggw8444wzSE9PJyMjg1NOOYVXX31V+/zzzz9n3rx52jk3YMAAfvWrX9Ha2hr2dx88eDD33HMPAPn5+eh0Ou69915t7uL18duI79Dxe6xZs4ZFixaRn59PamoqF198MVVVVZ22D/T9zjzzTN577z0OHDig/b4GDx4M+I8B/OSTTzjttNNITU0lKyuLiy66iO+++85rjPh77dmzh2uuuYasrCwyMzO59tprsVqtXmNXrFjBjBkzyMrKIi0tjZEjR3LHHXeE8VuVSCSJiLQAhsmll16K3W7npZdeYujQoVRUVLBy5Upqamo4evQoR48e5aGHHmLMmDEcOHCAG264gaNHj/Lvf/8bgObmZmbPns2FF17I008/jcPh4J577mHWrFkcOnQIk8kU428o6e28++67DB48mNNOO83n56effjqDBw/mvffe6/TZj370IwYPHsySJUtYt24djz/+OHV1dbz88suAmlxx3XXXMWXKFK6//noAhg0b5ncuy5YtY8GCBYwdO5bbb7+drKwsNm3axPLly7n88ssBeOONN7Bardx4443k5uby1Vdf8cQTT3D48GHeeOONsL77o48+yssvv8zbb7/NM888Q1paGhMmTAhrH4Kbb76Z7Oxs7rnnHvbv38+jjz7KTTfdxOuvvx7y97vzzjtpaGjg8OHDPPLII4DqQvfHxx9/zJw5cxg6dCj33nsvra2tPPHEE0yfPp2NGzdq4lHwox/9iCFDhrBkyRI2btzI3//+dwoKCvjLX/4CqHGg559/PhMmTOC+++7DbDazZ88e1qxZE9HvRCKRJBCKJGTq6uoUQFm1alXI2/zrX/9SkpKSlPb2dkVRFOXrr79WAOXgwYPamC1btiiAsnv3bu29F198URkwYICSkpKizJ07V3nooYeUzMzMqH0XSd+kvr5eAZSLLroo4LgLL7xQAZTGxkZFURTlnnvuUQDlwgsv9Br385//XAGUzZs3a++lpqYqV199dad9vvjiiwqglJWVaXNJT09Xpk6dqrS2tnqNdblc2mur1dppX0uWLFF0Op1y4MAB7T0xx2CIcVVVVV7vA8o999zTafygQYO8vo/4HjNnzvSa569+9SvFYDAo9fX1YX2/8847Txk0aFCn45aVlSmA8uKLL2rvTZo0SSkoKFBqamq09zZv3qzo9Xrlqquu6vQdFyxY4LXPiy++WMnNzdV+fuSRR3z+LiQSSe9HuoDDIC0tjbS0NN555x1sNltI2zQ0NJCRkYHRqBpbR44cSW5uLs8//zx2u53W1laef/55Ro8era3e169fz8KFC7npppsoLS3lrLPO4o9//GN3fS1JH6KpqQmA9PT0gOPE542NjV7v/+IXv/D6+eabbwbg/fffD3suK1asoKmpidtuu61TrFvHJJGUlBTtdUtLC9XV1Zx66qkoisKmTZvCPm60uP76673medppp+F0Ojlw4AAQ+vcLlWPHjlFaWso111xDTk6O9v6ECRM455xzfP4NbrjhBq+fTzvtNGpqarS/q4iD/M9//oPL5Qp7ThKJJHGRAjAMjEYjy5Yt46WXXiIrK4vp06dzxx13sGXLFp/jq6uruf/++zVXGKg31lWrVvHKK6+QkpJCWloay5cv54MPPtBE4mOPPcbs2bP57W9/y4gRI7jllluYNWtWj3xHSe9GCDshBP3hTygOHz7c6+dhw4ah1+sjqu23d+9eAMaNGxdw3MGDBzXRk5aWRn5+PmeccQZAp/janmTgwIFeP2dnZwNQV1cHhP79QkUIy5EjR3b6bPTo0VRXV9PS0hLWHC+77DKmT5/OddddR2FhIT/+8Y/517/+JcWgRNIHkAIwTC699FKOHj3Kf//7X2bPns2qVauYPHlyp0DtxsZGzjvvPMaMGeMVWN7a2srChQuZPn0669atY82aNYwbN47zzjtPC2r/7rvvmDp1qtf+pk2b1t1fTdIHyMzMpLi42O+iRbBlyxb69etHRkZGwHHdUc6lI06nk3POOYf33nuP3/3ud7zzzjusWLFCO996Qqg4nU6f7/vLaFbiqLJWsDmmpKSwevVqPv74Yy3z+7LLLuOcc87x+70lEknvQArACEhOTuacc87hrrvu4ssvv+Saa67RMgtBtZ7Mnj2b9PR03n77ba/EjldffZX9+/fz4osvcsopp/C9732PV199lbKyMv7zn//E4utI+hjnn38+ZWVlfPHFFz4///zzz9m/fz/nn39+p892797t9fOePXtwuVxeyQehikKRHLJt2za/Y7Zu3cquXbt4+OGH+d3vfsdFF13EzJkzKSkpCekY4ZCdnd2pzJLdbufYsWMR7S+U7weh/74GDRoEwM6dOzt9tmPHDvLy8khNTQ1zlqDX6zn77LNZunQp27dv509/+hOffPIJn376adj7kkgkiYMUgFFgzJgxmuul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Nrbb8dpfR5bZF0gDUOIYGzd2FAHuL/z8c7t/f+InNrEJC/0zB2Y/+0kjjTzyWl9saRFdNSSS05lt21q7cgCFFLKHPexjn+fm1A0qKpvZzBa2tH/jlHNOv0FVYf16T8+iXxEcHExISIjt4evbufasj48P06dPZ32b7WexWFi/fj1z587t9DNz585tNx5g3bp1tvHDhw8nNja23Zja2lp27tzZ5TL7ggFpAL788sskJyfj5+fH7Nmz2bVrV7fjP/nkE8aOHYufnx+pqal8842bm2gPJmprex7jKU6pmt3HPnaww0OT6ZkiiviarzsKdvfnbSyRuINT9nEVldWspoD+m/qwi13s5ZSuK/25Ul+eR5zm/vvv54033uCdd97hyJEj3HnnnRgMBltV8PXXX9/Og3jPPfewdu1annnmGY4ePcpjjz3G7t27WbFiBSBUNe69917+8pe/8NVXX3Hw4EGuv/564uPjWbp0qSe+IjAADUBNn+fRRx8lLS2NyZMns2TJEkpLO08c1vR5br75Zvbu3cvSpUtZunQph7TG1xKJRCKRSCRWrr76av75z3/yyCOPMGXKFPbt28fatWttRRy5ubkUtREWnzdvHh988AGvv/46kydP5tNPP+WLL76waQAC/O53v+Puu+/mtttuY+bMmdTX17N27VqPaQDCAJSBcbc+j0QikUgkEklbVqxYYfPgncrGjRs7vLZs2TKWLVvW5fIUReGJJ57giSeecNcUXWZAeQA1fZ62Wjr26PO0HQ9Cn6er8QBNTU3tysXr6urc8wUkEolEIpFI+gEDygDsDX2eznjyySfblYuPHz/e9clLJBKJRCKR9BMGlAHYVzz00EPtysXT09PdstyVK1cSFxfHJ5984pblSdzLypUriZs2jU8OH3bPsk6z33rlypXEXXaZW7ZPX1G6oZS4uDgOH3btdxiIv+fKjRsH3Jx7QvsdSje6Lu6+cs2a0277uJOV330nt89pzoAyAHtDn6czfH1925WLBwe73uO2tLSU22+/neLiYm677bYui1YknsH2+5SVcdvXX1NaX+/0spqrmk+739q2fSorXd4+fUVzVTMZz2ZQXFzM11/fRn29c79Dff3AO3ZL6+u5/bnnbHN29rv3J9qeQ489c4zmqmbnl1Vfz+1/+tOA+k37ktL6em7/y1/k9jnNGVAGYG/o8/QFqqpyxx13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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "(,\n", - " ,\n", - " )" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "hmba_lattice.plot_beta()" ] @@ -287,7 +229,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "6d365a60-d172-4dee-a80d-d7796d8571ab", "metadata": {}, "outputs": [], @@ -316,7 +258,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "34f10de4-9123-45d4-a9e9-b071836d4f6c", "metadata": { "editable": true, @@ -361,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "816e6de7-9ec4-40bc-be97-f63fcf9d189e", "metadata": {}, "outputs": [], @@ -379,34 +321,10 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "0874d86a-63ad-40b7-adba-e805b5108265", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "1 constraints, 1 variables, using method trf\n", - "\n", - "`gtol` termination condition is satisfied.\n", - "Function evaluations 8, initial cost 2.6515e+00, final cost 7.1885e-27, first-order optimality 8.52e-14.\n", - "\n", - "Constraints:\n", - "\n", - "location Initial Actual Low bound High bound residual \n", - "beta[y]\n", - " BPM_01 5.30283 3.0 3.0 3.0 1.19332e-16 \n", - "\n", - "Variables:\n", - "\n", - " Name Initial Final Variation\n", - "\n", - " DR_01 2.651400e+00 9.693778e-01 -1.682022e+00\n" - ] - } - ], + "outputs": [], "source": [ "variables = VariableList([var0])\n", "constraints = ObservableList(hmba_lattice, [obs1])\n", @@ -423,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "46848c34-339c-41ad-88ba-949e32dba6c3", "metadata": { "editable": true, @@ -432,30 +350,7 @@ }, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Drift:\n", - "\tFamName : DR_01\n", - "\tLength : 0.9693778252605573\n", - "\tPassMethod : DriftPass\n", - "Monitor:\n", - "\tFamName : BPM_01\n", - "\tLength : 0.0\n", - "\tPassMethod : IdentityPass\n", - "\tOffset : [0 0]\n", - "\tScale : [1 1]\n", - "\tReading : [0 0]\n", - "\tRotation : [0 0]\n", - "Drift:\n", - "\tFamName : DR_02\n", - "\tLength : 1.7245741747394425\n", - "\tPassMethod : DriftPass\n" - ] - } - ], + "outputs": [], "source": [ "for elem in hmba_lattice.select([2,3,4]):\n", " print(elem)" @@ -463,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "a1ecc073-fb8a-4883-ad43-85f37392ccb2", "metadata": { "editable": true, @@ -472,30 +367,7 @@ }, "tags": [] }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHHCAYAAAAveOlqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOydd3hb1f3/X1qWLe89shOyJwGShoRVQpMwAzSlgbISSqFltGnTMsootL+0jLBHS4FA+VIoZbQUCIRACIQkEBJnELKdHe9t2ZIl3d8fV+fKirUtW5J9Xs+jR7J07r1Htu+97/OZOkVRFCQSiUQikUgkfQZ9rCcgkUgkEolEIulZpACUSCQSiUQi6WNIASiRSCQSiUTSx5ACUCKRSCQSiaSPIQWgRCKRSCQSSR9DCkCJRCKRSCSSPoYUgBKJRCKRSCR9DCkAJRKJRCKRSPoYUgBKJBKJRCKR9DGkAJRIJD3CsmXL0Ol07N+/v1v2f++996LT6UIa+49//INRo0ZhMpnIysrqlvl0lf3796PT6Vi2bFmspyKRSHohUgBKJH2Ub7/9lp/85Cf069cPs9lMSUkJV1xxBd9++22X9vv//t//45133onOJLuBHTt2cM011zBs2DCee+45/va3v8V0Pq+++iqPPvpoTOcgkUj6HjrZC1gi6Xu89dZbzJ8/n5ycHBYuXMiQIUPYv38/zz//PDU1Nbz22mtcfPHFEe07LS2NH/7wh50sV06nk/b2dsxmc8iWunC49957+cMf/kCwS9qzzz7LjTfeyO7duznhhBOiPo9wOf/889m2bVsny6iiKNhsNkwmEwaDITaTk0gkvRZjrCcgCY1ly5bxy1/+kvr6+lhPRZLg7N27lyuvvJKhQ4eyevVq8vPztc9uvfVWTjvtNK688kq2bNnC0KFDo3Zcg8EQF0KmsrISIG5dvwKdTkdycnKspyGRSHop0gUcJlVVVdx4440MHDgQs9lMUVERs2bNYs2aNdTW1nLzzTczcuRIUlJSGDhwILfccgsNDQ1e+/j66685++yzycrKIjs7m1mzZrF58+YYfSNJX+PBBx/EarXyt7/9zUv8AeTl5fHXv/6VlpYWHnjgAe19EV+3Y8cOfvSjH5GRkUFubi633norbW1t2jidTkdLSwsvvfQSOp0OnU7HNddcA/iPAfzggw8444wzSE9PJyMjg1NOOYVXX31V+/zzzz9n3rx52jk3YMAAfvWrX9Ha2hr2dx88eDD33HMPAPn5+eh0Ou69915t7uL18duI79Dxe6xZs4ZFixaRn59PamoqF198MVVVVZ22D/T9zjzzTN577z0OHDig/b4GDx4M+I8B/OSTTzjttNNITU0lKyuLiy66iO+++85rjPh77dmzh2uuuYasrCwyMzO59tprsVqtXmNXrFjBjBkzyMrKIi0tjZEjR3LHHXeE8VuVSCSJiLQAhsmll16K3W7npZdeYujQoVRUVLBy5Upqamo4evQoR48e5aGHHmLMmDEcOHCAG264gaNHj/Lvf/8bgObmZmbPns2FF17I008/jcPh4J577mHWrFkcOnQIk8kU428o6e28++67DB48mNNOO83n56effjqDBw/mvffe6/TZj370IwYPHsySJUtYt24djz/+OHV1dbz88suAmlxx3XXXMWXKFK6//noAhg0b5ncuy5YtY8GCBYwdO5bbb7+drKwsNm3axPLly7n88ssBeOONN7Bardx4443k5uby1Vdf8cQTT3D48GHeeOONsL77o48+yssvv8zbb7/NM888Q1paGhMmTAhrH4Kbb76Z7Oxs7rnnHvbv38+jjz7KTTfdxOuvvx7y97vzzjtpaGjg8OHDPPLII4DqQvfHxx9/zJw5cxg6dCj33nsvra2tPPHEE0yfPp2NGzdq4lHwox/9iCFDhrBkyRI2btzI3//+dwoKCvjLX/4CqHGg559/PhMmTOC+++7DbDazZ88e1qxZE9HvRCKRJBCKJGTq6uoUQFm1alXI2/zrX/9SkpKSlPb2dkVRFOXrr79WAOXgwYPamC1btiiAsnv3bu29F198URkwYICSkpKizJ07V3nooYeUzMzMqH0XSd+kvr5eAZSLLroo4LgLL7xQAZTGxkZFURTlnnvuUQDlwgsv9Br385//XAGUzZs3a++lpqYqV199dad9vvjiiwqglJWVaXNJT09Xpk6dqrS2tnqNdblc2mur1dppX0uWLFF0Op1y4MAB7T0xx2CIcVVVVV7vA8o999zTafygQYO8vo/4HjNnzvSa569+9SvFYDAo9fX1YX2/8847Txk0aFCn45aVlSmA8uKLL2rvTZo0SSkoKFBqamq09zZv3qzo9Xrlqquu6vQdFyxY4LXPiy++WMnNzdV+fuSRR3z+LiQSSe9HuoDDIC0tjbS0NN555x1sNltI2zQ0NJCRkYHRqBpbR44cSW5uLs8//zx2u53W1laef/55Ro8era3e169fz8KFC7npppsoLS3lrLPO4o9//GN3fS1JH6KpqQmA9PT0gOPE542NjV7v/+IXv/D6+eabbwbg/fffD3suK1asoKmpidtuu61TrFvHJJGUlBTtdUtLC9XV1Zx66qkoisKmTZvCPm60uP76673medppp+F0Ojlw4AAQ+vcLlWPHjlFaWso111xDTk6O9v6ECRM455xzfP4NbrjhBq+fTzvtNGpqarS/q4iD/M9//oPL5Qp7ThKJJHGRAjAMjEYjy5Yt46WXXiIrK4vp06dzxx13sGXLFp/jq6uruf/++zVXGKg31lWrVvHKK6+QkpJCWloay5cv54MPPtBE4mOPPcbs2bP57W9/y4gRI7jllluYNWtWj3xHSe9GCDshBP3hTygOHz7c6+dhw4ah1+sjqu23d+9eAMaNGxdw3MGDBzXRk5aWRn5+PmeccQZAp/janmTgwIFeP2dnZwNQV1cHhP79QkUIy5EjR3b6bPTo0VRXV9PS0hLWHC+77DKmT5/OddddR2FhIT/+8Y/517/+JcWgRNIHkAIwTC699FKOHj3Kf//7X2bPns2qVauYPHlyp0DtxsZGzjvvPMaMGeMVWN7a2srChQuZPn0669atY82aNYwbN47zzjtPC2r/7rvvmDp1qtf+pk2b1t1fTdIHyMzMpLi42O+iRbBlyxb69etHRkZGwHHdUc6lI06nk3POOYf33nuP3/3ud7zzzjusWLFCO996Qqg4nU6f7/vLaFbiqLJWsDmmpKSwevVqPv74Yy3z+7LLLuOcc87x+70lEknvQArACEhOTuacc87hrrvu4ssvv+Saa67RMgtBtZ7Mnj2b9PR03n77ba/EjldffZX9+/fz4osvcsopp/C9732PV199lbKyMv7zn//E4utI+hjnn38+ZWVlfPHFFz4///zzz9m/fz/nn39+p892797t9fOePXtwuVxeyQehikKRHLJt2za/Y7Zu3cquXbt4+OGH+d3vfsdFF13EzJkzKSkpCekY4ZCdnd2pzJLdbufYsWMR7S+U7weh/74GDRoEwM6dOzt9tmPHDvLy8khNTQ1zlqDX6zn77LNZunQp27dv509/+hOffPIJn376adj7kkgkiYMUgFFgzJgxmuul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Nrbb8dpfR5bZF0gDUOIYGzd2FAHuL/z8c7t/f+InNrEJC/0zB2Y/+0kjjTzyWl9saRFdNSSS05lt21q7cgCFFLKHPexjn+fm1A0qKpvZzBa2tH/jlHNOv0FVYf16T8+iXxEcHExISIjt4evbufasj48P06dPZ32b7WexWFi/fj1z587t9DNz585tNx5g3bp1tvHDhw8nNja23Zja2lp27tzZ5TL7ggFpAL788sskJyfj5+fH7Nmz2bVrV7fjP/nkE8aOHYufnx+pqal8842bm2gPJmprex7jKU6pmt3HPnaww0OT6ZkiiviarzsKdvfnbSyRuINT9nEVldWspoD+m/qwi13s5ZSuK/25Ul+eR5zm/vvv54033uCdd97hyJEj3HnnnRgMBltV8PXXX9/Og3jPPfewdu1annnmGY4ePcpjjz3G7t27WbFiBSBUNe69917+8pe/8NVXX3Hw4EGuv/564uPjWbp0qSe+IjAADUBNn+fRRx8lLS2NyZMns2TJEkpLO08c1vR5br75Zvbu3cvSpUtZunQph7TG1xKJRCKRSCRWrr76av75z3/yyCOPMGXKFPbt28fatWttRRy5ubkUtREWnzdvHh988AGvv/46kydP5tNPP+WLL76waQAC/O53v+Puu+/mtttuY+bMmdTX17N27VqPaQDCAJSBcbc+j0QikUgkEklbVqxYYfPgncrGjRs7vLZs2TKWLVvW5fIUReGJJ57giSeecNcUXWZAeQA1fZ62Wjr26PO0HQ9Cn6er8QBNTU3tysXr6urc8wUkEolEIpFI+gEDygDsDX2eznjyySfblYuPHz/e9clLJBKJRCKR9BMGlAHYVzz00EPtysXT09PdstyVK1cSFxfHJ5984pblSdzLypUriZs2jU8OH3bPsk6z33rlypXEXXaZW7ZPX1G6oZS4uDgOH3btdxiIv+fKjRsH3Jx7QvsdSje6Lu6+cs2a0277uJOV330nt89pzoAyAHtDn6czfH1925WLBwe73uO2tLSU22+/neLiYm677bYui1YknsH2+5SVcdvXX1NaX+/0spqrmk+739q2fSorXd4+fUVzVTMZz2ZQXFzM11/fRn29c79Dff3AO3ZL6+u5/bnnbHN29rv3J9qeQ489c4zmqmbnl1Vfz+1/+tOA+k37ktL6em7/y1/k9jnNGVAGYG/o8/QFqqpyxx13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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "(,\n", - " ,\n", - " )" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "hmba_lattice.plot_beta()" ] diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index b811ce26b..379b0ba31 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -20,7 +20,8 @@ "if sys.version_info.minor < 9:\n", " from importlib_resources import files, as_file\n", "else:\n", - " from importlib.resources import files, as_file" + " from importlib.resources import files, as_file\n", + "import numpy as np" ] }, { @@ -50,7 +51,7 @@ "metadata": {}, "outputs": [], "source": [ - "from at.future import ElementVariable, RefptsVariable" + "from at.future import Variable, ElementVariable, RefptsVariable, CustomVariable" ] }, { @@ -197,8 +198,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.53945998]\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5394599781303304]\n", "\tK : 2.5394599781303304\n" ] } @@ -239,8 +240,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.5]\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5]\n", "\tK : 2.5\n" ] } @@ -305,8 +306,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.53945998]\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5394599781303304]\n", "\tK : 2.5394599781303304\n" ] } @@ -400,7 +401,7 @@ "tags": [] }, "source": [ - "The QF1 in newring is not affected.\n", + "The QF1 in `newring` is not affected.\n", "\n", "One can set upper and lower bounds on a variable. Trying to set a value out of the bounds will raise a {py:obj}`ValueError`. The default is (-{py:obj}`numpy.inf`, {py:obj}`numpy.inf`)." ] @@ -443,7 +444,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:235\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 229\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 230\u001b[0m \n\u001b[1;32m 231\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[1;32m 232\u001b[0m \u001b[38;5;124;03m value: New value to be applied on the variable\u001b[39;00m\n\u001b[1;32m 233\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 234\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 235\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 236\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 237\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:244\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 238\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 239\u001b[0m \n\u001b[1;32m 240\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[1;32m 241\u001b[0m \u001b[38;5;124;03m value: New value to be applied on the variable\u001b[39;00m\n\u001b[1;32m 242\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 243\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 244\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 245\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 246\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } @@ -545,7 +546,354 @@ "tags": [] }, "source": [ - "## Custom variables" + "## Custom variables\n", + "We take the example of a variable driving the length of two drifts (or other elements), such\n", + "that their sum stays constant. We choose the variable value as the length of the first element, the other one will be automatically adjusted. This variable allows to shift longitudinally the intermediate\n", + "part of the lattice without affecting its circumference, so we will call the variable an \"element shifter\".\n", + "\n", + "Note that defining correlated {py:class}`.Element` attributes may be easier done with Parameters. However Variables are not restricted to Element attributes, unlike Parameters.\n", + "\n", + "Similarly to the {py:class}`~.element_variables.RefptsVariable`, we will refer to the 2 variable elements by their `refpts`. Alternatively, one could give the elements themselves, as in {py:class}`~.element_variables.ElementVariable`.\n", + "\n", + "### Using the {py:class}`~.variables.CustomVariable`\n", + "\n", + "We need to define two functions for the \"get\" end \"set\" actions, and to give to the {py:class}`~.variables.CustomVariable` constructor the necessary arguments for these functions.\n", + "\n", + "We start with the \"set\" function. Since the elements may appear several times in the\n", + "lattice, we set them all. We are free to choose any useful argument." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8b5235c9-089d-46d2-a761-1044b445e583", + "metadata": {}, + "outputs": [], + "source": [ + "def setvar(value, ref1, ref2, total_length, ring=None):\n", + " if ring is None:\n", + " raise ValueError(\"Can't set values if ring is None\")\n", + " for elem in ring.select(ref1):\n", + " elem.Length = value\n", + " for elem in ring.select(ref2):\n", + " elem.Length = total_length - value" + ] + }, + { + "cell_type": "markdown", + "id": "d62f0b5e-400e-4436-8ba3-726926a79b7a", + "metadata": {}, + "source": [ + "In the \"get\" function, since we chose the first element as the variable value, we may ignore\n", + "the other arguments:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c4b8826a-7c11-4ebc-97bd-7392812f0694", + "metadata": {}, + "outputs": [], + "source": [ + "def getvar(ref1, ref2, total_length, ring=None):\n", + " if ring is None:\n", + " raise ValueError(\"Can't get values if ring is None\")\n", + " return np.mean([elem.Length for elem in ring.select(ref1)])" + ] + }, + { + "cell_type": "markdown", + "id": "16036935-da4e-4968-957e-40602db6fc15", + "metadata": {}, + "source": [ + "We can now select the elements, get the initial conditions and construct the variable:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "05d12006-a59f-4395-b1ac-f487403a5df2", + "metadata": {}, + "outputs": [], + "source": [ + "# select the variable elements\n", + "elem1 = \"DR_01\"\n", + "elem2 = \"DR_02\"\n", + "# Compute the initial total length\n", + "l1 = np.mean([elem.Length for elem in ring.select(elem1)])\n", + "l2 = np.mean([elem.Length for elem in ring.select(elem2)])\n", + "# Create the variable\n", + "elem_shifter1 = CustomVariable(setvar, getvar, elem1, elem2, l1+l2, bounds=(0, l1+l2))" + ] + }, + { + "cell_type": "markdown", + "id": "d3d58857-2447-48b5-a645-6d36ffbec402", + "metadata": {}, + "source": [ + "Here is the initial state of the lattice:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "bf2d12f7-3984-4f0f-b5b8-166ebed9ffd2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drift:\n", + "\tFamName : DR_01\n", + "\tLength : 2.6513999999999998\n", + "\tPassMethod : DriftPass\n", + "Drift:\n", + "\tFamName : DR_02\n", + "\tLength : 0.042552\n", + "\tPassMethod : DriftPass\n", + "\n", + "elem_shifter1.get: 2.6513999999999998\n" + ] + } + ], + "source": [ + "print(f\"{ring[2]}\\n{ring[4]}\")\n", + "print(\"\\nelem_shifter1.get:\", elem_shifter1.get(ring=ring, initial=True))" + ] + }, + { + "cell_type": "markdown", + "id": "1efa97a9-e19d-4004-9a3b-cbd7361903de", + "metadata": {}, + "source": [ + "Now, let's set a new value for the variable and look at the result:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "52d62737-8acc-498b-b41b-a3911180560d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drift:\n", + "\tFamName : DR_01\n", + "\tLength : 2.5\n", + "\tPassMethod : DriftPass\n", + "Drift:\n", + "\tFamName : DR_02\n", + "\tLength : 0.1939519999999999\n", + "\tPassMethod : DriftPass\n", + "\n", + "elem_shifter1.get: 2.5\n" + ] + } + ], + "source": [ + "elem_shifter1.set(2.5, ring=ring)\n", + "print(f\"{ring[2]}\\n{ring[4]}\")\n", + "print(\"\\nelem_shifter1.get:\", elem_shifter1.get(ring=ring))" + ] + }, + { + "cell_type": "markdown", + "id": "70085e74-406b-450b-844b-5880a7847610", + "metadata": {}, + "source": [ + "We can look at the history of the variable" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "786e4424-8840-490c-8ac0-1db350c0ef00", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2.6513999999999998, 2.5]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "elem_shifter1.history" + ] + }, + { + "cell_type": "markdown", + "id": "4f158c03-f238-43c8-912f-8bbcdc98efb0", + "metadata": {}, + "source": [ + "and go back to the initial value" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "9df21be4-0cf4-4499-aa0f-0fd7014f8934", + "metadata": {}, + "outputs": [], + "source": [ + "elem_shifter1.set_initial(ring=ring)" + ] + }, + { + "cell_type": "markdown", + "id": "0dc64ddf-26fd-4c19-a76b-b55f79b89717", + "metadata": {}, + "source": [ + "### By derivation of the {py:class}`Variable` class\n", + "\n", + "We will write a new variable class based on {py:class}`Variable` abstract base class. The main task is to implement the `_setfun` and `_getfun` abstract methods." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "547bdb0a-c1ea-4861-88d3-407329478391", + "metadata": {}, + "outputs": [], + "source": [ + "class ElementShifter(Variable):\n", + " def __init__(self, ref1, ref2, total_length=None, **kwargs):\n", + " \"\"\"Varies the length of the elements *dr1* and *dr2*\n", + " keeping the sum of their lengths equal to *total_length*.\n", + "\n", + " If *total_length* is None, it is set to the initial total length\n", + " \"\"\" \n", + " # Store the indices of the 2 variable elements\n", + " self.ref1 = ref1\n", + " self.ref2 = ref2\n", + " # Compute and store the initial total length\n", + " if total_length is None:\n", + " l1 = np.mean([elem.Length for elem in ring.select(ref1)])\n", + " l2 = np.mean([elem.Length for elem in ring.select(ref2)])\n", + " self.total_length = l1 + l2\n", + " self.length = total_length\n", + " # Initialise the parent class\n", + " super().__init__(bounds=(0.0, self.total_length), **kwargs)\n", + "\n", + " def _setfun(self, value, ring=None):\n", + " if ring is None:\n", + " raise ValueError(\"Can't get values if ring is None\")\n", + " for elem in ring.select(self.ref1):\n", + " elem.Length = value\n", + " for elem in ring.select(self.ref2):\n", + " elem.Length = self.total_length - value\n", + "\n", + " def _getfun(self, ring=None):\n", + " if ring is None:\n", + " raise ValueError(\"Can't get values if ring is None\")\n", + " return np.mean([elem.Length for elem in ring.select(self.ref1)])" + ] + }, + { + "cell_type": "markdown", + "id": "4195859c-3455-46ae-a443-f905926c7517", + "metadata": {}, + "source": [ + "We construct the variable:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "2c61510b-3fab-4d1c-8dd3-5a0dc9e8659f", + "metadata": {}, + "outputs": [], + "source": [ + "elem_shifter2 = ElementShifter(elem1, elem2)" + ] + }, + { + "cell_type": "markdown", + "id": "bed78285-ae04-426a-9c04-b3796d80533b", + "metadata": {}, + "source": [ + "Look at the initial state:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "2f75312e-c088-43fb-b7f8-d0179363167d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drift:\n", + "\tFamName : DR_01\n", + "\tLength : 2.6513999999999998\n", + "\tPassMethod : DriftPass\n", + "Drift:\n", + "\tFamName : DR_02\n", + "\tLength : 0.042552000000000145\n", + "\tPassMethod : DriftPass\n", + "\n", + "elem_shifter2.get: 2.6513999999999998\n" + ] + } + ], + "source": [ + "print(f\"{ring[2]}\\n{ring[4]}\")\n", + "print(\"\\nelem_shifter2.get:\", elem_shifter2.get(ring=ring, initial=True))" + ] + }, + { + "cell_type": "markdown", + "id": "117c2fe4-e88b-4486-bb36-e6dbc6514756", + "metadata": {}, + "source": [ + "Change the variable, and look at the result:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "6633eb54-1249-4d32-9906-173859f8154b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Drift:\n", + "\tFamName : DR_01\n", + "\tLength : 2.5\n", + "\tPassMethod : DriftPass\n", + "Drift:\n", + "\tFamName : DR_02\n", + "\tLength : 0.1939519999999999\n", + "\tPassMethod : DriftPass\n", + "\n", + "elem_shifter2.get: 2.5\n" + ] + } + ], + "source": [ + "elem_shifter2.set(2.5, ring=ring)\n", + "print(f\"{ring[2]}\\n{ring[4]}\")\n", + "print(\"\\nelem_shifter2.get:\", elem_shifter2.get(ring=ring))" + ] + }, + { + "cell_type": "markdown", + "id": "a93a7f09-16a8-41ac-a41c-9aa74092f247", + "metadata": {}, + "source": [ + "Both variables behave similarly. But the derivation allows more control by making use of the\n", + "`__init__` method. For instance here it includes the computation of the initial total length." ] } ], diff --git a/docs/p/variables_parameters.md b/docs/p/variables_parameters.md index c90b76bac..9941b93fc 100644 --- a/docs/p/variables_parameters.md +++ b/docs/p/variables_parameters.md @@ -7,19 +7,19 @@ a lattice. They may be used in parameter scans, matching, response matrices… ```{grid-item-card} Variables :shadow: md -{py:doc}`notebooks/variables` are **references** to any scalar quantity. AT includes two predefined -variable classes referring to scalar attributes of lattice elements: -- an {py:class}`~.element_variables.ElementVariable` is associated to an element object, and acts on - all occurences of this object. But it will not affect any copy, neither shallow - nor deep, of the original object, -- a {py:class}`.RefptsVariable` is not associated to an element object, but to an - element location in a {py:class}`.Lattice`. It acts on any copy of the initial +{py:doc}`notebooks/variables` are **references** to any scalar quantity. AT includes +two predefined variable classes referring to scalar attributes of lattice elements: +- an {py:class}`~.element_variables.ElementVariable` is associated with one or several + {py:class}`.Element` objects, and acts on all occurences of these objects. But it + will not affect any copy, neither shallow nor deep, of the original objects, +- a {py:class}`.RefptsVariable` is not associated with with element objects, but to + their location in a {py:class}`.Lattice`. It acts on any copy of the initial lattice. A *ring* argument must be provided to the *set* and *get* methods to identify the lattice. Variable referring to other quantities may be created by: - deriving the {py:class}`~.variables.Variable` base class. Usually this consist in - overloading the abstract methods *_setfun* and *_getfun* + simply implementing the *_setfun* and *_getfun* abstract methods, - Using the {py:class}`.CustomVariable` class. ``` ```{grid-item-card} Parameters @@ -30,6 +30,9 @@ values as {py:class}`.Element` attributes. Arithmetic combinations of parameters create new read-only parameters of class {py:class}`.ParamBase`, whose value is permanently kept up-to-date. This is useful to introduce correlation between attributes of different elements. + +The use of parameters is restricted to {py:class}`.Element` attributes, while +variables can vary any quantity. ``` ```` From 5f67db95a2aeb9f52c8ed675a1fd9142433fafc1 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Tue, 21 Nov 2023 12:04:04 +0100 Subject: [PATCH 04/29] Documentation of Parameters --- docs/p/notebooks/parameters.ipynb | 100 ++++++++++++++++++++++++++---- docs/p/notebooks/variables.ipynb | 18 +++++- pyat/at/lattice/elements.py | 11 +++- 3 files changed, 113 insertions(+), 16 deletions(-) diff --git a/docs/p/notebooks/parameters.ipynb b/docs/p/notebooks/parameters.ipynb index 9f290e110..8c0527254 100644 --- a/docs/p/notebooks/parameters.ipynb +++ b/docs/p/notebooks/parameters.ipynb @@ -553,6 +553,84 @@ "print(ring[117])" ] }, + { + "cell_type": "markdown", + "id": "619e9168-a58a-48ca-95ab-631a53543475", + "metadata": {}, + "source": [ + "Once a Parameter is assigned to an attribute, it acquires the type and the constraints of\n", + "the attribute. For instance:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "eebc8b1a-71a9-4e0d-b4d9-000c366c99aa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "14 \n" + ] + } + ], + "source": [ + "num_int_steps = Param(14.4)\n", + "ring.set_parameter(at.Multipole, 'NumIntSteps', num_int_steps)\n", + "print(ring[5].NumIntSteps, type(ring[5].NumIntSteps))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "bec9d7c3-08f4-4088-826b-e0c8903134d4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "raises-exception" + ] + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Value must be greater of equal to 0", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mnum_int_steps\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalue\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m5\u001b[39m\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:268\u001b[0m, in \u001b[0;36mVariable.value\u001b[0;34m(self, value)\u001b[0m\n\u001b[1;32m 266\u001b[0m \u001b[38;5;129m@value\u001b[39m\u001b[38;5;241m.\u001b[39msetter\n\u001b[1;32m 267\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mvalue\u001b[39m(\u001b[38;5;28mself\u001b[39m, value: Number):\n\u001b[0;32m--> 268\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:245\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 243\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[1;32m 244\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 245\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_setfun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 246\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:519\u001b[0m, in \u001b[0;36mParam._setfun\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 518\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_setfun\u001b[39m(\u001b[38;5;28mself\u001b[39m, value, ring\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[0;32m--> 519\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_evaluate \u001b[38;5;241m=\u001b[39m _Scalar(\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_conversion\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m)\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/elements.py:263\u001b[0m, in \u001b[0;36mElement.\u001b[0;34m(v)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Base class for AT elements\"\"\"\u001b[39;00m\n\u001b[1;32m 253\u001b[0m _BUILD_ATTRIBUTES \u001b[38;5;241m=\u001b[39m [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mFamName\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m 254\u001b[0m _conversions \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(FamName\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mstr\u001b[39m, PassMethod\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mstr\u001b[39m, Length\u001b[38;5;241m=\u001b[39m_float,\n\u001b[1;32m 255\u001b[0m R1\u001b[38;5;241m=\u001b[39m_array66, R2\u001b[38;5;241m=\u001b[39m_array66,\n\u001b[1;32m 256\u001b[0m T1\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m6\u001b[39m,)),\n\u001b[1;32m 257\u001b[0m T2\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m6\u001b[39m,)),\n\u001b[1;32m 258\u001b[0m RApertures\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m4\u001b[39m,)),\n\u001b[1;32m 259\u001b[0m EApertures\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m2\u001b[39m,)),\n\u001b[1;32m 260\u001b[0m KickAngle\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m2\u001b[39m,)),\n\u001b[1;32m 261\u001b[0m PolynomB\u001b[38;5;241m=\u001b[39m_array, PolynomA\u001b[38;5;241m=\u001b[39m_array,\n\u001b[1;32m 262\u001b[0m BendingAngle\u001b[38;5;241m=\u001b[39m_float,\n\u001b[0;32m--> 263\u001b[0m MaxOrder\u001b[38;5;241m=\u001b[39m_int, NumIntSteps\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: \u001b[43m_int\u001b[49m\u001b[43m(\u001b[49m\u001b[43mv\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mmin\u001b[39;49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m,\n\u001b[1;32m 264\u001b[0m Energy\u001b[38;5;241m=\u001b[39m_float,\n\u001b[1;32m 265\u001b[0m )\n\u001b[1;32m 267\u001b[0m _entrance_fields \u001b[38;5;241m=\u001b[39m [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mT1\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mR1\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m 268\u001b[0m _exit_fields \u001b[38;5;241m=\u001b[39m [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mT2\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mR2\u001b[39m\u001b[38;5;124m'\u001b[39m]\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/elements.py:37\u001b[0m, in \u001b[0;36m_int\u001b[0;34m(value, min, max)\u001b[0m\n\u001b[1;32m 35\u001b[0m intv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mint\u001b[39m(value)\n\u001b[1;32m 36\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mmin\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m intv \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mmin\u001b[39m:\n\u001b[0;32m---> 37\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValue must be greater of equal to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mmin\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 38\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mmax\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m intv \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mmax\u001b[39m:\n\u001b[1;32m 39\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValue must be smaller of equal to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mmax\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mValueError\u001b[0m: Value must be greater of equal to 0" + ] + } + ], + "source": [ + "num_int_steps.value = -5" + ] + }, + { + "cell_type": "markdown", + "id": "bca705f4-3074-4476-9c32-81075152cdd7", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The parameter behaves as the attribute." + ] + }, { "cell_type": "markdown", "id": "a60a830b-9ade-47ce-89ed-1e4d4a000c4e", @@ -572,7 +650,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 16, "id": "a4bea7d9-59c3-4af3-a96d-f2e4ee4c2c14", "metadata": { "editable": true, @@ -613,7 +691,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 17, "id": "a4343df3-a2b7-4b4f-9b74-3bdd3e20cfb2", "metadata": { "editable": true, @@ -659,7 +737,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 18, "id": "8c0eab2f-4ed0-4441-9dd6-a2c81ecdb765", "metadata": { "editable": true, @@ -707,7 +785,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 19, "id": "088bfb6e-1ede-4429-8f38-c179be2c0b9d", "metadata": { "editable": true, @@ -775,7 +853,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 20, "id": "744f6754-e681-4db4-b80d-842eb0bcb124", "metadata": { "editable": true, @@ -793,7 +871,7 @@ "\tFamName : QF1A\n", "\tLength : Param(0.311896, name='lf1')\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", + "\tNumIntSteps : Param(14, name='param8')\n", "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", @@ -804,7 +882,7 @@ "\tFamName : QF1A\n", "\tLength : Param(0.311896, name='lf1')\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", + "\tNumIntSteps : Param(14, name='param8')\n", "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", @@ -815,7 +893,7 @@ "\tFamName : QF1E\n", "\tLength : 0.311896\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", + "\tNumIntSteps : 14\n", "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", @@ -850,7 +928,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "id": "7be2ebce-fe54-4060-8481-a344c4ebf25f", "metadata": { "editable": true, @@ -893,7 +971,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 22, "id": "f191ea2a-997e-4547-b674-3e16ca2d629c", "metadata": { "editable": true, @@ -934,7 +1012,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 23, "id": "f92600ae-28da-439f-ae7c-6a787162c888", "metadata": { "editable": true, diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index 379b0ba31..f015400f4 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -535,6 +535,14 @@ "print(f\"newring: {newring[5].PolynomB[1]}\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "d877abc7-2578-45af-8b3b-e6e4e52014ca", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "204d24e6-1e9b-4838-a950-3a8e1df5cac4", @@ -560,14 +568,20 @@ "We need to define two functions for the \"get\" end \"set\" actions, and to give to the {py:class}`~.variables.CustomVariable` constructor the necessary arguments for these functions.\n", "\n", "We start with the \"set\" function. Since the elements may appear several times in the\n", - "lattice, we set them all. We are free to choose any useful argument." + "lattice, we set them all. We are free to declare any need argument." ] }, { "cell_type": "code", "execution_count": 19, "id": "8b5235c9-089d-46d2-a761-1044b445e583", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ "def setvar(value, ref1, ref2, total_length, ring=None):\n", diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 8cb559627..08013eb8a 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -31,8 +31,13 @@ def _float(value): return float(value) -def _int(value): - return int(value) +def _int(value, min:Optional[int] = None, max: Optional[int]=None): + intv = int(value) + if min is not None and intv < min: + raise ValueError(f"Value must be greater of equal to {min}") + if max is not None and intv > max: + raise ValueError(f"Value must be smaller of equal to {max}") + return intv class LongtMotion(ABC): @@ -255,7 +260,7 @@ class Element(object): KickAngle=lambda v: _array(v, (2,)), PolynomB=_array, PolynomA=_array, BendingAngle=_float, - MaxOrder=_int, NumIntSteps=_int, + MaxOrder=_int, NumIntSteps=lambda v: _int(v, min=0), Energy=_float, ) From 6aff2d4b7c2120557dea1eac562031b50deb6875 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Wed, 22 Nov 2023 20:17:39 +0100 Subject: [PATCH 05/29] PEP8 --- pyat/at/lattice/elements.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 08013eb8a..0a3eee0b7 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -31,12 +31,12 @@ def _float(value): return float(value) -def _int(value, min:Optional[int] = None, max: Optional[int]=None): +def _int(value, vmin: Optional[int] = None, vmax: Optional[int] = None): intv = int(value) - if min is not None and intv < min: - raise ValueError(f"Value must be greater of equal to {min}") - if max is not None and intv > max: - raise ValueError(f"Value must be smaller of equal to {max}") + if vmin is not None and intv < vmin: + raise ValueError(f"Value must be greater of equal to {vmin}") + if vmax is not None and intv > vmax: + raise ValueError(f"Value must be smaller of equal to {vmax}") return intv @@ -260,7 +260,7 @@ class Element(object): KickAngle=lambda v: _array(v, (2,)), PolynomB=_array, PolynomA=_array, BendingAngle=_float, - MaxOrder=_int, NumIntSteps=lambda v: _int(v, min=0), + MaxOrder=_int, NumIntSteps=lambda v: _int(v, vmin=0), Energy=_float, ) From 005d49e21faed9604c426ec4bab6a65e524b4732 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Tue, 28 Nov 2023 18:16:22 +0100 Subject: [PATCH 06/29] [0.0, k] instead of [0, k] --- pyat/at/lattice/elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 0a3eee0b7..24bc584bd 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -951,7 +951,7 @@ def __init__(self, family_name: str, length: float, kwargs.setdefault('EntranceAngle', 0.0) kwargs.setdefault('ExitAngle', 0.0) kwargs.setdefault('PassMethod', 'BndMPoleSymplectic4Pass') - super(Dipole, self).__init__(family_name, length, [], [0, k], **kwargs) + super(Dipole, self).__init__(family_name, length, [], [0.0, k], **kwargs) def items(self) -> Generator[tuple, None, None]: yield from super().items() From db1ee8cdd708e6588e4011d93f337d1b6b9a14ab Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Tue, 28 Nov 2023 20:00:39 +0100 Subject: [PATCH 07/29] Removed parameters --- docs/p/examples.rst | 9 - docs/p/index.rst | 5 +- docs/p/notebooks/observables.ipynb | 864 -------------------- docs/p/notebooks/parameters.ipynb | 1085 ------------------------- docs/p/notebooks/test_variables.ipynb | 397 --------- docs/p/notebooks/variables.ipynb | 16 +- docs/p/variables_parameters.md | 54 -- pyat/at/lattice/__init__.py | 2 +- pyat/at/lattice/axisdef.py | 33 +- pyat/at/lattice/element_variables.py | 3 - pyat/at/lattice/elements.py | 177 +--- pyat/at/lattice/lattice_object.py | 67 +- pyat/at/lattice/utils.py | 16 +- pyat/at/lattice/variables.py | 241 ------ 14 files changed, 58 insertions(+), 2911 deletions(-) delete mode 100644 docs/p/examples.rst delete mode 100644 docs/p/notebooks/observables.ipynb delete mode 100644 docs/p/notebooks/parameters.ipynb delete mode 100644 docs/p/notebooks/test_variables.ipynb delete mode 100644 docs/p/variables_parameters.md diff --git a/docs/p/examples.rst b/docs/p/examples.rst deleted file mode 100644 index 87dd30a9b..000000000 --- a/docs/p/examples.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _example-notebooks: - -Example notebooks -================= - -.. toctree:: - :maxdepth: 1 - - notebooks/test_variables diff --git a/docs/p/index.rst b/docs/p/index.rst index 68663ead5..1e9efcaf3 100644 --- a/docs/p/index.rst +++ b/docs/p/index.rst @@ -26,9 +26,7 @@ Sub-packages howto/Installation howto/Primer - variables_parameters - notebooks/observables - examples + notebooks/variables .. toctree:: :maxdepth: 2 @@ -45,7 +43,6 @@ Sub-packages :recursive: at.lattice - at.latticetools at.tracking at.physics at.load diff --git a/docs/p/notebooks/observables.ipynb b/docs/p/notebooks/observables.ipynb deleted file mode 100644 index 68297067e..000000000 --- a/docs/p/notebooks/observables.ipynb +++ /dev/null @@ -1,864 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "b3c94f57-7a41-4600-af2b-b2a0034ffe31", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "# Observables\n", - "\n", - "Observables provide a unified way to access a large quantity of figures resulting from various\n", - "computations on lattices. They may be used in parameter scans, matching, response matrices…\n", - "\n", - "AT provides a number of specific observables sharing a common interface, inherited from the\n", - "{py:class}`.Observable` base class. They are:\n", - "- {py:class}`.OrbitObservable`: {math}`x_{co}`…,\n", - "- {py:obj}`.GlobalOpticsObservable`: tunes, damping times…,\n", - "- {py:class}`.LocalOpticsObservable`: {math}`\\beta`, {math}`\\eta`…,\n", - "- {py:class}`.MatrixObservable`: {math}`T_{ij}`…,\n", - "- {py:class}`.TrajectoryObservable`: {math}`x, p_x`…,\n", - "- {py:class}`.EmittanceObservable`: {math}`\\epsilon_x`…,\n", - "- {py:class}`.LatticeObservable`: attributes of lattice elements,\n", - "- {py:class}`.GeometryObservable`\n", - "\n", - "An Observable has optional {py:attr}`~.Observable.target`, {py:attr}`~.Observable.weight` and {py:attr}`~.Observable.bounds` attributes for matching. After evaluation, it has the following main properties:\n", - "- {py:attr}`~.Observable.value`\n", - "- {py:attr}`~.Observable.weighted_value`: `value / weight`\n", - "- {py:attr}`~.Observable.deviation`: `value - target`\n", - "- {py:attr}`~.Observable.weighted_deviation`: `(value - target)/weight`\n", - "- {py:attr}`~.Observable.residual`: `((value - target)/weight)**2`\n", - "\n", - "Custom Observables may be created by providing the adequate evaluation function.\n", - "\n", - "For evaluation, observables must be grouped in an {py:class}`.ObservableList` which optimises the computation, avoiding redundant function calls. {py:class}`.ObservableList` provides the {py:meth}`~.ObservableList.evaluate` method, and the\n", - "{py:attr}`~.ObservableList.values`, {py:attr}`~.ObservableList.deviations`,\n", - "{py:attr}`~.ObservableList.residuals` and {py:attr}`~.ObservableList.sum_residuals` properties, among others.\n", - "\n", - "This example shows how to declare various Observables, how to evaluate them and how to extract and display their values.\n", - "\n", - "## Setup the environment" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "c44ec0ec-f0c7-476b-8de5-ac1d8b174dc0", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "import at\n", - "import sys\n", - "import numpy as np\n", - "if sys.version_info.minor < 9:\n", - " from importlib_resources import files, as_file\n", - "else:\n", - " from importlib.resources import files, as_file" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "6e2a6c6c-7460-45f6-a217-e5a07f7afb8d", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "from at import Observable, ObservableList, OrbitObservable, GlobalOpticsObservable, LocalOpticsObservable\n", - "from at import MatrixObservable, TrajectoryObservable, EmittanceObservable, LatticeObservable, GeometryObservable" - ] - }, - { - "cell_type": "markdown", - "id": "19a61c12-f62e-4114-9d87-1b3d19233991", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Load a test lattice" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "8bccc37e-3070-4b62-bdda-4da603b48a34", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "fname = 'hmba.mat'\n", - "with as_file(files('machine_data') / fname) as path:\n", - " hmba_lattice = at.load_lattice(path)" - ] - }, - { - "cell_type": "markdown", - "id": "3dedb508-981a-4c25-9dc0-5c0b2b25a561", - "metadata": {}, - "source": [ - "## Create Observables\n", - "\n", - "Create an empty ObservableList:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "b6b6d2e5-842a-400d-87ff-d34b131921d1", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "obs1=ObservableList(hmba_lattice)" - ] - }, - { - "cell_type": "markdown", - "id": "7fe3c40f-8eb2-4fbb-96d7-e20bf1424f63", - "metadata": {}, - "source": [ - "Horizontal closed orbit on all Monitors:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "99e48ce2-1336-4617-9114-cd15e9a3966d", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "obs1.append(OrbitObservable(at.Monitor, axis='x'))" - ] - }, - { - "cell_type": "markdown", - "id": "c29bc37a-8d3a-4330-b6c2-94408255e7c2", - "metadata": {}, - "source": [ - "Create a 2{sup}`nd` ObservableList:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "559a78af-bb1f-4555-99f7-be0cd55f75d4", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "obs2=ObservableList(hmba_lattice)" - ] - }, - { - "cell_type": "markdown", - "id": "d1216c95-a8e8-4ae5-b61e-b18fe0582d22", - "metadata": {}, - "source": [ - "Vertical $\\beta$ at all monitors, with a target and bounds.\n", - "\n", - "The vertical $\\beta$ is constrained in the interval\n", - "[*target*+*low_bound* *target*+*up_bound*], so here [*-Infinity 7.0*]\n", - "\n", - "The residual will be zero within the interval." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2c237c62-3b4f-4864-bea3-93325a8a7fce", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "obs2.append(LocalOpticsObservable(at.Monitor, 'beta', plane=1, target=7.0, bounds=(-np.inf, 0.0)))" - ] - }, - { - "cell_type": "markdown", - "id": "900683b5-6c61-427c-901d-edb22390738f", - "metadata": { - "tags": [] - }, - "source": [ - "check the concatenation of ObservableLists:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "99f87e5c-c004-423f-af52-7b77ff1f745d", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "allobs = obs1 + obs2" - ] - }, - { - "cell_type": "markdown", - "id": "7cee0839-8b64-4c65-bb85-869062ad168a", - "metadata": {}, - "source": [ - "Full transfer matrix to `BPM02`:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "828dd898-810e-4653-b717-fe955bed6bab", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(MatrixObservable(\"BPM_02\"))" - ] - }, - { - "cell_type": "markdown", - "id": "32e2c4af-dd58-4c67-a767-a585bf110fa1", - "metadata": {}, - "source": [ - "Maximum of vertical beta on monitors:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "7c763bd9-0299-47e0-9979-fae6ae230979", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "allobs.append(LocalOpticsObservable(at.Monitor, 'beta', plane='v', statfun=np.amax))" - ] - }, - { - "cell_type": "markdown", - "id": "8907cbd5-05d4-4775-a089-9f062b271394", - "metadata": {}, - "source": [ - "First 4 coordinates of the closed orbit at Quadrupoles:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "d9ddeb7c-ea88-4d09-8333-753ad4e0d8e4", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(LocalOpticsObservable(at.Quadrupole, 'closed_orbit', plane=slice(4), target=0.0, weight=1.e-6))" - ] - }, - { - "cell_type": "markdown", - "id": "5b3f80a0-f7ea-48c4-bafb-03e89c04e4e0", - "metadata": {}, - "source": [ - "Position along the lattice of all quadrupoles:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "7b3e32e8-5fd6-4d42-a92f-1691bdc330cf", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "allobs.append(LocalOpticsObservable(at.Quadrupole, 's_pos'))" - ] - }, - { - "cell_type": "markdown", - "id": "c630e18c-8e32-499a-b45a-d19f76b441fe", - "metadata": {}, - "source": [ - "Phase advance between elements 33 and 101 in all planes:\n", - "\n", - "First, let's define a custom evaluation function:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "e9c41097-7d89-4f3f-85cd-aff7aa9a84df", - "metadata": {}, - "outputs": [], - "source": [ - "def phase_advance(ring, elemdata):\n", - " mu = elemdata.mu\n", - " return (mu[-1] - mu[0])" - ] - }, - { - "cell_type": "markdown", - "id": "a19eca9f-fa1f-4a5f-8558-d7d19b58042b", - "metadata": {}, - "source": [ - "Then create the Observable. The evaluation function should return one value per refpoint (2 here). Alternatively,\n", - "it may return a single value (the difference, here), but then one must set `summary=True`." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "185b505f-1bea-4c72-baee-3a24d1a5bb8d", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(LocalOpticsObservable([33, 101], phase_advance, use_integer=True, summary=True))" - ] - }, - { - "cell_type": "markdown", - "id": "2f2a5e1a-f45d-4e6d-a27b-5565d784da44", - "metadata": {}, - "source": [ - "Horizontal tune with the integer part:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "7263d336-7e84-47f2-a949-ed79a13635aa", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(GlobalOpticsObservable('tune', plane=0, use_integer=True))" - ] - }, - { - "cell_type": "markdown", - "id": "43050abf-9a4c-4d86-b358-b7b22dfd4c1f", - "metadata": {}, - "source": [ - "Total phase advance at the end of the lattice (all planes):" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "265be4dd-b67c-4188-a3e4-15c1e4eac3e4", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(LocalOpticsObservable(at.End, 'mu', use_integer=True))" - ] - }, - { - "cell_type": "markdown", - "id": "b2d9ed85-db9b-4ab7-9425-53d20b703eae", - "metadata": {}, - "source": [ - "Chromaticity in all planes:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "28949857-7e9c-499c-ba69-e900932683fd", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(GlobalOpticsObservable('chromaticity', plane=None))" - ] - }, - { - "cell_type": "markdown", - "id": "8cbe4399-5670-4fd2-b745-3b1e8f9eb5b1", - "metadata": {}, - "source": [ - "Average of sextupole strengths:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "e271c5ad-e6c8-4c8a-a66b-671711db3832", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(LatticeObservable(at.Sextupole, 'H', statfun=np.mean))" - ] - }, - { - "cell_type": "markdown", - "id": "9ddd860b-c3f9-4c49-839b-64b5f478a9b5", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "Strengths of all sextupoles:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "d68df47a-4215-489c-8975-9748b592b949", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "allobs.append(LatticeObservable(at.Sextupole, 'PolynomB', index=2))" - ] - }, - { - "cell_type": "markdown", - "id": "7339554d-c12f-49fb-aade-47691bb5bbce", - "metadata": {}, - "source": [ - "Horizontal emittance:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "47ce8596-266c-4944-82d8-f186e34f09b8", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(EmittanceObservable('emittances', plane='x'))" - ] - }, - { - "cell_type": "markdown", - "id": "862e9a69-f018-4350-af40-89ae79e8ef39", - "metadata": {}, - "source": [ - "Ring circumference:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "f6844781-ee09-4ab6-979b-9fea8034bb7d", - "metadata": {}, - "outputs": [], - "source": [ - "def circumference(ring):\n", - " return ring.get_s_pos(len(ring))[0]\n", - "allobs.append(Observable(circumference))" - ] - }, - { - "cell_type": "markdown", - "id": "510ebd1a-eb20-43ad-9b74-d79fee8047a4", - "metadata": {}, - "source": [ - "p{sub}`x` component of the trajectory on all monitors:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "40a0895a-9aac-4509-86dd-9fb1f84a4e84", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(TrajectoryObservable(at.Monitor,axis='px'))" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "fef988a4-1413-4478-a8da-8c865b27d74b", - "metadata": {}, - "outputs": [], - "source": [ - "allobs.append(GeometryObservable(at.Monitor, 'x'))" - ] - }, - { - "cell_type": "markdown", - "id": "48167f3a-24fa-4963-97ec-1e78033f8895", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Evaluation\n", - "\n", - "An input trajectory is required for the trajectory Observable" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "f78c247c-0228-499f-9455-0f39c57ff6fd", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "r_in = np.zeros(6)\n", - "r_in[0] = 0.001\n", - "r_in[2] = 0.001\n", - "allobs.evaluate(hmba_lattice.enable_6d(copy=True), r_in=r_in, dp=0.0, initial=True)" - ] - }, - { - "cell_type": "markdown", - "id": "637f7487-80e1-4ca7-af5f-a490be880c90", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "### Extract a single Observable value\n", - "(phase advance between elements 3 and 101):" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "582b7317-5527-4afd-ad8c-278f0408dd11", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([3.10650512e+00, 2.99742405e+00, 1.40411771e-14])" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "allobs[6].value" - ] - }, - { - "cell_type": "markdown", - "id": "f9a17436-c827-47f7-8eb2-85ce9637c734", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "### Get the list of all Observable values:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "edfba8ca-ee8f-4064-9076-d2bc469739a4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[array([-3.02189723e-09, 4.50695010e-07, 4.08205708e-07, 2.37899777e-08,\n", - " -1.31783789e-08, 2.47230566e-08, -2.95310962e-08, -4.05598220e-07,\n", - " -4.47398212e-07, -2.24850930e-09]),\n", - " array([5.30279703, 7.17604152, 6.55087808, 2.31448878, 3.40498445,\n", - " 3.405044 , 2.3146451 , 6.55106241, 7.17614175, 5.30283837]),\n", - " array([[[-1.08194106e+00, 3.18809568e+00, 0.00000000e+00,\n", - " 0.00000000e+00, 8.22407787e-02, -1.72158979e-05],\n", - " [-6.80522735e-01, 1.08099571e+00, 0.00000000e+00,\n", - " 0.00000000e+00, 4.90131193e-02, -1.02601760e-05],\n", - " [ 0.00000000e+00, 0.00000000e+00, 7.55929650e-01,\n", - " 3.87059271e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [ 0.00000000e+00, 0.00000000e+00, -6.79279293e-01,\n", - " -2.15524755e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [-1.13309009e-08, -1.08615600e-07, 0.00000000e+00,\n", - " 0.00000000e+00, 9.99995907e-01, -2.09334442e-04],\n", - " [ 2.93742206e-03, 6.73567963e-02, 0.00000000e+00,\n", - " 0.00000000e+00, 2.83582558e-04, 9.99999941e-01]]]),\n", - " 7.176141753295557,\n", - " array([[-3.02810319e-09, -1.45845183e-10, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-1.78478316e-09, 2.17266708e-09, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [ 2.06038228e-07, 1.68970687e-07, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [ 4.63461827e-07, 2.65112155e-07, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [ 4.92846847e-07, -2.48829214e-09, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [ 2.39068148e-07, -2.69323468e-07, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [ 2.24947413e-08, -2.55612556e-08, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-2.95803326e-08, -2.51343244e-08, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [ 3.86437699e-08, 4.66071902e-08, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-1.14693229e-08, -5.89624819e-08, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-1.92472337e-07, -1.36142888e-07, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-4.58527547e-07, -2.67107467e-07, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-4.90185628e-07, -2.06357872e-09, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-2.42425126e-07, 2.63615764e-07, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-8.04954315e-10, -1.24373421e-09, 0.00000000e+00,\n", - " 0.00000000e+00],\n", - " [-1.92735224e-09, -1.83198298e-09, 0.00000000e+00,\n", - " 0.00000000e+00]]),\n", - " array([ 2.693952 , 3.4295565 , 5.52309303, 6.52741246, 7.08941246,\n", - " 8.14326589, 10.34278161, 11.93982486, 13.94182609, 15.63285034,\n", - " 18.00333506, 19.05718849, 19.61918849, 20.67257592, 22.71704445,\n", - " 23.36843995]),\n", - " array([3.10650512e+00, 2.99742405e+00, 1.40411771e-14]),\n", - " 0.3815630185798568,\n", - " array([[2.39743115e+00, 5.36820522e+00, 6.85248782e-04]]),\n", - " array([1.79196871e-01, 1.22425546e-01, 1.69458465e-04]),\n", - " -25.369212247139345,\n", - " array([-78.95535579, 77.03724443, -74.18952538, -74.18952538,\n", - " 77.03724443, -78.95535579]),\n", - " 1.3203910509097569e-10,\n", - " 26.374287952316944,\n", - " array([ 0.00000000e+00, -6.94370474e-04, 6.07151649e-04, 2.38468291e-04,\n", - " -6.81824078e-04, -4.78921797e-04, 4.41491589e-04, 7.01582199e-04,\n", - " -6.05543962e-04, -9.78684823e-05]),\n", - " array([ 2.6514 , 6.4783308 , 7.51380991, 10.28830988, 12.71979153,\n", - " 13.62739043, 16.04912109, 18.79203055, 19.81402164, 23.58054559])]" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "allobs.values" - ] - }, - { - "cell_type": "markdown", - "id": "22eb2441-39d9-432d-ae3d-fdee153f49cb", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "### Get a pretty output of all Observables.\n", - "\n", - "As no variation was made, *Actual* values are always equal to *Initial* values.\n", - "\n", - "The residual is zero for all Observables for which no *target* was specified" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "6126bf27-37e9-4c20-8997-c82ba9fad5b1", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "location Initial Actual Low bound High bound residual \n", - "orbit[x]\n", - " BPM_01 -3.0219e-09 -3.0219e-09 - - 0.0 \n", - " BPM_02 4.50695e-07 4.50695e-07 - - 0.0 \n", - " BPM_03 4.08206e-07 4.08206e-07 - - 0.0 \n", - " BPM_04 2.379e-08 2.379e-08 - - 0.0 \n", - " BPM_05 -1.31784e-08 -1.31784e-08 - - 0.0 \n", - " BPM_06 2.47231e-08 2.47231e-08 - - 0.0 \n", - " BPM_07 -2.95311e-08 -2.95311e-08 - - 0.0 \n", - " BPM_08 -4.05598e-07 -4.05598e-07 - - 0.0 \n", - " BPM_09 -4.47398e-07 -4.47398e-07 - - 0.0 \n", - " BPM_10 -2.24851e-09 -2.24851e-09 - - 0.0 \n", - "beta[y]\n", - " BPM_01 5.3028 5.3028 -inf 7.0 0.0 \n", - " BPM_02 7.17604 7.17604 -inf 7.0 0.0309906 \n", - " BPM_03 6.55088 6.55088 -inf 7.0 0.0 \n", - " BPM_04 2.31449 2.31449 -inf 7.0 0.0 \n", - " BPM_05 3.40498 3.40498 -inf 7.0 0.0 \n", - " BPM_06 3.40504 3.40504 -inf 7.0 0.0 \n", - " BPM_07 2.31465 2.31465 -inf 7.0 0.0 \n", - " BPM_08 6.55106 6.55106 -inf 7.0 0.0 \n", - " BPM_09 7.17614 7.17614 -inf 7.0 0.0310259 \n", - " BPM_10 5.30284 5.30284 -inf 7.0 0.0 \n", - "matrix\n", - " BPM_02 [-1.082 ...] [-1.082 ...] - - [ 0.0 ...] \n", - "amax(beta[y])\n", - " 7.17614 7.17614 - - 0.0 \n", - "closed_orbit[slice(None, 4, None)]\n", - " QF1A [-3.028e-09 ...] [-3.028e-09 ...] [ 0.0 ...] [ 0.0 ...] [ 9.169e-06 ...] \n", - " QD2A [-1.785e-09 ...] [-1.785e-09 ...] [ 0.0 ...] [ 0.0 ...] [ 3.185e-06 ...] \n", - " QD3A [ 2.06e-07 ...] [ 2.06e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.04245 ...] \n", - " QF4A [ 4.635e-07 ...] [ 4.635e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2148 ...] \n", - " QF4B [ 4.928e-07 ...] [ 4.928e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2429 ...] \n", - " QD5B [ 2.391e-07 ...] [ 2.391e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.05715 ...] \n", - " QF6B [ 2.249e-08 ...] [ 2.249e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.000506 ...] \n", - " QF8B [-2.958e-08 ...] [-2.958e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.000875 ...] \n", - " QF8D [ 3.864e-08 ...] [ 3.864e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.001493 ...] \n", - " QF6D [-1.147e-08 ...] [-1.147e-08 ...] [ 0.0 ...] [ 0.0 ...] [ 0.0001315 ...] \n", - " QD5D [-1.925e-07 ...] [-1.925e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.03705 ...] \n", - " QF4D [-4.585e-07 ...] [-4.585e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2102 ...] \n", - " QF4E [-4.902e-07 ...] [-4.902e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.2403 ...] \n", - " QD3E [-2.424e-07 ...] [-2.424e-07 ...] [ 0.0 ...] [ 0.0 ...] [ 0.05877 ...] \n", - " QD2E [-8.05e-10 ...] [-8.05e-10 ...] [ 0.0 ...] [ 0.0 ...] [ 6.48e-07 ...] \n", - " QF1E [-1.927e-09 ...] [-1.927e-09 ...] [ 0.0 ...] [ 0.0 ...] [ 3.715e-06 ...] \n", - "s_pos\n", - " QF1A 2.69395 2.69395 - - 0.0 \n", - " QD2A 3.42956 3.42956 - - 0.0 \n", - " QD3A 5.52309 5.52309 - - 0.0 \n", - " QF4A 6.52741 6.52741 - - 0.0 \n", - " QF4B 7.08941 7.08941 - - 0.0 \n", - " QD5B 8.14327 8.14327 - - 0.0 \n", - " QF6B 10.3428 10.3428 - - 0.0 \n", - " QF8B 11.9398 11.9398 - - 0.0 \n", - " QF8D 13.9418 13.9418 - - 0.0 \n", - " QF6D 15.6329 15.6329 - - 0.0 \n", - " QD5D 18.0033 18.0033 - - 0.0 \n", - " QF4D 19.0572 19.0572 - - 0.0 \n", - " QF4E 19.6192 19.6192 - - 0.0 \n", - " QD3E 20.6726 20.6726 - - 0.0 \n", - " QD2E 22.717 22.717 - - 0.0 \n", - " QF1E 23.3684 23.3684 - - 0.0 \n", - "phase_advance\n", - " [ 3.107 ...] [ 3.107 ...] - - [ 0.0 ...] \n", - "tune[x]\n", - " 0.381563 0.381563 - - 0.0 \n", - "mu\n", - " End [ 2.397 ...] [ 2.397 ...] - - [ 0.0 ...] \n", - "chromaticity\n", - " [ 0.1792 ...] [ 0.1792 ...] - - [ 0.0 ...] \n", - "mean(H)\n", - " -25.3692 -25.3692 - - 0.0 \n", - "PolynomB[2]\n", - " SD1A -78.9554 -78.9554 - - 0.0 \n", - " SF2A 77.0372 77.0372 - - 0.0 \n", - " SD1B -74.1895 -74.1895 - - 0.0 \n", - " SD1D -74.1895 -74.1895 - - 0.0 \n", - " SF2E 77.0372 77.0372 - - 0.0 \n", - " SD1E -78.9554 -78.9554 - - 0.0 \n", - "emittances[x]\n", - " 1.32039e-10 1.32039e-10 - - 0.0 \n", - "circumference\n", - " 26.3743 26.3743 - - 0.0 \n", - "trajectory[px]\n", - " BPM_01 0.0 0.0 - - 0.0 \n", - " BPM_02 -0.00069437 -0.00069437 - - 0.0 \n", - " BPM_03 0.000607152 0.000607152 - - 0.0 \n", - " BPM_04 0.000238468 0.000238468 - - 0.0 \n", - " BPM_05 -0.000681824 -0.000681824 - - 0.0 \n", - " BPM_06 -0.000478922 -0.000478922 - - 0.0 \n", - " BPM_07 0.000441492 0.000441492 - - 0.0 \n", - " BPM_08 0.000701582 0.000701582 - - 0.0 \n", - " BPM_09 -0.000605544 -0.000605544 - - 0.0 \n", - " BPM_10 -9.78685e-05 -9.78685e-05 - - 0.0 \n", - "geometry[x]\n", - " BPM_01 2.6514 2.6514 - - 0.0 \n", - " BPM_02 6.47833 6.47833 - - 0.0 \n", - " BPM_03 7.51381 7.51381 - - 0.0 \n", - " BPM_04 10.2883 10.2883 - - 0.0 \n", - " BPM_05 12.7198 12.7198 - - 0.0 \n", - " BPM_06 13.6274 13.6274 - - 0.0 \n", - " BPM_07 16.0491 16.0491 - - 0.0 \n", - " BPM_08 18.792 18.792 - - 0.0 \n", - " BPM_09 19.814 19.814 - - 0.0 \n", - " BPM_10 23.5805 23.5805 - - 0.0 \n" - ] - } - ], - "source": [ - "print(allobs)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.18" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/p/notebooks/parameters.ipynb b/docs/p/notebooks/parameters.ipynb deleted file mode 100644 index 8c0527254..000000000 --- a/docs/p/notebooks/parameters.ipynb +++ /dev/null @@ -1,1085 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "bc479f4a-a609-468f-a430-d71ad22b5cf2", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "import at\n", - "import sys\n", - "from at import Param, ParamArray\n", - "if sys.version_info.minor < 9:\n", - " from importlib_resources import files, as_file\n", - "else:\n", - " from importlib.resources import files, as_file" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "3399ebbe-81d6-453f-80d4-fe4832d0543e", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "fname = 'hmba.mat'\n", - "with as_file(files('machine_data') / fname) as path:\n", - " ring = at.load_lattice(path)" - ] - }, - { - "cell_type": "markdown", - "id": "ba73f6c0-aa60-4ced-8158-dfddcade4bd9", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "# Parameters\n", - "\n", - "Parameters are objects of class {py:class}`.Param` which can be used instead of numeric values as {py:class}`.Element` attributes.\n", - "\n", - "Parameters are initialised with a **scalar** numeric value. They have an optional name, only used to identify them in printed output:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "27f3abc6-7cfe-4632-82a1-dc17038983a2", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Param(2.5, name='total length') Param(1.0, name='param2')\n" - ] - } - ], - "source": [ - "total_length = Param(2.5, name='total length')\n", - "dlength = Param(1.0)\n", - "print(total_length, dlength)" - ] - }, - { - "cell_type": "markdown", - "id": "c61cb66b-2e98-4f1b-9d6d-9d896085f6b4", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "The value of a parameter can be read or modified through its {py:attr}`~.variables.Variable.value` property. {py:meth}`~.variables.Variable.set` and {py:meth}`~.variables.Variable.get` methods are also available:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "772a79ce-91ee-47fd-80bf-a7a76e935339", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2.5\n", - "Param(2.4, name='total length')\n" - ] - }, - { - "data": { - "text/plain": [ - "2.3" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(total_length.value)\n", - "total_length.value = 2.4\n", - "print(total_length)\n", - "total_length.set(2.3)\n", - "total_length.get()" - ] - }, - { - "cell_type": "markdown", - "id": "fa523242-3579-4e0f-aca8-2ecd230cd93c", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "Arithmetic combinations of parameters create new read-only parameters of class {py:class}`.ParamBase`, whose value is permanently kept up-to-date:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "a229555f-cbb2-4e36-b9d8-0ca3c0c7cf76", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ParamBase(1.2999999999999998, name='calc1')\n", - "ParamBase(1.4, name='calc1')\n" - ] - } - ], - "source": [ - "qlength = total_length - dlength\n", - "print(qlength)\n", - "dlength.value = 0.9\n", - "print(qlength)" - ] - }, - { - "cell_type": "markdown", - "id": "57eff2ca-87af-4667-84e8-4e86e5b2db74", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "Parameters may be assigned to {py:class}`.Element` attributes, for instance on initialisation:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "c49dc3e7-78ea-4e40-9e82-438674b5ba7e", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Drift:\n", - "\tFamName : DR1\n", - "\tLength : Param(0.9, name='param2')\n", - "\tPassMethod : DriftPass\n", - "Quadrupole:\n", - "\tFamName : QF1\n", - "\tLength : ParamBase(1.4, name='calc1')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0.0 0.0]\n", - "\tPolynomB : [0.0 0.6]\n", - "\tK : 0.6\n" - ] - } - ], - "source": [ - "dr1 = at.Drift('DR1', dlength)\n", - "qf1 = at.Quadrupole('QF1', qlength, 0.6)\n", - "print(dr1)\n", - "print(qf1)" - ] - }, - { - "cell_type": "markdown", - "id": "6c0f9595-9abc-4fee-967e-195dc1db0030", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "The {py:class}`.Element` attributes keep their type so that all the processing of elements either in python functions or in C integrators is unchanged:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "63baa52e-c1f1-4d0d-8470-fc80116239ec", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.9 \n" - ] - } - ], - "source": [ - "print(dr1.Length, type(dr1.Length))" - ] - }, - { - "cell_type": "markdown", - "id": "ca1a092d-2773-41bb-b5ac-65c4005593be", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Assigning parameters\n", - "\n", - "### To a single element\n", - "Parameters may be assigned to {py:class}`.Element` attributes in several ways:\n", - "\n", - "**At element creation:**" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "07e9ced4-f048-46ec-b187-3eb60314ee92", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "dr2 = at.Drift('DR2', dlength)" - ] - }, - { - "cell_type": "markdown", - "id": "a7846d65-d686-4496-94fa-16f18dd564e6", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "**By converting a numeric attribute into a parameter:**" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "24db8abd-6b85-4196-b1ae-a316e0ddd8ea", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Param(0.5, name='param3')\n" - ] - } - ], - "source": [ - "qd1 = at.Quadrupole('QD1', 0.5, -0.4)\n", - "ql = qd1.parametrise('Length')\n", - "print(ql)" - ] - }, - { - "cell_type": "markdown", - "id": "71a08731-9aed-497c-8084-15d6de0e8215", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "**By normal assignment:**" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "90b0b580-16cb-4d4b-a80b-a6262fe3dad5", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QD1\n", - "\tLength : Param(0.2, name='param5')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0.0 0.0]\n", - "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", - "\tK : Param(-0.5, name='quad strength')\n" - ] - } - ], - "source": [ - "qstrength = Param(-0.5, name='quad strength')\n", - "qd1.Length = Param(0.2)\n", - "qd1.PolynomB[1] = qstrength\n", - "print(qd1)" - ] - }, - { - "cell_type": "markdown", - "id": "3064a2f2-b913-4982-978c-268b8e76b1c1", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "**With the {py:meth}`~.Element.set_parameter` method:**" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "43c18e94-cd26-4639-98a9-bf93c736115d", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QD1\n", - "\tLength : ParamBase(1.4, name='calc1')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0.0 0.0]\n", - "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", - "\tK : Param(-0.5, name='quad strength')\n" - ] - } - ], - "source": [ - "qd1.set_parameter('Length', qlength)\n", - "print(qd1)" - ] - }, - { - "cell_type": "markdown", - "id": "6318ac4e-332e-44cb-b77f-d5efc4d4e799", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "### To selected elements of a {py:class}`.Lattice`\n", - "To act on several elements in a single step, {py:class}`.Lattice` methods similar to {py:class}`.Element` methods are available\n", - "\n", - "**Convert numeric attributes into parameters:**\n", - "\n", - "The attribute of all the selected elements is replaced by a single parameter whose initial value is the average of the original values." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "07b4603f-8089-451a-a53f-fb462729b79e", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Param(2.5394599781303304, name='kf1')\n", - "Quadrupole:\n", - "\tFamName : QF1A\n", - "\tLength : 0.311896\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 Param(2.5394599781303304, name='kf1')]\n", - "\tK : Param(2.5394599781303304, name='kf1')\n" - ] - } - ], - "source": [ - "kf1 = ring.parametrise('QF1[AE]', 'PolynomB', index=1, name='kf1')\n", - "print(kf1)\n", - "print(ring[5])" - ] - }, - { - "cell_type": "markdown", - "id": "b8931812-b5e8-4b0d-9561-bd2b4a1506e4", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "**Use the {py:meth}`~.Lattice.set_parameter` method:**\n", - "\n", - "The attribute of all the selected elements is replaced by the provided parameter," - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "276e2b71-ff91-4f55-a459-cc62d8bcc2d3", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QF1E\n", - "\tLength : Param(0.311896, name='lf1')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 Param(2.5394599781303304, name='kf1')]\n", - "\tK : Param(2.5394599781303304, name='kf1')\n" - ] - } - ], - "source": [ - "lf1 = Param(0.311896, name='lf1')\n", - "ring.set_parameter('QF1[AE]', 'Length', lf1)\n", - "print(ring[117])" - ] - }, - { - "cell_type": "markdown", - "id": "619e9168-a58a-48ca-95ab-631a53543475", - "metadata": {}, - "source": [ - "Once a Parameter is assigned to an attribute, it acquires the type and the constraints of\n", - "the attribute. For instance:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "eebc8b1a-71a9-4e0d-b4d9-000c366c99aa", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "14 \n" - ] - } - ], - "source": [ - "num_int_steps = Param(14.4)\n", - "ring.set_parameter(at.Multipole, 'NumIntSteps', num_int_steps)\n", - "print(ring[5].NumIntSteps, type(ring[5].NumIntSteps))" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "bec9d7c3-08f4-4088-826b-e0c8903134d4", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [ - "raises-exception" - ] - }, - "outputs": [ - { - "ename": "ValueError", - "evalue": "Value must be greater of equal to 0", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mnum_int_steps\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalue\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m5\u001b[39m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:268\u001b[0m, in \u001b[0;36mVariable.value\u001b[0;34m(self, value)\u001b[0m\n\u001b[1;32m 266\u001b[0m \u001b[38;5;129m@value\u001b[39m\u001b[38;5;241m.\u001b[39msetter\n\u001b[1;32m 267\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mvalue\u001b[39m(\u001b[38;5;28mself\u001b[39m, value: Number):\n\u001b[0;32m--> 268\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:245\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 243\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[1;32m 244\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 245\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_setfun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 246\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:519\u001b[0m, in \u001b[0;36mParam._setfun\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 518\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_setfun\u001b[39m(\u001b[38;5;28mself\u001b[39m, value, ring\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[0;32m--> 519\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_evaluate \u001b[38;5;241m=\u001b[39m _Scalar(\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_conversion\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m)\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/elements.py:263\u001b[0m, in \u001b[0;36mElement.\u001b[0;34m(v)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Base class for AT elements\"\"\"\u001b[39;00m\n\u001b[1;32m 253\u001b[0m _BUILD_ATTRIBUTES \u001b[38;5;241m=\u001b[39m [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mFamName\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m 254\u001b[0m _conversions \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(FamName\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mstr\u001b[39m, PassMethod\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mstr\u001b[39m, Length\u001b[38;5;241m=\u001b[39m_float,\n\u001b[1;32m 255\u001b[0m R1\u001b[38;5;241m=\u001b[39m_array66, R2\u001b[38;5;241m=\u001b[39m_array66,\n\u001b[1;32m 256\u001b[0m T1\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m6\u001b[39m,)),\n\u001b[1;32m 257\u001b[0m T2\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m6\u001b[39m,)),\n\u001b[1;32m 258\u001b[0m RApertures\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m4\u001b[39m,)),\n\u001b[1;32m 259\u001b[0m EApertures\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m2\u001b[39m,)),\n\u001b[1;32m 260\u001b[0m KickAngle\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: _array(v, (\u001b[38;5;241m2\u001b[39m,)),\n\u001b[1;32m 261\u001b[0m PolynomB\u001b[38;5;241m=\u001b[39m_array, PolynomA\u001b[38;5;241m=\u001b[39m_array,\n\u001b[1;32m 262\u001b[0m BendingAngle\u001b[38;5;241m=\u001b[39m_float,\n\u001b[0;32m--> 263\u001b[0m MaxOrder\u001b[38;5;241m=\u001b[39m_int, NumIntSteps\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mlambda\u001b[39;00m v: \u001b[43m_int\u001b[49m\u001b[43m(\u001b[49m\u001b[43mv\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mmin\u001b[39;49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m,\n\u001b[1;32m 264\u001b[0m Energy\u001b[38;5;241m=\u001b[39m_float,\n\u001b[1;32m 265\u001b[0m )\n\u001b[1;32m 267\u001b[0m _entrance_fields \u001b[38;5;241m=\u001b[39m [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mT1\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mR1\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m 268\u001b[0m _exit_fields \u001b[38;5;241m=\u001b[39m [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mT2\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mR2\u001b[39m\u001b[38;5;124m'\u001b[39m]\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/elements.py:37\u001b[0m, in \u001b[0;36m_int\u001b[0;34m(value, min, max)\u001b[0m\n\u001b[1;32m 35\u001b[0m intv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mint\u001b[39m(value)\n\u001b[1;32m 36\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mmin\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m intv \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mmin\u001b[39m:\n\u001b[0;32m---> 37\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValue must be greater of equal to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mmin\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 38\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mmax\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m intv \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mmax\u001b[39m:\n\u001b[1;32m 39\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValue must be smaller of equal to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mmax\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[0;31mValueError\u001b[0m: Value must be greater of equal to 0" - ] - } - ], - "source": [ - "num_int_steps.value = -5" - ] - }, - { - "cell_type": "markdown", - "id": "bca705f4-3074-4476-9c32-81075152cdd7", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "The parameter behaves as the attribute." - ] - }, - { - "cell_type": "markdown", - "id": "a60a830b-9ade-47ce-89ed-1e4d4a000c4e", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Retrieving parameters\n", - "\n", - "Since the values of {py:class}`.Element` attributes keep their original type, they cannot be used to access the underlying parameter. The only way to retrieve it is to use the {py:meth}`~.Element.get_parameter` method. a {py:obj}`TypeError` is raised if the attribute is not\n", - "a Parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "a4bea7d9-59c3-4af3-a96d-f2e4ee4c2c14", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ParamBase(1.4, name='calc1')\n", - "ql is qlength: True\n" - ] - } - ], - "source": [ - "ql = qd1.get_parameter('Length')\n", - "print(ql)\n", - "print(\"ql is qlength:\", ql is qlength)" - ] - }, - { - "cell_type": "markdown", - "id": "2ba193a5-5d84-4d7a-95e3-11dfeb7241fa", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "This also works for items in array attributes:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "a4343df3-a2b7-4b4f-9b74-3bdd3e20cfb2", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Param(-0.5, name='quad strength')\n", - "qs is qstrength: True\n" - ] - } - ], - "source": [ - "qs = qd1.get_parameter('PolynomB', index=1)\n", - "print(qs)\n", - "print(\"qs is qstrength:\", qs is qstrength)" - ] - }, - { - "cell_type": "markdown", - "id": "fe98da38-b39a-4e22-a009-16495ccfda58", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Checking parametrisation\n", - "\n", - "The {py:meth}`~.Element.is_parametrised` method may be applied to:\n", - "- a full element: it returns true is any of its attributes is a parameter,\n", - "- an array attribute: it returns true if any of its items is a parameter,\n", - "- a scalar attribute or an item of an array." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "8c0eab2f-4ed0-4441-9dd6-a2c81ecdb765", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n", - "True\n", - "False\n" - ] - } - ], - "source": [ - "print(qd1.is_parametrised())\n", - "print(qd1.is_parametrised('PolynomB', index=1))\n", - "print(qd1.is_parametrised('PolynomB', index=0))" - ] - }, - { - "cell_type": "markdown", - "id": "b62883e5-8dd4-42f2-9675-bdbe4d9be71c", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Removing parameters\n", - "Removing the parameters will \"freeze\" the element at its current value. The {py:meth}`~.Element.unparametrise` method is defined for both {py:class}`.Element` and {py:class}`.Lattice`, and may be applied to:\n", - "- a full element: all the parameters are replaced by their value,\n", - "- an array attribute: the whole {py:class}`.ParamArray` is replaced by a numpy array,\n", - "- a scalar attribute or an item of an array.\n", - "\n", - "### In an Element" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "088bfb6e-1ede-4429-8f38-c179be2c0b9d", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QD1\n", - "\tLength : ParamBase(1.4, name='calc1')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0.0 0.0]\n", - "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", - "\tK : Param(-0.5, name='quad strength')\n", - "Quadrupole:\n", - "\tFamName : QD1\n", - "\tLength : 1.4\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0.0 0.0]\n", - "\tPolynomB : [0.0 Param(-0.5, name='quad strength')]\n", - "\tK : Param(-0.5, name='quad strength')\n", - "Quadrupole:\n", - "\tFamName : QD1\n", - "\tLength : 1.4\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 10\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0.0 0.0]\n", - "\tPolynomB : [0.0 -0.5]\n", - "\tK : -0.5\n" - ] - } - ], - "source": [ - "print(qd1)\n", - "qd1.unparametrise('Length')\n", - "print(qd1)\n", - "qd1.unparametrise('PolynomB', 1)\n", - "print(qd1)" - ] - }, - { - "cell_type": "markdown", - "id": "295b9a0e-23a9-4321-9715-7a6197d2859e", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "### In a Lattice" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "744f6754-e681-4db4-b80d-842eb0bcb124", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QF1A\n", - "\tLength : Param(0.311896, name='lf1')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : Param(14, name='param8')\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 Param(2.5394599781303304, name='kf1')]\n", - "\tK : Param(2.5394599781303304, name='kf1')\n", - "Quadrupole:\n", - "\tFamName : QF1A\n", - "\tLength : Param(0.311896, name='lf1')\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : Param(14, name='param8')\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5394599781303304]\n", - "\tK : 2.5394599781303304\n", - "Quadrupole:\n", - "\tFamName : QF1E\n", - "\tLength : 0.311896\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 14\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5394599781303304]\n", - "\tK : 2.5394599781303304\n" - ] - } - ], - "source": [ - "print(ring[5])\n", - "ring.unparametrise('QF1[AE]', 'PolynomB', index=1)\n", - "print(ring[5])\n", - "ring.unparametrise('QF1[AE]')\n", - "print(ring[117])" - ] - }, - { - "cell_type": "markdown", - "id": "564674a3-0bf2-48b8-9978-8f2c9cdc4fd0", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Parameter history\n", - "Parameter values are kept in an {py:attr}`~.variables.Variable.history` buffer. The properties {py:attr}`~.variables.Variable.initial_value`, {py:attr}`~.variables.Variable.last_value` and {py:attr}`~.variables.Variable.previous_value` are also available:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "7be2ebce-fe54-4060-8481-a344c4ebf25f", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[1.0, 0.9, 1.1]\n", - "1.0\n", - "0.9\n" - ] - } - ], - "source": [ - "dlength.value = 1.1\n", - "print(dlength.history)\n", - "print(dlength.initial_value)\n", - "print(dlength.previous_value)" - ] - }, - { - "cell_type": "markdown", - "id": "d1898549-19dd-46a9-8e93-8356dfae6d8a", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "After varying parameters, in matching for instance, the current status can be printed:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "f191ea2a-997e-4547-b674-3e16ca2d629c", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " Name Initial Final Variation\n", - "\n", - " param2 1.000000e+00 1.100000e+00 1.000000e-01\n" - ] - } - ], - "source": [ - "print(dlength.status())" - ] - }, - { - "cell_type": "markdown", - "id": "0d155699-1005-431f-a00a-74d6406f5c21", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "Parameters may be reset to a previous history value with the {py:meth}`~.variables.Variable.set_initial` and {py:meth}`~.variables.Variable.set_previous` methods. The history is shortened accordingly." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "f92600ae-28da-439f-ae7c-6a787162c888", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Param(0.9, name='param2') [1.0, 0.9]\n", - "Param(1.0, name='param2') [1.0]\n" - ] - } - ], - "source": [ - "dlength.set_previous()\n", - "print(dlength, dlength.history)\n", - "dlength.set_initial()\n", - "print(dlength, dlength.history)" - ] - }, - { - "cell_type": "markdown", - "id": "a6f096fe-6cf0-4470-9fcb-3c2e4d77b6e5", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Copying and Saving\n", - "\n", - "In a shallow copy of an {py:class}`.Element`, the parameter attributes are shared between the {py:class}`.Element` and its copy.\n", - "\n", - "In a deep copy of an {py:class}`.Element`, a copy of the original parameter is set in the {py:class}`.Element` copy.\n", - "\n", - "When saving a lattice with parametrised elements, as a `.mat`, a `.m` or `.repr` file, all parametrisation is removed, and a \"frozen\" state of the lattice is saved.\n", - "\n", - "In pickle dumps of an {py:class}`.Element`, parameters are preserved." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.18" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/p/notebooks/test_variables.ipynb b/docs/p/notebooks/test_variables.ipynb deleted file mode 100644 index b5135822d..000000000 --- a/docs/p/notebooks/test_variables.ipynb +++ /dev/null @@ -1,397 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "d6034009-fea2-4195-9571-2a1a2b21acc3", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import at\n", - "import at.plot\n", - "import sys\n", - "if sys.version_info.minor < 9:\n", - " from importlib_resources import files, as_file\n", - "else:\n", - " from importlib.resources import files, as_file" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "353791c1-ac4b-4576-9d8d-c7bc8dcd07ef", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "ename": "ImportError", - "evalue": "cannot import name 'match' from 'at.future' (/Users/laurent/dev/libraries/at/pyat/at/future.py)", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[2], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mat\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfuture\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Variable, VariableList, ElementVariable, match\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mat\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LocalOpticsObservable, ObservableList\n", - "\u001b[0;31mImportError\u001b[0m: cannot import name 'match' from 'at.future' (/Users/laurent/dev/libraries/at/pyat/at/future.py)" - ] - } - ], - "source": [ - "from at.future import Variable, VariableList, ElementVariable, match\n", - "from at import LocalOpticsObservable, ObservableList" - ] - }, - { - "cell_type": "markdown", - "id": "cde6c736-f5b3-4d97-8c40-ef6215322401", - "metadata": {}, - "source": [ - "# Correlated variables\n", - "\n", - "In this example of correlation between variables, we vary the length of the two drifts\n", - "surrounding a monitor but keep the sum of their lengths constant.\n", - "\n", - "Using these 2 correlated variables, we will match a constraint on the monitor.\n", - "\n", - "## Load a test lattice" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2a7220ac-f85c-4aee-983f-1308feab346e", - "metadata": {}, - "outputs": [], - "source": [ - "fname = 'hmba.mat'\n", - "with as_file(files('machine_data') / fname) as path:\n", - " hmba_lattice = at.load_lattice(path)" - ] - }, - { - "cell_type": "markdown", - "id": "18d7c32f-5a73-4fc2-a7c4-6f7d435fe2c3", - "metadata": {}, - "source": [ - "Isolate the two drifts" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4df347e2-6439-438d-97b7-25ad25179a57", - "metadata": {}, - "outputs": [], - "source": [ - "dr1 = hmba_lattice[\"DR_01\"][0]\n", - "dr2 = hmba_lattice[\"DR_02\"][0]" - ] - }, - { - "cell_type": "markdown", - "id": "c8df614e-afec-47f1-bbb2-9e9349c91fb9", - "metadata": {}, - "source": [ - "Get the total length to be preserved" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8dbbe584-e434-4067-a8a5-157cc90e8f05", - "metadata": {}, - "outputs": [], - "source": [ - "l1 = dr1.Length\n", - "l2 = dr2.Length\n", - "ltot = l1 + l2" - ] - }, - { - "cell_type": "markdown", - "id": "0fdc04ae-df36-4f1e-b568-3b275739c646", - "metadata": {}, - "source": [ - "Create a constraint {math}`\\beta_y=3.0` on `BPM_01`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "510a6be4-519a-45ad-93f1-0641c6de5e1f", - "metadata": {}, - "outputs": [], - "source": [ - "obs1 = LocalOpticsObservable('BPM_01', 'beta', plane='v', target=3.0)" - ] - }, - { - "cell_type": "markdown", - "id": "9d398afa-1063-4af6-84c4-46159c1cd622", - "metadata": {}, - "source": [ - "## Method 1: using parameters\n", - "\n", - "We parametrise the lengths of the drifts surrounding the monitor\n", - "\n", - "### Parametrise the two drifts:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7d6fcc63-46be-416e-9754-1fb46eae0189", - "metadata": {}, - "outputs": [], - "source": [ - "param1 = dr1.parametrise('Length')\n", - "dr2.Length = ltot-param1" - ] - }, - { - "cell_type": "markdown", - "id": "d3487819-a2fd-4410-9e37-6a36c3bd86f8", - "metadata": {}, - "source": [ - "### Run the matching" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ae9630ad-cda8-4477-81fd-051ca67b0848", - "metadata": {}, - "outputs": [], - "source": [ - "variables = VariableList([param1])\n", - "constraints = ObservableList(hmba_lattice, [obs1])\n", - "match(hmba_lattice, variables, constraints, verbose=1)" - ] - }, - { - "cell_type": "markdown", - "id": "f908b523-b10d-4fc5-bade-99450231e044", - "metadata": {}, - "source": [ - "### Show the modified lattice" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3205df5f-f494-4900-b743-2468eacddf1c", - "metadata": {}, - "outputs": [], - "source": [ - "for elem in hmba_lattice.select([2,3,4]):\n", - " print(elem)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6eb16268-0ffe-4511-a8dc-43cb58afd633", - "metadata": {}, - "outputs": [], - "source": [ - "hmba_lattice.plot_beta()" - ] - }, - { - "cell_type": "markdown", - "id": "17403824-c7bd-4e36-91b9-4c406e3e64e4", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "The first BPM is moved to a location where {math}`\\beta_y=3.0`" - ] - }, - { - "cell_type": "markdown", - "id": "c8d6c102-e9b3-4e68-bee0-42409d282a38", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "Restore the lattice" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6d365a60-d172-4dee-a80d-d7796d8571ab", - "metadata": {}, - "outputs": [], - "source": [ - "dr1.Length = l1\n", - "dr2.Length = l2" - ] - }, - { - "cell_type": "markdown", - "id": "ae3b0459-6b13-4ead-905b-235b20cea2d9", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "## Method 2: custom variable\n", - "\n", - "We define a new variable class which will act on the two elements and fulfil the constraint\n", - "\n", - "### Define a variable coupling two drift lengths so that their sum is constant:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34f10de4-9123-45d4-a9e9-b071836d4f6c", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "class ElementShifter(Variable):\n", - " def __init__(self, dr1, dr2, total_length=None, **kwargs):\n", - " \"\"\"Varies the length of the elements *dr1* and *dr2*\n", - " keeping the sum of their lengths equal to *total_length*.\n", - "\n", - " If *total_length* is None, it is set to the initial total length\n", - " \"\"\" \n", - " # store indexes of the 2 variable elements\n", - " self.dr1 = dr1\n", - " self.dr2 = dr2\n", - " # store the initial total length\n", - " if total_length is None:\n", - " total_length = dr1.Length + dr2.Length\n", - " self.length = total_length\n", - " super().__init__(bounds=(0.0, total_length), **kwargs)\n", - "\n", - " def _setfun(self, value, ring=None):\n", - " dr1.Length = value\n", - " dr2.Length = self.length - value\n", - "\n", - " def _getfun(self, ring=None):\n", - " return dr1.Length" - ] - }, - { - "cell_type": "markdown", - "id": "1928d81f-dac5-464e-af96-2809306cac15", - "metadata": {}, - "source": [ - "Create a variable moving the monitor `BPM_01`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "816e6de7-9ec4-40bc-be97-f63fcf9d189e", - "metadata": {}, - "outputs": [], - "source": [ - "var0 = ElementShifter(dr1, dr2, name='DR_01', total_length=ltot)" - ] - }, - { - "cell_type": "markdown", - "id": "ebbd2f87-8541-4e89-8b30-acf81815549c", - "metadata": {}, - "source": [ - "### Run the matching" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0874d86a-63ad-40b7-adba-e805b5108265", - "metadata": {}, - "outputs": [], - "source": [ - "variables = VariableList([var0])\n", - "constraints = ObservableList(hmba_lattice, [obs1])\n", - "match(hmba_lattice, variables, constraints, verbose=1)" - ] - }, - { - "cell_type": "markdown", - "id": "ebef68f5-440c-47d9-be1c-cafffb258950", - "metadata": {}, - "source": [ - "### Show the modified lattice" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "46848c34-339c-41ad-88ba-949e32dba6c3", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "for elem in hmba_lattice.select([2,3,4]):\n", - " print(elem)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1ecc073-fb8a-4883-ad43-85f37392ccb2", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "hmba_lattice.plot_beta()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.18" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index f015400f4..c21795026 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -198,8 +198,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5394599781303304]\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.53945998]\n", "\tK : 2.5394599781303304\n" ] } @@ -240,8 +240,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5]\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.5]\n", "\tK : 2.5\n" ] } @@ -306,8 +306,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5394599781303304]\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.53945998]\n", "\tK : 2.5394599781303304\n" ] } @@ -444,7 +444,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:244\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 238\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 239\u001b[0m \n\u001b[1;32m 240\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[1;32m 241\u001b[0m \u001b[38;5;124;03m value: New value to be applied on the variable\u001b[39;00m\n\u001b[1;32m 242\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 243\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 244\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 245\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 246\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:181\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 175\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 176\u001b[0m \n\u001b[1;32m 177\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[1;32m 178\u001b[0m \u001b[38;5;124;03m value: New value to be applied on the variable\u001b[39;00m\n\u001b[1;32m 179\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 180\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 181\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 182\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 183\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } @@ -464,7 +464,7 @@ "tags": [] }, "source": [ - "Variables also accept a *step* keyword argument. Its value is used as the initial step in matching, and in the {py:meth}`~.variables.Variable.step_up` and {py:meth}`~.variables.Variable.step_down` methods." + "Variables also accept a *delta* keyword argument. Its value is used as the initial step in matching, and in the {py:meth}`~.variables.Variable.step_up` and {py:meth}`~.variables.Variable.step_down` methods." ] }, { diff --git a/docs/p/variables_parameters.md b/docs/p/variables_parameters.md deleted file mode 100644 index 9941b93fc..000000000 --- a/docs/p/variables_parameters.md +++ /dev/null @@ -1,54 +0,0 @@ -# Variables and Parameters -Variables and parameters provide a unified way of varying any scalar quantity affecting -a lattice. They may be used in parameter scans, matching, response matrices… - -````{grid} 1 1 1 1 -:gutter: 2 -```{grid-item-card} Variables -:shadow: md - -{py:doc}`notebooks/variables` are **references** to any scalar quantity. AT includes -two predefined variable classes referring to scalar attributes of lattice elements: -- an {py:class}`~.element_variables.ElementVariable` is associated with one or several - {py:class}`.Element` objects, and acts on all occurences of these objects. But it - will not affect any copy, neither shallow nor deep, of the original objects, -- a {py:class}`.RefptsVariable` is not associated with with element objects, but to - their location in a {py:class}`.Lattice`. It acts on any copy of the initial - lattice. A *ring* argument must be provided to the *set* and *get* methods to - identify the lattice. - -Variable referring to other quantities may be created by: -- deriving the {py:class}`~.variables.Variable` base class. Usually this consist in - simply implementing the *_setfun* and *_getfun* abstract methods, -- Using the {py:class}`.CustomVariable` class. -``` -```{grid-item-card} Parameters - -{py:doc}`notebooks/parameters` are objects of class {py:class}`.Param` which can be used instead of numeric -values as {py:class}`.Element` attributes. - -Arithmetic combinations of parameters create new read-only parameters of class -{py:class}`.ParamBase`, whose value is permanently kept up-to-date. This is useful to -introduce correlation between attributes of different elements. - -The use of parameters is restricted to {py:class}`.Element` attributes, while -variables can vary any quantity. -``` -```` - -Variables and parameters share a common interface inherited from the -{py:class}`~.variables.Variable` abstract base class. This includes the -{py:meth}`~.variables.Variable.get` and {py:meth}`~.variables.Variable.set` methods -and the {py:attr}`~.variables.Variable.value` property. - -Variables and parameters can be grouped in {py:class}`~.variables.VariableList` -containers providing the vectorised equivalent {py:meth}`~.variables.VariableList.get` -and {py:meth}`~.variables.VariableList.set` methods. - -```{toctree} -:maxdepth: 2 -:hidden: - -notebooks/variables.ipynb -notebooks/parameters.ipynb -``` \ No newline at end of file diff --git a/pyat/at/lattice/__init__.py b/pyat/at/lattice/__init__.py index 475f7e120..30b5230e5 100644 --- a/pyat/at/lattice/__init__.py +++ b/pyat/at/lattice/__init__.py @@ -10,7 +10,7 @@ from .options import DConstant, random from .particle_object import Particle # from .variables import * -from .variables import ParamBase, Param, ParamArray, VariableList +from .variables import VariableList from .elements import * from .rectangular_bend import * from .idtable_element import InsertionDeviceKickMap diff --git a/pyat/at/lattice/axisdef.py b/pyat/at/lattice/axisdef.py index 5a3b2c983..ae23141db 100644 --- a/pyat/at/lattice/axisdef.py +++ b/pyat/at/lattice/axisdef.py @@ -1,7 +1,6 @@ """Helper functions for axis and plane descriptions""" from __future__ import annotations from typing import Optional, Union - # For sys.version_info.minor < 9: from typing import Tuple @@ -17,31 +16,31 @@ ct=dict(index=5, label=r"$\beta c \tau$", unit=" [m]"), ) for xk, xv in [it for it in _axis_def.items()]: - xv["code"] = xk - _axis_def[xv["index"]] = xv + xv['code'] = xk + _axis_def[xv['index']] = xv _axis_def[xk.upper()] = xv -_axis_def["delta"] = _axis_def["dp"] -_axis_def["xp"] = _axis_def["px"] # For backward compatibility -_axis_def["yp"] = _axis_def["py"] # For backward compatibility -_axis_def["s"] = _axis_def["ct"] -_axis_def["S"] = _axis_def["ct"] -_axis_def[None] = dict(index=None, label="", unit="", code=":") +_axis_def['delta'] = _axis_def['dp'] +_axis_def['xp'] = _axis_def['px'] # For backward compatibility +_axis_def['yp'] = _axis_def['py'] # For backward compatibility +_axis_def['s'] = _axis_def['ct'] +_axis_def['S'] = _axis_def['ct'] +_axis_def[None] = dict(index=slice(None), label="", unit="", code=":") _axis_def[Ellipsis] = dict(index=Ellipsis, label="", unit="", code="...") _plane_def = dict( x=dict(index=0, label="x", unit=" [m]"), y=dict(index=1, label="y", unit=" [m]"), - z=dict(index=2, label="z", unit=""), + z=dict(index=2, label="z", unit="") ) for xk, xv in [it for it in _plane_def.items()]: - xv["code"] = xk - _plane_def[xv["index"]] = xv + xv['code'] = xk + _plane_def[xv['index']] = xv _plane_def[xk.upper()] = xv -_plane_def["h"] = _plane_def["x"] -_plane_def["v"] = _plane_def["y"] -_plane_def["H"] = _plane_def["x"] -_plane_def["V"] = _plane_def["y"] -_plane_def[None] = dict(index=None, label="", unit="", code=":") +_plane_def['h'] = _plane_def['x'] +_plane_def['v'] = _plane_def['y'] +_plane_def['H'] = _plane_def['x'] +_plane_def['V'] = _plane_def['y'] +_plane_def[None] = dict(index=slice(None), label="", unit="", code=":") _plane_def[Ellipsis] = dict(index=Ellipsis, label="", unit="", code="...") diff --git a/pyat/at/lattice/element_variables.py b/pyat/at/lattice/element_variables.py index bbc9c9414..31e9adc99 100644 --- a/pyat/at/lattice/element_variables.py +++ b/pyat/at/lattice/element_variables.py @@ -113,9 +113,6 @@ def __init__( self._elements = {elements} else: self._elements = set(elements) - # Check that the attribute is not already a parameter - if any(el.is_parametrised(attrname, index=index) for el in self._elements): - raise TypeError(f"{attrname} attribute is a Variable") self._getf = getval(attrname, index=index) self._setf = setval(attrname, index=index) super().__init__(**kwargs) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 24bc584bd..6a8823ea2 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -13,18 +13,18 @@ from abc import ABC from collections.abc import Generator, Iterable from typing import Optional -from .variables import Param, ParamBase, ParamArray # noinspection PyProtectedMember from .variables import _nop def _array(value, shape=(-1,), dtype=numpy.float64): # Ensure proper ordering(F) and alignment(A) for "C" access in integrators - return ParamArray(value, shape=shape, dtype=dtype) + return numpy.require(value, dtype=dtype, requirements=['F', 'A']).reshape( + shape, order='F') -def _array66(value, dtype=numpy.float64): - return _array(value, shape=(6, 6), dtype=dtype) +def _array66(value): + return _array(value, shape=(6, 6)) def _float(value): @@ -282,18 +282,14 @@ def __init__(self, family_name: str, **kwargs): self.update(kwargs) def __setattr__(self, key, value): - if isinstance(value, ParamBase): - value.set_conversion(self._conversions.get(key, _nop)) - else: + try: value = self._conversions.get(key, _nop)(value) - super(Element, self).__setattr__(key, value) - - def __getattribute__(self, key): - attr = super(Element, self).__getattribute__(key) - if isinstance(attr, (ParamBase, ParamArray)): - return attr.value + except Exception as exc: + exc.args = ('In element {0}, parameter {1}: {2}'.format( + self.FamName, key, exc),) + raise else: - return attr + super(Element, self).__setattr__(key, value) def __str__(self): first3 = ['FamName', 'Length', 'PassMethod'] @@ -422,150 +418,6 @@ def is_collective(self) -> bool: """:py:obj:`True` if the element involves collective effects""" return self._get_collective() - def evaluate(self): - attrs = dict(self.items()) - for k, v in attrs.items(): - if isinstance(v, (ParamBase, ParamArray)): - setattr(self, k, v.value) - - def set_parameter(self, attrname: str, value, index: int = None) -> None: - """Set an element's parameter - - This allows setting a parameter into an item of an array attribute. - - Args: - attrname: Attribute name - value: Parameter or value to be set - index: Index into an array attribute. If *value* is a - parameter, the attribute is converted to a - :py:class:`.ParamArray`. - """ - if index is None: - setattr(self, attrname, value) - else: - attr = self.__dict__[attrname] - if isinstance(value, ParamBase) and not isinstance(attr, ParamArray): - # replace the numpy array with a ParamArray - attr = ParamArray(attr) - setattr(self, attrname, attr) - attr[index] = value - - def _get_attribute(self, attrname: str, index: Optional[int] = None): - attr = self.__dict__[attrname] - if index is not None: - attr = attr[index] - return attr - - def get_parameter(self, attrname: str, index: Optional[int] = None): - """Extract a parameter of an element - - Unlike :py:func:`getattr`, :py:func:`get_parameter` returns the - parameter itself instead of its value. It the item is not a parameter, - both functions are equivalent, the value is returned. Properties cannot - be accessed, one must use the associated array item. - - Args: - attrname: Attribute name - index: Index in an array attribute. If :py:obj:`None`, the - whole attribute is set - """ - attr = self._get_attribute(attrname, index=index) - if not isinstance(attr, ParamBase): - message = f"\n\n{self.FamName}.{attrname} is not a parameter.\n" - # warn(AtWarning(message)) - raise TypeError(message) - return attr - - def is_parametrised(self, attrname: Optional[str] = None, - index: Optional[int] = None) -> bool: - """Check for the parametrisation of an element - - Args: - attrname: Attribute name. If :py:obj:`None`, return :py:obj:`True` - if any attribute is parametrized - index: Index in an array attribute. If :py:obj:`None`, the - whole attribute is tested for parametrisation - """ - if attrname is None: - for attr in self.__dict__: - if self.is_parametrised(attr): - return True - return False - else: - attr = self._get_attribute(attrname, index=index) - if isinstance(attr, ParamBase): - return True - elif isinstance(attr, numpy.ndarray): - for item in attr.flat: - if isinstance(item, ParamBase): - return True - return False - else: - return False - - def parametrise(self, attrname: str, index: Optional[int] = None, - name: str = '') -> ParamBase: - """Convert an attribute into a parameter - - The value of the attribute is kept unchanged. If the attribute is - already parametrised, the existing parameter is returned. - - Args: - attrname: Attribute name - index: Index in an array. If :py:obj:`None`, the - whole attribute is parametrised - name: Name of the created parameter - - Returns: - param: A :py:class:`.ParamArray` for an array attribute, - a :py:class:`.Param` for a scalar attribute or an item in an - array attribute - - """ - vini = self._get_attribute(attrname, index=index) - - if isinstance(vini, ParamBase): - return vini - - attr = Param(vini, name=name) # raises TypeError if vini is not a Number - - if index is None: - setattr(self, attrname, attr) - else: - varr = self._get_attribute(attrname) - varr[index] = attr # raises IndexError it the attr is not an array - return attr - - def unparametrise(self, attrname: Optional[str] = None, - index: Optional[int] = None) -> None: - """Freeze the parameter values - - Args: - attrname: Attribute name. If :py:obj:`None`, all the attributes - are frozen - index: Index in an array. If :py:obj:`None`, the whole - attribute is frozen - """ - - def unparam_attr(attrname, attr): - if isinstance(attr, ParamBase): - setattr(self, attrname, attr.value) - elif isinstance(attr, numpy.ndarray): - for i, item in enumerate(attr.flat): - if isinstance(item, ParamBase): - ij = numpy.unravel_index(i, attr.shape) - attr[ij] = item.value - - if attrname is None: - for key, attr in self.__dict__.items(): - unparam_attr(key, attr) - else: - if index is None: - unparam_attr(attrname, self._get_attribute(attrname)) - else: - attr = self._get_attribute(attrname) - attr[index] = attr[index].value - class LongElement(Element): """Base class for long elements @@ -783,8 +635,8 @@ def lengthen(poly, dl): # PolynomA and PolynomB and convert to ParamArray prmpola = self._conversions["PolynomA"](kwargs.pop("PolynomA", poly_a)) prmpolb = self._conversions["PolynomB"](kwargs.pop("PolynomB", poly_b)) - poly_a, len_a, ord_a = getpol(prmpola.value) - poly_b, len_b, ord_b = getpol(prmpolb.value) + poly_a, len_a, ord_a = getpol(prmpola) + poly_b, len_b, ord_b = getpol(prmpolb) deforder = max(getattr(self, 'DefaultOrder', 0), ord_a, ord_b) # Remove MaxOrder maxorder = kwargs.pop('MaxOrder', deforder) @@ -866,7 +718,7 @@ def K(self) -> float: # noinspection PyPep8Naming @K.setter def K(self, strength: float): - self.set_parameter('PolynomB', strength, index=1) + self.PolynomB[1] = strength # noinspection PyPep8Naming @property @@ -878,7 +730,7 @@ def H(self) -> float: # noinspection PyPep8Naming @H.setter def H(self, strength): - self.set_parameter('PolynomB', strength, index=2) + self.PolynomB[2] = strength class Dipole(Radiative, Multipole): @@ -1215,6 +1067,7 @@ def __init__(self, family_name: str, betax: float = 1.0, super(SimpleQuantDiff, self).__init__(family_name, **kwargs) + class Corrector(LongElement): """Corrector element""" _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['KickAngle'] diff --git a/pyat/at/lattice/lattice_object.py b/pyat/at/lattice/lattice_object.py index fc98859d5..3fbd3e581 100644 --- a/pyat/at/lattice/lattice_object.py +++ b/pyat/at/lattice/lattice_object.py @@ -28,12 +28,11 @@ from .utils import AtError, AtWarning, Refpts # noinspection PyProtectedMember from .utils import get_uint32_index, get_bool_index, _refcount, Uint32Refpts -from .utils import refpts_iterator, checktype, getval +from .utils import refpts_iterator, checktype from .utils import get_s_pos, get_elements from .utils import get_value_refpts, set_value_refpts from .utils import set_shift, set_tilt, get_geometry from . import elements as elt -from .variables import Param from .elements import Element _TWO_PI_ERROR = 1.E-4 @@ -1331,18 +1330,13 @@ def reduce_filter(_, itelem): def replace(self, refpts: Refpts, **kwargs) -> Lattice: """Return a shallow copy of the lattice replacing the selected - elements by an unparametrised deep copy + elements by a deep copy Parameters: refpts: element selector """ - def newelem(elem): - newelem = elem.deepcopy() - newelem.unparametrise() - return newelem - check = get_bool_index(self, refpts) - elems = (newelem(el) if ok else el for el, ok in zip(self, check)) + elems = (el.deepcopy() if ok else el for el, ok in zip(self, check)) return Lattice(elem_generator, elems, iterator=self.attrs_filter, **kwargs) @@ -1397,61 +1391,6 @@ def radiation(self) -> bool: self._radiation = radiate return radiate - def set_parameter(self, refpts: Refpts, attrname: str, value, - index: Optional[int] = None) -> None: - """Set a parameter as an attribute of the selected elements - - Args: - refpts: Element selector - attrname: Attribute name - value: Parameter or value to be set - index: Index into an array attribute. If *value* is a - parameter, the attribute is converted to a - :py:class:`.ParamArray`. - """ - for elem in self.select(refpts): - elem.set_parameter(attrname, value, index=index) - - def parametrise(self, refpts: Refpts, attrname: str, - index: Optional[int] = None, name: str = '') -> Param: - """Convert an attribute of the selected elements into a parameter - - A single parameter is created and assigned to all the selected - elements. Its initial value is the mean of the original values. - - Args: - refpts: Element selector - attrname: Attribute name - index: Index into an array attribute. If *value* is a - parameter, the attribute is converted to a - :py:class:`.ParamArray`. - name: Name of the created parameter - - Returns: - param: The created parameter - """ - elems = self[refpts] - getf = getval(attrname, index=index) - vals = numpy.array([getf(elem) for elem in elems]) - attr = Param(numpy.mean(vals), name=name) - for elem in elems: - elem.set_parameter(attrname, attr, index=index) - return attr - - def unparametrise(self, refpts, attrname: Optional[str] = None, - index: Optional[int] = None) -> None: - """Freeze the value of attributes of the selected elements - - Args: - refpts: Element selector - attrname: Attribute name. If :py:obj:`None`, all the attributes - are frozen - index: Index in an array. If :py:obj:`None`, the whole - attribute is frozen - """ - for elem in self.select(refpts): - elem.unparametrise(attrname=attrname, index=index) - def lattice_filter(params, lattice): """Copy lattice parameters and run through all lattice elements diff --git a/pyat/at/lattice/utils.py b/pyat/at/lattice/utils.py index 48473a75a..49e5c207e 100644 --- a/pyat/at/lattice/utils.py +++ b/pyat/at/lattice/utils.py @@ -841,7 +841,13 @@ def get_value_refpts(ring: Sequence[Element], refpts: Refpts, Returns: attrvalues: numpy Array of attribute values. """ - getf = getval(attrname, index=index) + if index is None: + def getf(elem): + return getattr(elem, attrname) + else: + def getf(elem): + return getattr(elem, attrname)[index] + return numpy.array([getf(elem) for elem in refpts_iterator(ring, refpts, regex=regex)]) @@ -878,7 +884,13 @@ def set_value_refpts(ring: Sequence[Element], refpts: Refpts, elements are shared with the original lattice. Any further modification will affect both lattices. """ - setf = setval(attrname, index=index) + if index is None: + def setf(elem, value): + setattr(elem, attrname, value) + else: + def setf(elem, value): + getattr(elem, attrname)[index] = value + if increment: attrvalues += get_value_refpts(ring, refpts, attrname, index=index, diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 60daa71e6..9a4882e5a 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -10,9 +10,6 @@ :py:class:`Variable`\ (name, bounds, delta) -- :py:class:`.ParamBase`\ (...) - - - :py:class:`.Param`\ (value) - :py:class:`~.element_variables.ElementVariable`\ (elements, attrname, index, ...) - :py:class:`~.element_variables.RefptsVariable`\ (refpts, attrname, index, ...) - :py:class:`CustomVariable`\ (setfun, getfun, ...) @@ -87,16 +84,11 @@ def _getfun(self, **kwargs): import numpy as np import abc from numbers import Number -from operator import add, sub, mul, truediv, pos, neg from collections.abc import Iterable, Sequence, Callable -from typing import Any __all__ = [ "Variable", "CustomVariable", - "ParamBase", - "Param", - "ParamArray", "VariableList", ] @@ -105,61 +97,6 @@ def _nop(value): return value -def _default_array(value): - return np.require(value, dtype=float, requirements=["F", "A"]) - - -class _Evaluate(abc.ABC): - @abc.abstractmethod - def __call__(self): - ... - - -class _Scalar(_Evaluate): - __slots__ = "value" - - def __init__(self, value): - if not isinstance(value, Number): - raise TypeError("The parameter value must be a scalar") - self.value = value - - def __call__(self): - return self.value - - -class _BinaryOp(_Evaluate): - __slots__ = ["oper", "left", "right"] - - @staticmethod - def _set_type(value): - if isinstance(value, Number): - return _Scalar(value) - elif isinstance(value, Variable): - return value - else: - msg = "Param Operation not defined for type {0}".format(type(value)) - raise TypeError(msg) - - def __init__(self, oper, left, right): - self.oper = oper - self.right = self._set_type(right) - self.left = self._set_type(left) - - def __call__(self): - return self.oper(self.left.value, self.right.value) - - -class _UnaryOp(_Evaluate): - __slots__ = ["oper", "param"] - - def __init__(self, oper, param): - self.oper = oper - self.param = param - - def __call__(self): - return self.oper(self.param.value) - - class Variable(abc.ABC): """A :py:class:`Variable` abstract base class @@ -334,42 +271,6 @@ def status(self, **kwargs): """ return "\n".join((self._header(), self._line(**kwargs))) - def __add__(self, other): - fun = _BinaryOp(add, self, other) - return ParamBase(fun) - - def __radd__(self, other): - return self.__add__(other) - - def __pos__(self): - return ParamBase(_UnaryOp(pos, self)) - - def __neg__(self): - return ParamBase(_UnaryOp(neg, self)) - - def __sub__(self, other): - fun = _BinaryOp(sub, self, other) - return ParamBase(fun) - - def __rsub__(self, other): - fun = _BinaryOp(sub, other, self) - return ParamBase(fun) - - def __mul__(self, other): - fun = _BinaryOp(mul, self, other) - return ParamBase(fun) - - def __rmul__(self, other): - return self.__mul__(other) - - def __truediv__(self, other): - fun = _BinaryOp(truediv, self, other) - return ParamBase(fun) - - def __rtruediv__(self, other): - fun = _BinaryOp(add, other, self) - return ParamBase(fun) - def __float__(self): return float(self.value) @@ -433,148 +334,6 @@ def _setfun(self, value: Number, ring=None): self.setfun(value, *self.args, ring=ring, **self.kwargs) -class ParamBase(Variable): - """Read-only base class for parameters - - It is used for computed parameters, and should not be instantiated - otherwise. See :py:class:`.Variable` for a description of inherited - methods - """ - - _counter = 0 - _prefix = "calc" - - def __init__( - self, - evaluate: _Evaluate, - *, - name: str = "", - conversion: Callable[[Any], Number] = _nop, - bounds: tuple[float, float] = (-np.inf, np.inf), - delta: float = 1.0, - ): - """ - - Args: - evaluate: Evaluator function - name: Name of the parameter - conversion: data conversion function - bounds: Lower and upper bounds of the parameter value - delta: Initial variation step - """ - super(ParamBase, self).__init__(name=name, bounds=bounds, delta=delta) - if not isinstance(evaluate, _Evaluate): - raise TypeError("'Evaluate' must be an _Evaluate object") - self._evaluate = evaluate - self._conversion = conversion - - def _getfun(self, **kwargs): - return self._conversion(self._evaluate()) - - def set_conversion(self, conversion: Callable[[Number], Number]): - """Set the data type. Called when a parameter is assigned to an - :py:class:`.Element` attribute""" - if conversion is not self._conversion: - if self._conversion is _nop: - self._conversion = conversion - else: - raise ValueError("Cannot change the data type of the parameter") - - -class Param(ParamBase): - """Standalone scalar parameter - - See :py:class:`.Variable` for a description of inherited methods - """ - - _counter = 0 - _prefix = "param" - - def __init__( - self, - value: Number, - *, - name: str = "", - conversion: Callable[[Number], Number] = _nop, - bounds: tuple[float, float] = (-np.inf, np.inf), - delta: float = 1.0, - ): - """ - Args: - value: Initial value of the parameter - name: Name of the parameter - conversion: data conversion function - bounds: Lower and upper bounds of the parameter value - delta: Initial variation step - """ - super(Param, self).__init__( - _Scalar(value), name=name, conversion=conversion, bounds=bounds, delta=delta - ) - self._history.append(self._evaluate()) - - def _getfun(self, ring=None): - return self._evaluate() - - def _setfun(self, value, ring=None): - self._evaluate = _Scalar(self._conversion(value)) - - def set_conversion(self, conversion: Callable[[Number], Number]): - oldv = self._evaluate() - super(Param, self).set_conversion(conversion) - self._evaluate = _Scalar(conversion(oldv)) - - -class _PArray(np.ndarray): - """Subclass of ndarray which reports to its parent ParamArray""" - - # This is the array obtained with an element get_attribute. - # It is also the one used when setting an item of an array attribute. - - def __new__(cls, value, dtype=np.float64): - obj = np.array(value, dtype=dtype, order="F").view(cls) - obj._parent = value - return obj - - def __array_finalize__(self, obj): - self._parent = getattr(obj, "_parent", None) - - def __setitem__(self, key, value): - super().__setitem__(key, value) - if self._parent is not None: - self._parent[key] = value - - def __repr__(self): - # Simulate a standard ndarray - return repr(self.view(np.ndarray)) - - -class ParamArray(np.ndarray): - """Simulate a numpy array where items may be parametrised""" - - def __new__(cls, value, shape=(-1,), dtype=np.float64): - obj = np.asfortranarray(value, dtype=object).reshape(shape).view(cls) - obj._value = _PArray(obj, dtype=dtype) - return obj - - def __array_finalize__(self, obj): - val = getattr(obj, "_value", None) - if val is not None: - self._value = _PArray(self, dtype=val.dtype) - - @property - def value(self): - self._value[:] = self - return self._value - - def __repr__(self): - return repr(self.value) - - def __str__(self): - it = np.nditer(self, flags=["refs_ok"], order="C") - contents = " ".join([str(el) for el in it]) - return f"[{contents}]" - - class VariableList(list): """Container for :py:class:`Variable` objects From 863de17bbce7074a52a2e37f4bce715fe6f05c37 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Mon, 4 Dec 2023 17:28:56 +0100 Subject: [PATCH 08/29] merged from master --- pyat/at/lattice/elements.py | 172 ++++++++++++++++++++++++++++++++++-- 1 file changed, 167 insertions(+), 5 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 6a8823ea2..b67c17dbf 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -487,6 +487,12 @@ class BeamMoments(Element): """Element to compute bunches mean and std""" def __init__(self, family_name: str, **kwargs): + """ + Args: + family_name: Name of the element + + Default PassMethod: ``BeamMomentsPass`` + """ kwargs.setdefault('PassMethod', 'BeamMomentsPass') self._stds = numpy.zeros((6, 1, 1), order='F') self._means = numpy.zeros((6, 1, 1), order='F') @@ -498,13 +504,118 @@ def set_buffers(self, nturns, nbunch): @property def stds(self): + """Beam 6d standard deviation""" return self._stds @property def means(self): + """Beam 6d center of mass""" return self._means +class SliceMoments(Element): + """Element to compute slices mean and std""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['nslice'] + _conversions = dict(Element._conversions, nslice=int) + + def __init__(self, family_name: str, nslice: int, **kwargs): + """ + Args: + family_name: Name of the element + nslice: Number of slices + + Keyword arguments: + startturn: Start turn of the acquisition (Default 0) + endturn: End turn of the acquisition (Default 1) + + Default PassMethod: ``SliceMomentsPass`` + """ + kwargs.setdefault('PassMethod', 'SliceMomentsPass') + self._startturn = kwargs.pop('startturn', 0) + self._endturn = kwargs.pop('endturn', 1) + super(SliceMoments, self).__init__(family_name, nslice=nslice, + **kwargs) + self._nbunch = 1 + self.startturn = self._startturn + self.endturn = self._endturn + self._dturns = self.endturn - self.startturn + self._stds = numpy.zeros((3, nslice, self._dturns), order='F') + self._means = numpy.zeros((3, nslice, self._dturns), order='F') + self._spos = numpy.zeros((nslice, self._dturns), order='F') + self._weights = numpy.zeros((nslice, self._dturns), order='F') + self.set_buffers(self._endturn, 1) + + def set_buffers(self, nturns, nbunch): + self.endturn = min(self.endturn, nturns) + self._dturns = self.endturn - self.startturn + self._nbunch = nbunch + self._stds = numpy.zeros((3, nbunch*self.nslice, self._dturns), + order='F') + self._means = numpy.zeros((3, nbunch*self.nslice, self._dturns), + order='F') + self._spos = numpy.zeros((nbunch*self.nslice, self._dturns), + order='F') + self._weights = numpy.zeros((nbunch*self.nslice, self._dturns), + order='F') + + @property + def stds(self): + """Slices x,y,dp standard deviation""" + return self._stds.reshape((3, self._nbunch, + self.nslice, + self._dturns)) + + @property + def means(self): + """Slices x,y,dp center of mass""" + return self._means.reshape((3, self._nbunch, + self.nslice, + self._dturns)) + + @property + def spos(self): + """Slices s position""" + return self._spos.reshape((self._nbunch, + self.nslice, + self._dturns)) + + @property + def weights(self): + """Slices weights in mA if beam current >0, + otherwise fraction of total number of + particles in the bunch + """ + return self._weights.reshape((self._nbunch, + self.nslice, + self._dturns)) + + @property + def startturn(self): + """Start turn of the acquisition""" + return self._startturn + + @startturn.setter + def startturn(self, value): + if value < 0: + raise ValueError('startturn must be greater or equal to 0') + if value >= self._endturn: + raise ValueError('startturn must be smaller than endturn') + self._startturn = value + + @property + def endturn(self): + """End turn of the acquisition""" + return self._endturn + + @endturn.setter + def endturn(self, value): + if value <= 0: + raise ValueError('endturn must be greater than 0') + if value <= self._startturn: + raise ValueError('endturn must be greater than startturn') + self._endturn = value + + class Aperture(Element): """Aperture element""" _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['Limits'] @@ -793,7 +904,8 @@ def __init__(self, family_name: str, length: float, KickAngle: Correction deviation angles (H, V) FieldScaling: Scaling factor applied to the magnetic field - Available PassMethods: :ref:`BndMPoleSymplectic4Pass`, :ref:`BendLinearPass`, + Available PassMethods: :ref:`BndMPoleSymplectic4Pass`, + :ref:`BendLinearPass`, :ref:`ExactSectorBendPass`, :ref:`ExactRectangularBendPass`, :ref:`ExactRectBendPass`, BndStrMPoleSymplectic4Pass @@ -1007,7 +1119,10 @@ def __init__(self, family_name: str, m66=None, **kwargs): class SimpleQuantDiff(_DictLongtMotion, Element): """ Linear tracking element for a simplified quantum diffusion, - radiation damping and energy loss + radiation damping and energy loss. + + Note: The damping times are needed to compute the correct + kick for the emittance. Radiation damping is NOT applied. """ _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES default_pass = {False: 'IdentityPass', True: 'SimpleQuantDiffPass'} @@ -1016,7 +1131,7 @@ def __init__(self, family_name: str, betax: float = 1.0, betay: float = 1.0, emitx: float = 0.0, emity: float = 0.0, espread: float = 0.0, taux: float = 0.0, tauy: float = 0.0, - tauz: float = 0.0, U0: float = 0.0, + tauz: float = 0.0, **kwargs): """ Args: @@ -1031,7 +1146,6 @@ def __init__(self, family_name: str, betax: float = 1.0, taux: Horizontal damping time [turns] tauy: Vertical damping time [turns] tauz: Longitudinal damping time [turns] - U0: Energy Loss [eV] Default PassMethod: ``SimpleQuantDiffPass`` """ @@ -1061,12 +1175,60 @@ def __init__(self, family_name: str, betax: float = 1.0, if espread > 0.0: assert tauz > 0.0, 'if espread is given, tauz must be non zero' - self.U0 = U0 self.betax = betax self.betay = betay super(SimpleQuantDiff, self).__init__(family_name, **kwargs) +class SimpleRadiation(_DictLongtMotion, Radiative, Element): + """Simple radiation damping and energy loss""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['taux', 'tauy', 'tauz'] + default_pass = {False: 'IdentityPass', True: 'SimpleRadiationPass'} + + def __init__(self, family_name: str, + taux: float = 0.0, tauy: float = 0.0, + tauz: float = 0.0, U0: float = 0.0, + **kwargs): + """ + Args: + family_name: Name of the element + + Optional Args: + taux: Horizontal damping time [turns] + tauy: Vertical damping time [turns] + tauz: Longitudinal damping time [turns] + U0: Energy loss per turn [eV] + + Default PassMethod: ``SimpleRadiationPass`` + """ + kwargs.setdefault('PassMethod', self.default_pass[True]) + + assert taux >= 0.0, 'taux must be greater than or equal to 0' + if taux == 0.0: + dampx = 1 + else: + dampx = numpy.exp(-1/taux) + + assert tauy >= 0.0, 'tauy must be greater than or equal to 0' + if tauy == 0.0: + dampy = 1 + else: + dampy = numpy.exp(-1/tauy) + + assert tauz >= 0.0, 'tauz must be greater than or equal to 0' + if tauz == 0.0: + dampz = 1 + else: + dampz = numpy.exp(-1/tauz) + + self.U0 = U0 + + self.damp_mat_diag = numpy.array([dampx, dampx, + dampy, dampy, + dampz, dampz]) + + super(SimpleRadiation, self).__init__(family_name, **kwargs) + class Corrector(LongElement): """Corrector element""" From 4f626b1ff5ed2adc1d53f04ca2477157f2778a46 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Wed, 13 Dec 2023 21:22:50 +0100 Subject: [PATCH 09/29] lattice_variables --- docs/p/notebooks/variables.ipynb | 64 +++++++- pyat/at/future.py | 2 +- pyat/at/lattice/__init__.py | 2 +- ...ment_variables.py => lattice_variables.py} | 4 +- pyat/at/lattice/variables.py | 147 ++++++++++++------ 5 files changed, 163 insertions(+), 56 deletions(-) rename pyat/at/lattice/{element_variables.py => lattice_variables.py} (98%) diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index c21795026..47c76bfba 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -444,7 +444,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:181\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 175\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 176\u001b[0m \n\u001b[1;32m 177\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[1;32m 178\u001b[0m \u001b[38;5;124;03m value: New value to be applied on the variable\u001b[39;00m\n\u001b[1;32m 179\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 180\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 181\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 182\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 183\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history\u001b[38;5;241m.\u001b[39mappend(value)\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:194\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 184\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 185\u001b[0m \n\u001b[1;32m 186\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 191\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 194\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 196\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } @@ -757,7 +757,7 @@ "metadata": {}, "outputs": [], "source": [ - "elem_shifter1.set_initial(ring=ring)" + "elem_shifter1.reset(ring=ring)" ] }, { @@ -909,6 +909,66 @@ "Both variables behave similarly. But the derivation allows more control by making use of the\n", "`__init__` method. For instance here it includes the computation of the initial total length." ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "8a46d3a4-2899-4427-af85-d2fb4ae0a6f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2.5394599781303304]" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kf1.history" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "bb6879e3-62f2-42ba-a283-ed2bdc168aa4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.5394599781303304" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kf1.initial_value" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "23dfcbd8-39fd-41d2-97e3-022c21d3dc32", + "metadata": {}, + "outputs": [], + "source": [ + "kf1.reset()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e1b5637-a2d1-4f09-8e35-e7106faa430d", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/pyat/at/future.py b/pyat/at/future.py index 8af008778..4dbf0b4ab 100644 --- a/pyat/at/future.py +++ b/pyat/at/future.py @@ -1,2 +1,2 @@ from .lattice.variables import * -from .lattice.element_variables import * +from .lattice.lattice_variables import * diff --git a/pyat/at/lattice/__init__.py b/pyat/at/lattice/__init__.py index 30b5230e5..527b212d2 100644 --- a/pyat/at/lattice/__init__.py +++ b/pyat/at/lattice/__init__.py @@ -16,7 +16,7 @@ from .idtable_element import InsertionDeviceKickMap from .utils import * from .lattice_object import * -# from .element_variables import * +# from .lattice_variables import * from .cavity_access import * from .variable_elements import * from .deprecated import * diff --git a/pyat/at/lattice/element_variables.py b/pyat/at/lattice/lattice_variables.py similarity index 98% rename from pyat/at/lattice/element_variables.py rename to pyat/at/lattice/lattice_variables.py index 31e9adc99..4049ec7d7 100644 --- a/pyat/at/lattice/element_variables.py +++ b/pyat/at/lattice/lattice_variables.py @@ -116,7 +116,9 @@ def __init__( self._getf = getval(attrname, index=index) self._setf = setval(attrname, index=index) super().__init__(**kwargs) - self._history.append(self._getfun()) + iniv = self._getfun() + self._initial = iniv + self._history.append(iniv) def _setfun(self, value: float, **kwargs): for elem in self._elements: diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 9a4882e5a..c1fe98d6b 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -21,7 +21,7 @@ - :py:meth:`~Variable.get` - :py:meth:`~Variable.set` - :py:meth:`~Variable.set_previous` -- :py:meth:`~Variable.set_initial` +- :py:meth:`~Variable.reset` - :py:meth:`~Variable.increment` - :py:meth:`~Variable.step_up` - :py:meth:`~Variable.step_down` @@ -84,6 +84,7 @@ def _getfun(self, **kwargs): import numpy as np import abc from numbers import Number +from collections import deque from collections.abc import Iterable, Sequence, Callable __all__ = [ @@ -113,17 +114,23 @@ def __init__( name: str = "", bounds: tuple[Number, Number] = (-np.inf, np.inf), delta: Number = 1.0, + history_length: int = None, ): """ Parameters: name: Name of the Variable bounds: Lower and upper bounds of the variable value delta: Initial variation step + history_length: Maximum length of the history buffer. :py:obj:`None` + means infinite """ - self.name = self._setname(name) - self.bounds = bounds - self.delta = delta - self._history = [] + self.name = self._setname(name) #: Variable name + self.bounds = bounds #: Variable bounds + self.delta = delta #: Increment step + #: Maximum length of the history buffer. :py:obj:`None` means infinite + self.history_length = history_length + self._initial = None + self._history = deque([], self.history_length) @classmethod def _setname(cls, name): @@ -134,66 +141,78 @@ def _setname(cls, name): return f"{cls._prefix}{cls._counter}" # noinspection PyUnusedLocal - def _setfun(self, value: Number, **kwargs): + def _setfun(self, value: Number, ring=None, **kwargs): classname = self.__class__.__name__ raise TypeError(f"{classname!r} is read-only") @abc.abstractmethod - def _getfun(self, **kwargs) -> Number: + def _getfun(self, ring=None, **kwargs) -> Number: ... @property def history(self) -> list[Number]: """History of the values of the variable""" - return self._history + return list(self._history) @property def initial_value(self) -> Number: """Initial value of the variable""" - if len(self._history) > 0: - return self._history[0] + if self._initial is not None: + return self._initial else: raise IndexError(f"{self.name}: No value has been set yet") @property def last_value(self) -> Number: """Last value of the variable""" - if len(self._history) > 0: + try: return self._history[-1] - else: - raise IndexError(f"{self.name}: No value has been set yet") + except IndexError as exc: + exc.args = (f"{self.name}: No value has been set yet",) + raise @property def previous_value(self) -> Number: """Value before the last one""" - if len(self._history) > 1: + try: return self._history[-2] - else: - raise IndexError(f"{self.name}: history too short") + except IndexError as exc: + exc.args = (f"{self.name}: history too short",) + raise - def set(self, value: Number, **kwargs) -> None: + def set(self, value: Number, ring=None, **kwargs) -> None: """Set the variable value Args: value: New value to be applied on the variable + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to set the variable. """ if value < self.bounds[0] or value > self.bounds[1]: raise ValueError(f"set value must be in {self.bounds}") - self._setfun(value, **kwargs) + self._setfun(value, ring=ring, **kwargs) + if self._initial is None: + self._initial = value self._history.append(value) - def get(self, initial=False, **kwargs) -> Number: + def get(self, initial=False, ring=None, **kwargs) -> Number: """Get the actual variable value Args: - initial: If :py:obj:`True`, set the variable initial value + initial: If :py:obj:`True`, clear the history and set the variable + initial value + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to get the variable value. Returns: value: Value of the variable """ - value = self._getfun(**kwargs) + value = self._getfun(ring=ring, **kwargs) if initial: - self._history = [value] + self._initial = value + self._history = deque([value], self.history_length) + elif self._initial is None: + self._initial = value return value @property @@ -204,44 +223,70 @@ def value(self): def value(self, value: Number): self.set(value) - def set_previous(self, **kwargs) -> None: - """Reset to the value before the last one""" - if len(self._history) > 1: + def set_previous(self, ring=None, **kwargs) -> None: + """Reset to the value before the last one + + Keyword Args: + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to set the variable. + """ + try: self._history.pop() # Remove the last value prev = self._history.pop() # retrieve the previous value - self.set(prev, **kwargs) + except IndexError as exc: + exc.args = (f"{self.name}: history too short",) + raise else: - raise IndexError(f"{self.name}: history too short") - - def set_initial(self, **kwargs) -> None: - """Reset to the initial value""" - if len(self._history) > 0: - iniv = self._history[0] - self._history = [] - self.set(iniv, **kwargs) + self.set(prev, ring=ring, **kwargs) + + def reset(self, ring=None, **kwargs) -> None: + """Reset to the initial value + + Args: + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to reset the variable. + """ + iniv = self._initial + if iniv is not None: + self._history = deque([], self.history_length) + self.set(iniv, ring=ring, **kwargs) else: - raise IndexError(f"{self.name}: No value has been set yet") + raise IndexError(f"reset {self.name}: No value has been set yet") - def increment(self, incr: Number, **kwargs) -> None: + def increment(self, incr: Number, ring=None, **kwargs) -> None: """Increment the variable value Args: incr: Increment value + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to increment the variable. """ if len(self._history) == 0: self.get(initial=True, **kwargs) - self.set(self.last_value + incr, **kwargs) + self.set(self.last_value + incr, ring=ring, **kwargs) + + def _step(self, step: Number, ring=None, **kwargs) -> None: + if self._initial is None: + self.get(initial=True, **kwargs) + self.set(self._initial + step, ring=ring, **kwargs) - def _step(self, step: Number, **kwargs) -> None: - self.set(self.initial_value + step, **kwargs) + def step_up(self, ring=None, **kwargs) -> None: + """Set to initial_value + delta - def step_up(self, **kwargs) -> None: - """Set to initial_value + delta""" - self._step(self.delta, **kwargs) + Args: + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to set the variable. + """ + self._step(self.delta, ring=ring, **kwargs) - def step_down(self, **kwargs) -> None: - """Set to initial_value - delta""" - self._step(-self.delta, **kwargs) + def step_down(self, ring=None, **kwargs) -> None: + """Set to initial_value - delta + + Args: + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to set the variable. + """ + self._step(-self.delta, ring=ring, **kwargs) @staticmethod def _header(): @@ -252,10 +297,10 @@ def _header(): def _line(self, ring=None): if ring is not None: vnow = self.get(ring) - vini = self._history[0] + vini = self._initial elif len(self._history) > 0: vnow = self._history[-1] - vini = self._history[0] + vini = self._initial else: vnow = vini = np.nan @@ -327,11 +372,11 @@ def __init__( self.args = args self.kwargs = kwargs - def _getfun(self, ring=None) -> Number: - return self.getfun(*self.args, ring=ring, **self.kwargs) + def _getfun(self, **kwargs) -> Number: + return self.getfun(*self.args, **self.kwargs) - def _setfun(self, value: Number, ring=None): - self.setfun(value, *self.args, ring=ring, **self.kwargs) + def _setfun(self, value: Number, **kwargs): + self.setfun(value, *self.args, **self.kwargs) class VariableList(list): From 803659e42933dd5be00592709d2823fe08075bc9 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Wed, 13 Dec 2023 21:56:50 +0100 Subject: [PATCH 10/29] Refactored Variable to VariableBase --- pyat/at/lattice/lattice_variables.py | 6 +-- pyat/at/lattice/variables.py | 72 ++++++++++++++-------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/pyat/at/lattice/lattice_variables.py b/pyat/at/lattice/lattice_variables.py index 4049ec7d7..c5afe99c4 100644 --- a/pyat/at/lattice/lattice_variables.py +++ b/pyat/at/lattice/lattice_variables.py @@ -16,12 +16,12 @@ from .utils import Refpts, getval, setval from .elements import Element from .lattice_object import Lattice -from .variables import Variable +from .variables import VariableBase __all__ = ["RefptsVariable", "ElementVariable"] -class RefptsVariable(Variable): +class RefptsVariable(VariableBase): r"""A reference to a scalar attribute of :py:class:`.Lattice` elements. It can refer to: @@ -72,7 +72,7 @@ def _getfun(self, ring: Lattice = None) -> float: return np.average(values) -class ElementVariable(Variable): +class ElementVariable(VariableBase): r"""A reference to a scalar attribute of :py:class:`.Lattice` elements. It can refer to: diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index c1fe98d6b..d727b8464 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -1,52 +1,52 @@ r""" -Definition of :py:class:`.Variable` objects used in matching and +Definition of :py:class:`Variable <.VariableBase>` objects used in matching and response matrices. See :ref:`example-notebooks` for examples of matching and response matrices. -Each :py:class:`Variable` has a scalar value. +Each :py:class:`Variable <.VariableBase>` has a scalar value. .. rubric:: Class hierarchy -:py:class:`Variable`\ (name, bounds, delta) +:py:class:`VariableBase`\ (name, bounds, delta) -- :py:class:`~.element_variables.ElementVariable`\ (elements, attrname, index, ...) -- :py:class:`~.element_variables.RefptsVariable`\ (refpts, attrname, index, ...) +- :py:class:`~.lattice_variables.ElementVariable`\ (elements, attrname, index, ...) +- :py:class:`~.lattice_variables.RefptsVariable`\ (refpts, attrname, index, ...) - :py:class:`CustomVariable`\ (setfun, getfun, ...) -.. rubric:: Variable methods +.. rubric:: VariableBase methods -:py:class:`Variable` provides the following methods: +:py:class:`VariableBase` provides the following methods: -- :py:meth:`~Variable.get` -- :py:meth:`~Variable.set` -- :py:meth:`~Variable.set_previous` -- :py:meth:`~Variable.reset` -- :py:meth:`~Variable.increment` -- :py:meth:`~Variable.step_up` -- :py:meth:`~Variable.step_down` +- :py:meth:`~VariableBase.get` +- :py:meth:`~VariableBase.set` +- :py:meth:`~VariableBase.set_previous` +- :py:meth:`~VariableBase.reset` +- :py:meth:`~VariableBase.increment` +- :py:meth:`~VariableBase.step_up` +- :py:meth:`~VariableBase.step_down` -.. rubric:: Variable properties +.. rubric:: VariableBase properties -:py:class:`.Variable` provides the following properties: +:py:class:`.VariableBase` provides the following properties: -- :py:attr:`~Variable.initial_value` -- :py:attr:`~Variable.last_value` -- :py:attr:`~Variable.previous_value` -- :py:attr:`~Variable.history` +- :py:attr:`~VariableBase.initial_value` +- :py:attr:`~VariableBase.last_value` +- :py:attr:`~VariableBase.previous_value` +- :py:attr:`~VariableBase.history` -The :py:class:`Variable` abstract class may be used as a base class to define +The :py:class:`VariableBase` abstract class may be used as a base class to define custom variables (see examples). Typically, this consist in overloading the abstract methods *_setfun* and *_getfun* .. rubric:: Examples -Write a subclass of :py:class:`Variable` which varies two drift lengths so +Write a subclass of :py:class:`VariableBase` which varies two drift lengths so that their sum is constant: .. code-block:: python - class ElementShifter(at.Variable): + class ElementShifter(at.VariableBase): '''Varies the length of the elements identified by *ref1* and *ref2* keeping the sum of their lengths equal to *total_length*. @@ -88,7 +88,7 @@ def _getfun(self, **kwargs): from collections.abc import Iterable, Sequence, Callable __all__ = [ - "Variable", + "VariableBase", "CustomVariable", "VariableList", ] @@ -98,11 +98,11 @@ def _nop(value): return value -class Variable(abc.ABC): - """A :py:class:`Variable` abstract base class +class VariableBase(abc.ABC): + """A Variable abstract base class - Derived classes must implement the :py:meth:`_getfun` and - :py:meth:`_getfun` methods + Derived classes must implement the :py:meth:`~VariableBase._getfun` and + :py:meth:`~VariableBase._getfun` methods """ _counter = 0 @@ -329,8 +329,8 @@ def __repr__(self): return repr(self.value) -class CustomVariable(Variable): - r"""A :py:class:`.Variable` with user-defined get and set functions +class CustomVariable(VariableBase): + r"""A Variable with user-defined get and set functions This is a convenience function allowing user-defined *get* and *set* functions. But subclassing :py:class:`.Variable` should always be preferred @@ -380,17 +380,17 @@ def _setfun(self, value: Number, **kwargs): class VariableList(list): - """Container for :py:class:`Variable` objects + """Container for Variable objects :py:class:`VariableList` supports all :py:class:`list` methods, like appending, insertion or concatenation with the "+" operator. """ def get(self, initial=False, **kwargs) -> Sequence[float]: - r"""Get the current :py:class:`Variable`\ s' values + r"""Get the current values of Variables Args: - initial: If :py:obj:`True`, set the :py:class:`Variable`\ s' + initial: If :py:obj:`True`, set the Variables' initial value Returns: @@ -399,7 +399,7 @@ def get(self, initial=False, **kwargs) -> Sequence[float]: return np.array([var.get(initial=initial, **kwargs) for var in self]) def set(self, values: Iterable[float], **kwargs) -> None: - r"""Set the :py:class:`Variable`\ s' values + r"""Set the values of Variables Args: values: Iterable of values @@ -408,7 +408,7 @@ def set(self, values: Iterable[float], **kwargs) -> None: var.set(val, **kwargs) def increment(self, increment: Iterable[float], **kwargs) -> None: - r"""Increment the :py:class:`Variable`\ s' values + r"""Increment the values of Variables Args: increment: Iterable of values @@ -420,7 +420,7 @@ def increment(self, increment: Iterable[float], **kwargs) -> None: def status(self, **kwargs) -> str: """String description of the variables""" values = "\n".join(var._line(**kwargs) for var in self) - return "\n".join((Variable._header(), values)) + return "\n".join((VariableBase._header(), values)) def __str__(self) -> str: return self.status() From d3b9d3b57a3af0cafd97002fa8d4a70d8778dfed Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Thu, 14 Dec 2023 12:00:19 +0100 Subject: [PATCH 11/29] Refactored Variable to VariableBase --- docs/p/notebooks/variables.ipynb | 82 +++++--------------------------- pyat/at/lattice/variables.py | 8 ++-- 2 files changed, 16 insertions(+), 74 deletions(-) diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index 47c76bfba..c62f41de7 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -51,7 +51,7 @@ "metadata": {}, "outputs": [], "source": [ - "from at.future import Variable, ElementVariable, RefptsVariable, CustomVariable" + "from at.future import VariableBase, ElementVariable, RefptsVariable, CustomVariable" ] }, { @@ -70,12 +70,12 @@ "Variables are **references** to any scalar quantity. Predefined classes are available\n", "for accessing any scalar attribute of an element, or any item of an array attribute.\n", "\n", - "Any other quantity may be accessed by either subclassing the {py:class}`~.variables.Variable`\n", + "Any other quantity may be accessed by either subclassing the {py:class}`~.variables.VariableBase`\n", "abstract base class, or using a {py:class}`~.variables.CustomVariable`.\n", "\n", - "## {py:class}`~.element_variables.ElementVariable`\n", + "## {py:class}`~.lattice_variables.ElementVariable`\n", "\n", - "An {py:class}`~.element_variables.ElementVariable` refers to a single attribute (or item of an array attribute) of one or several {py:class}`.Element` objects.\n", + "An {py:class}`~.lattice_variables.ElementVariable` refers to a single attribute (or item of an array attribute) of one or several {py:class}`.Element` objects.\n", "\n", "We now create a variable pointing to the length of all QF1 magnets of *ring*:" ] @@ -322,7 +322,7 @@ "id": "c650be51-228a-4ee0-ac73-d741601f992b", "metadata": {}, "source": [ - "An {py:class}`~.element_variables.ElementVariable` is linked to Elements. It will not follow any copy of the element, neither shallow nor deep. So if we make a copy of ring:" + "An {py:class}`~.lattice_variables.ElementVariable` is linked to Elements. It will not follow any copy of the element, neither shallow nor deep. So if we make a copy of ring:" ] }, { @@ -444,7 +444,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:194\u001b[0m, in \u001b[0;36mVariable.set\u001b[0;34m(self, value, **kwargs)\u001b[0m\n\u001b[1;32m 184\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 185\u001b[0m \n\u001b[1;32m 186\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 191\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 194\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 196\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:192\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring, **kwargs)\u001b[0m\n\u001b[1;32m 184\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 185\u001b[0m \n\u001b[1;32m 186\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 189\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 190\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 191\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 192\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 194\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } @@ -480,7 +480,7 @@ "source": [ "## {py:class}`.RefptsVariable`\n", "\n", - "An {py:class}`.RefptsVariable` is similar to an {py:class}`~.element_variables.ElementVariable` but it is not associated with an {py:class}`~.Element`\n", + "An {py:class}`.RefptsVariable` is similar to an {py:class}`~.lattice_variables.ElementVariable` but it is not associated with an {py:class}`~.Element`\n", "itself, but with its location in a Lattice. So it will act on any lattice with the same elements.\n", "\n", "But it needs a *ring* keyword in its *set* and *get* methods, to identify the selected lattice." @@ -561,7 +561,7 @@ "\n", "Note that defining correlated {py:class}`.Element` attributes may be easier done with Parameters. However Variables are not restricted to Element attributes, unlike Parameters.\n", "\n", - "Similarly to the {py:class}`~.element_variables.RefptsVariable`, we will refer to the 2 variable elements by their `refpts`. Alternatively, one could give the elements themselves, as in {py:class}`~.element_variables.ElementVariable`.\n", + "Similarly to the {py:class}`~.lattice_variables.RefptsVariable`, we will refer to the 2 variable elements by their `refpts`. Alternatively, one could give the elements themselves, as in {py:class}`~.lattice_variables.ElementVariable`.\n", "\n", "### Using the {py:class}`~.variables.CustomVariable`\n", "\n", @@ -765,9 +765,9 @@ "id": "0dc64ddf-26fd-4c19-a76b-b55f79b89717", "metadata": {}, "source": [ - "### By derivation of the {py:class}`Variable` class\n", + "### By derivation of the {py:class}`~.variables.VariableBase` class\n", "\n", - "We will write a new variable class based on {py:class}`Variable` abstract base class. The main task is to implement the `_setfun` and `_getfun` abstract methods." + "We will write a new variable class based on {py:class}`~.variables.VariableBase` abstract base class. The main task is to implement the `_setfun` and `_getfun` abstract methods." ] }, { @@ -777,7 +777,7 @@ "metadata": {}, "outputs": [], "source": [ - "class ElementShifter(Variable):\n", + "class ElementShifter(VariableBase):\n", " def __init__(self, ref1, ref2, total_length=None, **kwargs):\n", " \"\"\"Varies the length of the elements *dr1* and *dr2*\n", " keeping the sum of their lengths equal to *total_length*.\n", @@ -909,66 +909,6 @@ "Both variables behave similarly. But the derivation allows more control by making use of the\n", "`__init__` method. For instance here it includes the computation of the initial total length." ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "8a46d3a4-2899-4427-af85-d2fb4ae0a6f4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[2.5394599781303304]" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kf1.history" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "bb6879e3-62f2-42ba-a283-ed2bdc168aa4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.5394599781303304" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kf1.initial_value" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "23dfcbd8-39fd-41d2-97e3-022c21d3dc32", - "metadata": {}, - "outputs": [], - "source": [ - "kf1.reset()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1e1b5637-a2d1-4f09-8e35-e7106faa430d", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index d727b8464..43c9d889c 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -346,6 +346,7 @@ def __init__( name: str = "", bounds: tuple[Number, Number] = (-np.inf, np.inf), delta: Number = 1.0, + history_length: int = None, **kwargs, ): """ @@ -366,17 +367,18 @@ def __init__( and *setfun* functions. Such arguments can always be avoided by using :py:func:`~functools.partial` or callable class objects. """ - super().__init__(name=name, bounds=bounds, delta=delta) + super().__init__(name=name, bounds=bounds, delta=delta, + history_length=history_length) self.getfun = getfun self.setfun = setfun self.args = args self.kwargs = kwargs def _getfun(self, **kwargs) -> Number: - return self.getfun(*self.args, **self.kwargs) + return self.getfun(*self.args, **kwargs, **self.kwargs) def _setfun(self, value: Number, **kwargs): - self.setfun(value, *self.args, **self.kwargs) + self.setfun(value, *self.args, **kwargs, **self.kwargs) class VariableList(list): From eb2e4bf35b8d98dcd777226aa05cdaf810244a25 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Thu, 14 Dec 2023 13:39:21 +0100 Subject: [PATCH 12/29] "black" formatting --- pyat/at/lattice/lattice_variables.py | 1 + pyat/at/lattice/variables.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pyat/at/lattice/lattice_variables.py b/pyat/at/lattice/lattice_variables.py index c5afe99c4..b502e403b 100644 --- a/pyat/at/lattice/lattice_variables.py +++ b/pyat/at/lattice/lattice_variables.py @@ -9,6 +9,7 @@ *ring* argument must be provided to the *set* and *get* methods to identify the lattice, which may be a possibly modified copy of the original lattice """ + from __future__ import annotations from collections.abc import Sequence from typing import Union, Optional diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 43c9d889c..fb11fdedc 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -125,8 +125,8 @@ def __init__( means infinite """ self.name = self._setname(name) #: Variable name - self.bounds = bounds #: Variable bounds - self.delta = delta #: Increment step + self.bounds = bounds #: Variable bounds + self.delta = delta #: Increment step #: Maximum length of the history buffer. :py:obj:`None` means infinite self.history_length = history_length self._initial = None @@ -146,8 +146,7 @@ def _setfun(self, value: Number, ring=None, **kwargs): raise TypeError(f"{classname!r} is read-only") @abc.abstractmethod - def _getfun(self, ring=None, **kwargs) -> Number: - ... + def _getfun(self, ring=None, **kwargs) -> Number: ... @property def history(self) -> list[Number]: @@ -367,8 +366,9 @@ def __init__( and *setfun* functions. Such arguments can always be avoided by using :py:func:`~functools.partial` or callable class objects. """ - super().__init__(name=name, bounds=bounds, delta=delta, - history_length=history_length) + super().__init__( + name=name, bounds=bounds, delta=delta, history_length=history_length + ) self.getfun = getfun self.setfun = setfun self.args = args From 21e6e81b36c24fb307bf06d265ab3a490b1223c6 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Thu, 14 Dec 2023 14:06:11 +0100 Subject: [PATCH 13/29] merged elements.py from master --- pyat/at/lattice/elements.py | 45 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index b67c17dbf..ea9491f7a 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -721,8 +721,8 @@ def __init__(self, family_name: str, poly_a, poly_b, **kwargs): """ Args: family_name: Name of the element - poly_a: Array of normal multipole components - poly_b: Array of skew multipole components + poly_a: Array of skew multipole components + poly_b: Array of normal multipole components Keyword arguments: MaxOrder: Number of desired multipoles. Default: highest @@ -789,8 +789,8 @@ def __init__(self, family_name: str, length: float, poly_a, poly_b, Args: family_name: Name of the element length: Element length [m] - poly_a: Array of normal multipole components - poly_b: Array of skew multipole components + poly_a: Array of skew multipole components + poly_b: Array of normal multipole components Keyword arguments: MaxOrder: Number of desired multipoles. Default: highest @@ -873,16 +873,14 @@ def __init__(self, family_name: str, length: float, family_name: Name of the element length: Element length [m] bending_angle: Bending angle [rd] - poly_a: Array of normal multipole components - poly_b: Array of skew multipole components - k=0: Field index + k: Focusing strength [m^-2] Keyword arguments: EntranceAngle=0.0: entrance angle ExitAngle=0.0: exit angle PolynomB: straight multipoles PolynomA: skew multipoles - MaxOrder: Number of desired multipoles + MaxOrder=0: Number of desired multipoles NumIntSt=10: Number of integration steps FullGap: Magnet full gap FringeInt1: Extension of the entrance fringe field @@ -905,9 +903,9 @@ def __init__(self, family_name: str, length: float, FieldScaling: Scaling factor applied to the magnetic field Available PassMethods: :ref:`BndMPoleSymplectic4Pass`, - :ref:`BendLinearPass`, - :ref:`ExactSectorBendPass`, :ref:`ExactRectangularBendPass`, - :ref:`ExactRectBendPass`, BndStrMPoleSymplectic4Pass + :ref:`BendLinearPass`, :ref:`ExactSectorBendPass`, + :ref:`ExactRectangularBendPass`, :ref:`ExactRectBendPass`, + BndStrMPoleSymplectic4Pass Default PassMethod: :ref:`BndMPoleSymplectic4Pass` """ @@ -965,12 +963,12 @@ def __init__(self, family_name: str, length: float, Args: family_name: Name of the element length: Element length [m] - k: strength [mˆ-2] + k: Focusing strength [mˆ-2] Keyword Arguments: PolynomB: straight multipoles PolynomA: skew multipoles - MaxOrder: Number of desired multipoles + MaxOrder=1: Number of desired multipoles NumIntSteps=10: Number of integration steps FringeQuadEntrance: 0: no fringe field effect (default) @@ -1099,7 +1097,7 @@ def set_longt_motion(self, enable, new_pass=None, **kwargs): class M66(Element): """Linear (6, 6) transfer matrix""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ["M66"] _conversions = dict(Element._conversions, M66=_array66) def __init__(self, family_name: str, m66=None, **kwargs): @@ -1113,7 +1111,8 @@ def __init__(self, family_name: str, m66=None, **kwargs): if m66 is None: m66 = numpy.identity(6) kwargs.setdefault('PassMethod', 'Matrix66Pass') - super(M66, self).__init__(family_name, M66=m66, **kwargs) + kwargs.setdefault("M66", m66) + super(M66, self).__init__(family_name, **kwargs) class SimpleQuantDiff(_DictLongtMotion, Element): @@ -1182,7 +1181,10 @@ def __init__(self, family_name: str, betax: float = 1.0, class SimpleRadiation(_DictLongtMotion, Radiative, Element): """Simple radiation damping and energy loss""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['taux', 'tauy', 'tauz'] + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + _conversions = dict(Element._conversions, U0=float, + damp_mat_diag=lambda v: _array(v, shape=(6,))) + default_pass = {False: 'IdentityPass', True: 'SimpleRadiationPass'} def __init__(self, family_name: str, @@ -1201,8 +1203,6 @@ def __init__(self, family_name: str, Default PassMethod: ``SimpleRadiationPass`` """ - kwargs.setdefault('PassMethod', self.default_pass[True]) - assert taux >= 0.0, 'taux must be greater than or equal to 0' if taux == 0.0: dampx = 1 @@ -1221,11 +1221,10 @@ def __init__(self, family_name: str, else: dampz = numpy.exp(-1/tauz) - self.U0 = U0 - - self.damp_mat_diag = numpy.array([dampx, dampx, - dampy, dampy, - dampz, dampz]) + kwargs.setdefault('PassMethod', self.default_pass[True]) + kwargs.setdefault("U0", U0) + kwargs.setdefault("damp_mat_diag", + numpy.array([dampx, dampx, dampy, dampy, dampz, dampz])) super(SimpleRadiation, self).__init__(family_name, **kwargs) From ceb133b3b6810056f99b71aa27848a0c5f651701 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Sat, 16 Dec 2023 14:49:27 +0100 Subject: [PATCH 14/29] str and repr --- docs/p/notebooks/variables.ipynb | 6 +- pyat/at/lattice/lattice_variables.py | 5 +- pyat/at/lattice/variables.py | 123 ++++++++++++++------------- 3 files changed, 68 insertions(+), 66 deletions(-) diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index c62f41de7..c982ea8ea 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -9,9 +9,7 @@ "slideshow": { "slide_type": "" }, - "tags": [ - "remove-cell" - ] + "tags": [] }, "outputs": [], "source": [ @@ -444,7 +442,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:192\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring, **kwargs)\u001b[0m\n\u001b[1;32m 184\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 185\u001b[0m \n\u001b[1;32m 186\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 189\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 190\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 191\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 192\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 194\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:198\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 190\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 191\u001b[0m \n\u001b[1;32m 192\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 196\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 197\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 198\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 199\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 200\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } diff --git a/pyat/at/lattice/lattice_variables.py b/pyat/at/lattice/lattice_variables.py index b502e403b..4fac67d80 100644 --- a/pyat/at/lattice/lattice_variables.py +++ b/pyat/at/lattice/lattice_variables.py @@ -54,6 +54,8 @@ def __init__( bounds (tuple[float, float]): Lower and upper bounds of the variable value. Default: (-inf, inf) delta (float): Step. Default: 1.0 + ring (Lattice): If specified, it is used to get and store the initial + value of the variable. Otherwise, the initial value is set to None """ self._getf = getval(attrname, index=index) self._setf = setval(attrname, index=index) @@ -117,9 +119,6 @@ def __init__( self._getf = getval(attrname, index=index) self._setf = setval(attrname, index=index) super().__init__(**kwargs) - iniv = self._getfun() - self._initial = iniv - self._history.append(iniv) def _setfun(self, value: float, **kwargs): for elem in self._elements: diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index fb11fdedc..0843f2f05 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -115,6 +115,7 @@ def __init__( bounds: tuple[Number, Number] = (-np.inf, np.inf), delta: Number = 1.0, history_length: int = None, + ring=None, ): """ Parameters: @@ -123,14 +124,20 @@ def __init__( delta: Initial variation step history_length: Maximum length of the history buffer. :py:obj:`None` means infinite + ring: provided to an attempt to get the initial value of the + variable """ self.name = self._setname(name) #: Variable name self.bounds = bounds #: Variable bounds self.delta = delta #: Increment step #: Maximum length of the history buffer. :py:obj:`None` means infinite self.history_length = history_length - self._initial = None + self._initial = np.nan self._history = deque([], self.history_length) + try: + self.get(ring=ring, initial=True) + except ValueError: + pass @classmethod def _setname(cls, name): @@ -141,12 +148,12 @@ def _setname(cls, name): return f"{cls._prefix}{cls._counter}" # noinspection PyUnusedLocal - def _setfun(self, value: Number, ring=None, **kwargs): + def _setfun(self, value: Number, ring=None): classname = self.__class__.__name__ raise TypeError(f"{classname!r} is read-only") @abc.abstractmethod - def _getfun(self, ring=None, **kwargs) -> Number: ... + def _getfun(self, ring=None) -> Number: ... @property def history(self) -> list[Number]: @@ -156,7 +163,7 @@ def history(self) -> list[Number]: @property def initial_value(self) -> Number: """Initial value of the variable""" - if self._initial is not None: + if not np.isnan(self._initial): return self._initial else: raise IndexError(f"{self.name}: No value has been set yet") @@ -179,7 +186,7 @@ def previous_value(self) -> Number: exc.args = (f"{self.name}: history too short",) raise - def set(self, value: Number, ring=None, **kwargs) -> None: + def set(self, value: Number, ring=None) -> None: """Set the variable value Args: @@ -189,12 +196,12 @@ def set(self, value: Number, ring=None, **kwargs) -> None: """ if value < self.bounds[0] or value > self.bounds[1]: raise ValueError(f"set value must be in {self.bounds}") - self._setfun(value, ring=ring, **kwargs) - if self._initial is None: + self._setfun(value, ring=ring) + if np.isnan(self._initial): self._initial = value self._history.append(value) - def get(self, initial=False, ring=None, **kwargs) -> Number: + def get(self, ring=None, *, initial=False) -> Number: """Get the actual variable value Args: @@ -206,53 +213,55 @@ def get(self, initial=False, ring=None, **kwargs) -> Number: Returns: value: Value of the variable """ - value = self._getfun(ring=ring, **kwargs) + value = self._getfun(ring=ring) if initial: self._initial = value self._history = deque([value], self.history_length) - elif self._initial is None: + elif np.isnan(self._initial): self._initial = value return value - @property - def value(self): - return self.get() - - @value.setter - def value(self, value: Number): - self.set(value) + value = property(get, set, doc="Actual value") - def set_previous(self, ring=None, **kwargs) -> None: + def _safe_value(self, ring=None): + try: + v = self.get(ring=ring) + except ValueError: + try: + v = self._history[-1] + except IndexError: + v = np.nan + return v + + def set_previous(self, ring=None) -> None: """Reset to the value before the last one - Keyword Args: + Args: ring: Depending on the variable type, a :py:class:`.Lattice` argument may be necessary to set the variable. """ - try: + if len(self._history) >= 2: self._history.pop() # Remove the last value prev = self._history.pop() # retrieve the previous value - except IndexError as exc: - exc.args = (f"{self.name}: history too short",) - raise + self.set(prev, ring=ring) else: - self.set(prev, ring=ring, **kwargs) + raise IndexError(f"{self.name}: history too short",) - def reset(self, ring=None, **kwargs) -> None: - """Reset to the initial value + def reset(self, ring=None) -> None: + """Reset to the initial value and clear the history buffer Args: ring: Depending on the variable type, a :py:class:`.Lattice` argument may be necessary to reset the variable. """ iniv = self._initial - if iniv is not None: + if not np.isnan(iniv): self._history = deque([], self.history_length) - self.set(iniv, ring=ring, **kwargs) + self.set(iniv, ring=ring) else: raise IndexError(f"reset {self.name}: No value has been set yet") - def increment(self, incr: Number, ring=None, **kwargs) -> None: + def increment(self, incr: Number, ring=None) -> None: """Increment the variable value Args: @@ -261,31 +270,31 @@ def increment(self, incr: Number, ring=None, **kwargs) -> None: may be necessary to increment the variable. """ if len(self._history) == 0: - self.get(initial=True, **kwargs) - self.set(self.last_value + incr, ring=ring, **kwargs) + self.get(initial=True) + self.set(self.last_value + incr, ring=ring) - def _step(self, step: Number, ring=None, **kwargs) -> None: + def _step(self, step: Number, ring=None) -> None: if self._initial is None: - self.get(initial=True, **kwargs) - self.set(self._initial + step, ring=ring, **kwargs) + self.get(initial=True) + self.set(self._initial + step, ring=ring) - def step_up(self, ring=None, **kwargs) -> None: + def step_up(self, ring=None) -> None: """Set to initial_value + delta Args: ring: Depending on the variable type, a :py:class:`.Lattice` argument may be necessary to set the variable. """ - self._step(self.delta, ring=ring, **kwargs) + self._step(self.delta, ring=ring) - def step_down(self, ring=None, **kwargs) -> None: + def step_down(self, ring=None) -> None: """Set to initial_value - delta Args: ring: Depending on the variable type, a :py:class:`.Lattice` argument may be necessary to set the variable. """ - self._step(-self.delta, ring=ring, **kwargs) + self._step(-self.delta, ring=ring) @staticmethod def _header(): @@ -294,38 +303,32 @@ def _header(): ) def _line(self, ring=None): - if ring is not None: - vnow = self.get(ring) - vini = self._initial - elif len(self._history) > 0: - vnow = self._history[-1] - vini = self._initial - else: - vnow = vini = np.nan + vnow = self._safe_value(ring=ring) + vini = self._initial return "{:>12s}{: 16e}{: 16e}{: 16e}".format( self.name, vini, vnow, (vnow - vini) ) - def status(self, **kwargs): + def status(self, ring=None): """Return a string describing the current status of the variable Returns: status: Variable description """ - return "\n".join((self._header(), self._line(**kwargs))) + return "\n".join((self._header(), self._line(ring=ring))) def __float__(self): - return float(self.value) + return float(self._safe_value()) def __int__(self): - return int(self.value) + return int(self._safe_value()) def __str__(self): - return f"{self.__class__.__name__}({self.value}, name={self.name!r})" + return f"{self.__class__.__name__}({self._safe_value()}, name={self.name!r})" def __repr__(self): - return repr(self.value) + return repr(self._safe_value()) class CustomVariable(VariableBase): @@ -346,6 +349,7 @@ def __init__( bounds: tuple[Number, Number] = (-np.inf, np.inf), delta: Number = 1.0, history_length: int = None, + ring=None, **kwargs, ): """ @@ -366,19 +370,20 @@ def __init__( and *setfun* functions. Such arguments can always be avoided by using :py:func:`~functools.partial` or callable class objects. """ - super().__init__( - name=name, bounds=bounds, delta=delta, history_length=history_length - ) self.getfun = getfun self.setfun = setfun self.args = args self.kwargs = kwargs + super().__init__( + name=name, bounds=bounds, delta=delta, history_length=history_length, + ring=ring + ) - def _getfun(self, **kwargs) -> Number: - return self.getfun(*self.args, **kwargs, **self.kwargs) + def _getfun(self, ring=None) -> Number: + return self.getfun(*self.args, ring=ring, **self.kwargs) - def _setfun(self, value: Number, **kwargs): - self.setfun(value, *self.args, **kwargs, **self.kwargs) + def _setfun(self, value: Number, ring=None): + self.setfun(value, *self.args, ring=ring, **self.kwargs) class VariableList(list): From fe46583902ab9de038835bf7ea30423c518c37ca Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Tue, 19 Dec 2023 11:24:55 +0100 Subject: [PATCH 15/29] str and repr --- pyat/at/lattice/variables.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 0843f2f05..320bdd746 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -186,6 +186,14 @@ def previous_value(self) -> Number: exc.args = (f"{self.name}: history too short",) raise + @property + def _safe_value(self): + try: + v = self._history[-1] + except IndexError: + v = np.nan + return v + def set(self, value: Number, ring=None) -> None: """Set the variable value @@ -223,16 +231,6 @@ def get(self, ring=None, *, initial=False) -> Number: value = property(get, set, doc="Actual value") - def _safe_value(self, ring=None): - try: - v = self.get(ring=ring) - except ValueError: - try: - v = self._history[-1] - except IndexError: - v = np.nan - return v - def set_previous(self, ring=None) -> None: """Reset to the value before the last one @@ -303,7 +301,7 @@ def _header(): ) def _line(self, ring=None): - vnow = self._safe_value(ring=ring) + vnow = self._safe_value vini = self._initial return "{:>12s}{: 16e}{: 16e}{: 16e}".format( @@ -319,16 +317,16 @@ def status(self, ring=None): return "\n".join((self._header(), self._line(ring=ring))) def __float__(self): - return float(self._safe_value()) + return float(self._safe_value) def __int__(self): - return int(self._safe_value()) + return int(self._safe_value) def __str__(self): - return f"{self.__class__.__name__}({self._safe_value()}, name={self.name!r})" + return f"{self.__class__.__name__}({self._safe_value}, name={self.name!r})" def __repr__(self): - return repr(self._safe_value()) + return repr(self._safe_value) class CustomVariable(VariableBase): From 40afdf94729aca12f77b33053c46bae231b4d019 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Tue, 19 Dec 2023 21:06:43 +0100 Subject: [PATCH 16/29] small fixes --- docs/p/notebooks/variables.ipynb | 22 +++------ pyat/at/lattice/variables.py | 78 ++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index c982ea8ea..235904652 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -196,8 +196,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.53945998]\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5394599781303304]\n", "\tK : 2.5394599781303304\n" ] } @@ -238,8 +238,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.5]\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5]\n", "\tK : 2.5\n" ] } @@ -304,8 +304,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.53945998]\n", + "\tPolynomA : [0 0]\n", + "\tPolynomB : [0.0 2.5394599781303304]\n", "\tK : 2.5394599781303304\n" ] } @@ -442,7 +442,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:198\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 190\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 191\u001b[0m \n\u001b[1;32m 192\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 196\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 197\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 198\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 199\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 200\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:266\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 258\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 259\u001b[0m \n\u001b[1;32m 260\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 263\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 264\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 265\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 266\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 267\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 268\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } @@ -533,14 +533,6 @@ "print(f\"newring: {newring[5].PolynomB[1]}\")" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "d877abc7-2578-45af-8b3b-e6e4e52014ca", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "204d24e6-1e9b-4838-a950-3a8e1df5cac4", diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 320bdd746..5c5ce45cc 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -186,14 +186,6 @@ def previous_value(self) -> Number: exc.args = (f"{self.name}: history too short",) raise - @property - def _safe_value(self): - try: - v = self._history[-1] - except IndexError: - v = np.nan - return v - def set(self, value: Number, ring=None) -> None: """Set the variable value @@ -209,28 +201,41 @@ def set(self, value: Number, ring=None) -> None: self._initial = value self._history.append(value) - def get(self, ring=None, *, initial=False) -> Number: + def get( + self, ring=None, *, initial: bool = False, check_bounds: bool = False + ) -> Number: """Get the actual variable value Args: - initial: If :py:obj:`True`, clear the history and set the variable - initial value ring: Depending on the variable type, a :py:class:`.Lattice` argument may be necessary to get the variable value. + initial: If :py:obj:`True`, clear the history and set the variable + initial value + check_bounds: If :py:obj:`True`, raise a ValueError if the value is out + of bounds Returns: value: Value of the variable """ value = self._getfun(ring=ring) - if initial: + if initial or np.isnan(self._initial): self._initial = value self._history = deque([value], self.history_length) - elif np.isnan(self._initial): - self._initial = value + if check_bounds: + if value < self.bounds[0] or value > self.bounds[1]: + raise ValueError(f"value out of {self.bounds}") return value value = property(get, set, doc="Actual value") + @property + def _safe_value(self): + try: + v = self._history[-1] + except IndexError: + v = np.nan + return v + def set_previous(self, ring=None) -> None: """Reset to the value before the last one @@ -240,8 +245,8 @@ def set_previous(self, ring=None) -> None: """ if len(self._history) >= 2: self._history.pop() # Remove the last value - prev = self._history.pop() # retrieve the previous value - self.set(prev, ring=ring) + value = self._history.pop() # retrieve the previous value + self.set(value, ring=ring) else: raise IndexError(f"{self.name}: history too short",) @@ -267,13 +272,13 @@ def increment(self, incr: Number, ring=None) -> None: ring: Depending on the variable type, a :py:class:`.Lattice` argument may be necessary to increment the variable. """ - if len(self._history) == 0: - self.get(initial=True) + if self._initial is None: + self.get(ring=ring, initial=True) self.set(self.last_value + incr, ring=ring) def _step(self, step: Number, ring=None) -> None: if self._initial is None: - self.get(initial=True) + self.get(ring=ring, initial=True) self.set(self._initial + step, ring=ring) def step_up(self, ring=None) -> None: @@ -300,7 +305,7 @@ def _header(): "Name", "Initial", "Final ", "Variation" ) - def _line(self, ring=None): + def _line(self): vnow = self._safe_value vini = self._initial @@ -308,13 +313,13 @@ def _line(self, ring=None): self.name, vini, vnow, (vnow - vini) ) - def status(self, ring=None): + def status(self): """Return a string describing the current status of the variable Returns: status: Variable description """ - return "\n".join((self._header(), self._line(ring=ring))) + return "\n".join((self._header(), self._line())) def __float__(self): return float(self._safe_value) @@ -373,8 +378,11 @@ def __init__( self.args = args self.kwargs = kwargs super().__init__( - name=name, bounds=bounds, delta=delta, history_length=history_length, - ring=ring + name=name, + bounds=bounds, + delta=delta, + history_length=history_length, + ring=ring, ) def _getfun(self, ring=None) -> Number: @@ -391,35 +399,45 @@ class VariableList(list): appending, insertion or concatenation with the "+" operator. """ - def get(self, initial=False, **kwargs) -> Sequence[float]: + def get(self, ring=None, **kwargs) -> Sequence[float]: r"""Get the current values of Variables Args: + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to set the variable. + + Keyword Args: initial: If :py:obj:`True`, set the Variables' initial value + check_bounds: If :py:obj:`True`, raise a ValueError if the value is out + of bounds Returns: values: 1D array of values of all variables """ - return np.array([var.get(initial=initial, **kwargs) for var in self]) + return np.array([var.get(ring=ring, **kwargs) for var in self]) - def set(self, values: Iterable[float], **kwargs) -> None: + def set(self, values: Iterable[float], ring=None) -> None: r"""Set the values of Variables Args: values: Iterable of values + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to set the variable. """ for var, val in zip(self, values): - var.set(val, **kwargs) + var.set(val, ring=ring) - def increment(self, increment: Iterable[float], **kwargs) -> None: + def increment(self, increment: Iterable[float], ring=None) -> None: r"""Increment the values of Variables Args: increment: Iterable of values + ring: Depending on the variable type, a :py:class:`.Lattice` argument + may be necessary to increment the variable. """ for var, incr in zip(self, increment): - var.increment(incr, **kwargs) + var.increment(incr, ring=ring) # noinspection PyProtectedMember def status(self, **kwargs) -> str: From edff80d36dd810afda29cab707c4345281ee629e Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Wed, 20 Dec 2023 20:54:43 +0100 Subject: [PATCH 17/29] utils.py --- pyat/at/lattice/utils.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/pyat/at/lattice/utils.py b/pyat/at/lattice/utils.py index 49e5c207e..49a979048 100644 --- a/pyat/at/lattice/utils.py +++ b/pyat/at/lattice/utils.py @@ -841,13 +841,7 @@ def get_value_refpts(ring: Sequence[Element], refpts: Refpts, Returns: attrvalues: numpy Array of attribute values. """ - if index is None: - def getf(elem): - return getattr(elem, attrname) - else: - def getf(elem): - return getattr(elem, attrname)[index] - + getf = getval(attrname, index=index) return numpy.array([getf(elem) for elem in refpts_iterator(ring, refpts, regex=regex)]) @@ -884,13 +878,7 @@ def set_value_refpts(ring: Sequence[Element], refpts: Refpts, elements are shared with the original lattice. Any further modification will affect both lattices. """ - if index is None: - def setf(elem, value): - setattr(elem, attrname, value) - else: - def setf(elem, value): - getattr(elem, attrname)[index] = value - + setf = setval(attrname, index=index) if increment: attrvalues += get_value_refpts(ring, refpts, attrname, index=index, @@ -903,8 +891,7 @@ def setf(elem, value): # noinspection PyShadowingNames @make_copy(copy) def apply(ring, refpts, values, regex): - for elm, val in zip(refpts_iterator(ring, refpts, - regex=regex), values): + for elm, val in zip(refpts_iterator(ring, refpts, regex=regex), values): setf(elm, val) return apply(ring, refpts, attrvalues, regex) From 6d0e8036ae45a98cea3a2130ee7001f0a4676978 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Fri, 29 Dec 2023 11:10:08 +0100 Subject: [PATCH 18/29] changed Number --- pyat/at/lattice/variables.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 5c5ce45cc..1f4d19a95 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -83,9 +83,9 @@ def _getfun(self, **kwargs): from __future__ import annotations import numpy as np import abc -from numbers import Number from collections import deque from collections.abc import Iterable, Sequence, Callable +from typing import Union __all__ = [ "VariableBase", @@ -93,6 +93,8 @@ def _getfun(self, **kwargs): "VariableList", ] +Number = Union[int, float] + def _nop(value): return value From 6643ae2d3bc0104538e360dc2cf63188de0b3749 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Sun, 31 Dec 2023 15:50:20 +0100 Subject: [PATCH 19/29] minimised differences with master --- docs/p/notebooks/variables.ipynb | 29 ++++++++++++++-------------- pyat/at/lattice/lattice_object.py | 32 ++++++++++++++----------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index 235904652..19653e0dd 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -15,10 +15,7 @@ "source": [ "import at\n", "import sys\n", - "if sys.version_info.minor < 9:\n", - " from importlib_resources import files, as_file\n", - "else:\n", - " from importlib.resources import files, as_file\n", + "from importlib.resources import files, as_file\n", "import numpy as np" ] }, @@ -37,8 +34,8 @@ }, "outputs": [], "source": [ - "fname = 'hmba.mat'\n", - "with as_file(files('machine_data') / fname) as path:\n", + "fname = \"hmba.mat\"\n", + "with as_file(files(\"machine_data\") / fname) as path:\n", " ring = at.load_lattice(path)" ] }, @@ -196,8 +193,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5394599781303304]\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.53945998]\n", "\tK : 2.5394599781303304\n" ] } @@ -238,8 +235,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5]\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.5]\n", "\tK : 2.5\n" ] } @@ -304,8 +301,8 @@ "\tFringeQuadEntrance : 1\n", "\tFringeQuadExit : 1\n", "\tMaxOrder : 1\n", - "\tPolynomA : [0 0]\n", - "\tPolynomB : [0.0 2.5394599781303304]\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.53945998]\n", "\tK : 2.5394599781303304\n" ] } @@ -442,7 +439,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:266\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 258\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 259\u001b[0m \n\u001b[1;32m 260\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 263\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 264\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 265\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 266\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 267\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 268\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:200\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 193\u001b[0m \n\u001b[1;32m 194\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 197\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 198\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 199\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 200\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 202\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } @@ -627,7 +624,9 @@ "l1 = np.mean([elem.Length for elem in ring.select(elem1)])\n", "l2 = np.mean([elem.Length for elem in ring.select(elem2)])\n", "# Create the variable\n", - "elem_shifter1 = CustomVariable(setvar, getvar, elem1, elem2, l1+l2, bounds=(0, l1+l2))" + "elem_shifter1 = CustomVariable(\n", + " setvar, getvar, elem1, elem2, l1 + l2, bounds=(0, l1 + l2)\n", + ")" ] }, { @@ -773,7 +772,7 @@ " keeping the sum of their lengths equal to *total_length*.\n", "\n", " If *total_length* is None, it is set to the initial total length\n", - " \"\"\" \n", + " \"\"\"\n", " # Store the indices of the 2 variable elements\n", " self.ref1 = ref1\n", " self.ref2 = ref2\n", diff --git a/pyat/at/lattice/lattice_object.py b/pyat/at/lattice/lattice_object.py index 3fbd3e581..a9972f967 100644 --- a/pyat/at/lattice/lattice_object.py +++ b/pyat/at/lattice/lattice_object.py @@ -326,7 +326,6 @@ def insert(self, idx: SupportsIndex, elem: Element, copy_elements=False): super().insert(idx, elem) def extend(self, elems: Iterable[Element], copy_elements=False): - # noinspection PyUnresolvedReferences r"""This method adds all the elements of `elems` to the end of the lattice. The behavior is the same as for a :py:obj:`list` @@ -348,25 +347,24 @@ def extend(self, elems: Iterable[Element], copy_elements=False): super().extend(elems) def append(self, elem: Element, copy_elements=False): - # noinspection PyUnresolvedReferences - r"""This method overwrites the inherited method :py:meth:`list.append()`, - its behavior is changed, it accepts only AT lattice elements - :py:obj:`Element` as input argument. - - Equivalents syntaxes: - >>> ring.append(elem) - >>> ring += [elem] - - Parameters: - elem (Element): AT element to be appended to the lattice - copy_elements(bool): Default :py:obj:`True`. - If :py:obj:`True` a deep copy of elem - is used + r"""This method overwrites the inherited method + :py:meth:`list.append()`, + it behavior is changed, it accepts only AT lattice elements + :py:obj:`Element` as input argument. + + Equivalents syntaxes: + >>> ring.append(elem) + >>> ring += [elem] + + Parameters: + elem (Element): AT element to be appended to the lattice + copy_elements(bool): Default :py:obj:`True`. + If :py:obj:`True` a deep copy of elem + is used """ self.extend([elem], copy_elements=copy_elements) def repeat(self, n: int, copy_elements=True): - # noinspection SpellCheckingInspection,PyUnresolvedReferences,PyRedeclaration r"""This method allows to repeat the lattice `n` times. If `n` does not divide `ring.periodicity`, the new ring periodicity is set to 1, otherwise it is et to @@ -411,7 +409,6 @@ def copy_fun(elem, copy): def concatenate(self, *lattices: Iterable[Element], copy_elements=False, copy=False): - # noinspection PyUnresolvedReferences,SpellCheckingInspection,PyRedeclaration """Concatenate several `Iterable[Element]` with the lattice Equivalents syntaxes: @@ -445,7 +442,6 @@ def concatenate(self, *lattices: Iterable[Element], return lattice if copy else None def reverse(self, copy=False): - # noinspection PyUnresolvedReferences r"""Reverse the order of the lattice and swapt the faces of elements. Alignment errors are not swapped From 0f6ee60ea76ebd974f128540b41fa0f986daa184 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Sun, 31 Dec 2023 17:33:43 +0100 Subject: [PATCH 20/29] Optimised imports --- pyat/at/lattice/lattice_variables.py | 9 ++++++--- pyat/at/lattice/variables.py | 12 +++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pyat/at/lattice/lattice_variables.py b/pyat/at/lattice/lattice_variables.py index 4fac67d80..aa94069b0 100644 --- a/pyat/at/lattice/lattice_variables.py +++ b/pyat/at/lattice/lattice_variables.py @@ -11,16 +11,19 @@ """ from __future__ import annotations + +__all__ = ["RefptsVariable", "ElementVariable"] + from collections.abc import Sequence from typing import Union, Optional + import numpy as np -from .utils import Refpts, getval, setval + from .elements import Element from .lattice_object import Lattice +from .utils import Refpts, getval, setval from .variables import VariableBase -__all__ = ["RefptsVariable", "ElementVariable"] - class RefptsVariable(VariableBase): r"""A reference to a scalar attribute of :py:class:`.Lattice` elements. diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 1f4d19a95..791fe94d1 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -81,11 +81,6 @@ def _getfun(self, **kwargs): """ from __future__ import annotations -import numpy as np -import abc -from collections import deque -from collections.abc import Iterable, Sequence, Callable -from typing import Union __all__ = [ "VariableBase", @@ -93,6 +88,13 @@ def _getfun(self, **kwargs): "VariableList", ] +import abc +from collections import deque +from collections.abc import Iterable, Sequence, Callable +from typing import Union + +import numpy as np + Number = Union[int, float] From 960827fb9343d6721e354231e170558aa9751014 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Sun, 7 Jan 2024 16:31:48 +0100 Subject: [PATCH 21/29] Documentation --- pyat/at/lattice/variables.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 791fe94d1..63dc0fd6e 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -131,9 +131,9 @@ def __init__( ring: provided to an attempt to get the initial value of the variable """ - self.name = self._setname(name) #: Variable name - self.bounds = bounds #: Variable bounds - self.delta = delta #: Increment step + self.name: str = self._setname(name) #: Variable name + self.bounds: tuple[Number, Number] = bounds #: Variable bounds + self.delta: Number = delta #: Increment step #: Maximum length of the history buffer. :py:obj:`None` means infinite self.history_length = history_length self._initial = np.nan From d75764103222f428b7f313e3f3081800e4b4605c Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Thu, 11 Jan 2024 17:49:15 +0100 Subject: [PATCH 22/29] PEP8 checks --- pyat/at/lattice/elements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index ea9491f7a..71bdf5f0f 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -1076,9 +1076,9 @@ def _part(self, fr, sumfr): return pp def is_compatible(self, other) -> bool: - return (super().is_compatible(other) and - self.Frequency == other.Frequency and - self.TimeLag == other.TimeLag) + return (super().is_compatible(other) + and self.Frequency == other.Frequency + and self.TimeLag == other.TimeLag) def merge(self, other) -> None: super().merge(other) From c3e31eace93e6091886b670ba835e7c401cf4309 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Mon, 22 Jan 2024 09:44:36 +0100 Subject: [PATCH 23/29] variable notebook --- docs/p/notebooks/variables.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index 19653e0dd..14d6828be 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -439,7 +439,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:200\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 193\u001b[0m \n\u001b[1;32m 194\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 197\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 198\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 199\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 200\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 202\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", + "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:202\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 194\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 195\u001b[0m \n\u001b[1;32m 196\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 199\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 200\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 202\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 203\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 204\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" ] } @@ -459,7 +459,7 @@ "tags": [] }, "source": [ - "Variables also accept a *delta* keyword argument. Its value is used as the initial step in matching, and in the {py:meth}`~.variables.Variable.step_up` and {py:meth}`~.variables.Variable.step_down` methods." + "Variables also accept a *delta* keyword argument. Its value is used as the initial step in matching, and in the {py:meth}`~.variables.VariableBase.step_up` and {py:meth}`~.variables.VariableBase.step_down` methods." ] }, { From bf213fb58c4db7a39605a4f59fcf3a67f5e744ba Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Tue, 23 Jan 2024 16:27:20 +0100 Subject: [PATCH 24/29] axisdef --- pyat/at/lattice/axisdef.py | 34 ++++++++++++------------ pyat/at/lattice/elements.py | 6 ++--- pyat/at/lattice/lattice_object.py | 43 +++++++++++++++++-------------- 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/pyat/at/lattice/axisdef.py b/pyat/at/lattice/axisdef.py index ae23141db..6f51608cf 100644 --- a/pyat/at/lattice/axisdef.py +++ b/pyat/at/lattice/axisdef.py @@ -1,6 +1,8 @@ """Helper functions for axis and plane descriptions""" + from __future__ import annotations from typing import Optional, Union + # For sys.version_info.minor < 9: from typing import Tuple @@ -16,31 +18,31 @@ ct=dict(index=5, label=r"$\beta c \tau$", unit=" [m]"), ) for xk, xv in [it for it in _axis_def.items()]: - xv['code'] = xk - _axis_def[xv['index']] = xv + xv["code"] = xk + _axis_def[xv["index"]] = xv _axis_def[xk.upper()] = xv -_axis_def['delta'] = _axis_def['dp'] -_axis_def['xp'] = _axis_def['px'] # For backward compatibility -_axis_def['yp'] = _axis_def['py'] # For backward compatibility -_axis_def['s'] = _axis_def['ct'] -_axis_def['S'] = _axis_def['ct'] -_axis_def[None] = dict(index=slice(None), label="", unit="", code=":") +_axis_def["delta"] = _axis_def["dp"] +_axis_def["xp"] = _axis_def["px"] # For backward compatibility +_axis_def["yp"] = _axis_def["py"] # For backward compatibility +_axis_def["s"] = _axis_def["ct"] +_axis_def["S"] = _axis_def["ct"] +_axis_def[None] = dict(index=None, label="", unit="", code=":") _axis_def[Ellipsis] = dict(index=Ellipsis, label="", unit="", code="...") _plane_def = dict( x=dict(index=0, label="x", unit=" [m]"), y=dict(index=1, label="y", unit=" [m]"), - z=dict(index=2, label="z", unit="") + z=dict(index=2, label="z", unit=""), ) for xk, xv in [it for it in _plane_def.items()]: - xv['code'] = xk - _plane_def[xv['index']] = xv + xv["code"] = xk + _plane_def[xv["index"]] = xv _plane_def[xk.upper()] = xv -_plane_def['h'] = _plane_def['x'] -_plane_def['v'] = _plane_def['y'] -_plane_def['H'] = _plane_def['x'] -_plane_def['V'] = _plane_def['y'] -_plane_def[None] = dict(index=slice(None), label="", unit="", code=":") +_plane_def["h"] = _plane_def["x"] +_plane_def["v"] = _plane_def["y"] +_plane_def["H"] = _plane_def["x"] +_plane_def["V"] = _plane_def["y"] +_plane_def[None] = dict(index=None, label="", unit="", code=":") _plane_def[Ellipsis] = dict(index=Ellipsis, label="", unit="", code="...") diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 71bdf5f0f..45324944e 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -930,9 +930,9 @@ def is_compatible(self, other) -> bool: def invrho(dip: Dipole): return dip.BendingAngle / dip.Length - return (super().is_compatible(other) and - self.ExitAngle == -other.EntranceAngle and - abs(invrho(self) - invrho(other)) <= 1.e-6) + return (super().is_compatible(other) + and self.ExitAngle == -other.EntranceAngle + and abs(invrho(self) - invrho(other)) <= 1.e-6) def merge(self, other) -> None: super().merge(other) diff --git a/pyat/at/lattice/lattice_object.py b/pyat/at/lattice/lattice_object.py index a9972f967..2467f56f4 100644 --- a/pyat/at/lattice/lattice_object.py +++ b/pyat/at/lattice/lattice_object.py @@ -319,13 +319,13 @@ def insert(self, idx: SupportsIndex, elem: Element, copy_elements=False): If :py:obj:`True` a deep copy of elem is used. """ - # noinspection PyUnusedLocal # scan the new element to update it - elist = list(self._addition_filter([elem], + elist = list(self._addition_filter([elem], # noqa: F841 copy_elements=copy_elements)) super().insert(idx, elem) def extend(self, elems: Iterable[Element], copy_elements=False): + # noinspection PyUnresolvedReferences r"""This method adds all the elements of `elems` to the end of the lattice. The behavior is the same as for a :py:obj:`list` @@ -347,24 +347,25 @@ def extend(self, elems: Iterable[Element], copy_elements=False): super().extend(elems) def append(self, elem: Element, copy_elements=False): - r"""This method overwrites the inherited method - :py:meth:`list.append()`, - it behavior is changed, it accepts only AT lattice elements - :py:obj:`Element` as input argument. - - Equivalents syntaxes: - >>> ring.append(elem) - >>> ring += [elem] - - Parameters: - elem (Element): AT element to be appended to the lattice - copy_elements(bool): Default :py:obj:`True`. - If :py:obj:`True` a deep copy of elem - is used + # noinspection PyUnresolvedReferences + r"""This method overwrites the inherited method :py:meth:`list.append()`, + its behavior is changed, it accepts only AT lattice elements + :py:obj:`Element` as input argument. + + Equivalents syntaxes: + >>> ring.append(elem) + >>> ring += [elem] + + Parameters: + elem (Element): AT element to be appended to the lattice + copy_elements(bool): Default :py:obj:`True`. + If :py:obj:`True` a deep copy of elem + is used """ self.extend([elem], copy_elements=copy_elements) def repeat(self, n: int, copy_elements=True): + # noinspection SpellCheckingInspection,PyUnresolvedReferences,PyRedeclaration r"""This method allows to repeat the lattice `n` times. If `n` does not divide `ring.periodicity`, the new ring periodicity is set to 1, otherwise it is et to @@ -409,6 +410,7 @@ def copy_fun(elem, copy): def concatenate(self, *lattices: Iterable[Element], copy_elements=False, copy=False): + # noinspection PyUnresolvedReferences,SpellCheckingInspection,PyRedeclaration """Concatenate several `Iterable[Element]` with the lattice Equivalents syntaxes: @@ -442,6 +444,7 @@ def concatenate(self, *lattices: Iterable[Element], return lattice if copy else None def reverse(self, copy=False): + # noinspection PyUnresolvedReferences r"""Reverse the order of the lattice and swapt the faces of elements. Alignment errors are not swapped @@ -520,7 +523,7 @@ def copy(self) -> Lattice: def deepcopy(self) -> Lattice: """Returns a deep copy of the lattice""" return copy.deepcopy(self) - + def slice_elements(self, refpts: Refpts, slices: int = 1) -> Lattice: """Create a new lattice by slicing the elements at refpts @@ -542,7 +545,7 @@ def slice_generator(_): else: yield el - return Lattice(slice_generator, iterator=self.attrs_filter) + return Lattice(slice_generator, iterator=self.attrs_filter) def slice(self, size: Optional[float] = None, slices: Optional[int] = 1) \ -> Lattice: @@ -639,8 +642,8 @@ def energy(self) -> float: def energy(self, energy: float): # Set the Energy attribute of radiating elements for elem in self: - if (isinstance(elem, (elt.RFCavity, elt.Wiggler)) or - elem.PassMethod.endswith('RadPass')): + if (isinstance(elem, (elt.RFCavity, elt.Wiggler)) + or elem.PassMethod.endswith('RadPass')): elem.Energy = energy # Set the energy attribute of the Lattice # Use a numpy scalar to allow division by zero From 3cb94aec94719d89a99a256058c6a820b4111440 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Sun, 28 Jan 2024 20:48:04 +0100 Subject: [PATCH 25/29] rebased on master --- pyat/at/lattice/elements.py | 161 ++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 71 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 45324944e..080ed1538 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -5,14 +5,18 @@ appropriate attributes. If a different PassMethod is set, it is the caller's responsibility to ensure that the appropriate attributes are present. """ + from __future__ import annotations + import abc import re -import numpy -from copy import copy, deepcopy from abc import ABC from collections.abc import Generator, Iterable -from typing import Optional +from copy import copy, deepcopy +from typing import Any, Optional + +import numpy + # noinspection PyProtectedMember from .variables import _nop @@ -27,11 +31,11 @@ def _array66(value): return _array(value, shape=(6, 6)) -def _float(value): +def _float(value) -> float: return float(value) -def _int(value, vmin: Optional[int] = None, vmax: Optional[int] = None): +def _int(value, vmin: Optional[int] = None, vmax: Optional[int] = None) -> int: intv = int(value) if vmin is not None and intv < vmin: raise ValueError(f"Value must be greater of equal to {vmin}") @@ -53,6 +57,7 @@ class LongtMotion(ABC): * ``set_longt_motion(self, enable, new_pass=None, copy=False, **kwargs)`` must enable or disable longitudinal motion. """ + @abc.abstractmethod def _get_longt_motion(self): return False @@ -114,7 +119,8 @@ class _DictLongtMotion(LongtMotion): Defines a class such that :py:meth:`set_longt_motion` will select ``'IdentityPass'`` or ``'IdentityPass'``. - """ + """ + def _get_longt_motion(self): return self.PassMethod != self.default_pass[False] @@ -172,16 +178,20 @@ def set_longt_motion(self, enable, new_pass=None, copy=False, **kwargs): if new_pass is None or new_pass == self.PassMethod: return self if copy else None if enable: + def setpass(el): el.PassMethod = new_pass el.Energy = kwargs['energy'] + else: + def setpass(el): el.PassMethod = new_pass try: del el.Energy except AttributeError: pass + if copy: newelem = deepcopy(self) setpass(newelem) @@ -292,12 +302,12 @@ def __setattr__(self, key, value): super(Element, self).__setattr__(key, value) def __str__(self): - first3 = ['FamName', 'Length', 'PassMethod'] + first3 = ["FamName", "Length", "PassMethod"] # Get values and parameter objects attrs = dict(self.items()) keywords = [f"\t{k} : {attrs.pop(k)!s}" for k in first3] keywords += [f"\t{k} : {v!s}" for k, v in attrs.items()] - return '\n'.join((type(self).__name__ + ':', '\n'.join(keywords))) + return "\n".join((type(self).__name__ + ":", "\n".join(keywords))) def __repr__(self): # Get values only, even for parameters @@ -305,10 +315,13 @@ def __repr__(self): arguments = [attrs.pop(k) for k in self._BUILD_ATTRIBUTES] defelem = self.__class__(*arguments) keywords = [f"{v!r}" for v in arguments] - keywords += [f"{k}={v!r}" for k, v in sorted(attrs.items()) - if not numpy.array_equal(v, getattr(defelem, k, None))] - args = re.sub(r'\n\s*', ' ', ', '.join(keywords)) - return '{0}({1})'.format(self.__class__.__name__, args) + keywords += [ + f"{k}={v!r}" + for k, v in sorted(attrs.items()) + if not numpy.array_equal(v, getattr(defelem, k, None)) + ] + args = re.sub(r"\n\s*", " ", ", ".join(keywords)) + return "{0}({1})".format(self.__class__.__name__, args) def equals(self, other) -> bool: """Whether an element is equivalent to another. @@ -339,10 +352,12 @@ def divide(self, frac) -> list[Element]: def swap_faces(self, copy=False): """Swap the faces of an element, alignment errors are ignored""" + def swapattr(element, attro, attri): val = getattr(element, attri) delattr(element, attri) return attro, val + if copy: el = self.copy() else: @@ -373,7 +388,7 @@ def update(self, *args, **kwargs): Update the element attributes with the given arguments """ attrs = dict(*args, **kwargs) - for (key, value) in attrs.items(): + for key, value in attrs.items(): setattr(self, key, value) def copy(self) -> Element: @@ -384,7 +399,7 @@ def deepcopy(self) -> Element: """Return a deep copy of the element""" return deepcopy(self) - def items(self) -> Generator[tuple, None, None]: + def items(self) -> Generator[tuple[str, Any], None, None]: """Iterates through the data members""" # Properties may be added by overloading this method yield from vars(self).items() @@ -397,8 +412,7 @@ def merge(self, other) -> None: """Merge another element""" if not self.is_compatible(other): badname = getattr(other, 'FamName', type(other)) - raise TypeError('Cannot merge {0} and {1}'.format(self.FamName, - badname)) + raise TypeError("Cannot merge {0} and {1}".format(self.FamName, badname)) # noinspection PyMethodMayBeStatic def _get_longt_motion(self): @@ -420,8 +434,8 @@ def is_collective(self) -> bool: class LongElement(Element): - """Base class for long elements - """ + """Base class for long elements""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['Length'] def __init__(self, family_name: str, length: float, *args, **kwargs): @@ -455,8 +469,7 @@ def popattr(element, attr): # Remove entrance and exit attributes fin = dict(popattr(el, key) for key in vars(self) if key in self._entrance_fields) - fout = dict(popattr(el, key) for key in vars(self) if - key in self._exit_fields) + fout = dict(popattr(el, key) for key in vars(self) if key in self._exit_fields) # Split element element_list = [el._part(f, numpy.sum(frac)) for f in frac] # Restore entrance and exit attributes @@ -467,8 +480,22 @@ def popattr(element, attr): return element_list def is_compatible(self, other) -> bool: - return type(other) is type(self) and \ - self.PassMethod == other.PassMethod + def compatible_field(fieldname): + f1 = getattr(self, fieldname, None) + f2 = getattr(other, fieldname, None) + if f1 is None and f2 is None: # no such field + return True + elif f1 is None or f2 is None: # only one + return False + else: # both + return numpy.all(f1 == f2) + + if not (type(other) is type(self) and self.PassMethod == other.PassMethod): + return False + for fname in ("RApertures", "EApertures"): + if not compatible_field(fname): + return False + return True def merge(self, other) -> None: super().merge(other) @@ -515,6 +542,7 @@ def means(self): class SliceMoments(Element): """Element to compute slices mean and std""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['nslice'] _conversions = dict(Element._conversions, nslice=int) @@ -533,8 +561,7 @@ def __init__(self, family_name: str, nslice: int, **kwargs): kwargs.setdefault('PassMethod', 'SliceMomentsPass') self._startturn = kwargs.pop('startturn', 0) self._endturn = kwargs.pop('endturn', 1) - super(SliceMoments, self).__init__(family_name, nslice=nslice, - **kwargs) + super(SliceMoments, self).__init__(family_name, nslice=nslice, **kwargs) self._nbunch = 1 self.startturn = self._startturn self.endturn = self._endturn @@ -549,45 +576,33 @@ def set_buffers(self, nturns, nbunch): self.endturn = min(self.endturn, nturns) self._dturns = self.endturn - self.startturn self._nbunch = nbunch - self._stds = numpy.zeros((3, nbunch*self.nslice, self._dturns), - order='F') - self._means = numpy.zeros((3, nbunch*self.nslice, self._dturns), - order='F') - self._spos = numpy.zeros((nbunch*self.nslice, self._dturns), - order='F') - self._weights = numpy.zeros((nbunch*self.nslice, self._dturns), - order='F') + self._stds = numpy.zeros((3, nbunch*self.nslice, self._dturns), order="F") + self._means = numpy.zeros((3, nbunch*self.nslice, self._dturns), order="F") + self._spos = numpy.zeros((nbunch*self.nslice, self._dturns), order="F") + self._weights = numpy.zeros((nbunch*self.nslice, self._dturns), order="F") @property def stds(self): """Slices x,y,dp standard deviation""" - return self._stds.reshape((3, self._nbunch, - self.nslice, - self._dturns)) + return self._stds.reshape((3, self._nbunch, self.nslice, self._dturns)) @property def means(self): """Slices x,y,dp center of mass""" - return self._means.reshape((3, self._nbunch, - self.nslice, - self._dturns)) + return self._means.reshape((3, self._nbunch, self.nslice, self._dturns)) @property def spos(self): """Slices s position""" - return self._spos.reshape((self._nbunch, - self.nslice, - self._dturns)) + return self._spos.reshape((self._nbunch, self.nslice, self._dturns)) @property def weights(self): """Slices weights in mA if beam current >0, - otherwise fraction of total number of - particles in the bunch + otherwise fraction of total number of + particles in the bunch """ - return self._weights.reshape((self._nbunch, - self.nslice, - self._dturns)) + return self._weights.reshape((self._nbunch, self.nslice, self._dturns)) @property def startturn(self): @@ -618,6 +633,7 @@ def endturn(self, value): class Aperture(Element): """Aperture element""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['Limits'] _conversions = dict(Element._conversions, Limits=lambda v: _array(v, (4,))) @@ -696,6 +712,7 @@ def insert(self, class Collimator(Drift): """Collimator element""" + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['RApertures'] def __init__(self, family_name: str, length: float, limits, **kwargs): @@ -714,8 +731,8 @@ def __init__(self, family_name: str, length: float, limits, **kwargs): class ThinMultipole(Element): """Thin multipole element""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['PolynomA', - 'PolynomB'] + + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['PolynomA', 'PolynomB'] def __init__(self, family_name: str, poly_a, poly_b, **kwargs): """ @@ -772,19 +789,17 @@ def __setattr__(self, key, value): intval = int(value) lmax = min(len(getattr(self, k)) for k in polys) if not intval < lmax: - raise ValueError( - 'MaxOrder must be smaller than {0}'.format(lmax)) + raise ValueError('MaxOrder must be smaller than {0}'.format(lmax)) super(ThinMultipole, self).__setattr__(key, value) class Multipole(_Radiative, LongElement, ThinMultipole): """Multipole element""" - _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['PolynomA', - 'PolynomB'] + + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['PolynomA', 'PolynomB'] _conversions = dict(ThinMultipole._conversions, K=float, H=float) - def __init__(self, family_name: str, length: float, poly_a, poly_b, - **kwargs): + def __init__(self, family_name: str, length: float, poly_a, poly_b, **kwargs): """ Args: family_name: Name of the element @@ -804,12 +819,10 @@ def __init__(self, family_name: str, length: float, poly_a, poly_b, """ kwargs.setdefault('PassMethod', 'StrMPoleSymplectic4Pass') kwargs.setdefault('NumIntSteps', 10) - super(Multipole, self).__init__(family_name, length, - poly_a, poly_b, **kwargs) + super(Multipole, self).__init__(family_name, length, poly_a, poly_b, **kwargs) def is_compatible(self, other) -> bool: - if super().is_compatible(other) and \ - self.MaxOrder == other.MaxOrder: + if super().is_compatible(other) and self.MaxOrder == other.MaxOrder: for i in range(self.MaxOrder + 1): if self.PolynomB[i] != other.PolynomB[i]: return False @@ -917,7 +930,7 @@ def __init__(self, family_name: str, length: float, def items(self) -> Generator[tuple, None, None]: yield from super().items() - yield 'K', vars(self)["PolynomB"][1] + yield "K", vars(self)["PolynomB"][1] def _part(self, fr, sumfr): pp = super(Dipole, self)._part(fr, sumfr) @@ -947,6 +960,7 @@ def merge(self, other) -> None: class Quadrupole(Radiative, Multipole): """Quadrupole element""" + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['K'] _conversions = dict(Multipole._conversions, FringeQuadEntrance=int, FringeQuadExit=int) @@ -985,16 +999,16 @@ def __init__(self, family_name: str, length: float, Default PassMethod: ``StrMPoleSymplectic4Pass`` """ kwargs.setdefault("PassMethod", "StrMPoleSymplectic4Pass") - super(Quadrupole, self).__init__(family_name, length, [], [0.0, k], - **kwargs) + super(Quadrupole, self).__init__(family_name, length, [], [0.0, k], **kwargs) def items(self) -> Generator[tuple, None, None]: yield from super().items() - yield 'K', vars(self)["PolynomB"][1] + yield "K", vars(self)["PolynomB"][1] class Sextupole(Multipole): """Sextupole element""" + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['H'] DefaultOrder = 2 @@ -1024,11 +1038,12 @@ def __init__(self, family_name: str, length: float, def items(self) -> Generator[tuple, None, None]: yield from super().items() - yield 'H', vars(self)["PolynomB"][2] + yield "H", vars(self)["PolynomB"][2] class Octupole(Multipole): """Octupole element, with no changes from multipole at present""" + _BUILD_ATTRIBUTES = Multipole._BUILD_ATTRIBUTES DefaultOrder = 3 @@ -1036,6 +1051,7 @@ class Octupole(Multipole): class RFCavity(LongtMotion, LongElement): """RF cavity element""" + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['Voltage', 'Frequency', 'HarmNumber', @@ -1097,6 +1113,7 @@ def set_longt_motion(self, enable, new_pass=None, **kwargs): class M66(Element): """Linear (6, 6) transfer matrix""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ["M66"] _conversions = dict(Element._conversions, M66=_array66) @@ -1107,7 +1124,7 @@ def __init__(self, family_name: str, m66=None, **kwargs): m66: Transfer matrix. Default: Identity matrix Default PassMethod: ``Matrix66Pass`` - """ + """ if m66 is None: m66 = numpy.identity(6) kwargs.setdefault('PassMethod', 'Matrix66Pass') @@ -1123,6 +1140,7 @@ class SimpleQuantDiff(_DictLongtMotion, Element): Note: The damping times are needed to compute the correct kick for the emittance. Radiation damping is NOT applied. """ + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES default_pass = {False: 'IdentityPass', True: 'SimpleQuantDiffPass'} @@ -1147,8 +1165,8 @@ def __init__(self, family_name: str, betax: float = 1.0, tauz: Longitudinal damping time [turns] Default PassMethod: ``SimpleQuantDiffPass`` - """ - kwargs.setdefault('PassMethod', self.default_pass[True]) + """ + kwargs.setdefault("PassMethod", self.default_pass[True]) assert taux >= 0.0, 'taux must be greater than or equal to 0' self.taux = taux @@ -1181,6 +1199,7 @@ def __init__(self, family_name: str, betax: float = 1.0, class SimpleRadiation(_DictLongtMotion, Radiative, Element): """Simple radiation damping and energy loss""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES _conversions = dict(Element._conversions, U0=float, damp_mat_diag=lambda v: _array(v, shape=(6,))) @@ -1202,7 +1221,7 @@ def __init__(self, family_name: str, U0: Energy loss per turn [eV] Default PassMethod: ``SimpleRadiationPass`` - """ + """ assert taux >= 0.0, 'taux must be greater than or equal to 0' if taux == 0.0: dampx = 1 @@ -1231,6 +1250,7 @@ def __init__(self, family_name: str, class Corrector(LongElement): """Corrector element""" + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['KickAngle'] def __init__(self, family_name: str, length: float, kick_angle, **kwargs): @@ -1256,6 +1276,7 @@ class Wiggler(Radiative, LongElement): See atwiggler.m """ + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['Lw', 'Bmax', 'Energy'] _conversions = dict(Element._conversions, Lw=float, Bmax=float, @@ -1298,14 +1319,12 @@ def __init__(self, family_name: str, length: float, wiggle_period: float, for i, b in enumerate(self.By.T): dk = abs(b[3] ** 2 - b[4] ** 2 - b[2] ** 2) / abs(b[4]) if dk > 1e-6: - raise ValueError("Wiggler(H): kx^2 + kz^2 -ky^2 !=0, i = " - "{0}".format(i)) + raise ValueError("Wiggler(H): kx^2 + kz^2 -ky^2 !=0, i = {0}".format(i)) for i, b in enumerate(self.Bx.T): dk = abs(b[2] ** 2 - b[4] ** 2 - b[3] ** 2) / abs(b[4]) if dk > 1e-6: - raise ValueError("Wiggler(V): ky^2 + kz^2 -kx^2 !=0, i = " - "{0}".format(i)) + raise ValueError("Wiggler(V): ky^2 + kz^2 -kx^2 !=0, i = {0}".format(i)) self.NHharm = self.By.shape[1] self.NVharm = self.Bx.shape[1] From 5076ed32367d129ea2ca5327f48288e1dd1ae5d8 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Fri, 22 Mar 2024 11:28:55 +0100 Subject: [PATCH 26/29] merged from master --- pyat/at/lattice/elements.py | 6 +- pyat/at/lattice/lattice_object.py | 121 ++++++++++++++---------------- 2 files changed, 61 insertions(+), 66 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 080ed1538..e6a296c43 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -928,7 +928,7 @@ def __init__(self, family_name: str, length: float, kwargs.setdefault('PassMethod', 'BndMPoleSymplectic4Pass') super(Dipole, self).__init__(family_name, length, [], [0.0, k], **kwargs) - def items(self) -> Generator[tuple, None, None]: + def items(self) -> Generator[tuple[str, Any], None, None]: yield from super().items() yield "K", vars(self)["PolynomB"][1] @@ -1001,7 +1001,7 @@ def __init__(self, family_name: str, length: float, kwargs.setdefault("PassMethod", "StrMPoleSymplectic4Pass") super(Quadrupole, self).__init__(family_name, length, [], [0.0, k], **kwargs) - def items(self) -> Generator[tuple, None, None]: + def items(self) -> Generator[tuple[str, Any], None, None]: yield from super().items() yield "K", vars(self)["PolynomB"][1] @@ -1036,7 +1036,7 @@ def __init__(self, family_name: str, length: float, super(Sextupole, self).__init__(family_name, length, [], [0.0, 0.0, h], **kwargs) - def items(self) -> Generator[tuple, None, None]: + def items(self) -> Generator[tuple[str, Any], None, None]: yield from super().items() yield "H", vars(self)["PolynomB"][2] diff --git a/pyat/at/lattice/lattice_object.py b/pyat/at/lattice/lattice_object.py index 2467f56f4..923a5aab5 100644 --- a/pyat/at/lattice/lattice_object.py +++ b/pyat/at/lattice/lattice_object.py @@ -11,9 +11,12 @@ As an example, see the at.physics.orbit module """ from __future__ import annotations + +__all__ = ['Lattice', 'Filter', 'type_filter', 'params_filter', 'lattice_filter', + 'elem_generator', 'no_filter'] + import sys import copy -import numpy import math from typing import Optional, Union if sys.version_info.minor < 9: @@ -23,17 +26,18 @@ from typing import SupportsIndex from collections.abc import Callable, Iterable, Generator from warnings import warn -from ..constants import clight, e_mass + +import numpy + +from . import elements as elt +from .elements import Element from .particle_object import Particle from .utils import AtError, AtWarning, Refpts +from .utils import get_s_pos, get_elements,get_value_refpts, set_value_refpts # noinspection PyProtectedMember from .utils import get_uint32_index, get_bool_index, _refcount, Uint32Refpts -from .utils import refpts_iterator, checktype -from .utils import get_s_pos, get_elements -from .utils import get_value_refpts, set_value_refpts -from .utils import set_shift, set_tilt, get_geometry -from . import elements as elt -from .elements import Element +from .utils import refpts_iterator, checktype, set_shift, set_tilt, get_geometry +from ..constants import clight, e_mass _TWO_PI_ERROR = 1.E-4 Filter = Callable[..., Iterable[Element]] @@ -69,10 +73,7 @@ ) } -__all__ = ['Lattice', 'type_filter', 'params_filter', 'lattice_filter', - 'elem_generator', 'no_filter'] - -# Don't warn on floating-pont errors +# Don't warn on floating-point errors numpy.seterr(divide='ignore', invalid='ignore') @@ -96,9 +97,7 @@ class Lattice(list): '_fillpattern') # noinspection PyUnusedLocal - def __init__(self, *args, - iterator: Filter = None, - scan: bool = False, **kwargs): + def __init__(self, *args, iterator: Filter = None, scan: bool = False, **kwargs): """ Lattice(elements, **params) Lattice(filter, [filter, ...,] iterator=iter,**params) @@ -181,7 +180,7 @@ def __init__(self, *args, energy and periodicity if not yet defined. """ if iterator is None: - arg1, = args or [[]] # accept 0 or 1 argument + (arg1,) = args or [[]] # accept 0 or 1 argument if isinstance(arg1, Lattice): elems = lattice_filter(kwargs, arg1) else: @@ -195,25 +194,21 @@ def __init__(self, *args, for attr in self._excluded_attributes: kwargs.pop(attr, None) # set default values - kwargs.setdefault('name', '') - periodicity = kwargs.setdefault('periodicity', 1) - kwargs.setdefault('_particle', Particle()) + kwargs.setdefault("name", "") + periodicity = kwargs.setdefault("periodicity", 1) + kwargs.setdefault("particle", kwargs.pop("_particle", Particle())) + kwargs.setdefault("beam_current", kwargs.pop("_beam_current", 0.0)) # dummy initialization in case the harmonic number is not there - kwargs.setdefault('_fillpattern', numpy.ones(1)) + kwargs.setdefault("_fillpattern", numpy.ones(1)) # Remove temporary keywords - frequency = kwargs.pop('_frequency', None) - cell_length = kwargs.pop('_length', None) - cell_h = kwargs.pop('_harmnumber', math.nan) - ring_h = kwargs.pop('harmonic_number', periodicity*cell_h) - bcurrent = kwargs.pop('beam_current', 0.0) - kwargs.setdefault('_beam_current', bcurrent) - - if 'energy' in kwargs: - kwargs.pop('_energy', None) - elif '_energy' not in kwargs: - raise AtError('Lattice energy is not defined') - if 'particle' in kwargs: - kwargs.pop('_particle', None) + frequency: Optional[float] = kwargs.pop("_frequency", None) + cell_length: Optional[float] = kwargs.pop("_length", None) + cell_h = kwargs.pop("cell_harmnumber", kwargs.pop("_cell_harmnumber", math.nan)) + ring_h = kwargs.pop("harmonic_number", cell_h * periodicity) + + energy = kwargs.setdefault("energy", kwargs.pop("_energy", None)) + if energy is None: + raise AtError("Lattice energy is not defined") # set attributes self.update(kwargs) @@ -223,12 +218,12 @@ def __init__(self, *args, rev = self.beta * clight / cell_length self._cell_harmnumber = int(round(frequency / rev)) try: - fp = kwargs.pop('_fillpattern', numpy.ones(1)) + fp = kwargs.pop("_fillpattern", numpy.ones(1)) self.set_fillpattern(bunches=fp) except AssertionError: self.set_fillpattern() elif not math.isnan(ring_h): - self.harmonic_number = ring_h + self._cell_harmnumber = ring_h / periodicity def __getitem__(self, key): try: # Integer @@ -329,7 +324,8 @@ def extend(self, elems: Iterable[Element], copy_elements=False): r"""This method adds all the elements of `elems` to the end of the lattice. The behavior is the same as for a :py:obj:`list` - Equivalents syntaxes: + Equivalent syntaxes: + >>> ring.extend(elems) >>> ring += elems @@ -352,7 +348,8 @@ def append(self, elem: Element, copy_elements=False): its behavior is changed, it accepts only AT lattice elements :py:obj:`Element` as input argument. - Equivalents syntaxes: + Equivalent syntaxes: + >>> ring.append(elem) >>> ring += [elem] @@ -364,22 +361,23 @@ def append(self, elem: Element, copy_elements=False): """ self.extend([elem], copy_elements=copy_elements) - def repeat(self, n: int, copy_elements=True): + def repeat(self, n: int, copy_elements: bool = True): # noinspection SpellCheckingInspection,PyUnresolvedReferences,PyRedeclaration r"""This method allows to repeat the lattice `n` times. If `n` does not divide `ring.periodicity`, the new ring - periodicity is set to 1, otherwise it is et to + periodicity is set to 1, otherwise it is set to `ring.periodicity /= n`. - Equivalents syntaxes: + Equivalent syntaxes: + >>> newring = ring.repeat(n) >>> newring = ring * n Parameters: - n (int): number of repetition - copy_elements(bool): Default :py:obj:`True`. - If :py:obj:`True` deepcopies of the - lattice are used for the repetition + n : number of repetitions + copy_elements: If :py:obj:`True`, deep copies of the lattice are used for + the repetition. Otherwise, the original elements are repeated in the + developed lattice. Returns: newring (Lattice): the new repeated lattice @@ -398,22 +396,22 @@ def copy_fun(elem, copy): warn(AtWarning('Non-integer number of cells: {}/{}. Periodi' 'city set to 1'.format(self.periodicity, n))) periodicity = 1 + hdict = dict(periodicity=periodicity) try: - cell_h = self._cell_harmnumber + hdict.update(harmonic_number=self.cell_harmnumber*n*periodicity) except AttributeError: - hdict = {} - else: - hdict = dict(_cell_harmnumber=n*cell_h) + pass elems = (copy_fun(el, copy_elements) for _ in range(n) for el in self) return Lattice(elem_generator, elems, iterator=self.attrs_filter, - periodicity=periodicity, **hdict) + **hdict) def concatenate(self, *lattices: Iterable[Element], copy_elements=False, copy=False): # noinspection PyUnresolvedReferences,SpellCheckingInspection,PyRedeclaration """Concatenate several `Iterable[Element]` with the lattice - Equivalents syntaxes: + Equivalent syntaxes: + >>> newring = ring.concatenate(r1, r2, r3, copy=True) >>> newring = ring + r1 + r2 + r3 @@ -467,20 +465,19 @@ def reverse(self, copy=False): reversed_list = list(elems) self[:] = reversed_list - def develop(self) -> Lattice: + def develop(self, copy_elements: bool = True) -> Lattice: """Develop a periodical lattice by repeating its elements *self.periodicity* times - The elements of the new lattice are deep copies ot the original - elements, so that they are all independent. + Parameters: + copy_elements: If :py:obj:`True`, deep copies of the elements are used for + the repetition. Otherwise, the original elements are repeated in the + developed lattice. Returns: newlattice: The developed lattice """ - elist = (el.deepcopy() for _ in range(self.periodicity) for el in self) - return Lattice(elem_generator, elist, - iterator=self.attrs_filter, periodicity=1, - harmonic_number=self.harmonic_number) + return self.repeat(self.periodicity, copy_elements=copy_elements) @property def attrs(self) -> dict: @@ -1418,11 +1415,10 @@ def elem_generator(params, elems: Iterable[Element]) -> Iterable[Element]: return elems -no_filter = elem_generator # provided for backward compatibility +no_filter: Filter = elem_generator # provided for backward compatibility -def type_filter(params, elems: Iterable[Element]) \ - -> Generator[Element, None, None]: +def type_filter(params, elems: Iterable[Element]) -> Generator[Element, None, None]: """Run through all elements and check element validity. Analyse elements for radiation state @@ -1432,7 +1428,7 @@ def type_filter(params, elems: Iterable[Element]) \ Yields: lattice ``Elements`` - """ + """ radiate = False for idx, elem in enumerate(elems): if isinstance(elem, Element): @@ -1445,8 +1441,7 @@ def type_filter(params, elems: Iterable[Element]) \ params['_radiation'] = radiate -def params_filter(params, elem_filter: Filter, *args) \ - -> Generator[Element, None, None]: +def params_filter(params, elem_filter: Filter, *args) -> Generator[Element, None, None]: """Run through all elements, looking for energy and periodicity. Remove the Energy attribute of non-radiating elements From 1adbf9af3ef28421507d102f7d1a5817c99ad1ac Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Fri, 22 Mar 2024 11:39:51 +0100 Subject: [PATCH 27/29] merged from master --- pyat/at/lattice/elements.py | 2 +- pyat/at/lattice/lattice_object.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index e6a296c43..26d0e3302 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -781,7 +781,7 @@ def __setattr__(self, key, value): """Check the compatibility of MaxOrder, PolynomA and PolynomB""" polys = ('PolynomA', 'PolynomB') if key in polys: - lmin = getattr(self, 'MaxOrder') + lmin = self.MaxOrder if not len(value) > lmin: raise ValueError( 'Length of {0} must be larger than {1}'.format(key, lmin)) diff --git a/pyat/at/lattice/lattice_object.py b/pyat/at/lattice/lattice_object.py index 923a5aab5..357df9d91 100644 --- a/pyat/at/lattice/lattice_object.py +++ b/pyat/at/lattice/lattice_object.py @@ -33,7 +33,7 @@ from .elements import Element from .particle_object import Particle from .utils import AtError, AtWarning, Refpts -from .utils import get_s_pos, get_elements,get_value_refpts, set_value_refpts +from .utils import get_s_pos, get_elements, get_value_refpts, set_value_refpts # noinspection PyProtectedMember from .utils import get_uint32_index, get_bool_index, _refcount, Uint32Refpts from .utils import refpts_iterator, checktype, set_shift, set_tilt, get_geometry @@ -296,11 +296,11 @@ def _addition_filter(self, elems: Iterable[Element], copy_elements=False): if cavities and not hasattr(self, '_cell_harmnumber'): cavities.sort(key=lambda el: el.Frequency) try: - self._cell_harmnumber = getattr(cavities[0], 'HarmNumber') + self._cell_harmnumber = cavities[0].HarmNumber except AttributeError: length += self.get_s_pos(len(self))[0] rev = self.beta * clight / length - frequency = getattr(cavities[0], 'Frequency') + frequency = cavities[0].Frequency self._cell_harmnumber = int(round(frequency / rev)) self._radiation |= params.pop('_radiation') @@ -1475,7 +1475,7 @@ def params_filter(params, elem_filter: Filter, *args) -> Generator[Element, None cavities = [] cell_length = 0 - for idx, elem in enumerate(elem_filter(params, *args)): + for elem in elem_filter(params, *args): if isinstance(elem, elt.RFCavity): cavities.append(elem) elif hasattr(elem, 'Energy'): From 8d0ed9ba61bb488fbbbb260db6e46fefe58bd8ba Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Fri, 22 Mar 2024 18:38:48 +0100 Subject: [PATCH 28/29] code style --- pyat/at/lattice/elements.py | 6 +++--- pyat/at/lattice/variables.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 26d0e3302..4dbfff9bd 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -732,7 +732,7 @@ def __init__(self, family_name: str, length: float, limits, **kwargs): class ThinMultipole(Element): """Thin multipole element""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['PolynomA', 'PolynomB'] + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ["PolynomA", "PolynomB"] def __init__(self, family_name: str, poly_a, poly_b, **kwargs): """ @@ -789,14 +789,14 @@ def __setattr__(self, key, value): intval = int(value) lmax = min(len(getattr(self, k)) for k in polys) if not intval < lmax: - raise ValueError('MaxOrder must be smaller than {0}'.format(lmax)) + raise ValueError("MaxOrder must be smaller than {0}".format(lmax)) super(ThinMultipole, self).__setattr__(key, value) class Multipole(_Radiative, LongElement, ThinMultipole): """Multipole element""" - _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['PolynomA', 'PolynomB'] + _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ["PolynomA", "PolynomB"] _conversions = dict(ThinMultipole._conversions, K=float, H=float) def __init__(self, family_name: str, length: float, poly_a, poly_b, **kwargs): diff --git a/pyat/at/lattice/variables.py b/pyat/at/lattice/variables.py index 63dc0fd6e..5b90e5f5a 100644 --- a/pyat/at/lattice/variables.py +++ b/pyat/at/lattice/variables.py @@ -252,7 +252,7 @@ def set_previous(self, ring=None) -> None: value = self._history.pop() # retrieve the previous value self.set(value, ring=ring) else: - raise IndexError(f"{self.name}: history too short",) + raise IndexError(f"{self.name}: history too short") def reset(self, ring=None) -> None: """Reset to the initial value and clear the history buffer From ebcd2d23591fe1da90296d56a7f562a204ef5bc6 Mon Sep 17 00:00:00 2001 From: Laurent Farvacque Date: Sun, 28 Apr 2024 14:41:50 +0200 Subject: [PATCH 29/29] Changed the examples of custom variables in the "variables" notebook --- docs/p/notebooks/variables.ipynb | 570 ++++++++++++++++--------------- 1 file changed, 294 insertions(+), 276 deletions(-) diff --git a/docs/p/notebooks/variables.ipynb b/docs/p/notebooks/variables.ipynb index 14d6828be..e9b6fb05a 100644 --- a/docs/p/notebooks/variables.ipynb +++ b/docs/p/notebooks/variables.ipynb @@ -22,26 +22,6 @@ { "cell_type": "code", "execution_count": 2, - "id": "e9955365-2514-4915-a2e4-5a6b26c1beb0", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "fname = \"hmba.mat\"\n", - "with as_file(files(\"machine_data\") / fname) as path:\n", - " ring = at.load_lattice(path)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, "id": "d993e922-806d-42fc-a793-51e8c5e82995", "metadata": {}, "outputs": [], @@ -66,19 +46,60 @@ "for accessing any scalar attribute of an element, or any item of an array attribute.\n", "\n", "Any other quantity may be accessed by either subclassing the {py:class}`~.variables.VariableBase`\n", - "abstract base class, or using a {py:class}`~.variables.CustomVariable`.\n", + "abstract base class, or by using a {py:class}`~.variables.CustomVariable`.\n", "\n", "## {py:class}`~.lattice_variables.ElementVariable`\n", "\n", "An {py:class}`~.lattice_variables.ElementVariable` refers to a single attribute (or item of an array attribute) of one or several {py:class}`.Element` objects.\n", "\n", - "We now create a variable pointing to the length of all QF1 magnets of *ring*:" + "We now create a variable pointing to the length of a QF1 magnet:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d42d5dc7-f40a-4d49-be7b-3fcf5e1e18ea", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "99fe5abf-a4d5-48c7-b99a-fb8fd8208699", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quadrupole:\n", + "\tFamName : QF1\n", + "\tLength : 0.5\n", + "\tPassMethod : StrMPoleSymplectic4Pass\n", + "\tNumIntSteps : 10\n", + "\tMaxOrder : 1\n", + "\tPolynomA : [0. 0.]\n", + "\tPolynomB : [0. 2.1]\n", + "\tK : 2.1\n" + ] + } + ], + "source": [ + "qf1 = at.Quadrupole(\"QF1\", 0.5, 2.1)\n", + "print(qf1)" ] }, { "cell_type": "code", "execution_count": 4, - "id": "15063c05-ef7f-43ec-88d3-0109c8ea0592", + "id": "e07cfef9-b821-4924-8032-112801723d53", "metadata": { "editable": true, "slideshow": { @@ -91,13 +112,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "lf1: ElementVariable(0.311896, name='lf1')\n", - "0.311896\n" + "lf1: ElementVariable(0.5, name='lf1')\n", + "0.5\n" ] } ], "source": [ - "lf1 = ElementVariable(ring[\"QF1[AE]\"], \"Length\", name=\"lf1\")\n", + "lf1 = ElementVariable(qf1, \"Length\", name=\"lf1\")\n", "print(f\"lf1: {lf1}\")\n", "print(lf1.value)" ] @@ -107,7 +128,7 @@ "id": "b1271329-08be-4655-8884-77d7eec67558", "metadata": {}, "source": [ - "and another variable pointing to the strength of the same magnets:" + "and another variable pointing to the strength of the same magnet:" ] }, { @@ -126,13 +147,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "kf1: ElementVariable(2.5394599781303304, name='kf1')\n", - "2.5394599781303304\n" + "kf1: ElementVariable(2.1, name='kf1')\n", + "2.1\n" ] } ], "source": [ - "kf1 = ElementVariable(ring[\"QF1[AE]\"], \"PolynomB\", index=1, name=\"kf1\")\n", + "kf1 = ElementVariable(qf1, \"PolynomB\", index=1, name=\"kf1\")\n", "print(\"kf1:\", kf1)\n", "print(kf1.value)" ] @@ -154,8 +175,7 @@ { "data": { "text/plain": [ - "{Quadrupole('QF1A', 0.311896, 2.5394599781303304, FringeQuadEntrance=1, FringeQuadExit=1, NumIntSteps=20),\n", - " Quadrupole('QF1E', 0.311896, 2.5394599781303304, FringeQuadEntrance=1, FringeQuadExit=1, NumIntSteps=20)}" + "{Quadrupole('QF1', 0.5, 2.1)}" ] }, "execution_count": 6, @@ -167,42 +187,6 @@ "kf1.elements" ] }, - { - "cell_type": "markdown", - "id": "648083b2-b16c-44fc-9bf6-f6e79002d8f4", - "metadata": {}, - "source": [ - "`kf1` drives 2 quadrupoles. Let's look at the 1{sup}`st` one:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d686f64a-1855-4b8a-a6f8-63c52ed038bf", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quadrupole:\n", - "\tFamName : QF1A\n", - "\tLength : 0.311896\n", - "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", - "\tMaxOrder : 1\n", - "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.53945998]\n", - "\tK : 2.5394599781303304\n" - ] - } - ], - "source": [ - "print(ring[5])" - ] - }, { "cell_type": "markdown", "id": "5dc6ea71-8d0a-4b8e-b00f-bebd0b09a874", @@ -214,12 +198,12 @@ "tags": [] }, "source": [ - "We can now change the strength of both QF1 magnets and check again the 1{sup}`st` one:" + "We can now change the strength of QF1 magnets and check again:" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "d74f4a63-bacf-4ee4-b42a-de75bfbea193", "metadata": {}, "outputs": [ @@ -228,12 +212,10 @@ "output_type": "stream", "text": [ "Quadrupole:\n", - "\tFamName : QF1A\n", - "\tLength : 0.311896\n", + "\tFamName : QF1\n", + "\tLength : 0.5\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", + "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", "\tPolynomA : [0. 0.]\n", "\tPolynomB : [0. 2.5]\n", @@ -243,7 +225,7 @@ ], "source": [ "kf1.set(2.5)\n", - "print(ring[5])" + "print(qf1)" ] }, { @@ -256,17 +238,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "413145df-30e5-4601-b05a-df2b042028ff", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[2.5394599781303304, 2.5]" + "[2.1, 2.5]" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -285,7 +267,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "70a1bca4-af2d-49b9-8462-989d47ad1efe", "metadata": {}, "outputs": [ @@ -294,22 +276,20 @@ "output_type": "stream", "text": [ "Quadrupole:\n", - "\tFamName : QF1A\n", - "\tLength : 0.311896\n", + "\tFamName : QF1\n", + "\tLength : 0.5\n", "\tPassMethod : StrMPoleSymplectic4Pass\n", - "\tNumIntSteps : 20\n", - "\tFringeQuadEntrance : 1\n", - "\tFringeQuadExit : 1\n", + "\tNumIntSteps : 10\n", "\tMaxOrder : 1\n", "\tPolynomA : [0. 0.]\n", - "\tPolynomB : [0. 2.53945998]\n", - "\tK : 2.5394599781303304\n" + "\tPolynomB : [0. 2.1]\n", + "\tK : 2.1\n" ] } ], "source": [ "kf1.set_previous()\n", - "print(ring[5])" + "print(qf1)" ] }, { @@ -317,12 +297,12 @@ "id": "c650be51-228a-4ee0-ac73-d741601f992b", "metadata": {}, "source": [ - "An {py:class}`~.lattice_variables.ElementVariable` is linked to Elements. It will not follow any copy of the element, neither shallow nor deep. So if we make a copy of ring:" + "An {py:class}`~.lattice_variables.ElementVariable` is linked to Elements. It will apply wherever the element appears but it will not follow any copy of the element, neither shallow nor deep. So if we make a copy of QF1:" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "314c398c-fbbc-43ce-abcc-76ba48cf3c03", "metadata": {}, "outputs": [ @@ -330,15 +310,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "ring: 2.5394599781303304\n", - "newring: 2.5394599781303304\n" + "qf1: 2.1\n", + "qf2: 2.1\n" ] } ], "source": [ - "newring = ring.deepcopy()\n", - "print(f\"ring: {ring[5].PolynomB[1]}\")\n", - "print(f\"newring: {newring[5].PolynomB[1]}\")" + "qf2 = qf1.deepcopy()\n", + "print(f\"qf1: {qf1.PolynomB[1]}\")\n", + "print(f\"qf2: {qf2.PolynomB[1]}\")" ] }, { @@ -351,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "8458ab19-3be1-427c-a55d-0ccb029c9f26", "metadata": {}, "outputs": [], @@ -361,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "f4c1e653-eeaf-4fd5-be08-2a488bd7df9d", "metadata": { "editable": true, @@ -375,14 +355,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "ring: 2.6\n", - "newring: 2.5394599781303304\n" + "qf1: 2.6\n", + "qf2: 2.1\n" ] } ], "source": [ - "print(f\"ring: {ring[5].PolynomB[1]}\")\n", - "print(f\"newring: {newring[5].PolynomB[1]}\")" + "print(f\"qf1: {qf1.PolynomB[1]}\")\n", + "print(f\"qf2: {qf2.PolynomB[1]}\")" ] }, { @@ -396,14 +376,14 @@ "tags": [] }, "source": [ - "The QF1 in `newring` is not affected.\n", + "The copy of QF1 in is not affected.\n", "\n", "One can set upper and lower bounds on a variable. Trying to set a value out of the bounds will raise a {py:obj}`ValueError`. The default is (-{py:obj}`numpy.inf`, {py:obj}`numpy.inf`)." ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "738f6c51-2968-4d77-8599-261e7998d52b", "metadata": { "editable": true, @@ -414,12 +394,12 @@ }, "outputs": [], "source": [ - "lfbound = ElementVariable(ring[\"QF1[AE]\"], \"Length\", bounds=(0.30, 0.35))" + "lfbound = ElementVariable(qf1, \"Length\", bounds=(0.45, 0.55))" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "ba4e728e-fa3e-4d71-9dcb-658d240fd61c", "metadata": { "editable": true, @@ -433,14 +413,14 @@ "outputs": [ { "ename": "ValueError", - "evalue": "set value must be in (0.3, 0.35)", + "evalue": "set value must be in (0.45, 0.55)", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[14], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mlfbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m~/dev/libraries/at/pyat/at/lattice/variables.py:202\u001b[0m, in \u001b[0;36mVariableBase.set\u001b[0;34m(self, value, ring)\u001b[0m\n\u001b[1;32m 194\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Set the variable value\u001b[39;00m\n\u001b[1;32m 195\u001b[0m \n\u001b[1;32m 196\u001b[0m \u001b[38;5;124;03mArgs:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 199\u001b[0m \u001b[38;5;124;03m may be necessary to set the variable.\u001b[39;00m\n\u001b[1;32m 200\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;241m<\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mor\u001b[39;00m value \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds[\u001b[38;5;241m1\u001b[39m]:\n\u001b[0;32m--> 202\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mset value must be in \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbounds\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 203\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_setfun(value, ring\u001b[38;5;241m=\u001b[39mring)\n\u001b[1;32m 204\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m np\u001b[38;5;241m.\u001b[39misnan(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial):\n", - "\u001b[0;31mValueError\u001b[0m: set value must be in (0.3, 0.35)" + "\u001b[0;31mValueError\u001b[0m: set value must be in (0.45, 0.55)" ] } ], @@ -478,7 +458,30 @@ "An {py:class}`.RefptsVariable` is similar to an {py:class}`~.lattice_variables.ElementVariable` but it is not associated with an {py:class}`~.Element`\n", "itself, but with its location in a Lattice. So it will act on any lattice with the same elements.\n", "\n", - "But it needs a *ring* keyword in its *set* and *get* methods, to identify the selected lattice." + "But it needs a *ring* keyword in its *set* and *get* methods, to identify the selected lattice.\n", + "\n", + "Let's load a test ring and make a copy of it:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "10bf3120-0126-484f-8704-1b8ee02eabe0", + "metadata": {}, + "outputs": [], + "source": [ + "fname = \"hmba.mat\"\n", + "with as_file(files(\"machine_data\") / fname) as path:\n", + " ring = at.load_lattice(path)\n", + "newring = ring.deepcopy()" + ] + }, + { + "cell_type": "markdown", + "id": "30f5880c-5473-4452-8d00-d4a0853e1562", + "metadata": {}, + "source": [ + "and create a {py:class}`.RefptsVariable`" ] }, { @@ -520,13 +523,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "ring: 2.55\n", + " ring: 2.55\n", "newring: 2.45\n" ] } ], "source": [ - "print(f\"ring: {ring[5].PolynomB[1]}\")\n", + "print(f\" ring: {ring[5].PolynomB[1]}\")\n", "print(f\"newring: {newring[5].PolynomB[1]}\")" ] }, @@ -542,164 +545,156 @@ }, "source": [ "## Custom variables\n", - "We take the example of a variable driving the length of two drifts (or other elements), such\n", - "that their sum stays constant. We choose the variable value as the length of the first element, the other one will be automatically adjusted. This variable allows to shift longitudinally the intermediate\n", - "part of the lattice without affecting its circumference, so we will call the variable an \"element shifter\".\n", + "Custom variables allow access to almost any quantity in AT. This can be achieved either by subclassing the {py:class}`~.variables.VariableBase` abstract base class, or by using a {py:class}`~.variables.CustomVariable`.\n", "\n", - "Note that defining correlated {py:class}`.Element` attributes may be easier done with Parameters. However Variables are not restricted to Element attributes, unlike Parameters.\n", + "We will take 2 examples:\n", "\n", - "Similarly to the {py:class}`~.lattice_variables.RefptsVariable`, we will refer to the 2 variable elements by their `refpts`. Alternatively, one could give the elements themselves, as in {py:class}`~.lattice_variables.ElementVariable`.\n", + "1. A variable accessing the *DPStep* parameter used in chromaticity computations. It does not look like a very\n", + " useful variable, it's for demonstration purpose,\n", + "2. A variable accessing the energy of a given lattice\n", "\n", "### Using the {py:class}`~.variables.CustomVariable`\n", "\n", - "We need to define two functions for the \"get\" end \"set\" actions, and to give to the {py:class}`~.variables.CustomVariable` constructor the necessary arguments for these functions.\n", + "Using a {py:class}`~.variables.CustomVariable` makes it very easy to define simple variables: we just need\n", + "to define two functions for the \"get\" and \"set\" actions, and give them to the {py:class}`~.variables.CustomVariable` constructor.\n", + "\n", + "#### Example 1\n", "\n", - "We start with the \"set\" function. Since the elements may appear several times in the\n", - "lattice, we set them all. We are free to declare any need argument." + "We define 2 functions for setting and getting the variable value:" ] }, { "cell_type": "code", "execution_count": 19, - "id": "8b5235c9-089d-46d2-a761-1044b445e583", - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, + "id": "55a35ed2-a93b-4611-8a57-d7bce36a39f3", + "metadata": {}, "outputs": [], "source": [ - "def setvar(value, ref1, ref2, total_length, ring=None):\n", - " if ring is None:\n", - " raise ValueError(\"Can't set values if ring is None\")\n", - " for elem in ring.select(ref1):\n", - " elem.Length = value\n", - " for elem in ring.select(ref2):\n", - " elem.Length = total_length - value" + "def setvar1(value, ring=None):\n", + " at.DConstant.DPStep = value\n", + "\n", + "\n", + "def getvar1(ring=None):\n", + " return at.DConstant.DPStep" ] }, { - "cell_type": "markdown", - "id": "d62f0b5e-400e-4436-8ba3-726926a79b7a", + "cell_type": "code", + "execution_count": 20, + "id": "347abd5d-bfc9-469c-aae8-715fdfa11009", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3e-06\n" + ] + } + ], "source": [ - "In the \"get\" function, since we chose the first element as the variable value, we may ignore\n", - "the other arguments:" + "dpstep_var = CustomVariable(setvar1, getvar1, bounds=(1.0e-12, 0.1))\n", + "print(dpstep_var.value)" ] }, { "cell_type": "code", - "execution_count": 20, - "id": "c4b8826a-7c11-4ebc-97bd-7392812f0694", + "execution_count": 21, + "id": "f563564d-a862-4a11-9e9d-2e8864fa082d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0002\n" + ] + } + ], "source": [ - "def getvar(ref1, ref2, total_length, ring=None):\n", - " if ring is None:\n", - " raise ValueError(\"Can't get values if ring is None\")\n", - " return np.mean([elem.Length for elem in ring.select(ref1)])" + "dpstep_var.value = 2.0e-4\n", + "print(at.DConstant.DPStep)" ] }, { "cell_type": "markdown", - "id": "16036935-da4e-4968-957e-40602db6fc15", + "id": "a52236a1-bcc2-4040-811e-4a1aadc11a42", "metadata": {}, "source": [ - "We can now select the elements, get the initial conditions and construct the variable:" + "#### Example 2\n", + "\n", + "We can give to the {py:class}`~.variables.CustomVariable` constructor any positional or keyword argument\n", + "necessary for the *set* and *get* functions. Here we will send the lattice as a positional argument:" ] }, { "cell_type": "code", - "execution_count": 21, - "id": "05d12006-a59f-4395-b1ac-f487403a5df2", - "metadata": {}, + "execution_count": 22, + "id": "8b5235c9-089d-46d2-a761-1044b445e583", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ - "# select the variable elements\n", - "elem1 = \"DR_01\"\n", - "elem2 = \"DR_02\"\n", - "# Compute the initial total length\n", - "l1 = np.mean([elem.Length for elem in ring.select(elem1)])\n", - "l2 = np.mean([elem.Length for elem in ring.select(elem2)])\n", - "# Create the variable\n", - "elem_shifter1 = CustomVariable(\n", - " setvar, getvar, elem1, elem2, l1 + l2, bounds=(0, l1 + l2)\n", - ")" + "def setvar2(value, lattice, ring=None):\n", + " lattice.energy = value\n", + "\n", + "\n", + "def getvar2(lattice, ring=None):\n", + " return lattice.energy\n", + "\n", + "\n", + "energy_var = CustomVariable(setvar2, getvar2, newring)" ] }, { "cell_type": "markdown", - "id": "d3d58857-2447-48b5-a645-6d36ffbec402", + "id": "24d9b25f-d712-4056-800f-7f9bca6b2749", "metadata": {}, "source": [ - "Here is the initial state of the lattice:" + "Here, the *newring* positional argument given to the variable constructor is available as a positional argument\n", + "in both the *set* and *get* functions." ] }, { "cell_type": "code", - "execution_count": 22, - "id": "bf2d12f7-3984-4f0f-b5b8-166ebed9ffd2", + "execution_count": 23, + "id": "957c8990-d5e8-435d-959d-31109ff7cd17", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Drift:\n", - "\tFamName : DR_01\n", - "\tLength : 2.6513999999999998\n", - "\tPassMethod : DriftPass\n", - "Drift:\n", - "\tFamName : DR_02\n", - "\tLength : 0.042552\n", - "\tPassMethod : DriftPass\n", - "\n", - "elem_shifter1.get: 2.6513999999999998\n" + "6000000000.0\n" ] } ], "source": [ - "print(f\"{ring[2]}\\n{ring[4]}\")\n", - "print(\"\\nelem_shifter1.get:\", elem_shifter1.get(ring=ring, initial=True))" - ] - }, - { - "cell_type": "markdown", - "id": "1efa97a9-e19d-4004-9a3b-cbd7361903de", - "metadata": {}, - "source": [ - "Now, let's set a new value for the variable and look at the result:" + "print(energy_var.value)" ] }, { "cell_type": "code", - "execution_count": 23, - "id": "52d62737-8acc-498b-b41b-a3911180560d", + "execution_count": 24, + "id": "3db01f54-db31-4042-b8b3-ac3289591ccb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Drift:\n", - "\tFamName : DR_01\n", - "\tLength : 2.5\n", - "\tPassMethod : DriftPass\n", - "Drift:\n", - "\tFamName : DR_02\n", - "\tLength : 0.1939519999999999\n", - "\tPassMethod : DriftPass\n", - "\n", - "elem_shifter1.get: 2.5\n" + "6100000000.0\n" ] } ], "source": [ - "elem_shifter1.set(2.5, ring=ring)\n", - "print(f\"{ring[2]}\\n{ring[4]}\")\n", - "print(\"\\nelem_shifter1.get:\", elem_shifter1.get(ring=ring))" + "energy_var.value = 6.1e9\n", + "print(energy_var.value)" ] }, { @@ -712,23 +707,23 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "786e4424-8840-490c-8ac0-1db350c0ef00", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[2.6513999999999998, 2.5]" + "[6000000000.0, 6100000000.0]" ] }, - "execution_count": 24, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "elem_shifter1.history" + "energy_var.history" ] }, { @@ -741,12 +736,12 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "9df21be4-0cf4-4499-aa0f-0fd7014f8934", "metadata": {}, "outputs": [], "source": [ - "elem_shifter1.reset(ring=ring)" + "energy_var.reset()" ] }, { @@ -756,47 +751,97 @@ "source": [ "### By derivation of the {py:class}`~.variables.VariableBase` class\n", "\n", - "We will write a new variable class based on {py:class}`~.variables.VariableBase` abstract base class. The main task is to implement the `_setfun` and `_getfun` abstract methods." + "The derivation of {py:class}`~.variables.VariableBase` allows more control on the created variable by using\n", + "the class constuctor and its arguments to setup the variable.\n", + "\n", + "We will write a new variable class based on {py:class}`~.variables.VariableBase` abstract base class. The main task is to implement the `_setfun` and `_getfun` abstract methods.\n", + "\n", + "#### Example 1" ] }, { "cell_type": "code", - "execution_count": 26, - "id": "547bdb0a-c1ea-4861-88d3-407329478391", + "execution_count": 27, + "id": "7474764c-88ee-49df-9f85-b9e7f04aefbb", "metadata": {}, "outputs": [], "source": [ - "class ElementShifter(VariableBase):\n", - " def __init__(self, ref1, ref2, total_length=None, **kwargs):\n", - " \"\"\"Varies the length of the elements *dr1* and *dr2*\n", - " keeping the sum of their lengths equal to *total_length*.\n", + "class DPStepVariable(VariableBase):\n", + "\n", + " def _setfun(self, value, ring=None):\n", + " at.DConstant.DPStep = value\n", + "\n", + " def _getfun(self, ring=None):\n", + " return at.DConstant.DPStep" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "d838c699-8f99-4eb7-8784-acd503731888", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0002\n" + ] + } + ], + "source": [ + "dpstep_var = DPStepVariable()\n", + "print(dpstep_var.value)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "13fcbedf-f2e8-46f5-ab01-5a8c80608c76", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3e-06\n" + ] + } + ], + "source": [ + "dpstep_var.value = 3.0e-6\n", + "print(dpstep_var.value)" + ] + }, + { + "cell_type": "markdown", + "id": "03d34984-79a4-4736-b902-7b77bdbd5a89", + "metadata": {}, + "source": [ + "#### Example 2\n", "\n", - " If *total_length* is None, it is set to the initial total length\n", - " \"\"\"\n", - " # Store the indices of the 2 variable elements\n", - " self.ref1 = ref1\n", - " self.ref2 = ref2\n", - " # Compute and store the initial total length\n", - " if total_length is None:\n", - " l1 = np.mean([elem.Length for elem in ring.select(ref1)])\n", - " l2 = np.mean([elem.Length for elem in ring.select(ref2)])\n", - " self.total_length = l1 + l2\n", - " self.length = total_length\n", + "Here we will store the lattice as an instance variable in the class constructor:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "547bdb0a-c1ea-4861-88d3-407329478391", + "metadata": {}, + "outputs": [], + "source": [ + "class EnergyVariable(VariableBase):\n", + " def __init__(self, lattice, *args, **kwargs):\n", + " # Store the lattice\n", + " self.lattice = lattice\n", " # Initialise the parent class\n", - " super().__init__(bounds=(0.0, self.total_length), **kwargs)\n", + " super().__init__(*args, **kwargs)\n", "\n", " def _setfun(self, value, ring=None):\n", - " if ring is None:\n", - " raise ValueError(\"Can't get values if ring is None\")\n", - " for elem in ring.select(self.ref1):\n", - " elem.Length = value\n", - " for elem in ring.select(self.ref2):\n", - " elem.Length = self.total_length - value\n", + " self.lattice.energy = value\n", "\n", " def _getfun(self, ring=None):\n", - " if ring is None:\n", - " raise ValueError(\"Can't get values if ring is None\")\n", - " return np.mean([elem.Length for elem in ring.select(self.ref1)])" + " return self.lattice.energy" ] }, { @@ -809,12 +854,12 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 31, "id": "2c61510b-3fab-4d1c-8dd3-5a0dc9e8659f", "metadata": {}, "outputs": [], "source": [ - "elem_shifter2 = ElementShifter(elem1, elem2)" + "energy_var = EnergyVariable(ring)" ] }, { @@ -827,7 +872,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 32, "id": "2f75312e-c088-43fb-b7f8-d0179363167d", "metadata": {}, "outputs": [ @@ -835,68 +880,41 @@ "name": "stdout", "output_type": "stream", "text": [ - "Drift:\n", - "\tFamName : DR_01\n", - "\tLength : 2.6513999999999998\n", - "\tPassMethod : DriftPass\n", - "Drift:\n", - "\tFamName : DR_02\n", - "\tLength : 0.042552000000000145\n", - "\tPassMethod : DriftPass\n", - "\n", - "elem_shifter2.get: 2.6513999999999998\n" + "6000000000.0\n" ] } ], "source": [ - "print(f\"{ring[2]}\\n{ring[4]}\")\n", - "print(\"\\nelem_shifter2.get:\", elem_shifter2.get(ring=ring, initial=True))" - ] - }, - { - "cell_type": "markdown", - "id": "117c2fe4-e88b-4486-bb36-e6dbc6514756", - "metadata": {}, - "source": [ - "Change the variable, and look at the result:" + "print(energy_var.value)" ] }, { "cell_type": "code", - "execution_count": 29, - "id": "6633eb54-1249-4d32-9906-173859f8154b", + "execution_count": 33, + "id": "8fb40e51-9c5d-4f81-abfe-64e577a74656", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Drift:\n", - "\tFamName : DR_01\n", - "\tLength : 2.5\n", - "\tPassMethod : DriftPass\n", - "Drift:\n", - "\tFamName : DR_02\n", - "\tLength : 0.1939519999999999\n", - "\tPassMethod : DriftPass\n", - "\n", - "elem_shifter2.get: 2.5\n" + "6100000000.0\n" ] } ], "source": [ - "elem_shifter2.set(2.5, ring=ring)\n", - "print(f\"{ring[2]}\\n{ring[4]}\")\n", - "print(\"\\nelem_shifter2.get:\", elem_shifter2.get(ring=ring))" + "energy_var.value = 6.1e9\n", + "print(energy_var.value)" ] }, { - "cell_type": "markdown", - "id": "a93a7f09-16a8-41ac-a41c-9aa74092f247", + "cell_type": "code", + "execution_count": 34, + "id": "54d43d08-0480-43bc-a260-631cf44f800a", "metadata": {}, + "outputs": [], "source": [ - "Both variables behave similarly. But the derivation allows more control by making use of the\n", - "`__init__` method. For instance here it includes the computation of the initial total length." + "energy_var.reset()" ] } ], @@ -916,7 +934,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.18" + "version": "3.9.19" } }, "nbformat": 4,