Give each atom a different detuning¶
Apply one local waveform with different weights on two atoms. Read the bound values, inspect target-grid adjustments and identify which program actually runs. Complete waveform design first; this local two-atom example uses 120 time steps and 16 shots.
A local detuning contributes −Δ_local(t) Σ_i w_i n_i to the Hamiltonian. With global detuning included, site i sees Δ_global(t) + w_i Δ_local(t) for this single local term. The weights are dimensionless. They scale a detuning, not the probability of choosing a site.
Here Δ_local rises from zero to 0.8 rad/us, with weights 1 on q0 and 0.25 on q1. Predict the two final detunings. Also explain why this diagonal control alone would not excite an atom starting in a computational-basis state; the global Rabi drive supplies state transitions in this experiment.
Keep time and space separate¶
"""Apply a time-dependent local detuning with a static site pattern.
The waveform carries time dependence; ``SitePattern`` carries register-aligned
dimensionless weights. They remain separate typed objects and are validated
against a target that explicitly supports local AHS controls.
"""
from __future__ import annotations
import json
from cascaqit import (
AHSProgram,
AtomRegister,
MockNeutralAtomTarget,
SitePattern,
Waveform,
)
def main() -> None:
"""Bind, validate, discretize, and run one addressed detuning term."""
program = AHSProgram(
AtomRegister.line(count=2, spacing=5.0),
program_id="lesson.experimentalist.detuning",
)
delta = program.parameter("delta", default=0.8, lower_bound=-2.0, upper_bound=2.0)
program.drive(
rabi=Waveform.constant(0.6, duration=0.3),
detuning=Waveform.constant(0.0, duration=0.3),
phase=0.0,
).local_detuning(
waveform=Waveform.linear(0.0, delta, duration=0.3),
pattern=SitePattern.from_mapping({"q0": 1.0, "q1": 0.25}),
).measure()
bound = program.bind({})
target = MockNeutralAtomTarget.local_ahs_v0_1()
validated = bound.validate(target, shots=16)
discretized, report = validated.discretize(target)
result = bound.run(shots=16, seed=303, time_steps=120)
term = discretized.program_ir.hamiltonian.local_detuning_terms[0]
payload = {
"track": "quantum_experimentalist",
"level": "applied",
"lesson": "local_detuning",
"facts": {
"site_ids": list(term.addressing.site_ids),
"weights": list(term.addressing.weights[0]),
"addressing_frame_count": term.addressing.frame_count,
"waveform_values": list(term.waveform.values or ()),
"discretized_paths": [
item.path
for item in report.field_reports
if "local_detuning" in item.path
],
"validation_errors": [
item.code for item in validated.diagnostics if item.severity == "error"
],
"counts_total": sum(result.counts.values()),
},
"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/03_applied_local_detuning_en.py
bind({}) uses the declared default for delta; it is not an instruction to leave the parameter unresolved. SitePattern.from_mapping supplies every filled site. The SDK aligns the weights with register order, so dictionary insertion order does not determine the physical assignment.
{
"boundaries": {
"cloud_execution": false,
"credentials_loaded": false,
"hardware_execution": false,
"network_accessed": false
},
"facts": {
"addressing_frame_count": 1,
"counts_total": 16,
"discretized_paths": [],
"site_ids": [
"q0",
"q1"
],
"validation_errors": [],
"waveform_values": [
0.0,
0.8
],
"weights": [
1.0,
0.25
]
},
"lesson": "local_detuning",
"level": "applied",
"track": "quantum_experimentalist"
}
The final local detunings are 0.8 and 0.2 rad/us. addressing_frame_count = 1 means that the spatial pattern is static even though its waveform changes with time. The default numbers already fit the mock target grid, so discretized_paths is empty.
The script has two branches: it inspects discretized.program_ir, but bound.run(...) executes the original bound program. For these on-grid values the distinction is harmless. If you introduce off-grid values, do not describe its counts as execution of the adjusted program. To run that adjusted program, pass the discretized object to LocalAhsSimulator(target=target).run(discretized, shots=16), as in the Analog guide.
The helper bound.run() selects a local-control-capable mock target when none is supplied. An explicitly supplied conservative target is not silently replaced. Geometry and any selected blockade model still matter when interpreting a two-atom probability distribution.
Change one part of the control¶
- Swap the two weights. Predict the final detuning at each site and inspect the aligned arrays.
- Change the default to
0.8004. Compare the bound waveform withwaveform_valuesanddiscretized_pathsin the output. Which program does the unchanged run line execute? - Remove
q1from the pattern. Then try a weight larger than one. Read the input errors instead of assigning physical meaning to an invalid configuration.
Check your reasoning
Swapping weights gives 0.2 on q0 and 0.8 rad/us on q1. With the 0.8004 default, nearest-grid projection changes the waveform endpoint to 0.8 and records its path, while the original bound waveform remains 0.8004. The unchanged bound.run() still uses that original. A dense SitePattern must cover every filled site and its weights must remain in [0, 1], with at least one nonzero weight. Use a sparse SiteMask when binary selection expresses the intended control better.
A small difference in sixteen sampled counts cannot establish a site-specific response. Inspect probabilities or a specified observable and repeat the comparison with a sampling budget chosen in advance. Continue with local Rabi control for a pulse that directly drives a selected atom. The local-detuning guide covers multiple additive terms and dynamic masks.