Skip to content

feat(sc-linac): give LCLS-II-HE cavities their own loaded-Q window - #284

Open
lisazacarias wants to merge 2 commits into
slaclab:mainfrom
lisazacarias:feat/he-loaded-q-limits
Open

feat(sc-linac): give LCLS-II-HE cavities their own loaded-Q window#284
lisazacarias wants to merge 2 commits into
slaclab:mainfrom
lisazacarias:feat/he-loaded-q-limits

Conversation

@lisazacarias

Copy link
Copy Markdown
Collaborator

What this changes

Every cavity in L4B (CM 37–59) is an LCLS-II-HE cavity, and those accept a wider
loaded-Q window than the original LCLS-II design: 3e7 – 7e7 rather than
2.5e7 – 5.1e7. Wider at both ends, not shifted — a different cavity, not the same
one held to a looser standard.

25 changed lines of production code, but please read them with the blast radius
in mind rather than the size: measured_loaded_q_in_tolerance is consulted by
Cavity.finish_characterization(), so this changes pass/fail and push
behaviour for L4B cavities in auto setup as well as in commissioning.

Groundwork for the Cavity Characterization commissioning phase, which needs to
flag loaded Q correctly. Landed here rather than in that phase so both callers
get the same answer.

Operator-visible

Cavity characterization on an L4B cavity now judges loaded Q against 3e7 – 7e7
instead of 2.5e7 – 5.1e7. A measured loaded Q between 5.1e7 and 7e7 on an L4B
cavity previously reported "Loaded Q out of tolerance" and the value was not
pushed to the cavity; it now reports in tolerance and is pushed. A value between
2.5e7 and 3e7 changes the other way — it used to pass and will now be flagged.

This applies to automated setup as well as to commissioning, since both read the
same tolerance check. Nothing changes for L0B–L3B or for the harmonic linearizer
cryomodules (H1, H2).

Scope

25 changed lines excluding tests — within target.

Decisions worth recording

The numbers come from Ryan and should be confirmed by him. Source is his
cavity characterization outline: "Flag loaded Q is < 3E7 or > 7E7 (for HE
cavities)". The provenance note sits on the constants in linac_utils so the
next reader knows where they came from and that they were not derived from
anything in the codebase. If they are wrong, this is the one place to fix them.

In the hardware model, not the commissioning phase. The phase that needs
this could have carried its own limits, but measured_loaded_q_in_tolerance
already exists on Cavity and auto setup already uses it. Two sources of truth
for the same threshold would mean auto setup keeps flagging HE cavities
incorrectly while commissioning does not — a difference nobody would expect and
which would be found the hard way.

Only the loaded-Q pair is overridden. HE cavities share the standard length,
frequency and scale-factor limits. No separate HE scale-factor values were
specified, so inventing them would have been guessing; if they differ, that is a
follow-up with its own source.

Cryomodule.is_high_energy mirrors is_harmonic_linearizer — same shape,
self.name in L4B against self.name in L1BHL. The two are mutually exclusive,
so the limit branches cannot both fire; there is a test asserting that holds for
every cryomodule in the machine rather than relying on it being obvious.

Learning reviewer

@hmarts9Cavity.__init__ is where a cavity's class-specific limits get
resolved, and finish_characterization() is what consumes them. Worth a read to
see how the model distinguishes cavity types. No approval needed.

Testing

Checked against the real machine rather than only through mocks:

CM01 → L0B  HE=False  [2.50e7, 5.10e7]
CM37 → L4B  HE=True   [3.00e7, 7.00e7]
CM59 → L4B  HE=True   [3.00e7, 7.00e7]
CMH1 → L1B  HE=False  [1.50e7, 3.50e7]

There is also a test for the behavioural consequence directly: a measured loaded
Q of 6e7 passes measured_loaded_q_in_tolerance on CM37 and fails on CM01.

Two things worth knowing about the test changes:

  • test_loaded_q_limits needed updating. Its fixture's cryomodule is a Mock,
    so the new is_high_energy property returned a truthy Mock and selected the HE
    window. The fixture sets linac.name to L0B–L3B, so its intent is plainly
    non-HE; it now sets is_high_energy explicitly.

  • CM 36 does not exist — L3B ends at 35 and L4B starts at 37. My first
    version of the test asserted on it and raised KeyError. Now noted in the test.

  • pytest passes

  • Coverage still clears 80%

  • Checked against simulation (PYDM_DEFAULT_PROTOCOL=fake / sc-sim) where applicable

Every cavity in L4B (CM 37-59) is an LCLS-II-HE cavity, and those accept a
wider loaded-Q window than the original LCLS-II design: 3e7 to 7e7 rather than
2.5e7 to 5.1e7. Wider at both ends, not shifted — a different cavity, not the
same one held to a looser standard. Applying the standard window to L4B flags
correctly-performing HE cavities as out of tolerance.

Source: Ryan Porter's cavity characterization outline, "Flag loaded Q is < 3E7
or > 7E7 (for HE cavities)". Worth his confirmation before treating the numbers
as settled — the provenance note in linac_utils says so.

Cryomodule.is_high_energy mirrors the existing is_harmonic_linearizer, and the
two are mutually exclusive (L1BHL vs L4B), so the limit branches cannot
collide; there is a test asserting that holds for every cryomodule.

Only the loaded-Q pair is overridden. HE cavities share the standard length,
frequency and scale-factor limits, and no separate HE scale-factor values were
specified.

Landed in the hardware model rather than in the commissioning phase that needs
it, so auto setup gets the right limits too — measured_loaded_q_in_tolerance is
what both consult.

The cavity test fixture now sets is_high_energy explicitly: its cryomodule is a
Mock, so an unset property returns a truthy Mock and would have selected the HE
window for a fixture whose linac name is always L0B-L3B.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

🟢 PR size: 26 lines — within target

+26 / -0 in production code.
2 test and asset files excluded.

Largest files Change
src/sc_linac_physics/utils/sc_linac/linac_utils.py +12/-0
src/sc_linac_physics/utils/sc_linac/cavity.py +8/-0
src/sc_linac_physics/utils/sc_linac/cryomodule.py +6/-0

Target is 400 lines excluding tests, hard stop around 800 — see
"Scope and shipping" in CLAUDE.md. If this PR is over, the description
should say why it could not be split. Advisory only; this check never
fails the build.

Comment thread src/sc_linac_physics/utils/sc_linac/linac_utils.py Outdated
Comment thread tests/utils/sc_linac/test_cavity.py Outdated
…design

Sebastian's review correction: the wider HE window comes from a different
default Qext, not from the cavities being a different design. Comment and
test docstring only — no behaviour change.

Rewrapped rather than committed as-is; the suggestion was a single 166-char
line, over the 120-char flake8 limit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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