Skip to content

The tune-up chain

Before a measurement can run unattended, the spectrometer has to be brought to a working point: enough microwave power, the magnet on the resonance line, an integration window over the echo, the receiver phase zeroed, and the pulse amplitudes calibrated. epr_auto does this as an ordered chain of tuning steps, each one measuring a quantity, judging it, and storing the result for the steps that follow. This page walks through that chain in the order the shipped protocols/tune_up.yaml runs it and explains the physics behind each rule. For the parameter tables see the step reference; for how retries and on_fail wrap each step see Writing protocols.

The canonical order is:

[temperature] -> coarse power -> field -> echo window -> phase -> fine calibration

Each stage depends on the ones before it. Power is set first because every later measurement needs pulses that rotate the spins; the field search needs those pulses to produce an echo; the echo window and phase are measured on that echo; and the fine amplitude calibration is meaningful only once the window and phase are fixed. A temperature prologue (temp.set / temp.wait) is optional and, when present, runs before everything else so the whole tune-up happens at the measurement temperature.

Preliminary tuning

Use protocols/preliminary_tuning.yaml to establish a working point before fine tuning. Set the sample name, synthesizer scan bounds, field center and field span for your experiment. All transmit pulses in this workflow use the AWG SINE channel. The first two steps use built-in sequences and do not accept a preset parameter; echo steps use .phase_awg files.

RV (rotary-vane attenuator) controls the microwave excitation power at the sample. VA (video attenuation) controls the level of the received electrical signal sent to the ADC. It is set using two bridge controls: VA1 (Video Attenuation 1, video1_db: 0–30 dB in 2 dB steps) and VA2 (Video Attenuation 2, video2_db: 0–31.5 dB in 0.5 dB steps). Increasing either video attenuation reduces the measured signal amplitude; an additional 6 dB approximately halves it.

ringing check -> [resonator scan] -> echo field search -> repetition-rate scan -> amplitude/field optimization -> video check -> fine-tuning handoff

Ringing check

tune.ringing_check sets a nonresonant field (field: 100 G by default), homes the rotary vane (RV) to 60 dB, then checks 60, 40, 20, 10, 5 and 0 dB. Choose a field away from the sample resonance so the ladder measures receiver ringing without a sample echo. Each move finishes before acquisition; the measured signal must pass before the next attenuation is commanded. The built-in sequence uses +x,+x on both DETECTION and its single SINE pulse, so phase cycling cannot cancel the ringing. Acquisition defaults are listed in the step reference.

pulse_length sets the ladder’s SINE pulse length and defaults to 102.4 ns. Later steps must use the same IF and stay within the tested DAC amplitudes. The ladder does not limit their pulse lengths.

For a same-day rerun on the same setup, done: true declares that the ladder has already passed at the same IF. The hardware-free preflight still runs and the limits are recorded, but this step does not touch the RV, field or receiver and takes no trace. The default is done: false, which runs the ladder.

The limit is 100 mV on the maximum unsmoothed magnitude sqrt(I² + Q²) after protection. Missing or invalid data also fail the check. A failure stops the protocol and attempts a settled return to 60 dB; retries, on_fail: skip and a continuing series cannot override this safety stop. Operator cancellation during the ringing ladder also returns RV to 60 dB. Stopping at a checkpoint or in another step leaves RV unchanged.

The check starts after receiver protection ends, using timing derived from the pulse sequence and the configured acquisition alignment. It includes the initial ringing amplitude. Rotary-vane movements are mechanical: the runner waits for calibrated movement and settling before acquiring data.

Optional resonator scan

tune.resonator runs the built-in AWG SINE resonator procedure at RV = 10 dB. It measures reflected amplitude with the diode and oscilloscope. Frequency selection uses a common early-ringing time window, subtracts the pre-pulse baseline, and handles either diode polarity. Weak, competing, boundary or clipped peaks and centers that move excessively between nearby windows are rejected. The typical precision_mhz is 5. Set window: 4 ns for resonator selection; this is the recommended width from hardware runs, while the parameter default remains 2 ns. Stability is checked with comparison windows shifted by 1 ns and 2 ns.

When region is omitted, the ringing peak is searched only after the nominal pulse end. This avoids selecting the reflected-pulse plateau, which can exceed the trailing-edge ringing at off-resonance frequencies. An explicit region is searched in full.

