11  Sandbox

This chapter is reserved for exploratory notes, draft diagrams, implementation sketches, and benchmark-driven documentation that is not yet ready to be folded into the formal theory chapters.

The intended use is:

11.1 OPAL - OPALX Redesign notes

This is where work in progress is shown to the comunity.

10.1.1 are notes and comments to ideas from Jon/Chris.

10.2 - 10.6 expain implmentations alread in 391-placement-of-sbend-and-rbend-analytic-fields-including-fringe-fields.

11.1.1 Comments to the proposels of Jon/Chris

Download Jon’s OPAL element layout proposal.

OpalElemen vs. Element / ElemenBase

Could OpalElement absorb Element? Technically possible only with a broader refactor, but not directly, because Line and Sequence also depend on Element. We would need to move Element’s responsibilities somewhere else.

Could OpalElement absorb ElementBase? That would couple parser objects to the tracking/beamline runtime model and force either parser classes to implement geometry/tracking/visitor APIs or runtime classes to become OPAL directory objects. That would make ownership, cloning, sharing, and beamline traversal substantially worse.

TBeamline is a template and has std::list as base class. Is it safe to use std::list as base?

Short answer: it is legal C++, but it is, indeed, not a good or safe design.

A better shape would be composition:

template <class T>
class TBeamline : public Beamline {
public:
    using container_type = std::list<T>;
    using iterator = container_type::iterator;
    using const_iterator = container_type::const_iterator;

    iterator begin();
    iterator end();
    const_iterator begin() const;
    const_iterator end() const;

    void append(const T&);
    void prepend(const T&);
    iterator erase(iterator);
    iterator insert(iterator, const T&);

private:
    std::list<T> elements_;
};

That preserves most call-site ergonomics while giving TBeamline control over mutation.

The whole Beamline, TBeamlinem TLine and FlaggedBeamline Hierarchie is complext. Is it nessesary?

Mostly no: the concepts are necessary, but the current hierarchy is more complex than it needs to be.

What is actually needed:

  • A runtime beamline object that is an ElementBase, so a beamline can appear anywhere an element can appear. That is Beamline.
  • An ordered container of beamline entries. That is currently TBeamline<T>.
  • A beamline entry type that points to an ElementBase. That is ElmPtr.
  • Extra entry metadata for LINE: reflected/selected flags. That is FlaggedElmPtr.
  • Extra entry metadata for SEQUENCE: position, relation to previous/next/begin/end, generated drift info, parser-side Element pointer. That is SequenceMember.

So the data model is defensible. But the implementation is too indirect:

The weak part is not that LINE and SEQUENCE need different metadata. They do. The weak part is encoding this through public inheritance from std::list<T> plus several inherited wrapper objects.

A simpler design would be:

class Beamline : public ElementBase {
public:
    using EntryList = std::list<BeamlineEntry>;

    iterator begin();
    iterator end();

    void append(BeamlineEntry);
    void insert(iterator, BeamlineEntry);
    iterator erase(iterator);

private:
    EntryList entries_;
};

with one entry type:

struct BeamlineEntry {
    std::shared_ptr<ElementBase> element;

    bool reflected = false;
    bool selected = false;

    std::optional<SequencePlacement> sequencePlacement;
    std::shared_ptr<Element> parserElement;
};

or, cleaner, separate concrete types:

BeamlineBase
  -> LineBeamline      owns list<LineEntry>
  -> SequenceBeamline  owns list<SequenceEntry>

We could keep the conceptual split between runtime beamline, line entries, and sequence entries. But remove TBeamline<T> : public std::list<T> over time. TLine and FlaggedBeamline are not really independent abstractions; they are type aliases created because TBeamline is templated. That is a sign the implementation is serving the template, not the model.

Three more remarks w.r.t. Element Placing, Origin etc

See OPALX has 4 layers of structure in the next Sandbox chapter for the related structure and class-level context.

Why are bend element split into seperate loops in the compute3DLattice function?

This answer already is base on the branch 391-placement-of-sbend-and-rbend-analytic-fields-including-fringe-fields.

They are split because bends need two different pieces of placement state:

  1. The bend body/entry pose must be stored explicitly.

    In the first loop in OpalBeamline.cpp, the code scans only unpositioned bends. For each SBEND/RBEND, it computes the desired entry pose, then converts that into the element body pose:

    const CoordinateSystemTrafo desiredEntryPose =
            fromEndLastToBeginThis * currentCoordTrafo;
    const CoordinateSystemTrafo bodyFromEntry = element->getEdgeToBegin().inverted();
    setNominalPlacement(element, bodyFromEntry * desiredEntryPose);

    That is bend-specific because a bend’s useful reference frame is not just “translate by length along z”. It has chord length, arc length, bend angle, entrance angle, rotation axis, and sometimes a true design path.

  2. The second loop advances the global beamline chain for all elements.

    The second loop then walks every element in beamline order. Straight elements get placed directly with setNominalPlacement(...). Bends are already positioned by the first loop, so the second loop uses them mainly to advance currentCoordTrafo and endPriorPathLength across the curved/chord geometry.

So the split is basically a compatibility bridge:

  • first pass: pre-place bends using bend-entry/body geometry;
  • second pass: place straight elements and advance the running lattice transform through both straight and bend elements.

Is it necessary structurally? Probably not. It is necessary for the current implementation because bend placement is side-effectful and special-cased. A cleaner design would have one placement algorithm where every element exposes:

entryPort()
exitPort()
bodyPoseFromEntryPose()
advanceReferencePose()

Then bends and straight elements would differ behind the element interface, not through separate loops in OpalBeamline. The current split is a symptom that bend geometry is not yet fully hidden behind a uniform placement/port contract. And I guess is a hint that we need to think about this in a very general way … as we do now!

The bend element

The current enum checks should be treated as legacy compatibility logic, not as the design model. The code often does this pattern:

if (element->getType() == ElementType::SBEND ||
    element->getType() == ElementType::RBEND) {
    ...
}

and then applies bend-specific logic. That is brittle because it confuses two questions:

  1. Is this element named/type-tagged as SBEND or RBEND?
  2. Does this element provide bend geometry/placement behavior?

Those are not the same. MultipoleT::bends() can return true when bendAngle_m != 0.0, while old-style Multipole::bends() returns false. ScalingFFAMagnet and Ring also bend. So ElementType::SBEND || ElementType::RBEND is not a general “bending element” test. It is really a test for “OPALX analytic BendBase element with the current SBEND/RBEND placement model.”

The better design is capability-based:

if (auto* bend = dynamic_cast<BendBase*>(element.get())) {
    // use BendBase geometry API
}

or better still, avoid the branch entirely by giving every element a common placement/port API:

element->getEntryPort();
element->getExitPort();
element->advanceReferencePose(...);
element->getPlacementGeometry();

Then SBEND, RBEND, curved MultipoleT, straight elements, and future elements implement their own geometry. OpalBeamline should not need to know the enum except for diagnostics, serialization, or legacy compatibility.

So the comment is absolutly valid we have to replace these checks with capability-based polymorphism: either a BendBase interface for analytic bend-specific operations, or preferably a general placement/port interface implemented by all elements. This avoids hard-coded type lists and makes the placement algorithm open to new element classes.

Nested LINE statements will call recursive calls

Yes. A named nested LINE will cause recursive visitor calls.

There is an important distinction in Line::parseList():

  • Anonymous nested lists like (A, B, C) are flattened immediately into the parent line.
  • Named LINE objects are inserted as an ElementBase* pointing to their own FlaggedBeamline, so they remain nested and are expanded later by recursive visitor traversal.

This is another reason the Beamline / TBeamline / FlaggedBeamline / visitor design is hard to reason about: structural nesting is not normalized in one place. Some nesting is flattened during parsing, while named line nesting is expanded during tracking.

There may also be a real behavioral risk: DefaultVisitor::visitFlaggedElmPtr() toggles local_flip for reflected members, but ParallelTracker::visitBeamline() currently does:

fbl->iterate(*this, false);

It ignores local_flip. So a reflected named nested line may not be traversed in reverse by ParallelTracker, even though the generic DefaultVisitor::visitBeamline() would honor local_flip. That is worth checking with a small nested/reflected LINE test.

Proposal - Layout Schemes

The proposal is useful because it names three user-visible layout policies that old OPAL treated inconsistently:

  1. offset longitudinal placement using ELEMEDGE,
  2. relative longitudinal placement, the Lego-block model,
  3. beamline-offset placement using X, Y, Z, THETA, PHI, and PSI.

However, we should not make these three policies the primary architecture. The project direction is the more general Chapter 2 placement model: elements have placements, ports, nominal/actual poses, and possibly a separate field-local or reference-orbit chart. The current branch 391-placement-of-sbend-and-rbend-analytic-fields-including-fringe-fields already moves in that direction through PlacedElement, PlacementPose, Misalignment, entry/body/exit ports, and explicit field-local transforms for analytic bends.

