System configuration
Use system_id as the canonical selector for one concrete instrument setup.
A system ties together chip metadata, the backend kind, backend-specific runtime
settings, and the mapping between readout MUXes and physical ports.
chip_id and system_id are user-defined labels. Qubex does not require a
specific naming convention for either of them.
If you manage configuration files yourself, pass both config_dir and
params_dir explicitly. This keeps the file layout under your control and
avoids relying on legacy path conventions.
When you omit them, Qubex resolves the config root in this order:
QUBEX_CONFIG_ROOT~/qubex-config/opt/qubex-config- legacy
/home/shared/qubex-config
If none of these paths exist, Qubex defaults to ~/qubex-config.
Recommended directory layout
Keep shared configuration files in one config directory, and keep system-specific parameter files in separate directories for the systems you want to run.
qubex-config/
config/
chip.yaml
box.yaml
system.yaml
wiring.yaml
external_devices.yaml # when using external instruments
skew.yaml # for QuEL-1/QuBE
params/
SYSTEM_A/
control_frequency.yaml
readout_frequency.yaml
control_amplitude.yaml
readout_amplitude.yaml
measurement_defaults.yaml
capture_delay.yaml
...
calibration/
SYSTEM_A/
calib_note.json
config/stores the shared system configuration files.- Each file under
params/<system_id>/stores one parameter family. calibration/<system_id>/calib_note.jsonis the default calibration file location.external_devices.yamlis optional and configures instruments outside the QuEL control system, such as a DC voltage source used for JPA bias control.skew.yamlis optional, but it is required for synchronized experiments that use multiple QuEL-1 control units.
Define shared configuration files
chip.yaml
Define chip metadata once per chip.
CHIP_A:
name: "Example chip"
n_qubits: 64
topology:
type: square_lattice
mux_size: 4
box.yaml
Register the hardware units that may appear in the wiring.
BOX_A:
name: "quel3-02-a01"
type: "quel3"
BOX_B:
name: "QuEL-1 #5-01"
type: "quel1-a"
address: "10.1.0.73"
adapter: "500202A50TAAA"
BOX_C:
name: "QuEL-1 SE R8 #1"
type: "quel1se-riken8"
address: "10.1.0.160"
adapter: "500202A800RAA"
options:
- "se8_mxfe1_awg2222"
For QuEL-3 entries, address and adapter are optional. For QuBE and QuEL-1
entries, they are required.
options is optional and accepts a list of backend option labels for that box.
Use it when a box needs a non-default hardware profile.
For example, quel1se-riken8 accepts an AWG options label such as
se8_mxfe1_awg1331, se8_mxfe1_awg2222, or se8_mxfe1_awg3113. Here, the last
four digits refer to the number of FNCOs for each control port defined during
the link-up procedure. When no AWG options label is specified, Qubex uses
se8_mxfe1_awg2222.
external_devices.yaml
The file mirrors the main system configuration: devices declares each
external instrument like box.yaml, wiring connects muxes to device
outputs like wiring.yaml, and settings holds the control policy.
devices:
ONS1:
driver: ons61797
channels: [1, 2]
params:
port: /dev/ttyACM0
wiring:
- mux: 6
bias: ONS1-1
- mux: 7
bias: ONS1-2
settings:
ramp:
rate_v_per_s: 0.1
step_size_v: 0.01
wait_s: 0.1
readback:
tolerance_v: 0.001
max_attempts: 3
reset_voltage: 0.0
overrides:
- mux: 7
ramp:
rate_v_per_s: 0.05
devices—driverselects the adapter andchannelslists the device outputs. Everything underparamsis driver-specific and validated by the selected driver (ONS61797:portfor serial orip_addressfor network, not both). Qubex limits ONS61797 writes to 0 V through 4 V and channels 1 through 16, and requires its independent output mode (OMD 0); the Qblox driver accepts -4 V through 4 V.wiring— each entry names a role (bias) and references an output asDEVICE-CHANNEL(ONS1-2= channel 2 of deviceONS1, one-based). Only wired outputs can be driven: an unwired mux or an unlisted channel raises an error instead of guessing.settings— the body sets the defaults for every wired mux andoverridesadjusts them per mux. All outputs of onesettings(rolebiasby default, changeable withrole) must be on the same device.
The idle voltage — where each output remains while DC bias is not in use — is a
calibrated per-mux value: idle_voltage in jpa_params.yaml, falling back
to reset_voltage for uncalibrated muxes. A DC voltage context holds one
device connection across its voltage applications, sweeps, and readbacks,
then ramps back to the idle voltage and closes the connection on exit.
The direct ons61797 driver owns the instrument connection exclusively for
that context. Do not open another context or process for a different channel
on the same instrument concurrently. A future server-backed NF driver is the
planned multi-client path.
To control a Qblox SPI Rack through a server process that owns its USB
connection, use the qblox_backend driver. Qubex connects to that server as a
TCP client. Because a single process keeps ownership of the serial device,
this is the recommended configuration when multiple systems use the same
instrument.
devices:
Qblox1:
driver: qblox_backend
channels: [1, 2]
params:
host: "<qblox-backend-host>"
port: <qblox-backend-port>
timeout_s: 1200
wiring:
- mux: 6
bias: Qblox1-1
settings:
ramp:
rate_v_per_s: 0.1
step_size_v: 0.01
wait_s: 0.1
readback:
tolerance_v: 0.001
max_attempts: 3
reset_voltage: 0.0
The server names each output <device name>-<channel>, so name the device
exactly as the backend reports it (Qblox1 → Qblox1-15); for irregular
names use device_names: {channel: name} in params. Ramps run as one
server-side sweep — no other client can interleave setpoints, but other
channels may wait until a sweep finishes. Do not expose the unauthenticated
socket outside a trusted network.
The D5a module has no per-channel output switch and its standard bipolar
span is -4 V to 4 V: idle and shutdown only ramp to their configured
voltages without electrically disconnecting the output, and the reported
voltage is the module's stored setting, not an independent measurement.
The ramp values use the backend sweep's own vocabulary and are passed to
it verbatim: rate_v_per_s sets the overall speed (duration ≈ |dV| / rate),
step_size_v the setpoint spacing, and wait_s the minimum dwell per
setpoint. A setpoint succeeds when its readback error is within
readback.tolerance_v, retried up to readback.max_attempts times.
Qblox-specific rate, step, and wait limits are checked while loading the
configuration, before any channel is changed. Software-generated ONS61797
ramps avoid a readback round trip at each intermediate setpoint and verify
the final target instead.
apply_voltage() ramps an enabled output from its current voltage to the
target; an off output raises instead of being switched on implicitly. DC
voltage operations are scoped to a context; exiting it ramps back to the
idle voltage.
with experiment.external_devices.dc_voltage(mux=6) as dc:
dc.apply_voltage(0.27)
state = dc.state
Applying a voltage requires the output to be on already — nothing switches
it implicitly. Use reset_dc_voltages() to initialize selected outputs at
reset_voltage (default 0 V), then ramp them to idle or an operating point.
For maintenance or a deliberate safe stop, use shutdown_dc_voltages() to
ramp selected outputs back to reset_voltage and switch them off when the
device supports it. Normal experiment contexts return to idle instead.
get_dc_voltage_states() reads every active wired mux on one connection;
reset_dc_voltages() brings muxes to their reset voltages with the outputs
on, bias_dc_voltages() ramps calibrated muxes to their optimal voltages,
idle_dc_voltages() ramps them back to idle, and
shutdown_dc_voltages() switches them off when supported. Like box operations,
these methods take an optional muxes selection (indices or labels; all active
wired muxes when omitted), and all writes prompt for confirmation, like a box
push.
Each bulk write and its resulting readback share one device connection.
An empty selection or a declined confirmation returns {} without opening a
device connection.
To bias every active wired mux with a calibrated optimal_voltage outside a
temporary context, use the bulk operation.
experiment.external_devices.bias_dc_voltages()
sweep() ramps through each supplied target using the same profile.
Control Layout Resolution
configuration_mode is a priority-ordered request, not a fixed channel-count
guarantee.
ge-ef-crresolves channels in the orderge,ef,cr.ge-ef-fhresolves three-channel ports asge,ef,fh; two-channel ports usegeon channel 0 and share channel 1 betweenefandfh.ge-cr-crresolves channels in the orderge,cr,cr.- Other control ports with fewer channels keep only the leftmost roles.
For quel1se-riken8, the AWG profile controls the four profile-dependent
control ports.
se8_mxfe1_awg1331resolves those ports as1-3-3-1. Withconfiguration_mode="ge-ef-cr", the resolved layouts arege,ge-ef-cr,ge-ef-cr,ge.se8_mxfe1_awg2222resolves those ports as2-2-2-2. Withconfiguration_mode="ge-ef-cr", each port resolves toge-ef. Withconfiguration_mode="ge-ef-fh", each port resolves to sharedge-ef/fh. Withconfiguration_mode="ge-cr-cr", each port resolves toge-cr.
system.yaml
Create one entry per runnable setup. Multiple systems may point to the same
chip_id.
SYSTEM_A:
chip_id: CHIP_A
backend: quel1
quel1:
clock_master: 10.0.0.10
- The top-level key is the
system_id. backendselects the backend family for this system.- The backend-specific section uses the same name as
backend.
For a QuEL-1 system that uses skew measurement or clock synchronization, set
the clock master IP address in quel1.clock_master.
Using QuEL-3
To use QuEL-3, set backend to quel3 and define the endpoint connection in
the quel3 section.
SYSTEM_QUEL3:
chip_id: CHIP_A
backend: quel3
quel3:
endpoint: localhost
port: 50051
transport: grpc
QuEL-3 uses native gRPC when transport is omitted or set to grpc.
Connecting to a cloud-hosted QuEL-3
To connect to a QuEL-3 endpoint hosted in the cloud and protected by
Cloudflare Access, set transport to https. Map each secret header to its
mounted secret file, and do not store the header values directly in system.yaml.
SYSTEM_CLOUDFLARE:
chip_id: CHIP_A
backend: quel3
quel3:
endpoint: api.example.com
# port: 443
transport: https
http_transport:
# base_path: /your-api-base-path
default_timeout_seconds: 30.0
proxy:
url_path: /run/secrets/quelware-proxy-url
secret_header_paths:
CF-Access-Client-Id: /run/secrets/cf-access-client-id
CF-Access-Client-Secret: /run/secrets/cf-access-client-secret
For the https transport, omit port to use 443. Requests target the
endpoint root unless http_transport.base_path is set.
http_transport.default_timeout_seconds limits the complete HTTP request and
response-body read in seconds; omit it when no transport-wide default is
needed.
secret_header_paths is valid only for HTTPS. Each key is an HTTP header name,
and each value is a file containing that header's secret value. The transport
sends the configured headers on every request. For Cloudflare Access, configure
the application with a matching Service Auth policy. HTTPS certificates are
validated against the system trust store. Redirects are followed only within
the same scheme, host, and effective port. A cross-origin redirect fails before
request credentials are sent to the redirect target.
The https transport uses the standard HTTP_PROXY, HTTPS_PROXY,
and NO_PROXY environment variables when proxy is omitted. To set a proxy
for one system explicitly, write its complete HTTP URL to the file named by
proxy.url_path. The explicit URL takes precedence over HTTP_PROXY or
HTTPS_PROXY, while NO_PROXY still bypasses it for matching endpoints.
For a Basic-authenticated proxy, use a file containing a URL such as
http://user:password@proxy.example.com:3128. Percent-encode reserved
characters in the username or password, protect the file as a secret, and do
not put the URL directly in system.yaml. SOCKS, NTLM, and Kerberos proxy
authentication and HTTPS proxy URLs are not supported. A private CA used by a
TLS-inspecting proxy must be installed in the system trust store.
wiring.yaml
Key the wiring by the same system_id and define one row per mux.
SYSTEM_A:
- mux: 0
ctrl: [BOX_A:4, BOX_A:2, BOX_A:11, BOX_A:9]
read_out: BOX_A:1
read_in: BOX_A:0
- mux: 1
ctrl: [BOX_A:16, BOX_A:14, BOX_A:17, BOX_A:15]
read_out: BOX_A:8
read_in: BOX_A:7
Qubex accepts both BOX:PORT and BOX-PORT forms in wiring.yaml, but using
one style consistently is easier to maintain.
skew.yaml
Use skew.yaml for synchronized QuEL-1 or QuBE setups that require inter-box
timing adjustment.
box_setting:
BOX_A:
slot: 0
wait: 0
port_wait:
1: 0
BOX_B:
slot: 1
wait: 0
port_wait:
8: 0
monitor_port: BOX_A-12
reference_port: BOX_A-1
scale:
BOX_A-1: 0.125
target_port: !!set
BOX_A-1: null
BOX_B-8: null
time_to_start: 0
trigger_nport: 10
box_setting.<box>.slotdefines the coarse timing slot for each box.box_setting.<box>.waitdefines the box-common wait value.box_setting.<box>.port_waitdefines the per-port residual wait value.reference_portselects the reference signal source.monitor_portandtrigger_nportdefine the monitor capture path.target_portlists the ports included in the skew scan.
After loading the same config through Experiment, you can inspect and update
the file with the QuEL-1 skew helpers:
result = exp.tool.check_skew(["BOX_A", "BOX_B"])
exp.tool.update_skew(250, ["BOX_A", "BOX_B"], backup=True)
result = exp.tool.check_skew(["BOX_A", "BOX_B"])
exp.tool.update_skew(target, ...) shifts each measured effective wait by
target - measured_idx based on the previous check_skew(...) result, then
writes the box-common part to wait and measured-port residuals to port_wait.
Set backup=True when you want to save the previous file as a timestamped
backup such as skew.yaml.bak.20260520_124900.
For a full walkthrough, see QuEL-1 skew adjustment workflow.
Define parameter files
Put system-specific parameter files in the params_dir that you pass to Qubex.
The preferred format is one structured YAML file per parameter family, with
legacy params.yaml and props.yaml used only as compatibility fallbacks.
For the complete file catalog, source-priority rules, and frequency fallback
rules such as control_frequency.yaml taking precedence over
qubit_frequency.yaml, see Parameter files.
Load configuration from code
Pass the concrete system_id, the shared config directory, and the selected
parameter directory.
import qubex as qx
exp = qx.Experiment(
system_id="SYSTEM_A",
qubits=[0, 1],
config_dir="/path/to/qubex-config/config",
params_dir="/path/to/qubex-config/params/SYSTEM_A",
)
You can also load and inspect the same files directly through ConfigLoader.
from qubex.system import ConfigLoader
loader = ConfigLoader(
system_id="SYSTEM_A",
config_dir="/path/to/qubex-config/config",
params_dir="/path/to/qubex-config/params/SYSTEM_A",
)
system = loader.get_experiment_system()