Skip to content

Add pseudo-orbit BPM reading mode#28

Open
thellert wants to merge 1 commit intokparasch:developfrom
als-apg:feature/pseudo-orbit
Open

Add pseudo-orbit BPM reading mode#28
thellert wants to merge 1 commit intokparasch:developfrom
als-apg:feature/pseudo-orbit

Conversation

@thellert
Copy link
Copy Markdown

Summary

  • Adds BPMSystem.capture_pseudo_orbit() — injects, tracks N turns, averages per-BPM across turns via nanmean. Returns (n_bpms,) arrays matching orbit interface shape.
  • Adds pySCPseudoOrbitInterface that plugs directly into orbit_correction() using the orbit response matrix.
  • Extends Tuning.correct_orbit() with pseudo=True, n_turns=N — one-flag switch from closed-orbit to pseudo-orbit correction.
  • Fixes pre-existing np.concatnp.concatenate bug in fit_dispersive_orbit().
  • Removes the commented-out correct_pseudo_orbit_at_injection prototype.

Motivation

Pseudo-orbit is the natural reading mode between TBT (beam threading) and closed-orbit (stable operation). After stitching achieves multi-turn survival, pseudo-orbit averages out turn-by-turn jitter to give a smoother correction target. This is needed for ALSU accumulator ring commissioning.

@kparasch — I'm open to alternative implementations of this feature, but pseudo-orbit as a first-class reading mode is something we definitely need for ALSU commissioning. It would be nice to have a convenient high-level function for this in pySC. Happy to iterate on the API if you have preferences.

Usage

# Direct reading
x, y = SC.bpm_system.capture_pseudo_orbit(n_turns=20)

# Via interface (for custom correction loops)
from pySC.tuning.pySC_interface import pySCPseudoOrbitInterface
interface = pySCPseudoOrbitInterface(SC=SC, n_turns=20)
x, y = interface.get_orbit()

# One-flag switch on existing correction
SC.tuning.correct_orbit(pseudo=True, n_turns=20, n_reps=3)

Test plan

  • 3 new BPM system tests (shape, transmission, single-turn equivalence)
  • 1 new tuning smoke test (correct_orbit(pseudo=True))
  • All 29 existing tests pass, 0 regressions

Pseudo-orbit averages injection TBT data per-BPM across turns,
bridging the gap between raw TBT (beam threading) and closed-orbit
(stable operation) reading modes. Needed for ALSU commissioning
after stitching achieves multi-turn survival.

- BPMSystem.capture_pseudo_orbit(): delegates to capture_injection + nanmean
- pySCPseudoOrbitInterface: plugs into orbit_correction with orbit RM
- Tuning.correct_orbit(pseudo=True, n_turns=N): one-flag switch
- Fix pre-existing np.concat bug in fit_dispersive_orbit
- Remove commented-out correct_pseudo_orbit_at_injection prototype
@kparasch kparasch changed the base branch from develop to main March 30, 2026 08:13
@kparasch kparasch changed the base branch from main to develop March 30, 2026 08:13
Copy link
Copy Markdown
Owner

@kparasch kparasch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the API, I have requested some small changes.

return fake_trajectory_x_tbt, fake_trajectory_y_tbt

def capture_pseudo_orbit(self, n_turns=1, bba=True, subtract_reference=True,
use_design=False, return_transmission=False):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have a "starting_turn=1" option in case one wants to define the pseudo-orbit from later turns for reasons like fast-kicker orbit bumps, decoherence, e.t.c

)
if return_transmission:
x, y, transmission = result
return np.nanmean(x, axis=1), np.nanmean(y, axis=1), transmission
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to catch np.nanmean warnings. They can get quite annoying.

with warnings.catch_warnings():
    warnings.simplefilter("ignore", category=RuntimeWarning)
    mean_data = np.nanmean(data, axis=1)


class pySCPseudoOrbitInterface(pySCOrbitInterface):
SC: "SimulatedCommissioning" = Field(repr=False)
n_turns: int = 1
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

starting_turn=1 from above should be an attribute of the class here


def correct_orbit(self, n_reps=1, method='tikhonov', parameter=100, gain=1, virtual=False):
def correct_orbit(self, n_reps=1, method='tikhonov', parameter=100, gain=1, virtual=False,
pseudo=False, n_turns=1):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should raise an error/warning if n_turns and starting_turn is redefined but pseudo is False

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants