Skip to content

Commit f673247

Browse files
committed
Calculate Linear Transfer Map
Add a `transfer_map(ref, order="linear", fallback_identity_map=False)` method to the element list, supporting the calculation of a linear transfer map of all elements in the list.
1 parent 35fb3a3 commit f673247

3 files changed

Lines changed: 118 additions & 3 deletions

File tree

docs/source/usage/python.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,16 @@ This module provides elements and methods for the accelerator lattice.
769769
:return: True if at least one element of the specified kind exists
770770
:rtype: bool
771771

772+
.. py::method:: transfer_map(ref, order="linear", fallback_identity_map=False)
773+
774+
Calculate the transfer map of the elements in the list.
775+
776+
:param ref: A reference particle.
777+
:param order: So far, only the calculation of linear transfer maps are supported in this function.
778+
:param fallback_identity_map: For elements with an undefined transfer map in lattice, assume the identity matrix.
779+
:return: The transfer map map of all elements in the list.
780+
:rtype: Map6x6
781+
772782
.. py:method:: plot_survey(ref=None, ax=None, legend=True, legend_ncols=5)
773783
774784
Plot over s of all elements in the KnownElementsList.
@@ -777,7 +787,6 @@ This module provides elements and methods for the accelerator lattice.
777787

778788
Either populates the matplotlib axes in ax or creates a new axes containing the plot.
779789

780-
:param self: The KnownElementsList class in ImpactX
781790
:param ref: A reference particle, checked for the charge sign to plot focusing/defocusing strength directions properly.
782791
:param ax: A plotting area in matplotlib (called axes there).
783792
:param legend: Plot a legend if true.

src/python/elements.cpp

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <elements/All.H>
1010
#include <elements/mixin/lineartransport.H>
1111
#include <elements/transformation/Insert.H>
12+
#include <particles/CovarianceMatrix.H>
1213

1314
#include <AMReX_Enum.H>
1415
#include <AMReX_REAL.H>
@@ -20,7 +21,6 @@
2021
#include <utility>
2122
#include <variant>
2223
#include <vector>
23-
#include <particles/transformation/CoordinateTransformation.H>
2424

2525
namespace py = pybind11;
2626
using namespace impactx;
@@ -313,7 +313,7 @@ void init_elements(py::module& m)
313313
)
314314
;
315315

316-
/*
316+
/* TODO
317317
py::class_<elements::mixin::LinearTransport>(mx, "LinearTransport")
318318
// type of map
319319
.def_property_readonly_static("Map6x6",
@@ -2615,6 +2615,49 @@ void init_elements(py::module& m)
26152615
auto it = std::next(v.begin(), index);
26162616
return *it; // return by reference
26172617
}, py::return_value_policy::reference_internal)
2618+
2619+
.def(
2620+
"transfer_map",
2621+
[](
2622+
KnownElementsList &v,
2623+
RefPart const & ref,
2624+
std::string order,
2625+
bool fallback_identity_map
2626+
)
2627+
{
2628+
if (order != "linear") {
2629+
throw std::runtime_error("So far, only the calculation of linear transfer maps are supported in this function.");
2630+
}
2631+
Map6x6 result = Map6x6::Identity();
2632+
for (auto & el_v : v) {
2633+
std::visit([&result, &ref, &fallback_identity_map](auto const & el) {
2634+
using Element = std::decay_t<decltype(el)>;
2635+
std::string not_impl_msg = "Undefined transfer map in lattice for element ";
2636+
if (el.has_name()) not_impl_msg += el.name() + " ";
2637+
not_impl_msg += std::string("of type ") + Element::type;
2638+
2639+
if constexpr (std::is_base_of_v<elements::mixin::LinearTransport<Element>, Element>) {
2640+
try {
2641+
result = result * el.transport_map(ref);
2642+
} catch (std::exception const & e) {
2643+
if (!fallback_identity_map) {
2644+
throw std::runtime_error(not_impl_msg);
2645+
}
2646+
}
2647+
} else {
2648+
if (!fallback_identity_map) {
2649+
throw std::runtime_error(not_impl_msg);
2650+
}
2651+
}
2652+
}, el_v);
2653+
}
2654+
return result;
2655+
},
2656+
py::arg("ref"),
2657+
py::arg("order") = "linear",
2658+
py::arg("fallback_identity_map") = false,
2659+
"Calculate the transfer map of the elements in the list."
2660+
)
26182661
;
26192662

26202663

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright 2022-2024 The ImpactX Community
4+
#
5+
# Authors: Axel Huebl, Chad Mitchell
6+
# License: BSD-3-Clause-LBNL
7+
#
8+
# -*- coding: utf-8 -*-
9+
10+
import numpy as np
11+
import pytest
12+
13+
from impactx import RefPart, elements
14+
15+
16+
def test_lattice_linear_map():
17+
"""Calculate the linear transfer map of a lattice."""
18+
19+
# Create reference particle
20+
ref = RefPart()
21+
ref.set_charge_qe(-1.0).set_mass_MeV(0.510998950).set_kin_energy_MeV(1.0e3)
22+
23+
# Create a valid test lattice (all elements define a linear transfer map)
24+
lattice = elements.KnownElementsList()
25+
lattice.extend(
26+
[
27+
elements.Drift(name="drift1", ds=1.0),
28+
elements.Quad(name="quad1", ds=0.5, k=1.0),
29+
elements.Drift(name="drift2", ds=2.0),
30+
elements.Sbend(name="bend1", ds=1.0, rc=10.0),
31+
]
32+
)
33+
34+
# Expected result (matrix multiplication)
35+
R_expected = np.array(
36+
[
37+
[3.74670546e-01, 2.54005827e00, 0, 0, 0, -2.34864805e-01],
38+
[-4.76219076e-01, -5.59489407e-01, 0, 0, 0, 3.20646253e-02],
39+
[0, 0, 1.64872127e00, 6.59488508e00, 0, 0],
40+
[0, 0, 5.21095305e-01, 2.69091188e00, 0, 0],
41+
[9.98334297e-02, 4.99583537e-02, 0, 0, 1.00000000e00, -1.66466013e-03],
42+
[0, 0, 0, 0, 0, 1.00000000e00],
43+
]
44+
)
45+
46+
# Calculate Linear Transfer Map
47+
R = lattice.transfer_map(ref)
48+
assert np.allclose(R.to_numpy(), R_expected)
49+
50+
# Check unexpected/unsupported options
51+
with pytest.raises(RuntimeError):
52+
lattice.transfer_map(ref, order="invalid")
53+
54+
# Create a lattice with an element that does not define a linear transfer map
55+
lattice.append(elements.TaperedPL(k=0, taper=0, unit=0))
56+
57+
# Ensure that the calculation asserts
58+
with pytest.raises(RuntimeError):
59+
lattice.transfer_map(ref)
60+
61+
# Now the user explicitly assumes that undefined maps are identify maps
62+
R = lattice.transfer_map(ref, fallback_identity_map=True)
63+
assert np.allclose(R.to_numpy(), R_expected)

0 commit comments

Comments
 (0)