Drive two atoms with one waveform¶
Prepare two atoms in their ground state and apply the same laser control to both. This lesson connects atom positions, drive waveforms and excitation probabilities. You will also check which physical model the simulator actually runs.
Complete installation, neutral-atom basics and physical units. Run the script from the repository root. The experiment uses two atoms, eight time steps and 32 shots on a local CPU.
Specify the model before predicting the result¶
AtomRegister.line(count=2, spacing=6.0) places atoms at (0, 0) and (6, 0) in micrometres. The register lives in a two-dimensional coordinate system even though the atoms lie on a line. 0 denotes the ground state and 1 the Rydberg state.
The drive lasts 0.2 us. Its Rabi angular frequency rises linearly from 0 to 1 rad/us; detuning stays at -0.2 rad/us, and phase is zero. In units with ℏ = 1, the single-atom Hamiltonian is H(t) = Ω(t) X / 2 − Δ n, where n = |1⟩⟨1|. At zero phase, Rabi drive couples the two states; detuning shifts the excited-state energy.
This program has no finite C6/r^6 interaction. It also explicitly uses SimulationOptions(method="state_vector", blockade_mode="full"), so all four basis states remain available. These settings define a useful control experiment with two independent atoms. The positions still matter to target validation.
The pulse area is (0 + 1) × 0.2 / 2 = 0.1 rad. Ignoring the small detuning for an estimate, one atom has excitation probability sin²(0.1 / 2) ≈ 0.0025. Predict whether 32 shots will usually contain a double excitation before looking at the output.
Run the experiment¶
"""Run a first global Analog Hamiltonian Simulation experiment.
The lesson connects an atom register, Rabi/detuning/phase controls, terminal
measurement, validation, and local execution. Units follow CASCAQit's public
Analog convention: time in microseconds and angular frequencies in rad/us.
"""
from __future__ import annotations
import json
from cascaqit import (
AHSProgram,
AtomRegister,
LocalBackend,
MockNeutralAtomTarget,
SimulationOptions,
Waveform,
)
def main() -> None:
"""Build and sample a two-site global-drive program."""
# Register order defines the logical order used by controls and measurements.
register = AtomRegister.line(count=2, spacing=6.0)
program = AHSProgram(register, program_id="lesson.experimentalist.beginner")
# Rabi, detuning, and phase share one explicit duration.
program.drive(
rabi=Waveform.linear(0.0, 1.0, duration=0.2),
detuning=Waveform.constant(-0.2, duration=0.2),
phase=0.0,
).measure()
target = MockNeutralAtomTarget.v0_1()
validated = program.validate(target, shots=32)
options = SimulationOptions(method="state_vector", blockade_mode="full")
result = (
LocalBackend(target=target, analog_time_steps=8)
.run(program, shots=32, seed=301, options=options)
.result()
)
payload = {
"track": "quantum_experimentalist",
"level": "beginner",
"lesson": "global_ahs",
"facts": {
"site_ids": [site.site_id for site in register.sites],
"validation_errors": [
item.code for item in validated.diagnostics if item.severity == "error"
],
"counts_total": sum(result.counts.values()),
"probabilities": result.probabilities,
"finite_interaction_enabled": False,
"blockade_mode": options.blockade_mode,
"bit_order": result.metadata["bitstring_ordering"],
"probability_sum": round(sum((result.probabilities or {}).values()), 12),
"selected_simulator": result.metadata["selected_simulator"],
},
"boundaries": {
"hardware_execution": False,
"cloud_execution": False,
"network_accessed": False,
"credentials_loaded": False,
},
}
print(json.dumps(payload, sort_keys=True))
if __name__ == "__main__":
main()
python3 examples/user/tracks/quantum_experimentalist/01_beginner_global_ahs_en.py
program.validate(target, shots=32) checks the input against a mock target. The same target is passed to the local backend. An empty validation_errors list says that these input checks passed; it does not establish experimental accuracy or hardware readiness.
{
"boundaries": {
"cloud_execution": false,
"credentials_loaded": false,
"hardware_execution": false,
"network_accessed": false
},
"facts": {
"bit_order": {
"bitstring_index": "left_to_right_matches_logical_order",
"convention": "logical_order",
"logical_order": [
"q0",
"q1"
]
},
"blockade_mode": "full",
"counts_total": 32,
"finite_interaction_enabled": false,
"probabilities": {
"00": 0.9950108478690685,
"01": 0.002491456824543835,
"10": 0.0024914568245438325,
"11": 6.238481843843723e-06
},
"probability_sum": 1.0,
"selected_simulator": "ScalableIdealEngine",
"site_ids": [
"q0",
"q1"
],
"validation_errors": []
},
"lesson": "global_ahs",
"level": "beginner",
"track": "quantum_experimentalist"
}
Read the probability values before the samples. 00 is about 0.9950; 01 and 10 are each about 0.00249; 11 is about 0.00000624. The drive treats the atoms identically, so the single-excitation probabilities agree. For this independent-atom model, P(11) = p² and P(01) = P(10) = p(1−p), with p = P(10) + P(11).
bit_order.logical_order is ["q0", "q1"]; read strings from left to right in that order. Add print(result.counts) in a local copy to inspect samples. The total must be 32. Seeing only 00 is plausible: the probability of that event is roughly 0.9950^32 ≈ 0.85. More shots reveal rare events more often but leave the underlying model unchanged.
Why the explicit blockade option matters¶
Without an explicit option, the backend can select a hard-blockade subspace from the target's blockade radius. That approximation removes forbidden basis states. It is different from assigning a large but finite energy to a double excitation. The mock target used here has an 8 um blockade radius, so its automatic choice can exclude 11 for atoms 6 um apart.
Keep blockade_mode="full" while studying the independent-atom baseline. To study finite interactions, pass VanDerWaalsInteraction to AHSProgram and choose C6 and a cutoff explicitly. The Analog guide shows the public interface and units. A coordinate change alone does not add a finite interaction.
Change one condition¶
- With the explicit full-space options intact, change spacing from
6to10 um. Compare probabilities, validation and sampling separately. Should the probability distribution change? - Keep the geometry fixed and replace the ramp by
Waveform.constant(0.5, duration=0.2). It has the same pulse area. First set detuning to zero in both programs, then restore-0.2. Explain why equal pulse area is an exact prediction in the first comparison but not generally in the second. - Increase
analog_time_stepsfrom 8 to 32 and 128. Compare probabilities before changing shots. Which error source are you probing?
Check your reasoning
With no finite interaction and no blockade projection, spacing does not enter this Hamiltonian. Both geometries must still pass validation. At zero detuning and fixed phase, Hamiltonians at different times are proportional to X and commute, so pulse area determines the ideal final state. A nonzero detuning adds a term that does not commute with X; temporal shape can then matter. Increasing time resolution probes numerical integration error, while more shots address sampling fluctuations. The coarse-step result is a teaching starting point, not a general accuracy guarantee.
If validation reports errors, inspect each code and object path before running. Check units, atom spacing and waveform durations. A missing basis state in probabilities also warrants checking the selected state space before interpreting it as a physical suppression effect.
Continue with waveform design and the local-control lessons, or combine this idea with gates in your first D-A-D experiment.