Both built-in steps have if_mhz: 50 by default. Their IF values must match each other and the later echo preset's DETECTION IF. The scan axis is the bridge synthesizer frequency; the observation frequency is synthesizer minus IF. The selected synthesizer value is applied directly, without adding IF again. Omit this step to retain the existing synthesizer frequency.

Echo search and optimization

With the default adjust_video: true, tune.find_echo first homes RV and opens it through 60, 40, 20, 15, 10, 5 and 0 dB, ending at attenuation_db. A target between these values is the final point. The initial pulses run at center, normally near g = 2. The phasing tool's live preview reads the current 1D time trace from the ADC buffers during RV movement, so VA can be increased while the vane is opening. The next RV move starts after settling and a valid trace below 200 mV.

Above 200 mV, the runner adds video attenuation and remeasures. It uses the maximum unsmoothed I/Q magnitude after receiver protection. The increase is 20*log10(measured_mV/200) dB, rounded upward to available settings: VA1 first (2 dB steps, up to 30 dB), then VA2 (0.5 dB steps, up to 31.5 dB). Invalid readout or a signal still too strong at maximum attenuation stops the protocol and attempts RV home.

Set adjust_video: false in tune.find_echo to keep the current video attenuation and move directly to the requested RV. tune.maximize_echo and tune.video_attenuation inherit this choice unless explicitly overridden. The final video step skips both measurement and bridge access when disabled.

tune.find_echo scans the requested field range at the chosen attenuation_db (default 10 dB, allowed 0–60 dB), scores the full recorded magnitude, and confirms a resolved echo and its integration window. pulse_length sets a common length for both microwave pulses; when omitted, it uses the shortest active microwave pulse in the preset. No echo means stop and return to 60 dB. frequency_shift_mhz is a signed integer offset from the resonator-selected frequency, default 0. For a two-frequency experiment, -50 puts the echo 50 MHz below the resonator center while keeping the AWG IF unchanged. Without a resonator scan, the reference is the bridge frequency at the first echo search; repeated searches do not accumulate the offset.

tune.find_echo accepts a relative frequency_shift_mhz, not an absolute frequency. To choose an absolute synthesizer frequency instead of running the resonator scan, place a bridge.set step with frequency_mhz before the echo search. Leave frequency_shift_mhz: 0 to use that frequency unchanged.

tune.maximize_echo holds RV attenuation and pulse length fixed at the values chosen for tune.find_echo, unless you explicitly override them. It scans the π/2 amplitude a with the π amplitude at 2a: first across amplitude_range (default 5–50 %), then on a finer grid around the best point. Both pulses have the same pulse_length, so their rotation angles differ through amplitude. Pulse roles come from pulse_map: {P2: pi2, P3: pi} or are inferred from the preset. The selected amplitudes are followed by a narrower field sweep and an echo confirmation.

Each coarse or fine amplitude scan keeps the FPGA initialized across its points. The fine scan reuses values already measured in the coarse scan at the same video attenuation. Each stage saves the integrated curve and full I/Q trace matrices; the amplitude-trial records identify the matrix files and the zero-based row for each measured pair. Echo selection still uses the integrated magnitude within the echo window.

Field and amplitude scans check received traces once data for all receiver phases are available. Above 200 mV, the scan stops and saves its partial data, VA is increased, and the field and pulse settings of the reported point are remeasured. The field search then restarts; amplitude optimization repeats its coarse, fine and field comparisons, discarding scores from the previous VA setting. The optimized final pulse pair is checked again.

rep_rate optionally sets the repetition rate in Hz for tune.find_echo, from 0.1 to 10000 Hz. Without it, the preset rate is used. tune.maximize_echo inherits the selected rate and accepts an override. Both steps accept rep_rate: auto to use an earlier accepted tune.rep_rate result in the same session. A missing or temperature-invalidated result, or a recommendation outside the preliminary range, fails the step. All four handoff presets carry the resulting rate; the ringing check remains at 500 Hz.

The preliminary example finds an echo first, runs tune.rep_rate on its selected pulse settings, then runs tune.maximize_echo with rep_rate: auto. Keep the same preset for those steps so the scan uses the selected preliminary pulse lengths and amplitudes. It also inherits the selected field and echo window. The example uses mode: quantitative; sensitivity mode permits partial saturation and should not supply a quantitative relaxation experiment without a new rate check.