So the three layout schemes are best understood as input and compatibility policies that produce the same internal placement representation. They should not become three separate internal geometry mechanisms. This is where the proposal is less general than Chapter 2: it is still centered on arranging a LINE, while the Chapter 2 model is about geometric placement of elements and ports in space, independent of the particular input syntax that produced the placement.

Please see General Structure, OpalElement as Parser-to-Runtime Bridge, and Element Placing (Posing) for the design route we will follow.

Other Aims

Jon’s proposal also lists several implementation aims beyond the three layout modes. Several of them are already discussed above and are not repeated here: Element/ElementBase, TBeamline<T> : public std::list<T>, the Beamline hierarchy, bend enum checks, nested LINE traversal, and the repeated bend/non-bend placement scans.

The remaining cross-cutting aims are valid and useful:

  • Support the three layout schemes through one unified internal placement representation, rather than splitting responsibility between parser objects, OpalBeamline, and Ring.
  • Remove the visitor pattern from layout, or at least keep it out of the geometry decision path. This should simplify the tracker inheritance tree and reduce the amount of element-type-specific visitor boilerplate.

The important qualification is that the unified representation should be the Chapter 2 placement/port model, not a simpler line-list layout model. The line syntax is only one way to produce a placed beamline. The internal model must also support curved elements, combined-function elements, misalignments, nominal-versus-actual poses, reference-path threading, and field-local charts.

Proposal Input file syntax modifications

  1. All comments w.r.t LINE and ORIGIN/ORIENTATION are already realized
  2. CLOSED and CLOSED_TOLERANCE is a nessesary feature but maybe can also added to TRACK
  3. LAYOUT_MODE again TRACK is maybe an alternative for adding such an attribute
  4. RINDEFINITION is already out

Note: line is maybe problematic, suppose you have a injection line to your ring. the line gets flatten so where is now CLOSED etc. to be applied? You could have 3 tracks one for injection, then the ring and then exptraction. So the me this decoration make sense on the TRACK (t.b.f.d).

Notes to implementation sketches

–> still working on this one

The implementation sketch in Jon’s proposal suggests a single layout() flow:

ParallelTTracker
  -> FlaggedBeamline::layout()
  -> OpalBeamline::startLayout()
  -> element.layout(...)
  -> element.addField(this) when applicable
  -> geometry determines the next pose
  -> OpalBeamline::endLayout()

This is useful as a critique of the current code: it points out that layout, field-list construction, nested-line state, and element-specific behavior are currently entangled through visitor traversal and OpalBeamline side effects. The proposal is also right that carrying a small layout-state stack would be better than stashing and merging whole OpalBeamline objects.

Compared with branch 391-placement-of-sbend-and-rbend-analytic-fields-including-fringe-fields:

  • The branch does not introduce layout(), startLayout(), or endLayout(). Layout still enters through ParallelTracker::prepareSections(), ParallelTracker::visitBeamline(), visitor dispatch, and OpalBeamline::visit<T>().
  • Elements do not add themselves to the field list. OpalBeamline::visit<T>() still clones the visited element, initializes it, and pushes a BeamlineFieldElement into elements_m.
  • The visitor pattern remains part of layout collection. The branch has not moved layout responsibility fully into OpalBeamline plus element geometry.
  • The branch still sorts and prepares a FieldList; it also keeps declaredOrder_m and placementAssembly_m to preserve both tracking order and placement records.
  • compute3DLattice() has been renamed in spirit to compileCompatibilityPlacement(), with compute3DLattice() now just forwarding to it. This is useful: it makes the old routine look like a compatibility conversion from legacy ELEMEDGE placement into explicit nominal poses.
  • The proposal wanted one layout loop. The branch still uses two placement loops inside compileCompatibilityPlacement(): a bend pre-pass and then an all-elements pass. This is less than the old scattered model, but it is not the proposed single polymorphic layout pass.
  • The proposal wanted nested LINE handling through a stack of position/orientation state. The branch still has the old OpalBeamline stash/merge code in ParallelTracker::visitBeamline(), but it is commented out; the active code simply calls fbl->iterate(*this, false). So this part is unresolved rather than implemented.
  • The proposal wanted explicit enum tests removed. The branch still checks ElementType::SBEND, RBEND, RBEND3D, SOURCE, and MARKER in several places. Some of those checks are followed by dynamic_cast<BendBase*>, which is closer to a capability interface, but the dispatch is still enum-driven.
  • The proposal wanted the geometry object to determine the next pose. The branch has moved toward this with PlacedElement, entry/body/exit ports, getPlacementGeometry(), and getNominalEntryTransform() / getNominalExitTransform(). However, bend advance logic is still hard-coded in OpalBeamline::compileCompatibilityPlacement() instead of being hidden behind a uniform element/geometry interface.
  • The proposal wanted beamline-relative placement logic moved out of user-interface objects. The branch partially keeps the bridge: OpalElement::update() still interprets ORIGIN, ORIENTATION, X, Y, Z, THETA, PHI, PSI, stores placement state on the runtime element, and may fix the position.
  • The proposal’s suggestion that OpalBeamline may deserve a different name is still relevant. In this branch it is increasingly a runtime field/layout assembly object, not an OpalElement-style parser class. A name like BeamlineLayout or BeamlineAssembly would better describe the role.

Where the proposal is narrower than the Chapter 2 model:

  • It treats the line layout problem as the central abstraction. Chapter 2 treats placement, ports, nominal/actual transforms, and reference charts as the central abstraction. That is more general and is the direction we should keep.
  • It does not clearly separate body placement from field-local coordinates. This distinction matters for analytic SBEND/RBEND and will matter for other curved or combined-function elements.
  • It does not explicitly cover nominal versus actual poses and local misalignment corrections. Those are first-class concepts in the Chapter 2 model and in the current placement bridge.
  • It suggests element-driven addField(this) behavior, which may reduce visitor boilerplate but risks moving beamline assembly policy into every element. We should be careful not to replace one form of scattering with another.
  • It is mainly framed around LINE traversal. The eventual design also has to serve OrbitThreader, reference-path construction, diagnostics, geometry export, and tracking field evaluation.

Where the proposal is valid and useful:

  • It correctly identifies that old OPAL has several layout schemes implemented in different places.
  • It correctly criticizes public inheritance from std::list<T>.
  • It correctly questions the Beamline / TBeamline / TLine / FlaggedBeamline hierarchy.
  • It correctly identifies explicit enum checks as a barrier to adding new elements.
  • It correctly points out that nested LINE handling should use a small layout state stack rather than stashing entire OpalBeamline objects.
  • It correctly argues that layout should not require repeated full scans when a single geometric placement pass can be made to work.
  • It correctly observes that OpalBeamline is poorly named for its runtime assembly role.

Net assessment: Jon’s proposal is a useful critique and should influence the cleanup plan, but it should not replace the Chapter 2 placement model. The branch is best understood as a bridge stage toward that model, not as the final implementation. It introduces useful placement vocabulary and explicit pose records, but the architecture is still centered on visitor-based collection, FieldList, compatibility placement, and special-case bend logic. The next step is to turn the port/placement vocabulary into the actual layout API so that straight elements, analytic bends, curved multipoles, nested lines, misalignments, and field-local charts all participate through one coherent interface.

11.2 OPALX has 4 Layers of Structure (need better title)

Figure 1 extends the element-only (bend) class structure from the Elements chapter into the placement and tracking path used by the present SBEND and RBEND implementation in OPALX.

The diagram emphasizes four layers:

  • parser-facing Opal* element definitions,
  • runtime bend and field representations,
  • beamline placement and coordinate-system assembly,
  • reference-path and tracking-side infrastructure.
flowchart TD
  A["Parser layer<br/>OpalElement -> OpalBend<br/>OpalSBend / OpalRBend"] --> B["Runtime bend reps<br/>SBendRep / RBendRep"]
  B --> C["Curved geometry + field<br/>PlanarArcGeometry / RBendGeometry<br/>BMultipoleField"]
  B --> D["Placed beamline<br/>OpalBeamline / PlacedElement"]
  D --> E["Rigid transforms<br/>CoordinateSystemTrafo"]
  D --> F["Reference-path layer<br/>OrbitThreader / IndexMap<br/>ReferencePathModel"]
  D --> G["Tracking layer<br/>ParallelTracker"]
  F --> G
Figure 11.1: Draft class diagram for the current bend, placement, and tracking path in OPALX.

The parser-side bend objects define attributes, defaults, and input-language validation. The actual runtime geometry and field ownership lives in the representation classes SBendRep and RBendRep.

The next structural step is the placement layer. OpalBeamline assembles the declared components into a placed beamline and keeps a PlacedElement description for each runtime element, including the nominal and actual body, entry, exit, and support transforms.

For tracking, OrbitThreader threads a reference path through the placed beamline and builds the indexing and registration information needed by the tracker. ParallelTracker then consumes the placed beamline and the threaded reference-path information when updating external fields and advancing the reference particle.

