Repository navigation
Address the dipole kick through KickAngle where the pass method uses it #470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
thellert
wants to merge
2
commits into
python-accelerator-middle-layer:main
Choose a base branch
from
als-apg:fix/corrector-pass-kick-angle
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,85 @@ def _effective_lengths(elements: list[at.Element]) -> list[float]: | |
| return lengths | ||
|
|
||
|
|
||
| class _KickAngleCoefficients: | ||
| """ | ||
| Present the dipole strength of an element that carries a ``KickAngle`` as a single polynom coefficient. | ||
|
|
||
| AT holds the dipole kick of an element in two places. ``CorrectorPass`` integrates ``KickAngle`` only and ignores | ||
| ``PolynomA``/``PolynomB``; the multipole pass methods integrate the polynom *and* add ``KickAngle`` to it. This | ||
| view exposes the total at index 0 with the polynom convention (``PolynomB[0] * L = -KickAngle[0]``, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For me, it's not immediately clear what "index 0" means in a docstring here |
||
| ``PolynomA[0] * L = KickAngle[1]``), so that the accessors address every element the same way and read the kick | ||
| the beam sees. | ||
|
|
||
| Writing index 0 stores the value where the pass method reads it: in ``KickAngle`` for ``CorrectorPass`` (a polynom | ||
| carried by the element is kept in step), in the polynom otherwise, with the ``KickAngle`` component reset so that | ||
| it is not applied twice. Coefficients above the dipole are passed through to the polynom. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| element : at.Element | ||
| Element carrying a ``KickAngle`` that its pass method applies. | ||
| att_name : str | ||
| Polynom the view stands for, ``"PolynomA"`` or ``"PolynomB"``. | ||
| length : float | ||
| Length used to convert between the kick and the coefficient (see :func:`_effective_lengths`). | ||
| """ | ||
|
|
||
| def __init__(self, element: at.Element, att_name: str, length: float): | ||
| self._element = element | ||
| self._att_name = att_name | ||
| self._length = length | ||
| self._kick_only = element.PassMethod == "CorrectorPass" | ||
| self._plane, self._kick_sign = (0, -1.0) if att_name == "PolynomB" else (1, 1.0) | ||
|
|
||
| def _kick_coefficient(self) -> float: | ||
| if self._length == 0.0: | ||
| return 0.0 | ||
| return self._kick_sign * self._element.KickAngle[self._plane] / self._length | ||
|
|
||
| def __getitem__(self, index: int) -> float: | ||
| if index != 0: | ||
| return getattr(self._element, self._att_name)[index] | ||
| if self._kick_only: | ||
| return self._kick_coefficient() | ||
| return getattr(self._element, self._att_name)[0] + self._kick_coefficient() | ||
|
|
||
| def __setitem__(self, index: int, value: float): | ||
| polynom = getattr(self._element, self._att_name, None) | ||
| if index != 0: | ||
| polynom[index] = value | ||
| elif self._kick_only: | ||
| self._element.KickAngle[self._plane] = self._kick_sign * value * self._length | ||
| if polynom is not None: | ||
| polynom[0] = value | ||
| else: | ||
| polynom[0] = value | ||
| self._element.KickAngle[self._plane] = 0.0 | ||
|
|
||
|
|
||
| def _applies_kick_angle(element: at.Element) -> bool: | ||
| """ | ||
| Return True when the pass method of ``element`` applies its ``KickAngle``. | ||
|
|
||
| That is ``CorrectorPass`` and the multipole integrators (``StrMPole*``, ``BndMPole*``, ``ThinMPolePass``, | ||
| ``ExactMultipole*``). Other pass methods ignore the attribute. | ||
| """ | ||
| pass_method = element.PassMethod | ||
| return pass_method == "CorrectorPass" or "MPole" in pass_method or "Multipole" in pass_method | ||
|
|
||
|
|
||
| def _coefficients(element: at.Element, att_name: str, length: float): | ||
| """ | ||
| Return the indexable coefficients that hold the strength of ``element`` for the polynom ``att_name``. | ||
|
|
||
| This is the polynom itself, except for an element whose pass method applies a ``KickAngle``: its dipole strength | ||
| is then addressed through :class:`_KickAngleCoefficients`. | ||
| """ | ||
| if element.PassMethod == "CorrectorPass" or (hasattr(element, "KickAngle") and _applies_kick_angle(element)): | ||
| return _KickAngleCoefficients(element, att_name, length) | ||
| return getattr(element, att_name) | ||
|
|
||
|
|
||
| class RWHardwareScalar(abstract.ReadWriteFloatScalar): | ||
| """ | ||
| Provide read/write access to a simulated magnet in hardware units. | ||
|
|
@@ -75,11 +154,11 @@ def __init__(self, elements: list[at.Element], poly: PolynomInfo, model: MagnetM | |
| """ | ||
| self._model = model | ||
| self._elements = elements | ||
| self._poly = [e.__getattribute__(poly.attName) for e in elements] | ||
| self._sign = poly.sign | ||
| self._polyIdx = poly.index | ||
| self._is_thin = all(e.Length == 0 for e in elements) | ||
| self._lengths = _effective_lengths(elements) | ||
| self._poly = [_coefficients(e, poly.attName, L) for e, L in zip(elements, self._lengths, strict=True)] | ||
| self._length: float = sum(self._lengths) | ||
|
|
||
| def get_length(self) -> float: | ||
|
|
@@ -177,10 +256,10 @@ def __init__(self, elements: list[at.Element], poly: PolynomInfo, model: MagnetM | |
| """ | ||
| self._model = model | ||
| self._elements = elements | ||
| self._poly = [e.__getattribute__(poly.attName) for e in elements] | ||
| self._sign = poly.sign | ||
| self._polyIdx = poly.index | ||
| self._lengths = _effective_lengths(elements) | ||
| self._poly = [_coefficients(e, poly.attName, L) for e, L in zip(elements, self._lengths, strict=True)] | ||
| self._length: float = sum(self._lengths) | ||
|
|
||
| def get_element_length(self) -> float: | ||
|
|
@@ -211,7 +290,7 @@ def get(self, polynom: str = None, polyidx: int = None) -> float: | |
| else: | ||
| # Override strength access | ||
| pIdx = polyidx | ||
| poly = [e.__getattribute__(polynom) for e in self._elements] | ||
| poly = [_coefficients(e, polynom, L) for e, L in zip(self._elements, self._lengths, strict=True)] | ||
|
|
||
| s = 0 | ||
| for idx, _ in enumerate(self._elements): | ||
|
|
@@ -239,7 +318,7 @@ def set(self, value: float, polynom: str = None, polyidx: int = None): | |
| else: | ||
| # Override strength access | ||
| pIdx = polyidx | ||
| poly = [e.__getattribute__(polynom) for e in self._elements] | ||
| poly = [_coefficients(e, polynom, L) for e, L in zip(self._elements, self._lengths, strict=True)] | ||
|
|
||
| for idx, _ in enumerate(self._elements): | ||
| poly[idx][pIdx] = value / (self._length * self._sign) | ||
|
|
@@ -538,7 +617,7 @@ def __init__(self, elements: list[at.Element], poly: list[PolynomInfo], model: M | |
| self.__model = model | ||
| self.__length = _effective_lengths(elements[:1])[0] | ||
| for p in poly: | ||
| self.__poly.append(elements[0].__getattribute__(p.attName)) | ||
| self.__poly.append(_coefficients(elements[0], p.attName, self.__length)) | ||
| self.__polyIdx.append(p.index) | ||
| self.__sign.append(p.sign) | ||
|
|
||
|
|
@@ -629,7 +708,7 @@ def __init__(self, elements: list[at.Element], poly: list[PolynomInfo], model: M | |
| self.__model = model | ||
| self.__length = _effective_lengths(elements[:1])[0] | ||
| for p in poly: | ||
| self.__poly.append(elements[0].__getattribute__(p.attName)) | ||
| self.__poly.append(_coefficients(elements[0], p.attName, self.__length)) | ||
| self.__polyIdx.append(p.index) | ||
| self.__sign.append(p.sign) | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, we would be happy to even have only KickAngle for CorrectorPass without integration. But if it's already done, it's good.