Nexus guide
← Index/Nexus field guideWritten from this project's runs
Online lane · first-timer notes

Using Quantinuum Nexus from a Lovable project

This is what it actually took to get a Lovable-hosted research site talking to Quantinuum Nexus: the failures in the order they happened, the guards that survived, one costed dry run, and the list of things I would do differently. Every number here came out of a run in this repository.

What Nexus is, and what it is not

Selene is the free offline emulator that ships inside guppylang. It runs in your own sandbox, costs nothing, and is where almost all of this programme's work happens. Nexus is the separate online lane: hosted emulators carrying the vendor error model, and — with the right access — real H-series hardware.

On a plain account the real QPUs simply do not appear in the device list. That is not an error to debug. Hosted emulators such as H2-1LE are visible and usable; if H1-1, H2-1 or H2-1SC are missing, you do not have hardware access yet, and no amount of config will conjure them.

What this account can actually reach

The backends page is the ground truth, not the datasheet. Eleven backends are visible on this account and none of them is a QPU — every result on this site is therefore an emulator claim. Two details in this table bite if you ignore them: the widths are the emulator ceilings the account exposes (H2-Emulator shows 26 qubits even though H2-1 is a 56-qubit machine), and each family needs its own config class.

BackendQubitsTypeConfig class
H2-Emulator26Emulator (full H2 error model)QuantinuumConfig
H2-1LE26Simulator (noiseless H2)QuantinuumConfig
H1-Emulator20Emulator (full H1 error model)QuantinuumConfig
H1-1LE20Simulator (noiseless H1)QuantinuumConfig
Helios-1E-lite26Emulator (full Helios-1 error model)HeliosConfig
aer_simulator26Simulator (Qiskit Aer)AerConfig
aer_simulator_statevector26Simulator (statevector)AerConfig
aer_simulator_unitary26Simulator (unitary)AerConfig
QulacsBackend20Simulator (Qulacs)QulacsConfig
Selene26Simulator (Selene, hosted)SeleneConfig
SelenePlus26Simulator (Selene Plus)SelenePlusConfig

A name submitted through the wrong config class is accepted at job creation and then refused at submission with You do not have access to this machine (code: 14) — an access error for what is really a typing mistake. That is how the Helios leg was lost the first time. The preflight in quantum/backend_nexus.py now picks the class from the backend family, and an unreachable name prints the reachable list grouped the same way.

Which backends will actually take shots

Reachable is not the same as usable. A backend can be visible, correctly configured, and still refuse shot-level output, and a backend wide enough on paper can be too narrow for the run in hand. Every dry run now prints this matrix before anything is submitted, built by asking devices.supports_shots(config) with the config class that backend's family requires — so the devices a sweep would silently skip are named up front rather than discovered one submission at a time.

BackendKindWidthShotsVerdict
H2-Emulatorselectedemulator26qunknownunknown — no live session
aer_simulatoremulator26qunknownunknown — no live session
aer_simulator_statevectoremulator26qunknownunknown — no live session
aer_simulator_unitaryemulator26qunknownunknown — no live session
H1-1hardware20qunknownunknown — no live session
H1-1Eemulator20qunknownunknown — no live session
H1-1LEemulator20qunknownunknown — no live session
H1-Emulatoremulator20qunknownunknown — no live session
H2-1hardware56qunknownunknown — no live session
H2-1Eemulator26qunknownunknown — no live session
H2-1LEemulator26qunknownunknown — no live session
H2-2hardware56qunknownunknown — no live session
H2-2Eemulator26qunknownunknown — no live session
Helios-1hardware98qunknownunknown — no live session
Helios-1Eemulator26qunknownunknown — no live session
Helios-1E-liteemulator26qunknownunknown — no live session
Qulacsemulator20qunknownunknown — no live session
QulacsBackendemulator20qunknownunknown — no live session
Seleneemulator26qunknownunknown — no live session
SelenePlusemulator26qunknownunknown — no live session

The snapshot above was written by python -m quantum.device_matrix --dump on 2026-08-26T00:52:33+00:00 from the pinned source, sized for 16 qubits. Two rules keep it honest. An unknown verdict is missing information, never a refusal: if the session is down or the config class is absent from the installed client, the device stays available and the submission path re-checks it. And the matrix is advisory only — the real gate is still the per-submissionsupports_shots preflight, because a table generated minutes ago is not a promise about the job you are about to pay for.

Getting logged in from a sandbox

Login is a device-code flow. The call blocks until you open the URL it prints and approve the device; there is no way to script past that step. On success a token lands in ~/.qnx/auth and the Python client picks it up ambiently — you do not pass credentials to your own code, and you should not.

Two sandbox realities to plan for. Installing qnexus alone is not enough; it needs guppylang>=1.0 in the same place. And the sandbox home is ephemeral, so the login recurs — treat re-authentication as normal, and keep every script able to run in a dry lane without a session.

echo ".pydeps/" >> .gitignore          # do this FIRST
pip install --target .pydeps "guppylang>=1.0" qnexus
PYTHONPATH=.pydeps python -c "import qnexus; qnexus.login()"

Failures — the honest list

Each of these cost real time. They are listed roughly in the order they bit.

Symptom

Turns kept dying with a bare "internal error", and every file written in that turn vanished.

Cause

The vendored Python tree at .pydeps was tracked, so each turn tried to save tens of thousands of files and blew the save budget.

Fix

Add .pydeps/ to .gitignore before the first pip install, then reinstall. The rollbacks stopped immediately.

Symptom

ModuleNotFoundError: selene_core the moment a Nexus script imports anything.

Cause

qnexus was installed on its own; it expects the Guppy/Selene stack to be present alongside it.

Fix