RV attenuation and pulse length are operator choices; this step searches neither. A maximum at the upper amplitude bound stops with reduce attenuation; a maximum at the lower bound stops with increase attenuation. Change attenuation_db before rerunning. Ringing is checked in the initial ladder unless an earlier pass is declared with done: true. Later pulses must retain the tested IF and stay within the tested DAC amplitudes; their lengths are not limited by the ladder pulse.

Final video attenuation

tune.video_attenuation checks the final target preset after pulse tuning. It preserves pulse lengths and field and zeroes sweep increments for the check. Its single limit_mv defaults to 200 mV and can be lowered. After correcting any excess, it reduces attenuation one step at a time, VA2 first and then VA1, only when the predicted signal remains within the limit. Each change is remeasured. A corrective increase ends the opening attempt. The result records the final VA1/VA2 settings and measured peak.

- tune.video_attenuation:
    preset: tuned/echo_cal.phase_awg
    # adjust_video: false  # skip this check and leave VA unchanged

Fine-tuning handoff and bridge control

tune.save_presets publishes fine_tuning.yaml and four preset copies to publish_dir, which defaults to tuned/ beside the preliminary protocol. It also saves an archive in the preliminary run directory under handoff_NNN/; input presets are preserved.

Preset Purpose
echo.phase_awg Preliminary pulse pair for the first echo-window and receiver-phase measurements.
calibration.phase_awg Swept Rabi pulse at calibration_length, detected with the preliminary pulse pair.
field.phase_awg Echo-detected field sweep with both microwave pulses at calibration_length.
echo_cal.phase_awg Echo preset with both microwave pulses at calibration_length, for the second tuning pass and later experiments.

calibration_length defaults to the preliminary pulse length and is snapped to the AWG grid. It sets the Rabi pulse length in calibration.phase_awg and both echo-pulse lengths in field.phase_awg and echo_cal.phase_awg, so it also sets the pulse length used by a later experiment based on those presets. The initial amplitudes in the field and calibrated-echo presets are placeholders until fine calibration replaces them.

To change the exported pulse length, set calibration_length and rerun the preliminary protocol. Editing the Rabi pulse length in tuned/calibration.phase_awg alone leaves the exported field and echo-pulse lengths unchanged. Fine calibration then transfers the measured amplitudes to those lengths using the ratio of the calibration pulse length to the target pulse length.

The generated protocol restores RV, the selected synthesizer frequency and known VA settings with bridge.set, measures the echo window and receiver phase on echo.phase_awg, and runs amplitude calibration. It then calls tune.apply_calibration for both field.phase_awg and echo_cal.phase_awg before the EDFS. The field sweep covers the original tune.find_echo span, recentered on the tuned field, with 200 points by default; field_span and field_points in tune.save_presets override these settings. It does not use the narrower maximization span.

After the EDFS, the protocol repeats the echo-window and phase measurements on echo_cal.phase_awg, then repeats amplitude calibration. A final tune.apply_calibration writes the resulting pulse amplitudes, zero-order phase, echo window and field into echo_cal.phase_awg. The handoff then runs tune.video_attenuation on this final sequence, inheriting the preliminary choice to enable or skip VA adjustment. A later experiment can use this file with window: preset and apply_cal: none, without calibration results from the earlier session. The preset does not store RV, synthesizer frequency or VA; retain the final settings or restore them with bridge.set. Its video1_db and video2_db values must be on the 2 dB and 0.5 dB hardware grids respectively.

Open the MW bridge control window before a live run and allow its vane homing to finish. It may stay open: manual bridge commands are blocked while the runner owns the bridge, and the window resynchronizes when control is released. The ringing step explicitly homes RV unless done: true is set. A later bridge.set waits for any recorded move to finish and uses the recorded position; it homes again when a stale runner lock indicates an interrupted run or when no vane position has been recorded.

epr-auto validate protocols/preliminary_tuning.yaml
epr-auto run protocols/preliminary_tuning.yaml --test

The dry-run completes all six stages with canned acquisitions, automatically continues checkpoints, and creates no acquisition or handoff files. A live supervised run uses launcher dialogs or a real terminal for checkpoints, with the Atomize GUI open for plotting. GUI dry runs keep the checkpoint dialogs. Full parameter details are in the step reference.

