PennyLane
The qubitra.remote device makes any Qubitra Backend a PennyLane device. It ships as an
extra of the SDK:
pip install 'qubitra-sdk[pennylane]'PennyLane discovers the device through its plugin entry point, so there is nothing to import:
import pennylane as qml
dev = qml.device("qubitra.remote", wires=2)
@qml.qnode(dev)def bell(): qml.Hadamard(0) qml.CNOT(wires=[0, 1]) return qml.counts()
print(bell()) # {"00": 512, "11": 512}Credentials resolve the same way as QubitraClient: QUBITRA_API_KEY and
QUBITRA_API_URL from the environment, or explicitly:
dev = qml.device( "qubitra.remote", wires=2, backend_id="sim-statevector-26q", # the default — any Backend id works shots=1024, # default shot count for jobs from this device api_key="qpk_...", # or pass client=an existing QubitraClient)An existing QubitraClient wins over key arguments — pass client= to reuse a
configured one.
Supported measurements
Section titled “Supported measurements”| Measurement | Runs as | Notes |
|---|---|---|
qml.counts() |
sampler | computational basis; wires= subsets marginalize |
qml.sample() |
sampler | per-shot rows are synthesized from counts, not chronological |
qml.probs() |
sampler | frequencies from the returned counts |
qml.expval(op) |
estimator | op needs a Pauli representation (op.pauli_rep) |
Sampler measurements need shots; qml.expval also runs analytically. Several expvals
on one tape become several observables on one PUB and come back index-aligned. Anything
else — qml.var, qml.state, sampling an observable, or mixing sampler measurements
with expectation values on one tape — raises a DeviceError naming this table.
Operations decompose toward what the OpenQASM serializer can emit, so gates like
qml.Rot work. A sampler tape is submitted as OpenQASM 2 with terminal measurements, an
estimator tape as the bare circuit plus sparse Pauli terms.
A batch is one job
Section titled “A batch is one job”PennyLane’s parameter-shift rule evaluates two shifted circuits per trainable parameter, so every gradient of a 20-parameter circuit is a 40-tape batch. The device submits any batch as one multi-PUB job, never one job per tape:
@qml.qnode(dev, diff_method="parameter-shift")def circuit(params): qml.RY(params[0], wires=0) qml.RY(params[1], wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
qml.grad(circuit)(params) # 4 shifted tapes → one job, one round tripThe platform’s round trip takes seconds. Submitted as one job per tape, a 20-parameter gradient would cost 40 sequential round trips per optimisation step; as one job it is a single submit-poll-fetch regardless of the parameter count.
Bit order
Section titled “Bit order”Platform counts keys are little-endian — clbit 0 rightmost. PennyLane reads wires left to
right with wire 0 most significant. The device translates at the boundary, so results
match what default.qubit would say for the same circuit: qml.PauliX(0) on a
three-wire device reads {"100": shots}, never {"001": shots}.