Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
afcb817
Fix ISIS SBEND visualization
aaadelmann Sep 2, 2026
f95c0c8
delete accidentaily commited files
aaadelmann Sep 2, 2026
e8c43e1
Remove unused RING element type
aaadelmann Sep 3, 2026
91e5f87
Add RING beam sequence parser
aaadelmann Sep 3, 2026
539f1c6
Add RING ownership metadata
aaadelmann Sep 3, 2026
43ce06f
Honor TURNS for RING tracking
aaadelmann Sep 3, 2026
29b342d
Reuse one-turn IndexMap for RING tracking
aaadelmann Sep 3, 2026
eea7ea2
Correct ISIS ring reference tracking inputs
aaadelmann Sep 4, 2026
52a65df
Report RING progress by turn and angle
aaadelmann Sep 4, 2026
80c9eb2
Remove unused MAP_ORDER tracking option
aaadelmann Sep 4, 2026
1cc42cf
Add element linear transfer maps
aaadelmann Sep 4, 2026
8dae681
Fix linear-map ring input geometry
aaadelmann Sep 4, 2026
65b37a2
Support transfer maps through overlapping elements
aaadelmann Sep 4, 2026
2125523
Enable transfer maps in map ring example
aaadelmann Sep 4, 2026
c494ba2
Report combined-map symplecticity
aaadelmann Sep 4, 2026
8c965de
Report transfer-map determinant residual
aaadelmann Sep 4, 2026
f6b86e7
Add analytic transfer-map examples
aaadelmann Sep 4, 2026
8dd2e79
Organize analytic map examples by case
aaadelmann Sep 4, 2026
095de78
Save map example output at info level two
aaadelmann Sep 4, 2026
e036e49
Check full analytic transfer maps
aaadelmann Sep 4, 2026
b4e547d
Decouple transfer maps from tracking step budget
aaadelmann Sep 4, 2026
3ef744a
Add DBA time-step convergence study
aaadelmann Sep 4, 2026
be2d050
Extract linear-map builder and shared external-field ray tracker
aaadelmann Sep 4, 2026
926b097
Resolve field-support transitions in transfer-map ray integration
aaadelmann Sep 4, 2026
905b6ec
Add selectable Richardson maps and compensated ray bookkeeping
aaadelmann Sep 5, 2026
8a5b6bf
Add fixed-step RK4 and DOP853 for reference transfer maps
aaadelmann Sep 5, 2026
e15681b
Add finite-fringe DBA benchmark and independent full-map reference
aaadelmann Sep 5, 2026
86830ca
Separate nominal map ownership from field support and ring return length
aaadelmann Sep 5, 2026
b992a33
Remove accidentally tracked sandbox studies from the PR
aaadelmann Sep 5, 2026
feb02fb
improve doc
aaadelmann Sep 5, 2026
2d07d96
update mathjax
aaadelmann Sep 5, 2026
c8bfffb
prevent warning
aaadelmann Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ input-files/timing.dat
build_*
*.json
coverage_html/
.idea
.idea

# Local study inputs, outputs and exploratory work are not part of the source tree.
/sandbox/
6 changes: 4 additions & 2 deletions Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -1453,14 +1453,16 @@ MATHJAX_FORMAT = HTML-CSS
# The default value is: http://cdn.mathjax.org/mathjax/latest.
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
# The former cdn.mathjax.org endpoint is retired. Pin the MathJax 2 release
# used by this HTML-CSS configuration rather than following a major-version update.
MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2.7.9

# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_EXTENSIONS = AMSmath AMSsymbols
MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols

# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
# of code that will be used on startup of the MathJax code. See the MathJax site
Expand Down
55 changes: 54 additions & 1 deletion src/AbsBeamline/ElementBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@

extern Inform* gmsg;

bool ElementBase::isInsideBody(const Vector_t<double, 3>& r) const {
const auto& geometry = getGeometry();
const auto local = geometry.kind() == GeometryKind::SBend
? GeometryHelper::toBendArcCoords(r, geometry.getCurvature(), geometry.getElementLength())
: r;
return local(2) >= 0.0 && local(2) < geometry.getElementLength()
&& ApertureHelper::isInsideAperture(local, aperture_m);
}