11.2.1 QUADRUPOLE and MULTIPOLE

The same parser-to-runtime analysis can be carried out for the present quadrupole and generic multipole path. The main structural difference relative to bends is that OpalQuadrupole and OpalMultipole inherit directly from OpalElement. There is no intermediate parser-side family class analogous to OpalBend.

That detail matters because all common element-level attributes are inherited immediately from OpalElement, including:

  • design type and aperture,
  • placement and floor coordinates,
  • orientation angles,
  • local misalignments,
  • output and common element options.

At update time, both parser-side front ends call the common OpalElement machinery and then configure the same runtime representation MultipoleRep. QUADRUPOLE is therefore a restricted front end on top of the generic MULTIPOLE runtime path.

flowchart TD
  A["Parser layer<br/>OpalElement<br/>OpalQuadrupole / OpalMultipole"] --> B["Runtime multipole rep<br/>MultipoleRep"]
  B --> C["Straight geometry + field<br/>StraightGeometry<br/>BMultipoleField"]
  B --> D["Placed beamline<br/>OpalBeamline / PlacedElement"]
  D --> E["Rigid transforms<br/>CoordinateSystemTrafo"]
  D --> F["Reference-path layer<br/>OrbitThreader / IndexMap<br/>ReferencePathModel"]
  D --> G["Tracking layer<br/>ParallelTracker"]
  F --> G
Figure 11.2: Draft class diagram for the current QUADRUPOLE and MULTIPOLE placement and tracking path in OPALX.

The runtime path is straight-element based. MultipoleRep owns a StraightGeometry object and a BMultipoleField, and both MULTIPOLE and QUADRUPOLE enter the beamline placement pipeline as ordinary Component instances. Consequently, once the parser-side update has completed, the placement and tracking infrastructure is the same one used elsewhere in the beamline:

  • OpalBeamline stores the placed component and its transforms,
  • PlacedElement records the nominal and actual coordinate systems,
  • OrbitThreader evaluates the host reference-path field model through the placed beamline,
  • ParallelTracker uses the same placed component during bunch tracking.

In other words, the present quadrupole family differs from the bend family mainly on the parser side and in the runtime geometry type:

  • bends use OpalBend and specialized curved runtime representations,
  • QUADRUPOLE and MULTIPOLE use OpalElement directly and share one straight multipole runtime representation.

The separate MULTIPOLET implementation is not included in this draft diagram. It is a different combined-function magnet path and should be documented separately once the straight MULTIPOLE and QUADRUPOLE structure is fully settled.

11.3 OpalElement as Parser-to-Runtime Bridge

OpalElement is primarily a parser-side front-end class. It is not the object that is later threaded through OpalBeamline or stepped by ParallelTracker. Instead, it is the bridge between the OPAL input language and the runtime ElementBase hierarchy.

The essential structural point is:

  • OpalElement inherits from Element,
  • Element owns an embedded runtime ElementBase,
  • derived parser-side classes such as OpalSBend, OpalRBend, OpalQuadrupole, and OpalMultipole configure that embedded runtime object.

This means that OpalElement has two responsibilities:

  1. store and parse common OPAL element attributes,
  2. transfer common placement, orientation, aperture, and misalignment data into the embedded runtime element before the derived class adds physics-specific configuration.

The common parser-side attributes handled in OpalElement include:

  • L,
  • aperture,
  • ORIGIN and ORIENTATION,
  • X, Y, Z, THETA, PHI, PSI,
  • DX, DY, DZ, DTHETA, DPHI, DPSI,
  • ELEMEDGE,
  • the transverse-exit deletion flag.

The following diagram shows the role of OpalElement in the current control flow.

TikZ preview of the OpalElement control-flow bridge.

Control-flow view of OpalElement as the bridge from parsed OPAL element definitions to runtime ElementBase objects.

The corresponding class-level ownership relation is:

TikZ preview of the OpalElement class bridge.

Class-level bridge from OpalElement to the runtime ElementBase hierarchy.

So the answer to the practical question “where is OpalElement used?” is:

  • yes, first of all on the parser/input side,
  • but more precisely as the shared front-end bridge from parsed element attributes into the runtime ElementBase object,
  • after that handoff, placement, reference-path construction, and tracking are performed on the runtime object, not on OpalElement itself.

This distinction is especially important for the bend work, since parser-side inheritance and runtime-side inheritance are intentionally different:

  • parser side: OpalElement -> OpalBend -> OpalSBend / OpalRBend,
  • runtime side: ElementBase -> Component -> BendBase -> SBend / RBend -> Rep.

11.4 Element Placing (Posing)

This subsection is the working area for documenting how OPALX places, or poses, elements in space in a way that is consistent with Section 2: Coordinate Systems of the manual.

The immediate goal is not yet a final polished derivation. Instead, this sandbox section should fix the vocabulary, code anchors, and transformation chain used by the current implementation.

The placement topic will need to connect four layers:

  • the parser-side placement attributes in OpalElement,
  • the runtime pose stored in ElementBase,
  • the placed beamline view represented by PlacedElement,
  • the way OpalBeamline, OrbitThreader, and ParallelTracker consume those transforms.

11.4.1 Input placement, the current SE(3) bridge, and mismatch handling

The current OPALX implementation does not have a dedicated ((3)) class. The role of the rigid-motion carrier is instead played by CoordinateSystemTrafo. The important convention is that the stored transform maps parent-frame coordinates into local-frame coordinates. For a point \(\mathbf{r}_{\mathrm{parent}}\),

\[ \mathbf{r}_{\mathrm{local}} = Q\bigl(\mathbf{r}_{\mathrm{parent}} - \mathbf{o}\bigr). \]

This is a sound rigid transform, but it is the parent-to-local form rather than the local-to-parent pose matrix often used in robotics texts. Composition is still an ((3)) composition, with the OPALX convention

\[ (A \cdot B)(\mathbf{r}) = A(B(\mathbf{r})), \]

so the right-hand transform acts first.

The notation used in this subsection is:

  • bold symbols such as (), (), and ((s)) denote vectors or embedded points in (^3),
  • plain symbols such as (s), (t), and (t) denote scalars,
  • frame and space symbols such as (K), (K_s), ((3)), and ((3)) are written non-bold,
  • rigid transforms such as (G_i^{}), (i), and (B{i,}) are treated as maps in ((3)), acting on vectors by function application,
  • (Q (3)) denotes the rotational part of a rigid transform,
  • C++ identifiers such as CoordinateSystemTrafo or PlacedElement are kept in monospace.

To remain consistent with Chapter 2, this sandbox note reserves:

  • (P_i) for the placement map from the canonical local chart (U_i) into the laboratory frame (K),
  • (p_{i,}) for the abstract port consisting of a marked boundary chart together with an adapted frame,
  • (T_i(t)) for the Chapter 2 laboratory-frame pose of the canonical element frame.

The present code discussion therefore uses distinct implementation-side symbols:

  • (G_i^{}) for the stored parent-to-nominal-body rigid transform,
  • (G_i^{}) for the stored parent-to-actual-body rigid transform,
  • (B_{i,}) for the body-relative rigid transform of the port frame materialized by ElementGeometry.

The words nominal and actual need to be fixed before the bridge equations. In this note:

  • nominal means the rigid placement of the canonical element body frame in the parent frame before any local survey or misalignment correction is applied,
  • actual means the rigid placement of that same body frame after the local correction has been applied,
  • these terms refer to body placement in physical space, not yet to the field-local charts used later by analytic bends.

For element (i), the current bridge therefore distinguishes:

  • the nominal body pose (G_i^{} (3)), stored as PlacementPose(parentToNominal),
  • the local correction (_i (3)), stored as Misalignment(nominalToActual),
  • the actual body pose (G_i^{}), assembled from the first two quantities.

Draft TikZ preview of the floor Cartesian coordinate system.

Floor Cartesian frame \(K=(X,Y,Z)\) with the reference line shown in orange. Two placed element bodies, \(e_i\) and \(e_{i+1}\), are shown together with a representative point \(\mathbf{R}(s)\) on the reference line. The label placed element body is kept inside the figure as the only descriptive in-figure annotation.

The figure above should be read as the floor-frame interpretation of nominal placement. The global frame is the floor Cartesian frame (K=(X,Y,Z)). The orange reference line is the reference path in that frame, and ((s)) is a representative point on that path. The drawn element bodies (e_i) and (e_{i+1}) represent placed element bodies in floor space. At this stage of the discussion they should be read as nominal body poses. The corresponding actual body poses are obtained by applying the local correction (i) or ({i+1}) to those nominal poses; they are not drawn as separate boxes in this first figure.

The corresponding Chapter 2 to Sandbox crosswalk is:

Table 11.1: Table 10.3.1: Chapter 2 objects and their Sandbox implementation-side representatives.
Chapter 2 object Sandbox implementation-side representative Current code anchor Comment
(K) floor Cartesian parent frame beamline / lab frame same object as in Chapter 2
(P_i) conceptual local-to-lab placement map not stored directly Chapter 2 geometric notion
(G_i^{}) stored inverse rigid representative of the nominal body pose PlacementPose, ElementBase::getCSTrafoGlobal2Local() implementation-side parent-to-local map
(_i) nominal-to-actual local correction Misalignment, ElementBase::getMisalignment() same correction idea as in Chapter 2
(G_i^{} = _i G_i^{}) stored inverse rigid representative of the actual body pose assembled in PlacedElement implementation-side parent-to-local map
(p_{i,}) abstract port in the Chapter 2 sense not stored as a single object marked boundary chart plus adapted frame
(B_{i,}), (B_{i,}), (B_{i,}) body-relative rigid transforms of the port frames ElementGeometry via getEntryPort(), getBodyPort(), getExitPort() implementation-level port-frame representatives
field-local chart runtime field-evaluation chart, especially for analytic bends OpalBeamline::transformToFieldLocalCS() and related methods additional runtime chart beyond Chapter 2 rigid placement

This table makes the key distinction explicit: the bridge first constructs placement in floor space, and only afterwards do some runtime paths map positions and vectors into a separate field-local chart. Those two layers should not be conflated.

Under that convention, the current posing bridge can be stated compactly as:

\[ G_i^{\mathrm{act}} = \Delta_i \cdot G_i^{\mathrm{nom}}, \]

The canonical port-frame representatives (B_{i,}), (B_{i,} = I), and (B_{i,}) are body-relative marked frames. This means the same port can be queried either nominally or actually, depending on whether it is composed with (G_i^{}) or with (G_i^{}).

The corresponding nominal and actual port transforms are

\[ G_{i,\mathrm{entry}}^{\mathrm{nom}} = B_{i,\mathrm{entry}} \cdot G_i^{\mathrm{nom}}, \qquad G_{i,\mathrm{exit}}^{\mathrm{nom}} = B_{i,\mathrm{exit}} \cdot G_i^{\mathrm{nom}}, \]

and

\[ G_{i,\mathrm{entry}}^{\mathrm{act}} = B_{i,\mathrm{entry}} \cdot G_i^{\mathrm{act}}, \qquad G_{i,\mathrm{exit}}^{\mathrm{act}} = B_{i,\mathrm{exit}} \cdot G_i^{\mathrm{act}}. \]

This is exactly the bridge assembled in PlacedElement. ElementBase stores the ingredients PlacementPose and Misalignment, while PlacedElement materializes both the nominal and actual body/port transforms.

Table 11.2: Table 10.3.2: Input-file quantities and their placement-bridge interpretation.
Input-file quantity Geometric meaning Bridge object Main C++ entry points
ORIGIN, ORIENTATION explicit nominal body pose PlacementPose OpalElement::update(), ElementBase::setCSTrafoGlobal2Local(), ElementBase::fixPosition()
X, Y, Z, THETA, PHI, PSI explicit nominal body pose in split scalar form PlacementPose OpalElement::update(), ElementBase::setCSTrafoGlobal2Local(), ElementBase::fixPosition()
DX, DY, DZ, DTHETA, DPHI, DPSI local nominal-to-actual correction Misalignment OpalElement::update(), ElementBase::setMisalignment()
ELEMEDGE compatibility reference-path start coordinate elementPosition_m ElementBase::setElementPosition(), OpalBeamline::resolveVisitStartField(), OpalBeamline::compileCompatibilityPlacement()
legacy edge geometry body-relative entry/body/exit ports ElementGeometry ElementBase::getEntryPort(), getBodyPort(), getExitPort(), getPlacementGeometry()

The parser-side and runtime-side split is therefore:

  1. OpalElement::update() interprets the input attributes and pushes them into the embedded runtime element.
  2. ElementBase stores the nominal rigid transform, the local correction, and the optional ELEMEDGE reference coordinate.
  3. ElementBase::getPlacedElement() assembles these quantities into the bridge view PlacedElement.
  4. OpalBeamline consumes that placed-element view, either by respecting an already fixed pose or by compiling compatibility placement from reference order and ELEMEDGE.

TikZ preview of the placement SE(3) bridge.

Bridge from input-file placement syntax to placed-element transforms and runtime use.

Current mismatch detection is partial and distributed, not centralized:

  • malformed ORIGIN or ORIENTATION arrays are rejected in OpalElement::update() with an OpalException,
  • analytic SBEND and RBEND elements without explicit Z placement still require ELEMEDGE; this is enforced in OpalBeamline::resolveVisitStartField(),
  • explicit nominal placement is protected by fixPosition(), so the later compatibility placement pass does not overwrite an already posed element,
  • port continuity and field-local consistency are currently validated mainly by regression tests such as TestOpalBeamlinePlacement, together with exported placement summaries, rather than by a dedicated runtime validator.

This also clarifies where a future mismatch detector should sit. The natural place is not the parser, but the bridge boundary where PlacedElement combines the nominal pose, local correction, and body-relative ports into a single explicit geometric record.

11.4.2 Current placement path in the code

At the moment, the placement handoff in OPALX is centered around OpalElement::update(). That function interprets the parser-side placement attributes and writes them into the embedded runtime ElementBase object.

In particular, the current path includes:

  • global pose through setCSTrafoGlobal2Local(...),
  • the fixed-position flag through fixPosition(),
  • local misalignment through setMisalignment(...),
  • optional longitudinal placement through setElementPosition(...).

Once this has been done, ElementBase::getPlacedElement() provides the bridge to the explicit placed-element representation that is later used by the beamline assembly.

flowchart TD
  A["Parser-side OpalElement"] --> B["Placement attributes"]
  B --> C["OpalElement::update()"]
  C --> D["Runtime ElementBase<br/>pose + correction + anchor"]
  D --> E["ElementBase::getPlacedElement()"]
  E --> F["PlacedElement"]
  F --> G["Nominal / actual ports"]
  F --> H["OpalBeamline"]
  H --> I["OrbitThreader"]
  H --> J["ParallelTracker"]
Figure 11.3: Draft flow for element posing in the current OPALX implementation.

11.4.3 Scope of the placement note

The intended scope of this sandbox subsection is:

  1. restate the placement conventions from Section 2 in code-facing language,
  2. explain how ORIGIN / ORIENTATION relate to X, Y, Z, THETA, PHI, PSI,
  3. explain how local misalignments DX, DY, DZ, DTHETA, DPHI, DPSI are applied,
  4. explain the role of nominal versus actual placement,
  5. explain how entry, body, and exit transforms are represented for curved elements,
  6. prepare the later OrbitThreader documentation.

11.4.4 Straight elements, curved elements, and runtime charts

For accelerator physicists the practical question is not just how a rigid body is posed in floor space, but how that rigid placement is connected to the reference trajectory and to the coordinates used later for field evaluation and tracking.

The comoving reference-orbit frame is the next layer:

Draft TikZ preview of the comoving Frenet-Serret frame.

Comoving Frenet-Serret frame \(K_s=(x,y,s)\) attached to the reference orbit. The tripod at \(\mathbf{R}(s)\) defines the local axes \(\hat{\mathbf{x}}\), \(\hat{\mathbf{y}}\), and \(\hat{\mathbf{s}}\), and the brace indicates the local longitudinal coordinate \(s\) along the reference curve.

The comoving Frenet-Serret frame (K_s=(x,y,s)) is attached to the reference orbit rather than to the placed hardware. In the Chapter 2 language, this is the practical split between the world chart ((r,t)) used for placement in laboratory space and the reference chart (s) used to report where the reference orbit currently sits. For a straight element this distinction is almost invisible: the body (z)-direction and the reference longitudinal coordinate (s) coincide, so the body placement and the field chart are effectively the same to first order. For a curved element, however, that is no longer true. The rigid body is still placed in floor space by a single stored pose (G_i^{}), but the field chart follows the design orbit through the magnet.

This is the key machinery of the current OPALX bridge:

Draft TikZ preview of the transform chain between floor, element-local, comoving, and boosted coordinate systems.

Transform chain between the floor frame \(K\), the element-local frame \(K'_i\), the comoving Frenet-Serret frame \(K_s\), and the boosted frame \(K_s^\star\). The solid arrows represent the forward chain through placement pose (CoordinateSystemTrafo), reference-path transform, and Lorentz boost. The dashed arrows indicate inverse or field-query transforms used later in the runtime path.

In accelerator language, the chain is:

  1. place the element body in the floor frame (K),
  2. define entry/body/exit ports relative to that body,
  3. assemble nominal and actual placed-element transforms,
  4. pass to a reference-orbit-based chart when field evaluation requires it.

This leads to the following conventions.

Table 11.3: Table 10.3.3: Straight-element versus curved-element placement and chart conventions.
Topic Straight elements Curved elements (SBEND, RBEND, RBEND3D) Main code anchors
Nominal body pose one rigid stored pose (G_i^{}) in floor space one rigid stored pose (G_i^{}) in floor space PlacementPose, setCSTrafoGlobal2Local(), getPlacedElement()
Actual body pose (G_i^{} = _i G_i^{}) (G_i^{} = _i G_i^{}) Misalignment, PlacedElement::getActualBodyTransform()
Entry/body/exit ports rigid port-frame transforms (B_{i,}) aligned with the straight body chart rigid port-frame transforms (B_{i,}) attached to the curved element body; they define geometric entry and exit faces getEntryPort(), getBodyPort(), getExitPort(), ElementGeometry
Longitudinal chart used for geometry export body (z) coordinate nominal body ports plus bend design path / edge geometry getNominalEntryTransform(), getNominalExitTransform(), save3DLattice()
Longitudinal chart used for field evaluation body-local chart is usually sufficient separate field-local chart tied to the reference orbit transformToFieldLocalCS(), transformFromFieldLocalCS(), BendBase field-local mapping
Compatibility placement without explicit pose ELEMEDGE can supply the start coordinate, but explicit Z pose also works analytic bends remain strict: without explicit Z, ELEMEDGE is required resolveVisitStartField(), compileCompatibilityPlacement()

The parser-side orientation and misalignment angles should also be stated in the same code-facing way they are actually built:

  • the orientation quaternion is assembled in OpalElement::update() as rotTheta * (rotPhi * rotPsi),
  • the corresponding axes are theta -> y, phi -> x, and psi -> z,
  • the same axis order is used for the local misalignment rotation rotationY * rotationX * rotationZ,
  • the transform stored in CoordinateSystemTrafo is the conjugated, parent-to-local form.

For the current implementation it is therefore safest to document the parser angles by their construction order in code, not by introducing additional intrinsic/extrinsic terminology that might obscure the existing convention.

From the accelerator-physics point of view, three additional remarks matter.

First, there is a distinction between survey placement and reference orbit. The survey or input placement fixes where the hardware sits in the floor frame. The reference orbit is the path that the traced reference particle actually follows through the placed lattice. In a perfectly matched straight section those two viewpoints nearly coincide. In curved or misaligned sections they do not: the survey pose still describes the body in floor space, while the reference orbit defines the comoving longitudinal coordinate used later for tracking and field queries.

Second, the entry and exit faces are geometric ports, not yet the same thing as a hard-edge transport model. The ports determine where the canonical body chart begins and ends, and they are used for geometric chaining, export, and diagnostics. A hard-edge or fringe-field model then decides how the field support extends relative to those geometric faces. This distinction is especially important for bends, where body extent and field-support extent need not coincide.

Third, ELEMEDGE should be understood physically as a reference-path anchoring coordinate. It is not an independent survey pose. In the compatibility placement path it specifies where the element edge sits on the longitudinal reference coordinate used for beamline assembly. For ordinary straight elements this is mostly a convenience. For analytic bends without an explicit rigid Z placement it remains essential, because the code still needs an unambiguous longitudinal anchor from which the bend body and field support are laid out.

The main placement split for later runtime documentation is then:

  • OpalElement parses and constructs the rigid nominal pose and local correction,
  • ElementBase stores those ingredients and the optional compatibility longitudinal coordinate,
  • PlacedElement assembles the explicit nominal/actual body and port transforms,
  • OpalBeamline compiles these into the beamline placement assembly,
  • OrbitThreader and ParallelTracker consume that assembly, with bends introducing a separate field-local chart on top of the rigid body placement.

This subsection is now the right place to continue with a dedicated OrbitThreader note, because the distinction between floor placement, body ports, and reference-orbit charts has been made explicit.

11.5 OrbitThreader

OrbitThreader is the first stage where the placed geometry is converted into a traced reference orbit. For accelerator physicists, its role is simple to state:

  • start from the reference particle state,
  • move that particle through the placed lattice in the summed external fields,
  • record which elements are active along the traced longitudinal coordinate,
  • hand that occupancy and interval information to the main tracker.

In other words, OrbitThreader is the bridge between static placement and the dynamic reference-path picture used by time-based tracking.

The symbols (T_i), (i), and (P{i,*}) retain the meanings fixed in Table Table 11.1; C++ class and function names stay in monospace.

11.5.1 Physical role of the traced reference path

The placed lattice tells us where hardware sits in floor space. The traced reference path tells us which parts of that hardware are actually intersected by the reference particle and in what longitudinal order. Those are not the same question in a general 3D placed lattice.

This is why OrbitThreader exists at all. It does not merely sort elements by a nominal Z coordinate. It integrates the reference particle through the placed fields and constructs a path-based activation model from that integration.

flowchart TD
  A["Placed beamline<br/>OpalBeamline"] --> B["Reference state<br/>r, p, s, t, dt"]
  B --> C["OrbitThreader::execute()"]
  C --> D["Traced path<br/>DesignPath.dat"]
  C --> E["ReferencePathModel<br/>active occupancy"]
  C --> F["Action-range model<br/>registered intervals"]
  E --> G["ParallelTracker::computeExternalFields()"]
  F --> G
  G --> H["Bunch tracking"]
Figure 11.4: Draft OrbitThreader role in the OPALX tracking machinery.

11.5.2 Code-side path construction

The current implementation is centered around:

  • constructor: OrbitThreader(ref, r, p, s, maxDiffZBunch, t, dT, stepSizes, beamline)
  • main driver: OrbitThreader::execute()
  • local integrator: OrbitThreader::integrate(...)

The input state comes directly from the bunch reference particle in ParallelTracker:

  • reference particle rest data,
  • lab-frame reference position and momentum,
  • current path-length coordinate s,
  • bunch time t,
  • current time step dt.

So OrbitThreader does not invent a separate reference particle. It reuses the same physical reference state that the main time-based tracker is about to use.

11.5.3 What OrbitThreader produces

The class owns two distinct path models.

  1. ReferencePathModel from imap_m
  • this is the traced active-element occupancy model,
  • it answers: which elements are active around the current traced longitudinal position?
  1. actionRangeRegistrationModel_m
  • this is the registration model for backward-compatible action ranges,
  • it answers: which element intervals should be registered on the elements themselves for later legacy-style use?

This split matters physically. The first model is a traced occupancy model. The second is a registration model that translates the traced result into legacy element intervals and ELEMEDGE-compatible ranges.

11.5.4 Why bends need special treatment

The field evaluation stage later distinguishes straight elements from analytic bends. ParallelTracker::computeExternalFields() uses the OrbitThreader occupancy query, but then treats bends specially:

  • straight and most other elements use one rigid local field transform,
  • bends build tracking slices and use an entry-to-slice-local reference-path transform inside each slice.

So the information flow is:

  1. OrbitThreader says which bend is active along the traced path,
  2. ParallelTracker asks the bend for tracking slices,
  3. the bunch is mapped slice-by-slice into the bend’s local reference-path chart,
  4. the bend applies the slice field,
  5. the bunch is mapped back to the reference frame.

This is the point where the distinction made in 10.3.4 becomes operational: - rigid floor placement tells us where the bend is, - the field-local slice chart tells us how the bunch is advanced through it. - reference-path slices and bend-local tracking charts in more detail.

11.5.5 Disconnected placement and mismatch detection

OrbitThreader also contains the first real runtime consistency check for placed lattices. After the traced path has been built, validateVisitedElements(...) scans placed elements that should have been reachable and checks whether the reference path ever visited them.

If a nontrivial placed element lies within the traced longitudinal window but is never intersected, the code raises a user-facing exception and reports its nominal entry, body, and exit coordinates.

This is the current runtime answer to the practical question:

  • “Did I place the hardware in a way that the reference trajectory can actually thread through it?”

For an accelerator physicist, this is often the most useful placement diagnostic, because it converts a disconnected survey configuration into a clear statement about the traced reference orbit.

11.5.6 Relation to ELEMEDGE

OrbitThreader::registerElement(...) and processElementRegister() are where the traced path is turned back into legacy-compatible element ranges.

For general elements, the code infers an effective element-edge coordinate from the traced local entrance state. For bends, processElementRegister() applies the bend field extent explicitly:

  • it gets fieldBegin and fieldEnd,
  • if the bend already has an explicit ELEMEDGE, that value is kept as the anchor,
  • the registered action range is then built from that anchored field interval.

Physically, this means: - the traced path determines what the reference particle actually visited, - ELEMEDGE still acts as the longitudinal anchor used to express the bend field interval in the legacy coordinate language.

ELEMEDGE should be seen as depricayed, in a linear setup instead Z should be used.

11.5.7 Handoff to the main tracker

The handoff is in ParallelTracker:

  1. construct OrbitThreader,
  2. call execute(),
  3. retrieve the global bounding box,
  4. use oth.query(...) for active elements around each bunch container,
  5. also inspect oth.getActionRangeRegistrationModel() to ensure bend segments remain visible in the queried window.

So OrbitThreader is not only a diagnostic tool or a design-path logger. It is part of the live tracking machinery that decides which elements are considered active for field application.

11.5.8 Worked example: one placed bend

It is useful to make the machinery concrete on the simplest nontrivial case: a single placed analytic bend, for example one SBEND instance B1.

The physical story is:

  1. the input deck fixes the bend body pose in floor space, either explicitly by ORIGIN / ORIENTATION or X,Y,Z,THETA,PHI,PSI, or compatibly through a longitudinal anchor such as ELEMEDGE,
  2. OpalElement::update() turns that into a stored nominal body pose (G_{B1}^{}) and an optional local correction,
  3. OpalBeamline assembles the corresponding PlacedElement,
  4. OrbitThreader traces the reference particle and determines the interval along the traced path where B1 is actually active,
  5. ParallelTracker queries that interval and, because B1 is a bend, applies the bend field slice by slice in a reference-path-local chart.

TikZ preview of the one-bend OrbitThreader workflow.

Worked-example flow for one placed analytic bend.

For this one-bend case, the key objects and physical meanings are:

Table 11.4: Table 10.4.1: Worked-example dictionary for one placed analytic bend.
Stage Object or quantity Physical meaning Main code anchors
1 (G_{B1}^{}) nominal bend body pose in floor space PlacementPose, setCSTrafoGlobal2Local()
2 (_{B1}) local survey / misalignment correction Misalignment, setMisalignment()
3 (B_{B1,}), (B_{B1,}) body-relative port-frame transforms for the geometric bend entry and exit faces getEntryPort(), getExitPort(), ElementGeometry
4 traced active interval part of the reference path where the bend is actually intersected OrbitThreader::execute(), imap_m.add(...)
5 registered action range legacy-compatible bend field interval registerElement(), processElementRegister()
6 bend slices local reference-path pieces used for field application buildTrackingSlices(), applySlice()

The important physics point is that the rigid bend pose alone does not yet tell the tracker how to advance the bunch through the bend. It tells the code where the hardware sits. OrbitThreader then determines where the reference orbit actually threads that hardware, and the bend tracker finally resolves that path into slice-local field applications.

This is also where ELEMEDGE enters most clearly. For the worked bend:

  • if an explicit bend edge coordinate is already set, that coordinate remains the bend anchor,
  • the traced reference path then determines the visited field interval around that anchor,
  • the action-range registration model carries that interval forward so that the main tracker continues to see the bend in the correct longitudinal window.

So the one-bend example already contains the full machinery in miniature:

  • survey placement,
  • nominal/actual body pose,
  • geometric ports,
  • traced occupancy interval,
  • legacy-compatible registration interval,
  • slice-based bend tracking.

11.5.9 ToDo

  1. OrbitThreader: a short note on trackBack() and why the path is traced slightly backward before the forward pass,
  2. OrbitThreader: a diagram of the two internal models: ReferencePathModel versus action-range registration,

11.6 Autophasing

Autophasing fixes the RF phase, or lag, used by an accelerating element for a given reference particle. In the old OPAL formulation the RF elements are external field maps. The spatial field is sampled on a one-, two-, or three-dimensional grid, interpolated at the particle position, and multiplied by the time-dependent RF factor

\[ \cos(\omega t + \varphi), \]

where () is the angular RF frequency and () is the lag. The autophasing problem is therefore: choose () so that the reference particle obtains the desired, usually maximal, energy gain through the element.

This sandbox note uses the old OPAL derivation as the starting point, but keeps the wording OPALX-facing. The code-facing question for OPALX is how this phase condition is tied to the traced reference path, the element-local field map, and the time model used during reference-particle threading.

11.6.1 Standing-Wave Cavity

For a standing-wave cavity with longitudinal field (E_z(z,r)), the energy gain of a particle with charge (q) is written

\[ \Delta E(\varphi,r) = q V_0 \int_{z_{\mathrm{begin}}}^{z_{\mathrm{end}}} \cos\!\left(\omega t(z,\varphi) + \varphi\right) E_z(z,r)\, dz . \]

The auto-phase for maximum energy gain satisfies

\[ \frac{d\Delta E}{d\varphi}=0. \]

Keeping the possible phase-dependence of the arrival-time model gives

\[ \begin{aligned} 0 &= \int_{z_{\mathrm{begin}}}^{z_{\mathrm{end}}} \left(1+\omega \frac{\partial t}{\partial \varphi}\right) \sin\!\left(\omega t(z,\varphi)+\varphi\right)E_z(z,r)\,dz \\ &= \cos\varphi\,\Gamma_1 + \sin\varphi\,\Gamma_2 . \end{aligned} \]

Thus the lag is determined by

\[ \tan\varphi = -\frac{\Gamma_1}{\Gamma_2}. \]

For a sampled field map, OPAL assumes a linear field interpolation and a linear time interpolation between neighboring samples (z_{i-1}) and (z_i). With (z_i=z_i-z_{i-1}), (t_i=t_i-t_{i-1}), and (E_{z,i}=E_{z,i}-E_{z,i-1}),

\[ \Gamma_1 = \sum_{i=1}^{N-1} \left(1+\omega \frac{\partial t}{\partial\varphi}\right) \int_{z_{i-1}}^{z_i} \sin\!\left( \omega\left(t_{i-1}+\Delta t_i \frac{z-z_{i-1}}{\Delta z_i}\right) \right) \left( E_{z,i-1}+\Delta E_{z,i}\frac{z-z_{i-1}}{\Delta z_i} \right) dz \]

and

\[ \Gamma_2 = \sum_{i=1}^{N-1} \left(1+\omega \frac{\partial t}{\partial\varphi}\right) \int_{z_{i-1}}^{z_i} \cos\!\left( \omega\left(t_{i-1}+\Delta t_i \frac{z-z_{i-1}}{\Delta z_i}\right) \right) \left( E_{z,i-1}+\Delta E_{z,i}\frac{z-z_{i-1}}{\Delta z_i} \right) dz . \]

The products in these interval integrals can be evaluated analytically. In normalized interval coordinates (), define

\[ \begin{aligned} \Gamma_{11,i} &= \int_0^1 \sin\!\left(\omega(t_{i-1}+\tau\Delta t_i)\right)\,d\tau = -\frac{\cos(\omega t_i)-\cos(\omega t_{i-1})}{\omega\Delta t_i},\\ \Gamma_{12,i} &= \int_0^1 \tau\sin\!\left(\omega(t_{i-1}+\tau\Delta t_i)\right)\,d\tau = \frac{-\omega\Delta t_i\cos(\omega t_i) +\sin(\omega t_i)-\sin(\omega t_{i-1})} {\omega^2\Delta t_i^2},\\ \Gamma_{21,i} &= \int_0^1 \cos\!\left(\omega(t_{i-1}+\tau\Delta t_i)\right)\,d\tau = \frac{\sin(\omega t_i)-\sin(\omega t_{i-1})}{\omega\Delta t_i},\\ \Gamma_{22,i} &= \int_0^1 \tau\cos\!\left(\omega(t_{i-1}+\tau\Delta t_i)\right)\,d\tau = \frac{\omega\Delta t_i\sin(\omega t_i) +\cos(\omega t_i)-\cos(\omega t_{i-1})} {\omega^2\Delta t_i^2}. \end{aligned} \]

Then the sampled sums become

\[ \Gamma_1 = \sum_{i=1}^{N-1} \left(1+\omega \frac{\partial t}{\partial\varphi}\right) \Delta z_i \left[ E_{z,i-1}(\Gamma_{11,i}-\Gamma_{12,i}) +E_{z,i}\Gamma_{12,i} \right], \]

and

\[ \Gamma_2 = \sum_{i=1}^{N-1} \left(1+\omega \frac{\partial t}{\partial\varphi}\right) \Delta z_i \left[ E_{z,i-1}(\Gamma_{21,i}-\Gamma_{22,i}) +E_{z,i}\Gamma_{22,i} \right]. \]

The remaining ingredient is the time model (t(z,)). The old OPAL algorithm initializes it from an approximate kinetic-energy ramp,

K[i] = K[i-1] + (z[i] - z[0]) * q * V;
b[i] = sqrt(1. - 1. / ((K[i] - K[i-1]) / (2.*m*c^2) + 1)^2);
t[i] = t[0] + (z[i] - z[0]) / (c * b[i]);

which assumes that the kinetic energy increases linearly with position and is proportional to the maximum voltage. With that provisional time model one computes (), updates the kinetic-energy model using the corresponding field integral, recomputes the times, and repeats until the phase converges.

For OPALX this iteration should be interpreted as a reference-particle preprocessing step. It provides the RF lag consistent with the chosen reference trajectory and field-map convention before the main bunch tracking step uses the element.

11.6.2 Traveling-Wave Structure

For a traveling-wave structure, the same idea is applied to a field assembled from several phase-shifted pieces. The old manual describes the field as an entry fringe field, two core standing-wave contributions, and an exit fringe field. A representative energy-gain model is

\[ \begin{aligned} \Delta E(\varphi,r) &= q V_0 \int_{z_{\mathrm{begin}}}^{z_{\mathrm{beginCore}}} \cos(\omega t+\varphi) E_z(z,r)\,dz \\ &\quad + q V_{\mathrm{core}} \int_{z_{\mathrm{beginCore}}}^{z_{\mathrm{endCore}}} \cos(\omega t+\varphi_{\mathrm{c1}}+\varphi) E_z(z,r)\,dz \\ &\quad + q V_{\mathrm{core}} \int_{z_{\mathrm{beginCore}}}^{z_{\mathrm{endCore}}} \cos(\omega t+\varphi_{\mathrm{c2}}+\varphi) E_z(z+s,r)\,dz \\ &\quad + q V_0 \int_{z_{\mathrm{endCore}}}^{z_{\mathrm{end}}} \cos(\omega t+\varphi_{\mathrm{ef}}+\varphi) E_z(z,r)\,dz , \end{aligned} \]

where (s) is the cell length. Compared with the standing-wave case, the stationarity condition contains several (_1)- and (_2)-type sums with different phase offsets and integration ranges.

For the old FINLB02_RAC example,

FINLB02_RAC: TravelingWave, L=2.80, VOLT=14.750*30/31,
             NUMCELLS=40, FMAPFN="FINLB02-RAC.T7",
             ELEMEDGE=2.67066, MODE=1/3,
             FREQ=1498.956, LAG=FINLB02_RAC_lag;

the decomposition gives

\[ \begin{aligned} V_{\mathrm{core}} &= \frac{V_0}{\sin(2\pi/3)} = \frac{2V_0}{\sqrt{3}},\\ \varphi_{\mathrm{c1}} &= \frac{\pi}{6},\\ \varphi_{\mathrm{c2}} &= \frac{\pi}{2},\\ \varphi_{\mathrm{ef}} &= -2\pi(\mathrm{NUMCELLS}-1)\,\mathrm{MODE}. \end{aligned} \]

In the ultra-relativistic limit, where () changes only weakly along the structure, the arrival time can be approximated by

\[ t(z,\varphi) \simeq \frac{z}{\beta c}+t_0 . \]

The phase condition can then be simplified by exploiting the periodicity of the core field and the sinusoidal RF factor. In the old example the core field has period (3s) and (/(c)), which reduces the traveling-wave autophasing problem to a single periodic convolution-like condition over one three-cell interval. This approximation is useful as a diagnostic and a fast initial guess, but the general OPALX implementation should keep the full piecewise field-map and reference-time model available.

11.6.3 Consistency Notes for OPALX

  • The phase () is an element-local RF lag, not a global beamline coordinate.
  • The time samples (t_i) must be consistent with the reference path used by the element placement and tracking machinery.
  • The sign convention in ((t+)) must be kept aligned with the input-language definition of LAG and with any field-map convention.
  • Standing-wave and traveling-wave elements share the same mathematical structure: maximize the reference-particle energy gain with respect to the RF lag, then use the resulting lag during tracking.

11.7 Koordinate Systems

11.7.1 OPALX Coordinate Frames and Transform APIs

This note summarizes the coordinate frames currently used in OPALX and maps the PhysicsManual terminology to the concrete code structures and helper functions.

The PhysicsManual coordinate-system chapter uses the following conceptual language:

  • K = (X,Y,Z): laboratory or floor Cartesian frame.
  • B: reference base used for ordering and reporting, such as a transfer-line interval, ring circle, or graph.
  • s: reference/reporting coordinate on B.
  • U_i: canonical local chart of element i.
  • Omega_i: local support of the element field, aperture, or material model.
  • p_i: named port or marked boundary chart, such as entrance or exit.
  • P_i: rigid placement map embedding the local chart into the lab frame.
  • T_i: rigid placement transform, represented by a translation and rotation.
  • Gamma_i(ell): optional intrinsic reference path of element i.
  • C_pq or g_ij: port-to-port or chart-to-chart transition map.

In the current code, lab, global, and floor are often used interchangeably for the same Cartesian world frame. For element placement, treat these as aliases of the lab/floor frame K.

Core Transform Convention

The common implementation type is CoordinateSystemTrafo.

API Meaning
transformTo(r) Transform a point from the parent/source frame into this transform’s target frame.
transformFrom(r) Transform a point from the target frame back to the parent/source frame.
rotateTo(v) Rotate a vector into the target frame, without translation.
rotateFrom(v) Rotate a vector back to the parent/source frame, without translation.
transformBunchTo(R,n) Apply transformTo to a particle-position view.
transformBunchFrom(R,n) Apply transformFrom to a particle-position view.
rotateBunchTo(P,n) Apply rotateTo to a particle/vector view.
rotateBunchFrom(P,n) Apply rotateFrom to a particle/vector view.
inverted() Return the inverse transform.

Relevant implementation:

  • src/Algorithms/CoordinateSystemTrafo.h
  • src/Algorithms/CoordinateSystemTrafo.cpp

Frame Table

Frame PhysicsManual name Code storage Into this frame Out of this frame
Lab / floor / global Cartesian K = (X,Y,Z) implicit root frame identity identity
Beamline / line frame line ORIGIN, ORIENTATION; compatibility reference for ELEMEDGE OpalBeamline::coordTransformationTo_m OpalBeamline::transformTo, rotateTo, getCSTrafoLab2Local() transformFrom, rotateFrom, getCSTrafoLab2Local().inverted()
Element nominal local/body frame local chart U_i, placed by P_i ElementBase::csTrafoGlobal2Local_m OpalBeamline::transformToLocalCS(comp,r) or comp->getCSTrafoGlobal2Local().transformTo(r) transformFromLocalCS, rotateFromLocalCS, or comp->getCSTrafoGlobal2Local().transformFrom(r)
Explicitly posed element frame X,Y,Z,THETA,PHI,PSI placement set in OpalElement::update() same as element local frame after setCSTrafoGlobal2Local() same as element local frame
Misaligned element frame static error transform Delta T_i^err ElementBase::misalignment_m getMisalignment(comp) * getCSTrafoLab2Local(comp); in tracking also * pc->getToLabTrafo() invert the full chain, e.g. localToRefCSTrafo = refToLocalCSTrafo.inverted()
Element begin / entrance port p_i^in, entrance boundary chart ElementBase::getEdgeToBegin() lab to begin: getEdgeToBegin() * getCSTrafoGlobal2Local() use .transformFrom() on the composed transform
Element end / exit port p_i^out, exit boundary chart ElementBase::getEdgeToEnd() lab to end: getEdgeToEnd() * getCSTrafoGlobal2Local() use .transformFrom() on the composed transform
Bend intrinsic reference path Gamma_i(ell) PlanarArcGeometry, RBendGeometry, Euclid3D getTransform(s), getEntranceFrame(), getExitFrame() Euclid3D inverse / reverse transform logic; not the same API as CoordinateSystemTrafo
Runtime moving reference/bunch frame tracker reference frame around reference particle ParticleContainer::toLabTrafo_m, refPartR_m, refPartP_m, sPos_m reference to lab is used through pc->getToLabTrafo() in transform chains lab/reference update via ParticleContainer::updateRefToLabCSTrafo()
Element-local field-evaluation frame element local frame plus errors temporary chain in ParallelTracker::computeExternalFields() refToLocalCSTrafo = misalignment * (lab2local * pc->getToLabTrafo()); then pc->transformBunch(refToLocalCSTrafo) localToRefCSTrafo = refToLocalCSTrafo.inverted()
Beam / self-field frame beam frame, origin at reference, z along mean momentum local variables in ParallelTracker::computeSpaceChargeFields() referenceToBeamCSTrafo.transformBunchTo(R) beamToReferenceCSTrafo.transformBunchTo(R) and rotateBunchTo(E/B)
Mesh frame for self fields follows current particle R representation field solver mesh state if R is beam-frame, mesh is beam-frame after transform back, bunchUpdate() restores reference-frame mesh
Adaptive-bin rest frame per-bin quasi-rest frame BinnedFieldSolver::BinKinematics not a rigid CoordinateSystemTrafo; uses gammaBin, pmean, prepareRhoForBin() accumulateFieldToTemp() Lorentz-transforms E' to lab E,B

Important Code Paths

Explicit Element Placement

For elements with absolute placement attributes, OpalElement::update() builds the placement transform from X, Y, Z, THETA, PHI, and PSI:

CoordinateSystemTrafo global2local(origin, rotation.conjugate());
base->setCSTrafoGlobal2Local(global2local);
base->fixPosition();

This makes the element-local frame accessible through ElementBase::getCSTrafoGlobal2Local().

Relevant files:

  • src/Elements/OpalElement.cpp
  • src/AbsBeamline/ElementBase.h
Beamline to Element Local

OpalBeamline wraps the element transform API:

transformToLocalCS(comp, r)
transformFromLocalCS(comp, r)
rotateToLocalCS(comp, v)
rotateFromLocalCS(comp, v)
getCSTrafoLab2Local(comp)

Internally these use comp->getCSTrafoGlobal2Local().

Relevant file:

  • src/Elements/OpalBeamline.h
Begin and End Port Frames

The default element begin and end frames are:

getEdgeToBegin() = identity
getEdgeToEnd()   = translation by (0, 0, L)

The lab-frame begin and end poses are usually formed as:

CoordinateSystemTrafo toBegin = element->getEdgeToBegin()
                              * element->getCSTrafoGlobal2Local();

CoordinateSystemTrafo toEnd = element->getEdgeToEnd()
                            * element->getCSTrafoGlobal2Local();

Relevant files:

  • src/AbsBeamline/ElementBase.h
  • src/Elements/OpalBeamline.cpp
Runtime Field Evaluation Chain

During particle tracking, particle coordinates are stored in the moving reference frame. To apply an element, OPALX composes:

CoordinateSystemTrafo refToLocalCSTrafo =
    itsOpalBeamline_m.getMisalignment(element)
    * (itsOpalBeamline_m.getCSTrafoLab2Local(element) * pc->getToLabTrafo());

CoordinateSystemTrafo localToRefCSTrafo = refToLocalCSTrafo.inverted();

pc->transformBunch(refToLocalCSTrafo);
element->apply(pc);
pc->transformBunch(localToRefCSTrafo);

This is the active runtime transition:

reference frame -> lab frame -> nominal element local -> misaligned element local

and then back again.

Relevant file:

  • src/Algorithms/ParallelTracker.cpp
Moving Reference Frame

Each particle container stores the current moving reference state:

refPartR_m
refPartP_m
sPos_m
toLabTrafo_m

Useful accessors:

pc->getRefPartR()
pc->getRefPartP()
pc->get_sPos()
pc->getToLabTrafo()
pc->updateRefToLabCSTrafo(dt)

Relevant file:

  • src/PartBunch/ParticleContainer.hpp
Beam Frame for Space Charge

ParallelTracker::computeSpaceChargeFields() temporarily transforms the bunch into a beam frame whose origin is the reference particle and whose local z axis is aligned with the mean momentum.

The code builds:

CoordinateSystemTrafo beamToReferenceCSTrafo(
    Vector_t<double, 3>(0, 0, pc->get_sPos()),
    alignment.conjugate());

CoordinateSystemTrafo referenceToBeamCSTrafo =
    beamToReferenceCSTrafo.inverted();

Then it transforms particle positions to the beam frame, solves self-fields, and transforms positions and fields back to the reference frame.

Relevant file:

  • src/Algorithms/ParallelTracker.cpp
Adaptive-Bin Rest Frames

The binned space-charge solver does not use a rigid coordinate transform for the rest frame. Instead it computes per-bin kinematics:

BinnedFieldSolver::BinKinematics {
    Vector_t<double, Dim> pmean;
    double gammaBin;
}

The solver treats the mesh field as a bin-frame electric field E', applies the Lorentz scaling, and accumulates lab-frame E and B.

Relevant file:

  • src/PartBunch/BinnedFieldSolver.h

Notes and Terminology Cleanup

The current implementation would be easier to reason about if the transform names were made explicit:

  • Lab2Element
  • Element2Lab
  • Reference2Lab
  • Lab2Reference
  • Reference2Element
  • Element2Reference

In the current code, getCSTrafoLab2Local() returns getCSTrafoGlobal2Local(). This is mathematically fine because lab, global, and floor are aliases here, but the naming obscures the intended frame chain.

11.7.2 Boosted Frenet-Serret frame

The next refinement step here should be:

  1. align the boosted-frame notation with the field-solver sections,
  2. decide how K_s^\star should be named in the final text,
  3. connect the boosted-frame picture to the actual solver data flow.

Draft TikZ preview of the boosted Frenet-Serret frame.

Boosted Frenet-Serret-style frame used as a draft field-solver frame. The marked point denotes the bunch centroid, the dashed horizontal line indicates the longitudinal beam direction, the heavy diagonal arrow indicates the boost direction, and the dashed rectangle indicates the solver box in the boosted frame.

11.8 Low Energy e-/e+ in a High Energy Coulomb Field

This sandbox note describes the present OPALX model for low-energy electron/positron witness particles born near an interaction point and moving through the field of a high-energy primary bunch. The current implementation is useful as a first numerical experiment, but it is not yet a complete laboratory-frame treatment of a relativistic Coulomb field.

11.8.1 Implemented model

The implemented path is driven by a BEAMBEAM element. Container 0 is the source bunch. The optional attribute

WITNESS_CONTAINERS = "1,2"

selects passive witness containers. The default is

WITNESS_CONTAINERS = "NONE"

so legacy beam-beam tracking is unchanged unless witness kicks are requested.

When the source bunch enters the BeamBeam window, OPALX replaces the normal self-field mesh by a fixed interaction-window mesh. The longitudinal extent is the placed BEAMBEAM element length, centered on the interaction point. If an aperture is provided, the transverse half widths define the fixed x-y field domain. The source charge density is deposited on this mesh and the open Poisson problem is solved in the BeamBeam/source frame,

\[ \nabla^2 \phi_s(\mathbf r_s) = -\frac{\rho_s(\mathbf r_s)}{\epsilon_0}, \qquad \mathbf E_s(\mathbf r_s) = -\nabla \phi_s(\mathbf r_s). \]

The witness particles do not contribute to this charge density. They only sample the already-solved source field. For a witness particle stored relative to its own container reference path, the current code maps the longitudinal coordinate into the source frame with

\[ z_s = z_w + (s_w - s_s), \]

where (s_s) is the source-container reference path length and (s_w) is the witness-container reference path length. The transverse coordinates are rotated with the same BeamBeam source-frame rotation used for the source bunch. The field is gathered on the fixed BeamBeam mesh and then rotated back to the reference frame before the normal Boris kick.

This gives a well-defined one-way coupling:

  1. source container deposits charge,
  2. source field is solved on the BeamBeam window mesh,
  3. witness containers gather that field,
  4. witness momenta are kicked,
  5. witness charge never feeds back into the source solve.

This is the approximation currently used for the gamma-gamma pair-tracking prototype. It is a source-field sampling model, not yet a full transformation of the high-energy bunch field into the physical frame seen by the low-energy pairs.

11.8.2 Missing physics

The primary missing piece is the relativistic field transformation. The source solve is presently electrostatic in the BeamBeam/source frame. A high-energy bunch seen in the laboratory frame has compressed transverse electric fields and a magnetic field. Starting from a source rest-frame Coulomb field (E’), (B’=0), the idealized boost along the source velocity (_s c) gives, schematically,

\[ \mathbf E_{\parallel} = \mathbf E'_{\parallel}, \qquad \mathbf E_{\perp} = \gamma_s \mathbf E'_{\perp}, \qquad \mathbf B = \frac{1}{c}\,\boldsymbol\beta_s \times \mathbf E , \]

with signs fixed by the exact frame convention. The witness kick should then use the Lorentz force in a common physical frame,

\[ \frac{d\mathbf p_w}{dt} = q_w \left(\mathbf E_{\mathrm{lab}} + \mathbf v_w \times \mathbf B_{\mathrm{lab}}\right). \]

For low-energy pairs this magnetic term and the difference between source, lab, and witness frames are not optional details: the witnesses can have large angles and broad velocities, so a scalar electrostatic kick in the source frame is only a first approximation.

The implementation also still treats the witness field sample as synchronous with the source field solve. A more complete model must define the time at which pairs are created, the source bunch phase at that time, and whether retardation or a quasi-static boosted-field approximation is sufficient for the target accuracy.

11.8.3 Work needed

The next implementation step is to make the frame contract explicit:

  1. define the source field frame, the OPALX reference frame, and the witness container frame in one notation;
  2. decide whether the source field should be stored as a rest-frame field plus boost metadata, or directly as a laboratory-frame ((E,B));
  3. apply the Lorentz-transformed field to witnesses in a common frame;
  4. preserve the current one-way coupling switch so WITNESS_CONTAINERS="NONE" remains a true no-witness-space-charge mode.

The minimum validation set should include:

  1. a boosted point-charge or Gaussian-bunch manufactured solution for (E) and (B);
  2. a regression proving that witness containers receive a nonzero kick only when WITNESS_CONTAINERS is enabled;
  3. a check that witness particles outside the BeamBeam aperture are counted and reported;
  4. one-rank versus two-rank comparisons for the gamma-gamma pair input;
  5. conservation and reproducibility diagnostics for the source container when witnesses are enabled, verifying that witness charge is not deposited on the BeamBeam source mesh.