Cookbook¶
Practical choices for building models, running calculations and interpreting results. Frequencies are in GHz, times in ns, temperatures in mK and decay rates in 1/ns.
Choose the calculation¶
You need |
Use |
|---|---|
Dressed frequencies or interactions |
|
A spectrum over a parameter grid |
|
A pulse or an idle interval |
|
Related pulse experiments |
|
Steady-state populations or fields |
|
Small-signal S parameters |
|
A driven response with receiver noise |
|
Outcomes from a saved quantum state |
|
Bare parameters that meet dressed targets |
|
A reduced model around driven devices |
|
The guides develop these workflows. The focused studies apply them to specific physical questions.
Keep model inputs and observables distinct¶
from quchip import Capacitive, Chip, DuffingTransmon, Resonator
q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q")
r = Resonator(freq=7.0, levels=4, label="r")
chip = Chip([q, r], [Capacitive(q, r, g=0.05, label="qr")])
bare = q.freq
dressed = chip.freq(q)
readout_if_excited = chip.freq(r, when={q: 1})
Use device objects while building the model; use their labels when retrieving
results or binding parameters. chip.freq() dresses automatically. You do not
need a preceding dress() call.
dispersive_shift(q, r) returns the full conditional resonator pull. Divide by
two for the usual sigma-z coefficient χ. kerr_matrix() collects full pulls
off diagonal and dressed anharmonicities on the diagonal.
Inspect unresolved_hamiltonian() for the declared expression and
hamiltonian() for the expression after basis, frame and approximation
choices. chip.resolve().dropped_terms_summary() lists discarded terms.
Exact() retains the declared Hamiltonian terms; finite local spaces and
component approximations still apply.
Change parameters without rebuilding the example¶
shifted = chip.with_params({"q.freq": 5.1, "qr.g": 0.045})
noisy = chip.with_params({"q.T1": 30_000.0, "q.T2": 40_000.0})
with_params() returns a new model. chip.parameters lists the available
paths, including optional noise fields set to None. Change jointly
constrained values, such as pulse duration and edge length, in the same call.
Activate optional noise before JAX tracing, then vary its numerical values.
thermal_occupation is the bath’s mean occupation, not the qubit’s initial
excited-state probability. set_noise() replaces the complete noise
configuration; use with_params() for a partial change.
For FluxTunableTransmon, sweep flux_bias to move along its calibrated
frequency curve. Set freq and flux_bias together to change the calibration
anchor. to_dict() and from_dict() save and restore model declarations.
Keep the pulse handle for sweeps¶
from quchip import ChargeDrive, Gaussian, QuantumSequence
xy = ChargeDrive(q, label="xy")
chip.wire(xy)
sequence = QuantumSequence(chip)
pulse = sequence.schedule(
xy, envelope=Gaussian(duration=20.0, amplitude=0.02, sigmas=3.0),
freq=chip.freq(q),
)
batch = sequence.simulate_batch(
sequence.zip(
pulse.vary("duration", [20.0, 40.0]),
pulse.vary("amplitude", [0.02, 0.01]),
),
duration=60.0,
)
Separate sweep axes form a Cartesian grid; zip() pairs values. Here the two
pulses have equal nominal area. Use delay() and barrier() for serial timing,
and an explicit start_time for overlap.
A scheduled carrier stays fixed when device parameters change. Use the pulse’s
freq parameter to retune it. For independently built experiments, pass their
build_problem() results to solve_many().
Choose what to save¶
result = sequence.simulate(duration=60.0, states="all")
population = result.population(q, level=1)
final = result.state_at(result.times[-1])
counts = result.measure(q).sample(1024, seed=7).counts()
duration starts at zero and can extend the pulse schedule. An explicit
tlist sets the actual start and end times: [20, 30] starts evolution at
20 ns. It does not simulate the first 20 ns and discard them.
Use states="final" when only the final state matters. states="none" saves
only requested expectation traces; later state measurements need saved states.
observable_at() can select or interpolate a saved trace. State queries use
saved times and do not interpolate quantum states.
Prepare coupled eigenstates with chip.state(). Use chip.bare_state() for
bare product states. result.population(q, level=1) measures a local isolated
energy-state population; overlap() tests a particular joint state.
Declare network noise¶
from quchip import PortNetwork
network = PortNetwork()
attenuator = network.attenuator("cold", loss_db=20, thermal_occupation=0.01)
twpa = network.amplifier("twpa", gain_db=20, added_noise=0.5)
thermal_occupation is the passive load’s mean thermal population in quanta;
omitting it gives vacuum. added_noise is required, input-referred symmetrized
noise in quanta, at least (1 - 1/G) / 2 for power gain G. Both are constant
across frequency sweeps. The TWPA model describes linear phase-preserving gain.
For existing network declarations, rename occupation or loss_occupation
to thermal_occupation. Temperature, noise figure, and noise_frequency
arguments have been removed; supply noise quanta directly, including in saved
component parameters.
Reuse a measurement¶
With the model and ports from the fridge guide:
from quchip import IQReceiver, VNA
measurement = VNA(readout_chip).measure(
frequencies, amplitudes=20.0, input=drive, outputs=[readout],
)
sample = measurement.sample(1, receiver=IQReceiver(integration_time=1_000_000), seed=7)
quieter = measurement.sample(1, receiver=IQReceiver(integration_time=100_000_000), seed=7)
Changing receiver integration time or shot count reuses the stored spectra.
noise_spectrum(readout, unit="dBm/Hz") reports noise power density;
statistics(receiver=...).covariance(readout) gives integrated IQ covariance.
Field amplitudes at network ports are in sqrt(photons/ns).
For a saved quantum state, result.measure(q1, q2) retains joint outcome
correlations. Add calibrated assignment errors or an IQReadout to model the
detector. No readout pulse is required, but conditional IQ signals must be
supplied; qubit populations alone do not determine them. Reuse a simulation’s
wiring with result.iq_readout(), or use IQReadout.from_wiring() with another
wired model. Do not add apparatus noise twice to an existing calibration.
Check the quantity you report¶
Common ambiguity |
What to check |
|---|---|
Field versus occupation |
|
Jump rate versus output flux |
|
A dressed label near an avoided crossing |
Inspect |
No truncation warning |
Increase local levels and compare the observable; the warning only checks sampled boundary populations. |
A smooth trace |
Refine the output grid separately from the solver tolerances. |
QuTiP is the default. Choose dynamiqs for JAX gradients and compiled batches.
The initial state and declared dissipation select sesolve or mesolve
automatically. Use dissipation=False only for an intentional closed-system
comparison. See Backends and solvers for
integration settings and Gradients and parameter fitting
for checks on derivatives.