Coarse power stage — tune.power_for_length

- tune.power_for_length:
    target_length: 32 ns
    amplitude: 95
    checkpoint: true

The microwave power is set mechanically, by a RV in the bridge that attenuates the drive by a settable number of decibels. The step's job is to find the vane position at which a π pulse lands at the requested length while the AWG amplitude is held at a fixed, comfortable value (95 % of full scale in the example), leaving headroom for the fine calibration to work in.

It measures the current π length with a length nutation at the held amplitude, then moves the vane and repeats. The move size comes straight from the physics: the B₁ field the spins see scales as 10^(-dB/20) with vane attenuation, and a π rotation needs a fixed B₁·τ product, so to bring a measured π length to the target the attenuation is stepped by

dB += 20 * log10(target_length / measured_length)

which would hit the target in one move if B₁ were exactly this ideal function of attenuation; a few iterations (default four) absorb the residual nonlinearity of the real vane.

Two mechanical details matter. The vane is always driven to its target from above — if the new setting is a larger attenuation, the step first overshoots to a higher attenuation and comes back down — so that every approach compresses the same gear backlash the same way and the position is repeatable. And after commanding a move the step waits for the calibrated mechanical travel and settling before the next acquisition.

Because a vane move changes B₁ for everything, it invalidates the receiver phase and the fine amplitude calibration — see Calibration flow and invalidation below. This is why the coarse stage runs once, near the top of a protocol, and why the example marks it checkpoint: true: a vane move is the kind of action worth confirming before it proceeds.

Each iteration is hard-gated on its own nutation's echo_snr: if the length nutation is in the noise the step aborts rather than commanding a vane move from an argmin of noise, and the final iteration's calibration judges ride along into the step's judge list and the manifest. If the step runs out of iterations without hitting the target it does not move the vane past the last measurement — the reported attenuation and π length always describe the state the vane is actually left in.

Because a cold start has no echo to nutate on until the magnet is on the line, epr-auto validate warns when a tune.power_for_length (like tune.auto_phase and tune.pi_calibration) appears before any field.* step; the warning is informational — tuning at a manually pre-set field is legitimate.

Signal search — field.edfs with range: auto

- field.edfs:
    range: auto
    pick: max
    checkpoint: true

With power set, the run finds the resonance line by an echo-detected field sweep. range: auto turns the step into a blind signal search: it predicts the centre field from the resonance condition at the true observation frequency — the synthesizer/LO readout minus the preset's DETECTION-pulse intermediate frequency (this bridge upconverts lower-sideband, so the spins see ν = ν_LO − ν_IF):

B[G] = 0.714477 * ν[MHz] / g   (+ offset)

then sweeps ± a span (250 G by default) around it; the log shows the arithmetic (LO 9750 MHz - IF 50 MHz = ...). The magnet on this endstation is not absolutely calibrated, so a known setup shift is passed as offset and folded into the predicted centre — offset is the magnet-calibration shift only, the IF contribution is already accounted for. The step reports back the measured line-minus-centre distance as shift_g, which is exactly the value to feed in as offset next time. pick: max takes the working field at the magnitude maximum of the sweep and sets the magnet there.

The search escalates once. If the echo-SNR judge fails — no line above the noise floor in the first span — the span doubles and the sweep re-runs a single time. If it fails again the step stops without moving the magnet to a noise maximum and surfaces a diagnosis naming what to check:

  • "weak line found" — something did clear the weak-line threshold near a named field, so the advice is to add scans or narrow the range around it;
  • "flat everywhere" — nothing anywhere in the range, so the advice points at the resonator tuning, temperature, g-factor guess and sample position.

An explicit range: [start, end] skips the prediction and sweeps the given window; pick: value names a field independent of the echo, in which case a weak sweep neither escalates nor blocks — the magnet is still parked at the requested field.

Echo window — tune.echo_window

- tune.echo_window

The integration window is the time gate over which the echo is summed into a single complex point. tune.echo_window sets it from an averaged echo trace: it finds the centre at the maximum of the smoothed magnitude |V(t)|, measures the echo's full width at half maximum, and opens a window of FWHM × factor (factor 2 by default) around the centre, rounding the edges outward onto the ADC grid and clamping them to the acquisition window. The magnitude is smoothed with a fixed physical box (~3 ns), not a fraction of the trace length, so measuring the FWHM does not inflate the width of a short, sharp echo.