const std::vector<double> ElementBase::defaultAperture_m = std::vector<double>({1e6, 1e6});

const std::map<ElementType, std::string> ElementBase::elementTypeToString_s = {
Expand All @@ -40,7 +49,6 @@ const std::map<ElementType, std::string> ElementBase::elementTypeToString_s = {
{ElementType::SBEND, "SBEND"},
{ElementType::RBEND, "RBEND"},
{ElementType::RBEND3D, "RBEND3D"},
{ElementType::RING, "Ring"},
{ElementType::SOURCE, "SOURCE"},
{ElementType::SOLENOID, "SOLENOID"},
{ElementType::PROBE, "Probe"},
Expand All @@ -60,6 +68,7 @@ ElementBase::ElementBase(const ElementBase& right)
RefPartBunch_m(nullptr),
online_m(right.online_m),
elementID(right.elementID),
beamlineMembership_m(right.beamlineMembership_m),
userAttribs(right.userAttribs),
positionIsFixed(right.positionIsFixed),
elementPosition_m(right.elementPosition_m),
Expand All @@ -75,6 +84,7 @@ ElementBase::ElementBase(const std::string& name)
RefPartBunch_m(nullptr),
online_m(false),
elementID(name),
beamlineMembership_m(),
userAttribs(),
positionIsFixed(false),
elementPosition_m(0.0),
Expand All @@ -89,6 +99,49 @@ const std::string& ElementBase::getName() const { return elementID; }

void ElementBase::setName(const std::string& name) { elementID = name; }

const BeamlineMembership& ElementBase::getBeamlineMembership() const {
return beamlineMembership_m;
}

BeamlineTopology ElementBase::getBeamlineTopology() const { return beamlineMembership_m.topology; }

const std::string& ElementBase::getBeamlineOwnerName() const {
return beamlineMembership_m.ownerName;
}

bool ElementBase::hasLinearTransferMaps() const { return !linearTransferMaps_m.empty(); }

const std::vector<LinearTransferMap>& ElementBase::getLinearTransferMaps() const {
return linearTransferMaps_m;
}

void ElementBase::addLinearTransferMap(LinearTransferMap map) {
linearTransferMaps_m.push_back(std::move(map));
}

void ElementBase::clearLinearTransferMaps() { linearTransferMaps_m.clear(); }

bool ElementBase::isOverlapping() const { return isOverlapping_m; }

void ElementBase::setOverlapping(const bool overlapping) { isOverlapping_m = overlapping; }

void ElementBase::setBeamlineMembership(BeamlineTopology topology, std::string ownerName) {
if (topology == BeamlineTopology::RING && ownerName.empty()) {
throw GeneralOpalException(
"ElementBase::setBeamlineMembership()",
"RING membership requires a non-empty owner name");
}
if (topology == BeamlineTopology::LINE && !ownerName.empty()) {
throw GeneralOpalException(
"ElementBase::setBeamlineMembership()",
"LINE membership cannot have an owner name");
}

beamlineMembership_m = {topology, std::move(ownerName)};
}

void ElementBase::clearBeamlineMembership() { beamlineMembership_m = {}; }

void ElementBase::setOutputFN(const std::string fn) { outputfn_m = fn; }

std::string ElementBase::getOutputFN() const {
Expand Down
119 changes: 118 additions & 1 deletion src/AbsBeamline/ElementBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "AbsBeamline/AttributeSet.h"
#include "Algorithms/CoordinateSystemTrafo.h"
#include "Structure/LinearTransferMap.h"
#include "Algorithms/Quaternion.hpp"
#include "BeamlineGeometry/Geometry.h"
#include "OPALTypes.h"
Expand Down Expand Up @@ -49,7 +50,6 @@ enum class ElementType : unsigned short {
SBEND,
RBEND,
RBEND3D,
RING,
PROBE,
VACUUM,
SOLENOID,
Expand All @@ -58,6 +58,37 @@ enum class ElementType : unsigned short {
CONSTANTFOCUSING
};

/// Logical sequence topology, independent of ElementType and field-support overlap.
/// RING is a sequence declaration, not a new physical element type or a closure test.
enum class BeamlineTopology : unsigned short { LINE, RING };

/**
* @brief Logical RING membership carried by a sequence member or runtime occurrence.
*
* An occurrence is one use of an element in a sequence, rather than its reusable input
* definition. Ring::prepareForTracking() clones occurrences recursively and assigns the
* enclosing RING name to each, including members inside nested LINEs. For example, two
* uses of a quadrupole in ring R1 have distinct element objects but both carry
* {BeamlineTopology::RING, "R1"}; neither the quadrupole name nor an inner LINE name
* is the ownerName. The original quadrupole definition is not retagged.
*
* Ordinary LINE membership is {BeamlineTopology::LINE, ""}: LINE names and a full
* parent hierarchy are deliberately not recorded. ElementBase's copy constructor
* preserves this metadata, including when OpalBeamline clones tracking elements.
* This is host-side value metadata, not a pointer to a sequence, C++ memory ownership,
* a unique occurrence identifier, a turn counter, or evidence of orbit closure.
*
* Do not use membership to select fields or assign transfer-map intervals. Those use,
* respectively, ElementBase::isInside() and ElementBase::isInsideBody(). Field tails
* can act in another element's nominal body without changing either element's membership.
*/
struct BeamlineMembership {
/// LINE by default; RING after assignment by the enclosing Ring.
BeamlineTopology topology = BeamlineTopology::LINE;
/// Enclosing RING's name (required for RING), empty for LINE; stored by value.
std::string ownerName;
};

enum class ApertureType : unsigned short { RECTANGULAR, ELLIPTICAL };

/**
Expand Down Expand Up @@ -184,6 +215,70 @@ class ElementBase : public std::enable_shared_from_this<ElementBase> {
/// @return The element type string.
static std::string getTypeString(ElementType type);

/* ======================= Beamline membership =========================== */

/// Read the logical sequence tag; the returned reference is owned by this element.
/// See BeamlineMembership for its distinction from map ownership and field support.
const BeamlineMembership& getBeamlineMembership() const;

/// Return the topology of the owning beam sequence.
BeamlineTopology getBeamlineTopology() const;

/// Return the owning RING name, or an empty string for LINE membership.
const std::string& getBeamlineOwnerName() const;

/// @brief Whether this runtime occurrence owns at least one calculated linear transfer map.
bool hasLinearTransferMaps() const;

/**
* @brief Maps attached to this nominal body by the design-orbit threader.
*
* OrbitThreader appends in reference-path order. One element may receive several
* segments, and a segment with several nominal owners is copied to each owner.
* Field contribution alone does not imply ownership: a magnet's fringe can act
* in a drift-owned map. These are runtime results, not maps of isolated fields.
*
* The vector belongs to this element. References/pointers to its entries can be
* invalidated by addLinearTransferMap(), clearLinearTransferMaps(), or a new
* map-enabled design pass. Clones start with an empty vector.
* @see OpalBeamline::getLinearTransferMapsInReferenceOrder()
*/
const std::vector<LinearTransferMap>& getLinearTransferMaps() const;

/// Append a map by value; does not validate, sort, deduplicate, or update overlap flags.
void addLinearTransferMap(LinearTransferMap map);

/// Remove attached maps only; membership and isOverlapping() are unchanged.
void clearLinearTransferMaps();

/**
* @brief Whether the map-enabled design pass observed shared field support.
*
* True if this occurrence participated in a sampled support set containing more
* than one element along the requested reference interval. This is an accumulated
* flag for that pass, not an overlap partner list or a full 3D intersection test.
* Support selection can include a field-free drift; it does not test whether each
* selected element contributes a nonzero field. Thus true need not mean overlapping
* nominal bodies or an invalid lattice.
*
* Initially false, not inherited by clones, and reset before map-enabled design
* threading. Secondary-species threading leaves the design result unchanged.
* @see LinearTransferMap::includesOverlappingFields
*/
bool isOverlapping() const;

/// @brief Set overlap participation discovered by the design-orbit threader.
void setOverlapping(bool overlapping);

/// Assign the logical sequence tag only; does not change placement, maps or overlap flags.
/// @param topology LINE or RING.
/// @param ownerName Enclosing RING name, or empty for LINE.
/// @throws GeneralOpalException If RING has an empty name or LINE a nonempty name.
void setBeamlineMembership(BeamlineTopology topology, std::string ownerName = {});

/// Restore {BeamlineTopology::LINE, ""} only; maps and overlap flags are unchanged.
void clearBeamlineMembership();

/// @brief Apply a visitor.
/// @note This method must be overridden by derived classes. It should call
/// the method of the visitor corresponding to the element class. If
Expand All @@ -207,6 +302,19 @@ class ElementBase : public std::enable_shared_from_this<ElementBase> {
/// @return True if r is inside the field interval.
virtual bool isInside(const Vector_t<double, 3>& r) const;

/**
* @brief Nominal body containment, independent of longitudinal field support.
*
* The local longitudinal interval is [0,L), with arc coordinates for a sector bend.
* The transverse aperture is respected. A drift can own this interval even when a
* neighbouring magnet contributes a fringe field there. Zero-length bodies own no interval.
* This defines transfer-map ownership, not the region used for field evaluation.
* @param r Position [m] in the element entrance frame, not the lab frame.
* @return Whether the point belongs to the nominal body and transverse aperture.
* @see OpalBeamline::getBodyElements()
*/
bool isInsideBody(const Vector_t<double, 3>& r) const;

/// @brief Get the bounding box.
/// @return The bounding box in lab coordinates.
virtual BoundingBox getBoundingBoxInLabCoords() const;
Expand Down Expand Up @@ -425,6 +533,15 @@ class ElementBase : public std::enable_shared_from_this<ElementBase> {
std::string elementID;
static const std::map<ElementType, std::string> elementTypeToString_s;

// --- Beamline membership ---
/// Logical sequence tag, preserved by the copy constructor; see BeamlineMembership.
BeamlineMembership beamlineMembership_m;

/// Host-owned map copies. Runtime results below are deliberately not copied.
std::vector<LinearTransferMap> linearTransferMaps_m;
/// Reference-pass support-overlap diagnostic, not static lattice membership.
bool isOverlapping_m{false};

// --- User-defined attributes ---
AttributeSet userAttribs;

Expand Down
4 changes: 4 additions & 0 deletions src/AbstractObjects/BeamSequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class BeamSequence : public Element {
// The result it the ideal line.
virtual Beamline* fetchLine() const = 0;

/// Prepare occurrence-specific state before TrackRun constructs the tracking lattice.
/// The default is a no-op (LINE); Ring overrides it to clone/tag member occurrences.
virtual void prepareForTracking() {}

protected:
/// Constructor for exemplars.
BeamSequence(int size, const char* name, const char* help);
Expand Down
7 changes: 7 additions & 0 deletions src/Algorithms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ set (_SRCS
DistributionMoments.cpp
Flagger.cpp
IndexMap.cpp
ExternalFieldRayTracker.cpp
LinearTransferMapBuilder.cpp
OrbitThreader.cpp
ParallelTracker.cpp
PartData.cpp
Expand All @@ -22,10 +24,15 @@ set (HDRS
AbstractTimeDependence.h
AbstractTracker.h
CavityAutophaser.h
CompensatedSum.h
RungeKuttaTableau.h
DefaultVisitor.h
DistributionMoments.h
Flagger.h
IndexMap.h
ExternalFieldRayTracker.h
LinearTransferMapBuilder.h
LinearTransferMap.h
OrbitThreader.h
ParallelTracker.h
PartData.h
Expand Down
24 changes: 24 additions & 0 deletions src/Algorithms/CompensatedSum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2026, Paul Scherrer Institute, Villigen PSI, Switzerland
#ifndef OPAL_COMPENSATED_SUM_H
#define OPAL_COMPENSATED_SUM_H

namespace compensated {
/**
* @brief Kahan addition retaining increments below the current sum's spacing.
* The represented value is sum - correction. Do not reassociate these operations
* (e.g. with fast-math). Both values must be retained when copying a tracked state.
*/
inline void add(double increment, double& sum, double& correction) {
const double adjusted = increment - correction;
const double next = sum + adjusted;
correction = (next - sum) - adjusted;
sum = next;
}

/// Difference of two compensated values, preserving their small residuals.
inline double difference(double left, double leftCorrection, double right,
double rightCorrection) {
return (left - right) - (leftCorrection - rightCorrection);
}
}
#endif
Loading