Declaring Instrument Clocks
Every spectrometer carries a small set of reference clocks — the synthesizer that drives the up- and down-conversion chain, the arbitrary-waveform generator, the digitizer sample clock. Those clocks leave deterministic fingerprints in the spectrum: narrow, non-decaying tones at frequencies set by clock arithmetic (harmonics, mixing products, sample-rate images). They are spurs, not molecular lines, and the Stage 5 spur gate already removes the ones it can recognize. Declaring the instrument’s clocks grounds that recognition in the known clock arithmetic instead of an empirical guess.
A clock declaration adds two pieces of prior knowledge:
A sharper spur prior. The declared clock fundamentals generate a lattice of frequencies where spurs are expected. The spur gate uses the lattice both to know where to look and to lower its burden of proof on a tone that lands exactly where the clocks predict — so it catches instrumental tones an unprimed gate leaves behind. Any fitted line that lands on the lattice is flagged for review.
A self-calibrated frequency axis. A digitizer that is not locked to the laboratory frequency standard runs at a slightly wrong rate, which stretches the frequency axis by a constant fractional factor. That scale error is measurable — prior-free — from the Rb-locked clock spurs themselves, because their true frequencies are known exactly. The
timebaseobject measures it and Stage 6 applies it; the estimator and its precision are the subject of the timebase self-calibration methods note.
Both are additive prior knowledge. With no declaration the pipeline behaves exactly as it does without one (the spur gate falls back to a coarse integer-MHz anchor, and the frequency axis is reported as measured); the results are byte-identical. Declaring the clocks only ever adds information.
The instrument clock tree
The home instrument the example data come from is a useful worked example. Its clocks are:
an up-conversion synthesizer at 5760 MHz (doubled to the 11520 MHz LO, multiplied to the excitation band after a lower-sideband mix);
a down-conversion synthesizer at 5120 MHz (multiplied to the 40960 MHz probe LO);
an arbitrary-waveform generator clocked at 16000 MHz;
a digitizer sampling at 50 GSa/s (eight interleaved 6.25 GSa/s ADCs).
The first three are referenced to a 10 MHz rubidium standard; the digitizer is free-running. Every cross-fixture recurring spur in the example data is a direct identity of one of these clocks — in one of two frames:
the baseband frame, the frequency a tone enters the digitizer at, where the molecular frequency maps as \(f_\text{bb} = |f_\text{mol} - f_\text{probe}|\) (a synthesizer reference at 5120 MHz, a mixing product at 2×5120, the AWG half-rate at 8000 MHz, an ADC image at 2×6250);
the molecular (RF) frame, harmonics radiating directly into the receiver (5×5760, 6×5760).
All of the Rb-locked tones fall on a single comb — the intermodulation lattice of the locked fundamentals, whose spacing is their greatest common divisor. The free-running digitizer is the one source that drifts: its tones wander relative to the Rb-locked comb from one acquisition to the next.
This is the structure a declaration makes available to the pipeline.
Declaring the clocks
A declaration is a short list of clock sources, each a frequency in MHz, a
locked flag (referenced to the laboratory standard or not), and an optional
label. The clocks object manages it:
# Show the current declaration
$ ftmwpipeline clocks show exp.ftmw
# Replace it (token: freq[:locked|free[:label]])
$ ftmwpipeline clocks set exp.ftmw 5760:locked:upconv 5120:locked:downconv \
16000:locked:awg 6250:free:digitizer
# Append, remove by frequency, or clear
$ ftmwpipeline clocks add exp.ftmw 8000:locked:awg-half
$ ftmwpipeline clocks remove exp.ftmw 6250
$ ftmwpipeline clocks clear exp.ftmw
The same operations are available on the functional API and the Pipeline
object:
import ftmwpipeline.api as ftmw
ftmw.set_clock_sources(
"exp.ftmw",
[
{"freq_mhz": 5760, "locked": True, "label": "upconv"},
{"freq_mhz": 5120, "locked": True, "label": "downconv"},
{"freq_mhz": 16000, "locked": True, "label": "awg"},
{"freq_mhz": 6250, "locked": False, "label": "digitizer"},
],
)
ftmw.get_clock_sources("exp.ftmw")
Two conventions matter:
Declare fundamentals, not products. A harmonic or mixing product is generated from the fundamentals, so the lattice produces it for free. Declare the 5760 MHz synthesizer, not the 11520 MHz LO it is doubled to. Frequency division is the exception — a half-rate tone (here the AWG’s 8000 MHz) is not a generated harmonic, so if the instrument produces one it is declared as its own fundamental.
Integer-MHz fundamentals. Rb-locked synthesizers are set to exact frequencies; the lattice is built from the integer greatest common divisor of the declared locked clocks, so declare them as integers.
Where the declaration lives
The clocks verbs write the recommended settings layer — the same layer an
importer populates from instrument metadata. In the resolution order
(Settings and presets), an explicit fit-time clock argument and a
persisted Stage 5 spur declaration both outrank it, so the recommended
declaration is the “declare before you fit” baseline rather than an override. If a
Stage 5 fit already exists when you change the declaration, the change does not
retroactively alter that fit; rerun the fit to apply it. The CLI warns when this
is the case.
Automatic population from Blackchirp
A Blackchirp experiment records its synthesis chain directly. The loader reads
the clock chain and the digitizer sample rate at import and writes the
recommended declaration, so for the example data the lattice fundamentals are
already declared before you touch the clocks verbs. The one fact the metadata
does not carry is whether the digitizer is locked, which defaults per the
instrument preset.
The spur lattice prior
From the declared locked clocks the pipeline computes the lattice spacing \(g\) (their greatest common divisor) and predicts spurs wherever \(f_\text{mol}\) or \(f_\text{bb}\) is a multiple of \(g\), in both frames. For the home instrument’s three locked synthesizers (\(\gcd(5120, 5760, 16000) = 640\) MHz) this is a few dozen in-band points — a far tighter prior than “any integer MHz,” which spans tens of thousands of candidates. Because the locked tones are exact, the match tolerance is the measurement tolerance (about one analysis bin), not a clock tolerance.
Declaring a divided clock can tighten the lattice further: the AWG’s half-rate (8000 MHz) is itself a strong tone and, declared as its own fundamental, brings the spacing down to \(\gcd(5120, 5760, 8000) = 320\) MHz.
Each unlocked clock contributes a drifting sub-lattice: multiples of its fundamental, matched within a wider window (the measured drift scale). Membership marks a candidate as expected to drift; it never sharpens a frequency test.
The gate uses the lattice in four ways:
Where to look. With a declaration the gate’s frequency sweep runs over the lattice points (together with the legacy integer-MHz anchors as a union, so off-lattice instrumental tones — confirmed by the decay probe — keep gating).
Burden of proof. On a locked-lattice point the prior odds of a spur are far higher, so the gate relaxes its evidence bar: an on-lattice tone is gated unless the decay probe shows it clearly decays like a molecular line.
The drifting family. A wandering digitizer tone defeats a fixed-frequency decay probe, so an unlocked-lattice match is adjudicated by a drift-tolerant statistic (a late-versus-early band-power ratio) instead. This is what gates the drifting digitizer spur the example data carry.
Line annotation. Any fitted line that lands on the lattice is stamped with the matched identity (for example
640x8 (bb)— the eighth multiple of a 640 MHz lattice, in the baseband frame). The annotation is the conservative half of the feature: it never removes a line automatically. It appears as a marker infit show, as a flagged column in the Stage 6 report line tables, and makes an on-lattice line a one-command review removal.
An annotated line is a candidate instrumental artifact, not a verdict — a real molecular line can coincide with a lattice point. Treat the flag as a prompt to look, especially when the same identity recurs across experiments.
Timebase self-calibration
A free-running digitizer samples at a rate that is slightly off its nominal value,
which stretches the baseband frequency axis by a constant fractional scale
error \(\varepsilon\). That error is measurable, prior-free, from the
Rb-locked clock spurs themselves: their true frequencies are known exactly, so
their measured offsets recover \(\varepsilon\) directly. The timebase
object runs the measurement after import and persists the result; Stage 6 applies the correction and folds its uncertainty into the reported
per-line frequency budget.
# Measure epsilon from the Rb-locked spur lattice and persist it
$ ftmwpipeline timebase run exp.ftmw
# Print the summary and the per-tone diagnostic table
$ ftmwpipeline timebase show exp.ftmw
import ftmwpipeline.api as ftmw
result = ftmw.calibrate_timebase("exp.ftmw")
result.epsilon, result.sigma_epsilon # fractional scale error +/- 1 sigma
timebase show prints the consensus \(\varepsilon\) and its uncertainty, the
lattice spacing, and a per-tone table grouping the tones into those kept in the
fit, those rejected for inconsistency, and the drift controls (unlocked
multiples, never in the fit). The estimator — how each tone’s offset is read from
its phase rather than a fitted lineshape, the precision bound that follows, and how
the correction reaches the line list — is the subject of the timebase
self-calibration methods note.
Interfaces
Everything on this page is read-only with respect to the fit. The two objects
behave identically across the CLI, the functional API, and the Pipeline class:
clocks—show/set/add/remove/clear(get_clock_sources/set_clock_sources/remove_clock_sources/clear_clock_sources).timebase—run/show(calibrate_timebase/load_timebase_calibration).
The clock declaration is persisted with the file and travels with it; the timebase calibration is persisted as its own record and consumed by Stage 6.