search_from excludes early trace artifacts from the echo search; min_width rejects peaks too narrow to be an echo. Set these for the expected echo position and width. A rejected narrow peak is masked out and the search continues.

The window is stored relative to the DETECTION pulse start, not as an absolute time. That is the same frame the preset's own "Window left/right" live in, so the measured window transfers to any later preset whose echo sits at a different absolute delay — a T₂ decay and a T₁ recovery built from the same detection block reuse one window without re-measuring it.

The judge checks that the echo is fully resolved inside the trace: an FWHM edge sitting on the trace boundary means the acquisition window is cutting the echo, and the result is flagged.

This step must run before tune.auto_phase, because auto-phase integrates the signal over exactly this window to measure the residual phase. A phase measured over a wrong or unset window is meaningless, which is why the chain fixes the window first.

Receiver phase — tune.auto_phase

- tune.auto_phase

The digitizer demodulates the echo against a reference whose zero-order phase offset must be tuned so the signal lands on the real axis. tune.auto_phase acquires a short echo run, integrates it over the echo window, and measures the residual signal phase by the principal-axis method (auto_phase_zero in the FFT module — a sign-blind Σ S² estimate that phases a bipolar trace correctly). The demodulator rotates by exp(-i·zero_order), so the correction is a direction:

zero_order_new = zero_order_used - phi_residual

The step acquires 16 sweep points by default. The phase_coherence judge that gates it has an N-aware pass floor, max(0.7, 3/√n), so a trace of fewer than about ten points can never clear the noise floor — 16 gives the gate something to work with while staying a quick acquisition.

The updated zero-order is stored and flows into every later acquisition's build. Auto-phase also records the temperature it was measured at, which is what lets a later temperature move decide whether the phase is still valid.

Fine calibration — tune.pi_calibration

- tune.pi_calibration:
    mode: amplitude
    retries: 1

The coarse stage put a π near the target length at a fixed amplitude; the fine stage pins the pulse amplitudes precisely. The nutation presets are inversion-detection sequences — the pulse being swept precedes a fixed selective echo pair, so the echo amplitude goes as cos(θ) of the swept pulse, with π at the first minimum and π/2 at the zero crossing.

The rotation angle is fitted as a quadratic in the swept quantity,

theta(x) = b*x + s*x^2

The quadratic term absorbs amplifier compression — the rotation slows slightly as the amplitude climbs — so amp(π) and amp(π/2) come out independently from the fit rather than forcing the ideal amp(π) = 2·amp(π/2). (Because compression only ever slows the rotation, the fitted chirp is bounded to a physical range; without that bound the fit has a spurious accelerating-chirp basin that wrecks the π/2 root.) The π extremum is de-biased against the fit's envelope/decay degeneracy with a model-free parabola vertex at the observed minimum, and π/2 is then re-fitted with the angle map anchored at that measured π, which makes the π/2 root as reliable as π itself.