Install guppylang>=1.0 in the same target directory as qnexus. Selene ships inside guppylang.

Symptom

entry not found in database when starting an execute job.

Cause

The program ref handed to the execute call had never been through a compile job in that project.

Fix

Always compile first and pass the compiled ref, not the raw uploaded one.

Symptom

TypeError from jobs.get() with what looks like a perfectly good job id.

Cause

The call is keyword-only; a positional argument is silently the wrong parameter.

Fix

Call it with named arguments, and wrap the lookup so a resumed sweep fails loudly rather than re-submitting.

Symptom

Every shot classified as the wrong outcome; analysis looked like total decoherence.

Cause

Distribution keys are tuples of ints indexed by qubit, not strings — and the ordering is the mirror of the Qiskit convention many examples assume.

Fix

Run a one-gate X probe on each backend and each register shape before writing any analysis, and read the tuple by index.

Symptom

A 25-qubit, 8192-shot noisy emulator job sat in RUNNING for about three hours, then raised TimeoutError.

Cause

The noisy hosted emulator has a practical sizing cliff around 17 qubits and around 2048 shots.

Fix

Stay under the cliff, batch several small programs into one job rather than firing many, and cache each row as it lands.

Symptom

A backend rejected the config in a way that reads like an access-permission error.

Cause

Wrong config class — HeliosConfig and QuantinuumConfig are not interchangeable for a given device family.

Fix

Match the config class to the device family first; treat access errors as suspect until that is ruled out.

Symptom

Quota check came back healthy, then the job was refused.

Cause

The quota meters cover CPU time and storage. They say nothing at all about whether you can afford the HQCs.

Fix

Treat max_cost as the only real spend guard, and estimate cost explicitly before submitting.

Best practices that stuck

Dry run is the default

Every hardware path is inert unless an explicit confirmation flag is set. Cost estimation, quota reporting and device reachability all run in the dry lane, so a mistake costs nothing.

A hard max_cost ceiling

start_execute_job carries an explicit HQC ceiling. If the estimate creeps past it the submission is refused rather than quietly billed.

Typed job properties on every submission

gate, driver, n_qubits, shots and seed are declared once on the project and stamped on each job, so a sweep stays queryable weeks later instead of being a wall of anonymous ids.

Re-attach, never re-submit

Job ids go into the row cache as soon as they exist, and `python -m quantum.resume` turns any of them — or a whole run found by its gate label — back into shots. Nothing is ever bought twice.

Bit-order calibration per backend and shape

A 20-shot X probe pins which index is which before any analysis is written. It is the cheapest experiment you will ever run and it has caught real inversions.

Per-row JSON caches

One row of a sweep is one file. A timeout costs that row and nothing else, and the same files are what the site renders.

Resuming a sweep you already paid for

A hardware sweep loses its process more often than you would like — a rollback, a dropped websocket, a sandbox timeout. Nexus has still finished the jobs and still billed for them. Resubmitting to get those shots back is the single most expensive avoidable mistake on this lane, so recovery has to be one command rather than an ad-hoc script.

# ids in hand, from the console or a shipped dump
python -m quantum.resume --backend hardware --job-id exec-1 exec-2

# ids recorded in a dump the sweep wrote before it died
python -m quantum.resume --backend hardware --from-dump src/data/demos/x.json

# no ids at all — find the run by the labels every submission stamps
python -m quantum.resume --backend hardware --gate G16 --dry-run

Four details make the difference between a command that works months later and one that doesn't. The decode width comes from the n_qubits property stamped on the job, not from memory. A job that is not COMPLETED is reported with a plain reason and skipped, so one DEPLETED id does not abandon the other nine. The billed HQC from the original run stays in the meter, because a resumed sweep that reports itself as free has lost the true cost of the work. And the shots are cached on disk by job id, so the second resume is a file read and a driver's row loop consumes it exactly like a row it ran itself.

Recovery only works if you can see what is recoverable, so the saved-Ref store has its own audit view: the Ref ledger lists every persisted job by submission run, with its last known status and whether it still carries the sweep coordinates a re-attach needs.

Knowing a job ran is not the same as knowing what it cost. The estimate booked at submission and the figure Nexus actually billed are reconciled per job on the cost ledger, which flags any bill more than ten percent above its estimate and refuses to count a job with no bill back yet as free.

Successes — what actually worked

  • The ambient token file was recognised without any credential handling in project code — the session simply existed and the client used it.
  • A 20-shot X probe on H2-1LE pinned the bit order conclusively, and every downstream reader was written against that result rather than against an assumption.
  • A dry run of the ethylene QPDE kernel estimated 5.0384 HQC against a ceiling of 25.0, printed all four quota meters, and correctly refused the devices this account cannot reach.

The Lovable-specific constraint

A Lovable app's server code runs in a Cloudflare Worker. child_process is a stub that throws, and there is no arbitrary filesystem to read from. A Nexus call can therefore never live inside a server function, however tempting the "run experiment" button looks.

The working shape is a three-step loop: run Python in the sandbox, commit the result JSON into the repository, render a static view of that JSON. Every experiment page on this site is built that way. Keep each turn down to one committable artefact and a rollback costs you minutes rather than a day.

What I would do differently

  1. 01Calibrate bit order before writing a single line of analysis, not after the numbers look strange.
  2. 02Gitignore the vendored dependency tree on day one — it is the difference between a working session and six lost turns.
  3. 03Trust max_cost, not the quota meters, as the spend guard.
  4. 04Size noisy jobs well under the cliff and batch programs into one submission instead of firing many.
  5. 05Write job ids into the cache on the first run so a resume is always a download.
  6. 06Run the classical baseline first; half the time it tells you the quantum leg is not the interesting part yet.

Where to go next