Doppler Selectivity Explorer
Interactive tools for understanding velocity-enabled quantum computing
1. Doppler Shift Explorer
How does atom velocity translate into a frequency shift seen by the laser? Drag the sliders to find out.
viewof lambdaChoice = Inputs.radio(
new Map([
["698 nm (Sr clock transition)", 698e-9],
["689 nm (Sr intercombination)", 689e-9]
]),
{ value: 698e-9, label: "Laser wavelength" }
)html`<div style="
background: #f0f4ff;
border-left: 4px solid #3b82f6;
padding: 12px 18px;
border-radius: 6px;
margin: 10px 0;
font-size: 16px;
">
<strong>Doppler shift:</strong>
Δ = v / λ = ${v.toFixed(3)} m/s ÷ ${(lambdaChoice * 1e9).toFixed(0)} nm
= <span style="color: #2563eb; font-weight: bold; font-size: 18px;">
${dopplerShift_kHz.toFixed(1)} kHz
</span>
</div>`{
const sigma = 5; // natural linewidth proxy in kHz
const freqs = d3.range(-300, 300, 0.5);
const gaussian = (f, center) =>
Math.exp(-0.5 * ((f - center) / sigma) ** 2);
const data = freqs.flatMap(f => [
{ frequency: f, absorption: gaussian(f, 0), group: "Stationary atoms" },
{ frequency: f, absorption: gaussian(f, dopplerShift_kHz), group: "Moving atoms" }
]);
return Plot.plot({
title: "Absorption Spectrum: Stationary vs Moving Atoms",
width,
height: 340,
marginLeft: 60,
x: { label: "Detuning from resonance (kHz)", domain: [-300, 300] },
y: { label: "Absorption (arb. units)", domain: [0, 1.15] },
color: {
domain: ["Stationary atoms", "Moving atoms"],
range: ["#f97316", "#3b82f6"]
},
marks: [
Plot.ruleY([0]),
Plot.ruleX([0], { stroke: "#ccc", strokeDasharray: "4,4" }),
Plot.line(data, {
x: "frequency",
y: "absorption",
stroke: "group",
strokeWidth: 2.5
}),
Plot.ruleX([dopplerShift_kHz], {
stroke: "#3b82f6",
strokeDasharray: "2,4",
strokeOpacity: 0.5
}),
Plot.text([{ x: dopplerShift_kHz, y: 1.08 }], {
x: "x",
y: "y",
text: d => `${dopplerShift_kHz.toFixed(1)} kHz`,
fill: "#3b82f6",
fontSize: 12,
fontWeight: "bold"
}),
Plot.tip(
[`Peak separation = ${dopplerShift_kHz.toFixed(1)} kHz\nLinewidth ~ ${sigma} kHz`],
{ x: -200, y: 1.0, anchor: "left", fontSize: 12 }
)
]
});
}
Tip
Try this: Increase the velocity until the two peaks no longer overlap. At what velocity does the Doppler shift exceed the linewidth (~5 kHz)? This is the regime where velocity-based addressing becomes possible.
2. Velocity Selectivity: Infidelity on Stationary Atoms
The central result: when a laser drives a \(\pi\)-pulse on moving atoms (resonant), how much does it accidentally affect stationary atoms (off-resonant)? The infidelity depends on the distance \(d\) the atom travels during the pulse.
\[ \text{infidelity} = \frac{\pi^2}{4}\,\operatorname{sinc}^2\!\left(\frac{\pi}{2}\sqrt{1 + \left(\frac{2d}{\lambda_0}\right)^2}\right) \]
viewof lambda0_nm = Inputs.radio(
new Map([
["698 nm (Sr clock)", 698],
["556 nm (Yb)", 556],
["780 nm (Rb Raman)", 780]
]),
{ value: 698, label: "Wavelength λ₀" }
){
const lambda0_um = lambda0_nm / 1000; // convert nm to μm
const sincFn = x => x === 0 ? 1 : Math.sin(x) / x;
const infidelityFn = d => {
const ratio = 2 * d / lambda0_um;
const arg = (Math.PI / 2) * Math.sqrt(1 + ratio * ratio);
return (Math.PI * Math.PI / 4) * sincFn(arg) ** 2;
};
// Sample points densely to capture zero crossings
const dVals = d3.range(0.001, 5.001, 0.002);
const data = dVals.map(d => ({
d,
infidelity: Math.max(infidelityFn(d), 1e-12)
}));
// Find approximate zero-crossing points (local minima below 1e-6)
const zeroCrossings = [];
for (let i = 1; i < data.length - 1; i++) {
if (
data[i].infidelity < data[i - 1].infidelity &&
data[i].infidelity < data[i + 1].infidelity &&
data[i].infidelity < 1e-4
) {
zeroCrossings.push({ d: data[i].d, infidelity: data[i].infidelity });
}
}
// Current infidelity
const currentInfidelity = infidelityFn(d_um);
const currentPct = currentInfidelity * 100;
const quality =
currentInfidelity < 0.001
? "excellent"
: currentInfidelity < 0.01
? "good"
: "poor";
const qualityColor =
quality === "excellent"
? "#16a34a"
: quality === "good"
? "#ca8a04"
: "#dc2626";
const plot = Plot.plot({
title: `Infidelity on Stationary Atoms (λ₀ = ${lambda0_nm} nm)`,
width,
height: 400,
marginLeft: 70,
marginBottom: 50,
x: { label: "Distance traveled d (μm)", domain: [0, 5] },
y: {
label: "Infidelity",
type: "log",
domain: [1e-8, 1],
tickFormat: ".0e"
},
marks: [
// Reference lines
Plot.ruleY([0.001], {
stroke: "#ef4444",
strokeDasharray: "8,4",
strokeWidth: 1
}),
Plot.ruleY([0.01], {
stroke: "#f59e0b",
strokeDasharray: "8,4",
strokeWidth: 1
}),
Plot.text([{ y: 0.001, x: 4.8 }], {
x: "x",
y: "y",
text: d => "0.1% threshold",
fill: "#ef4444",
dy: -10,
fontSize: 11
}),
Plot.text([{ y: 0.01, x: 4.8 }], {
x: "x",
y: "y",
text: d => "1% threshold",
fill: "#f59e0b",
dy: -10,
fontSize: 11
}),
// Main curve
Plot.line(data, {
x: "d",
y: "infidelity",
stroke: "#6366f1",
strokeWidth: 2.5
}),
// Zero-crossing markers
Plot.dot(zeroCrossings, {
x: "d",
y: d => 1e-8,
fill: "#16a34a",
r: 5,
symbol: "diamond"
}),
Plot.tickX(zeroCrossings, {
x: "d",
stroke: "#16a34a",
strokeDasharray: "2,4",
strokeOpacity: 0.4,
y1: 1e-8,
y2: 1
}),
// Current position marker
Plot.ruleX([d_um], {
stroke: "#6366f1",
strokeWidth: 2,
strokeDasharray: "4,3"
}),
Plot.dot([{ d: d_um, infidelity: Math.max(currentInfidelity, 1e-8) }], {
x: "d",
y: "infidelity",
fill: "#6366f1",
r: 7,
stroke: "white",
strokeWidth: 2
})
]
});
const status = html`<div style="
background: ${quality === "excellent" ? "#f0fdf4" : quality === "good" ? "#fefce8" : "#fef2f2"};
border-left: 4px solid ${qualityColor};
padding: 12px 18px;
border-radius: 6px;
margin: 10px 0;
font-size: 16px;
">
At <strong>d = ${d_um.toFixed(2)} μm</strong>:
infidelity = <span style="color: ${qualityColor}; font-weight: bold;">
${currentPct < 0.001 ? currentInfidelity.toExponential(2) : currentPct.toFixed(4) + "%"}
</span>
— selectivity is
<span style="
color: ${qualityColor};
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.5px;
">${quality}</span>
</div>`;
return html`${plot}${status}`;
}
Note
Green diamonds mark zero-crossing distances where infidelity vanishes exactly – these correspond to complete \(2\pi\) rotations on the stationary atoms. Operating near these sweet spots gives the best selectivity.
3. Rabi Oscillations Simulator
Watch how resonant (moving) and detuned (stationary) atoms evolve differently under a driving laser field.
{
const omega = omega_kHz; // in kHz
const delta = delta_kHz; // in kHz
const omega_eff = Math.sqrt(omega ** 2 + delta ** 2);
const piTime = 1 / omega; // in ms (since omega is in kHz = 1/ms)
const tMax = Math.max(4 * piTime, 0.2); // show at least 4 pi-times
const nPoints = 800;
const times = d3.range(0, tMax, tMax / nPoints);
const data = times.flatMap(t => {
// Resonant: moving atoms (Δ = 0)
const pResonant = Math.sin(Math.PI * omega * t / 2) ** 2;
// Detuned: stationary atoms
const amplitude = (omega / omega_eff) ** 2;
const pDetuned = amplitude * Math.sin(Math.PI * omega_eff * t / 2) ** 2;
return [
{ time: t, population: pResonant, group: "Moving atoms (resonant)" },
{ time: t, population: pDetuned, group: "Stationary atoms (detuned)" }
];
});
// Values at pi-time
const pMovingAtPi = Math.sin(Math.PI * omega * piTime / 2) ** 2;
const ampStationary = (omega / omega_eff) ** 2;
const pStationaryAtPi =
ampStationary * Math.sin(Math.PI * omega_eff * piTime / 2) ** 2;
const plot = Plot.plot({
title: "Excited-State Population vs Time",
width,
height: 400,
marginLeft: 60,
marginBottom: 50,
x: { label: "Time (ms)", domain: [0, tMax] },
y: { label: "Excited-state population", domain: [0, 1.1] },
color: {
domain: ["Moving atoms (resonant)", "Stationary atoms (detuned)"],
range: ["#3b82f6", "#f97316"]
},
marks: [
Plot.ruleY([0]),
Plot.ruleY([1], { stroke: "#e5e7eb" }),
// Rabi oscillation curves
Plot.line(data, {
x: "time",
y: "population",
stroke: "group",
strokeWidth: 2.5
}),
// pi-time vertical line
Plot.ruleX([piTime], {
stroke: "#6b7280",
strokeWidth: 1.5,
strokeDasharray: "6,4"
}),
Plot.text([{ x: piTime, y: 1.07 }], {
x: "x",
y: "y",
text: d => `π-time = ${(piTime * 1000).toFixed(1)} μs`,
fill: "#6b7280",
fontSize: 12,
fontWeight: "bold"
}),
// Markers at pi-time
Plot.dot(
[
{ time: piTime, population: pMovingAtPi },
{ time: piTime, population: pStationaryAtPi }
],
{
x: "time",
y: "population",
fill: (d, i) => (i === 0 ? "#3b82f6" : "#f97316"),
r: 7,
stroke: "white",
strokeWidth: 2
}
)
]
});
const info = html`<div style="
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 10px 0;
">
<div style="
background: #eff6ff;
border-left: 4px solid #3b82f6;
padding: 12px 16px;
border-radius: 6px;
">
<strong style="color: #3b82f6;">Moving atoms</strong> at π-time:<br>
Excitation = <strong>${(pMovingAtPi * 100).toFixed(1)}%</strong>
</div>
<div style="
background: #fff7ed;
border-left: 4px solid #f97316;
padding: 12px 16px;
border-radius: 6px;
">
<strong style="color: #f97316;">Stationary atoms</strong> at π-time:<br>
Excitation = <strong>${(pStationaryAtPi * 100).toFixed(2)}%</strong>
</div>
</div>`;
const effInfo = html`<div style="
background: #f8fafc;
padding: 10px 16px;
border-radius: 6px;
font-size: 14px;
color: #475569;
margin-top: 4px;
">
Ω<sub>eff</sub> = √(Ω² + Δ²) = ${omega_eff.toFixed(1)} kHz
 | 
Amplitude suppression: (Ω/Ω<sub>eff</sub>)² = ${(ampStationary * 100).toFixed(2)}%
 | 
Δ/Ω = ${(delta / omega).toFixed(2)}
</div>`;
return html`${plot}${info}${effInfo}`;
}
Tip
Key insight: Set the detuning to zero and watch both curves overlap – no selectivity. Now increase the detuning. The orange (stationary) curve shrinks in amplitude and oscillates faster. At the \(\pi\)-time, the moving atoms are fully excited while the stationary atoms barely budge. That is velocity-based quantum gate selectivity.