mode: amplitude sweeps the AWG amplitude in percent at fixed length — the usual fine calibration; mode: length runs a linear-time length nutation and is what the coarse stage uses internally. After an amplitude calibration the soft detection pair (the selective refocusing/detection pulses, recognised as the active pulses at ≥ 2.5× the swept pulse's length) is re-scaled onto the calibrated value by the flip-angle relation — the on-resonance rotation goes as amp · L · ⟨envelope⟩, so the amplitude scales by the calibrated L·⟨envelope⟩ over the target slot's, the AWG amplitude being nearly linear. The envelope area factor ⟨envelope⟩ is 1 for a SINE pulse and the mean of the peak-referenced envelope for a shaped one (0.543 for the shipped GAUSS); a transfer across envelope shapes for which no area factor exists — the adiabatic WURST/SECH — is refused rather than mis-scaled. refine: true re-runs the nutation once with the re-scaled pair patched in, as insurance on a new sample.

If the fitted π amplitude runs into the rails — π unreachable within the sweep, or above the amplitude ceiling — the step fails on the amplitude_rails judge; an out-of-sweep π/2 rails the step the same way, so a π/2 outside the swept range is never shipped. A mode: length calibration is rail-checked identically by a length_rails judge. The retries: 1 in the example grants one plain re-attempt; on a rail failure specifically, the runner additionally re-runs the coarse power stage declared earlier in the protocol (and the auto-phase steps tuned after it, which the vane move invalidates) once and retries the calibration, so a sample that needs more power can recover without operator intervention. This fallback exists only when the protocol itself declared a tune.power_for_length step earlier; with no coarse stage to fall back to, the rail failure is reported as-is.

Repetition rate — tune.rep_rate

tune.rep_rate keeps the FPGA open at a fixed field and fixed τ while scanning a logarithmic grid of rates, slowest first. Each ordinary, nonempty curve returned by digitizer_get_curve(live_mode=1) provides a complex echo integral in the selected integration window. Each curve holds one complete phase cycle, which may span several ADC buffers when the cycle is longer than one buffer. The result is consumed as returned, including when its underlying buffers contain old or mixed-rate packets; no packet tags or epoch filtering are used. The card continues running between rates; there is no separate warmup train or one-second delay.

By default, three consecutive curves qualify when (max |sig| − min |sig|) / mean |sig| ≤ 0.05. Their mean complex signal supplies the amplitude at that rate, and the acquisition advances to the next rate. points sets the number of consecutive curves (default and minimum 3); it no longer sweeps τ. scans requests disjoint stable groups (default 1), with the combined groups also required to agree within 5%.

The scanned tune.rep_rate grid has a 10 Hz lower bound and a 10 Hz default rate_min. This floor applies only to the tuning grid; ordinary acquisition rates and fitted recommendations may still use the hardware's 0.1 Hz lower bound, subject to sequence timing. During tuning the Worker pins the ADC stream buffer to 512 KB before opening the card, regardless of the ADC window or rate, then restores the previous value after the card closes, including on Stop or failure. This tuning-only allocation does not add live-rate-specific changes to Insys_FPGA. max_wait: 120 s limits the wait at each rate, including buffer arrival; low rates or greater averaging may need a longer limit. A timeout fails the step without a new recommendation. The numbered *_rep_rate_live.csv file retains observations and their validity flags, including partial history on failure or Stop. The completed amplitude curve is saved separately as *_rep_rate_curve.csv.

The step applies automatic fine pulse calibration when available and otherwise preserves the selected preliminary pulse values; field, echo window and phase are inherited. Nd:YAG has a fixed 9.9 Hz repetition rate, so variable-rate tuning is rejected. It fits the saturation of the sequence repeated every period T = 1/rate:

A(T) = A0 * (1 - exp(-T / T1_eff))

The fitted T1_eff is the recovery time the sequence actually sees. From it the step recommends a working rate in one of two modes:

  • quantitative (default): period = factor × T1_eff, with factor 5 giving under 1 % residual saturation — the right choice when the saturation itself would bias a relaxation measurement;
  • sensitivity: period = 1.26 × T1_eff, the A(T)/√T optimum that maximises echo per unit √time. It accepts partial saturation and is fine for tuning and EDFS but not for quantitative relaxation runs.

A recommendation never exceeds the fastest tested rate, including after rounding. If the fitted recovery period requires less than the supported minimum of 0.1 Hz, the step fails instead of shortening that period. A non-flat curve must pass the saturation fit quality check (adjusted R² at least 0.9); a poor fit fails the hard rep_rate_fit check and stores no new recommendation.

Two edge cases are handled explicitly. If the amplitude spread is below 5% across the grid, the step reports the fastest tested rate with no resolved T1_eff. This tolerance does not establish the quantitative mode’s nominal under-1% saturation bound. If the fitted residual saturation at the slowest rate is 15% or more, the grid is insufficient and no recommendation is stored; when rate_min is above 10 Hz, extend it toward the 10 Hz floor, while a grid already starting at 10 Hz reports that the recovery is not covered at the tuning limit. Accepted recommendations are available through rep_rate: auto on tune.find_echo, tune.maximize_echo, exp.t1 and exp.t2.

The recovery model still assumes the sequence sufficiently resets longitudinal magnetization. A 5% stability result is a live-buffer steadiness check, not proof of quantitative 1% saturation accuracy. A flat curve selects the fastest tested rate under the same 5% tolerance; existing fit and coverage gates still apply. Check stability against a hand-measured recovery curve and with increased averaging before relying on the recommendation for quantitative work, especially after pulse calibration changes. Offline checks cover convergence, rate transitions, partial history and cleanup; validation on the spectrometer remains pending.

The step refuses to recommend a rate off something that is not an echo: all acquisitions concatenated must share one phase (the same resultant-length gate auto-phase uses), and on a strongly saturating curve the amplitude deviations must additionally lie on that phase axis — a constant instrumental phasor (LO leakage surviving the phase cycle) can fake the shared phase but not the structure, and is rejected with a note naming it. The live step requires exactly two active microwave pulses after DETECTION and clears their sweep increments, so Log Time or Amplitude labels are not themselves a hard rejection; a wrong pulse count fails with tune.rep_rate needs a two-pulse echo preset at fixed tau.

The --test dry-run pre-flights every rate of the grid, so a rate_max whose period is shorter than the sequence fits in fails the dry-run rather than aborting a live run.

Calibration flow and invalidation

Every tuning step writes its result into the session state, and every later build reads it back (Presets lists which preset fields each override replaces). This is how a value measured once propagates without being repeated:

Stored by Flows into
tune.echo_window the integration window of every later acquisition
tune.auto_phase the demodulator zero-order of every later acquisition
field.edfs / field.set the working field of every later acquisition
tune.rep_rate tune.find_echo, tune.maximize_echo and exp.* steps that set rep_rate: auto
tune.pi_calibration exp.* steps via apply_cal

The pulse calibration reaches the experiment presets through the apply_cal parameter on the exp.* steps. By default the runner infers which pulses to patch from the preset itself: among the hard microwave pulses it reads the two amplitude levels the preset already carries and maps the higher to π and the lower to π/2 (or, for an equal-amplitude preset, the 2×-longer pulse to π). When that is ambiguous you give an explicit map, e.g. apply_cal: {P2: pi2, P3: pi}; apply_cal: none opts a step out of patching entirely and runs the preset-stored amplitudes. Patched amplitudes are transferred to pulses of a different length — or a different envelope shape — by the same flip-angle relation the detection pair uses (the amplitude scales as the calibrated L·⟨envelope⟩ over the target slot's), so a calibration measured at one pulse length applies correctly at another; a transfer across envelope shapes with no area factor (adiabatic WURST/SECH) is refused. A calibration that itself railed is rejected here rather than shipped.

Calibrations do not survive changes that physically invalidate them. The rules are grounded in measured behaviour of this endstation:

Change Dropped Kept Why
Rotary-vane move auto_phase and pi_calibration rep_rate state The current policy clears phase and pulse calibration but retains the stored rate. Effective recovery depends on the complete sequence; remeasure after changing RV or pulse calibration before relying on the rate for quantitative work.
Temperature setpoint move (beyond rephase_delta) auto_phase and rep_rate pi_calibration Temperature detunes the resonator, so the demod zero-order drifts — a measured temperature series showed a monotonic swing of roughly a degree of phase per kelvin, which sets the small default rephase_delta. And T₁ — the whole basis of the tune.rep_rate recommendation — changes strongly with temperature, often by orders of magnitude across a cryostat range. B₁ is untouched, so the amplitude calibration still holds.
Field move nothing all three The current policy retains these results across field moves. This is a reuse policy, not a guarantee of identical recovery at every field; remeasure when the sample or selected transition requires it.

The temperature rule compares the new setpoint against the temperature each value was actually measured at (auto-phase and rep-rate both record it, and they are checked independently — in a series they may have been tuned at different points); only a move larger than rephase_delta drops a value, so small excursions do not force a needless re-tune. Dropping is not re-measuring: once the stored phase is gone, later acquisitions fall back to the preset's own zero-order — and once the stored rate is gone, a later rep_rate: auto step fails loudly instead of running saturated. A protocol that changes temperature should therefore follow its temp.wait with a fresh tune.auto_phase (and a fresh tune.rep_rate if the experiments use rep_rate: auto) — the invalidation guarantees a stale value is never silently carried, and the fresh step measures the new one.

Running the chain

epr-auto run protocols/tune_up.yaml --test

This exercises the whole chain end to end with no hardware attached: every step pre-flights its exact worker arguments under a test-mode device, the judges and the state flow run, and canned measurements stand in for the acquisitions. It is the pre-flight to run after editing a protocol. The endstation itself is described on the endstation page; the steps used here, with their full parameter tables, are in the step reference.