Skip to content

Execution and storage

This page is for contributors who change how a Run prepares circuits, submits them, collects their results and saves its state. _prepared_execution.Run implements this prepare, submit and collect lifecycle once, and the public functions scientist.prepare and scientist.submit use the same object. An adaptive Method chooses each next experiment from the observations collected so far. Its iteration code obtains that experiment through Plan.resolve, and its saved iteration state records the information needed to continue. Backend adapters implement native preparation, submission, retrieval and cancellation. Native means belonging to the backend's own SDK, so a native circuit is the object that the backend executes, such as a Qiskit circuit for Aer or a pytket circuit for Nexus. No object other than the Run reconstructs a request or changes the work counted against the limits.

An acquisition is one execution of a prepared circuit with its specified readout and shot count, or one invocation of a classical routine (a classical computation that a Program runs in place of a circuit, called a host kernel in the code), together with the collection of the resulting data. All the shots of a counts readout belong to one acquisition, and one submission to a backend can hold several acquisitions. Lowering builds a Qiskit circuit from a Program and its bound blocks. A preparation record (PreparedArtifact) describes one prepared circuit or classical routine as it was built. The run log is the SQLite file run.sqlite of a saved Run, written through _run_journal.LocalJournal, in which the Run records its limits, preparations, submissions, observations and checkpoints, each reservation before its work starts and each outcome after it completes.

Checks before native preparation

_prepared_execution.prepare_experiment checks the construction that planning chose and keeps only the native block bindings of exactly that construction. It then runs its checks in order of increasing cost, so an unsupported request fails before any circuit exists:

  1. Program readiness of the chosen construction.
  2. Simulator width. On a local simulator, the circuit width must not exceed max_simulation_qubits.
  3. Trajectory target support (trajectories only). A trajectory reads the state at several points of one coherent evolution. A backend that cannot execute its schedule of points, one of its readout views (a basis change applied before a point's readout and undone afterwards) or one of its reductions rejects the trajectory, naming the backend and the unsupported feature. This check precedes the checks of the trajectory's readout shape and metadata allowance, and native construction and submission.
  4. Readout items and payload bytes (_prepared_execution._admit_readout). The Run subtracts the bytes of the readout declaration and the completion-metadata allowance from max_data_bytes and divides the remaining bytes by the readout's bytes per item (its statistic stride) to obtain the largest admissible number of items. A trajectory reserves the sum of its point declarations, fixed observation headers and value payloads, whose sizes depend on their representation, plus one completion-metadata allowance for the whole acquisition. That allowance must hold the job text and the empty values field of each point's observation, together with the largest established growth of the acquisition's completion rows on the backend. For a trajectory, the readout shape check also refuses a chosen Program that declares measurement registers, because a trajectory reads the coherent state without measuring it. Readout reservation gives the reservation of each readout kind.
  5. Target readout support for readouts other than trajectories.
  6. One coherent evolution (trajectories only). The chosen Program, NWQLib's intermediate representation (IR) of the circuit, must be one coherent evolution along the executed prefix. An explicit reset of a nonempty register, or an outcome-dependent branch or adaptive loop that starts before the last point's boundary or appears in the tail or inverse of a readout view, is refused with a message that names the "selected IR".
  7. Reduction work and simulator memory (trajectories only). The trajectory's reductions are checked against the calling Method's remaining work allowance. Its live state, every save it keeps (probability marginals, expectations and saved states) and the transient marginal workspace are checked against simulator_memory_mb, with the thread cap that the simulator is given. The allowance comes from the Method's reduction_allowance(plan, point, *, observation, width, run) hook, which first applies the Method's own limit on the private workspace of its reducers.
  8. Direct state preparations. The largest direct state preparation of the Plan must not exceed max_direct_amplitudes.
  9. Amplitude materialization must fit max_data_bytes.
  10. Cumulative circuit cap. A new circuit preparation must fit max_total_circuits.

The readout of a classical routine skips steps 2 and 5 to 8.

Then, unless the caller supplies runtime, the Run draws the runtime seed and commits the preparation charge (PreparationCharge, the record that counts the preparation against the Run's limits) together with the position of its random-number generator (RNG), as step 2 of Write order and crash windows. Lowering starts only after that commit.

Lowering and native preparation

Lowering matches each reachable block call to its chosen definition by exact content hash. It rejects a missing or mismatched binding, a duplicate bound block and a bound block absent from the chosen construction. Random draws at run time come from the Run's saved RNG. Compiler options come from the chosen backend configuration, and a resource context (ResourceContext) cannot change lowering or execution. Preparation records of native circuits keep the readout shape, the layout, the compiler and the content hash of the backend configuration, the seed and the provenance of the acquisition.

Each chosen block lowers to a Qiskit gate, which cannot hold a reset, a measurement or an Initialize. After lowering and before submission, the adapter checks the top-level instructions of the lowered circuit. A trajectory's preparation record holds the body length and each point's resolved boundary.

The exact statevector target of Qiskit Aer executes a trajectory in one simulation. It inserts every point's saves at the point's boundary in the bound body before lowering, shares one save between identical requests at one boundary, and executes nothing after the last save. Its preparation record counts the native evolution operations executed through the final observation, counts no save instruction, and does not multiply that count by the number of labels or points. At a readout view's boundary, Aer applies the view's tail, the view's saves and, when a later observation follows, the exact inverse of that tail. A view executes for Pauli and probability points.

Submission

Before new acquisitions start, the shared submission code calls Method.before_submit(plan, prepared_tuple, run=run) once, before it commits the submission intent (the record of a submission that the Run writes before contacting the backend) and before any backend work. prepared_tuple holds one prepared circuit on a synchronous backend, which returns the result from its submit call, or the whole batch on a detached backend, whose jobs run outside the process and are retrieved later. Retrieval never repeats this hook. A Method uses it to keep each sample set valid under the Method's definition. For example, QPE and finite-Pauli sampling whose shot count was chosen from an Accuracy reject a known reuse of a fixed random stream, including data that completed but was not used and attempted work whose outcome is uncertain. Distinct UUIDs, circuits or seeds, or equal histograms, do not establish independence.

For a static Plan, whose list of experiments is fixed at planning, the inherited execution code asks the reduction_allowance hook again before it submits an existing preparation, including one that a reopened Run submits, so the acquisition is checked against the allowance and the limits in force at submission.

The complete fixed workload is checked before its first preparation. Each submission intent then records the circuits, shots, jobs and provider-managed sampling before execution. A failed call updates the attempt's event (ConsumptionEvent) and its submission record (SubmissionRecord) together. A lost acknowledgement leaves the submission uncertain, that is, with an outcome that cannot be established, and keeps its original job locators (JobLocator, the provider's identifiers of a job). A refresh retrieves the original job, saves each successful observation once and in a single transaction, and keeps earlier successful items if a later item fails. Repeated collection neither duplicates observations nor reduces the Run's exposure, its counts by outcome of the jobs, circuits, shots and provider-managed sampling it has submitted.

Execution limits

ExecutionLimits bounds the work that a Run counts and the data that it saves. The data limit is checked before each of these writes: saving an array, reserving output that a backend job has not yet returned, saving a native payload and writing to the run log. The simulator memory field (simulator_memory_mb) is passed to the simulator and does not bound the memory of the whole process. For provider estimates, sampling that the provider manages is counted separately from explicitly requested shots. The result that a simulator returns (for NWQ-Sim, its result packet) records the number of state evolutions it executed, independently of the shot count and of the number of MPI ranks.

Run.extend_limits writes only the new current limits and one LimitAmendment with a fixed set of fields, in one transaction of the run log. It records the known counters in the amendment before the commit, and the Run's limits and amendment history in memory change only after the commit succeeds. Limits equal to the current ones write nothing. An amendment does not copy the earlier amendments, the trace or arrays, and its encoded size counts against max_data_bytes. The header of the run log holds the initial limits, and reopening restores the saved current limits and the amendment history before the Run continues. A saved completed Result records the length of the amendment history and the byte counter at the time it was captured, so raising the Run's limits later does not change the limits that the Result reports.

Readout reservation

The check before an acquisition and the check at its completion compute JSON sizes with the same code. The upper bound for one item is the largest encoded size of one count, probability or Pauli statistic, and the bound for the readout is that per-item bound multiplied by the declared number of items. Labels of the scalars that a classical routine returns count with their escaped UTF-8 lengths, and arrays with their declared byte counts, so the size is obtained without enumerating outcomes. Strings count with their escaped UTF-8 size, integers with their decimal size, and finite binary64 values with a conservative upper bound of 32 bytes. A replaced row is credited with its existing encoded bytes, so a repeated update of a batch header counts only its growth.

ExecutionLimits.max_completion_metadata_bytes, the completion-metadata allowance, reserves by default 65,536 additional bytes per acquisition for fields of variable size, apart from declarations, statistic bodies and binary arrays. The table gives what each readout or field reserves and when it is checked.

Readout or field What is reserved When it is checked
Declared observations, bindings and register layouts Their known encoded sizes, exactly, in the fixed reservation At reservation
Application records (KernelApplication) of a classical routine whose JSON size bound the routine declares in SelectedKernel.application_bytes That bound, exactly, in the fixed reservation, because the Method fixes these records at planning At completion, the smaller of that declaration and the returned records' bound is subtracted before the variable metadata is checked
Provider annotations and undeclared application records of classical routines The completion-metadata allowance At completion
Single-endpoint probability readout Eight bytes per possible outcome, the declared fields of its observation and one completion-metadata allowance Before lowering and again before submission, the remaining fields of the observation, the manifests and summaries of the largest valid encoding of the probabilities, and the known net bytes of the rows written when the arrays and the completion are saved must fit that allowance
Counts, exact Pauli scalar and amplitude readouts The upper bound per item times the declared number of items, plus the completion-metadata allowance On Aer, their known completion-metadata requirement is checked against the allowance before native preparation. Another backend checks them when it saves them
Trajectory For each point's observation, its one-point declaration, fixed header fields and values, separately, plus one completion-metadata allowance. The fixed header of a probability point covers its array manifests, scalar summaries, the growth of its saved-array rows and its payload-reference rows at their largest valid encoding, so the allowance covers the job text and the empty values field of each point's observation and the acquisition's other completion rows Before native work, the Run refuses a trajectory whose required bytes exceed the allowance, including the completion-row growth below

The check of a trajectory before native work also reserves the largest established growth of the acquisition's completion rows on its backend (Readout and completion allowances and Aer simulator in Engineering constants):

Backend Completion rows Reserved growth
Aer Synchronous completion rows, including revision parents, job locator, timing evidence and microsecond timestamps 1,113 bytes without a forecast, 1,044 bytes when the event already has a revision parent
NWQ-Sim and its Slurm backend Detached completion 168 bytes, or 99 bytes with a forecast
Any other backend Its completion rows Temporarily zero, until a derivation of its completion lifecycle establishes the growth of its completion rows

A forecast in this table is the PlanEstimate given with the Plan, the resource estimate of the whole Plan with the device forecasts of its points. With a forecast, the Run revises each event to name its point assessment before the backend is contacted, so at completion the event already has a revision parent (parent_id, the content hash of the record it revises).

A backend whose job text or other completion fields have no upper bound checks their bytes when it saves the result. Replaced rows are credited once, when the result is saved. At completion, the Run checks a conservative JSON upper bound on the growth of the variable metadata and names max_completion_metadata_bytes in the error when the bound exceeds it. The allowance can be raised like the other execution limits. It reserves storage without allocating an output buffer and does not restrict the number of declared Pauli terms.

Saved Run storage

Before any work, a Run with a saved folder saves the inputs that planning chose, and it records checkpoints of the iteration state in the run log. Checkpoints reference existing observations and immutable arrays instead of copying a growing history. A Method saves and restores its numerical and native caches through its archive hooks. Loading restores those values without rebuilding eigensystems, references, circuits or chosen inputs. Unchanged cache objects reuse their files after reopening. A changed cache file becomes current only when the active frontier commits, and superseded files are then removed. The active frontier is the saved state from which a reopened Run continues (Cache rows and the active frontier).

Cache files

New cache payloads use the reserved .nwqlib-cache- filename prefix. An interruption can leave a completed or partial file without a committed reference in the run log. Reopening takes the Run's exclusive lock, loads the committed cache objects and the files they depend on, and then removes unreferenced files with that prefix before it counts the saved file bytes and writes any recovery revision. This order allows recovery even when uncommitted files fill the data allowance. Plan inputs, referenced cache files and files without the reserved prefix are kept. Recovery scans filenames once and uses the references gathered by ordinary cache restoration. It does not parse unreferenced arrays or circuits.

Checkpoints of the iteration state

A checkpoint of the iteration state is one header row, holding its sequence number and field names, and one row per top-level state field. The header, the changed field rows and the RNG position commit in one transaction. Run.checkpoint(state, changed=names) encodes and writes only the named fields, and every other field keeps its committed row. The caller names each field that changed since its previous checkpoint, including an added or removed field. The Run rejects an unnamed added or removed field but does not compare the values of the other fields. Omitting changed writes every field. run.checkpoint_state and RunData.controller join the committed rows into one state.

ADAPT names all of its small fields at every checkpoint. It adds the projected pencil when an analysis replaces it, and the decision history when a record is appended or the newest record is updated. Under ADAPT.sampling, the pilot counts and the shot allocation of the final stages are top-level fields of their own, pilot_counts and shot_allocation. ADAPT names pilot_counts when the stages start and shot_allocation when the stages start and when the allocation is decided. A checkpoint made while acquiring one observation therefore writes that observation's pending entry and the small fields, and does not encode the pencil, the pilot counts, the shot allocation or the decision history again.

Cache rows and the active frontier

Each immutable native definition, each lowered Aer source and each prepared circuit (PreparedHandle) has its own cache row under a fixed key. The code that changes the dictionaries holding them records the changed keys, so a checkpoint does not traverse all earlier prepared circuits or definitions.

The active frontier has two rows, each saved only when it changes:

Row Holds Saved when
frontier The current parameter specializations (definitions lowered for fixed parameter values) and backend data Each native preparation replaces its specializations and rewrites only this small row
method The current method context Each access to a method context that is not a dictionary marks the row for saving at the next write to the run log, unless the code responsible for the context marks its own changes

ADAPT marks its context after each projected analysis and when its iteration code returns, both for acquisitions of classical routines and for native preparations. That context holds keyed caches such as state vectors, Hamiltonian actions and compiled gates. If the process ends before the iteration code returns, a resumed Run recomputes at most the cache entries created after the last saved context.

File references are indexed per row. A successful replacement or deletion updates only those references and then removes newly unreferenced cache files. A failed commit restores the changed dictionary entries, the identities of the provisional files and the bytes counted for them. Traversing the active frontier, writing an explicit saved copy and the single restoration at reopening still visit every item they cover, so not every operation of the iteration code has constant cost.

Collection order

Each collected observation has its own positive collection ordinal in the run log, and zero means completed but not yet collected. The collection order is therefore independent of the order of completion and of preparation records, including when completed observations are collected in reverse order. Repeated collection keeps the original ordinal. The saved caches of ADAPT use the Run's canonical observations and rebuild their lookup and order once on restore, without running circuits or analysis. A standalone Result keeps its own saved copy of its observations and context.

Writes to the run log

Run._write encodes each changed row once and passes that checked encoding to SQLite. LocalJournal checks the byte total and the binary declarations of the transaction again. Upserts and deletions of cache rows and checkpoint fields can share one transaction, and conflicting keys are rejected before any row changes. Deletion is limited to cache rows and to checkpoint fields that the Method removed from its iteration state. Scientific observations, preparation records and payload associations cannot be deleted. The in-memory byte counters change only after the commit. The bytes of rows and binary data that the Run counts are distinct from the bytes that the database files allocate. Deleting rows does not necessarily shrink the SQLite files, and checkpoints do not run VACUUM.

Locking and durability

The run log relies on the durability of the local file system, and a POSIX advisory lock lets one process at a time hold the Run open. Close a Run before reopening it, and keep the Run folder available while arrays mapped from its files are in use. A lock file left by a crash is reused after its process exits, and its presence alone does not mean a live process. Sharing an open Run with a forked process, replacing its files from outside and opening a Run on a distributed or network file system are not supported. When a write fails, the Run blocks further changes and records no completion that did not happen. Reopening loads the current limits, including any raised ones, before the larger checkpoint data, and work already counted stays counted.

Cancellation and recovery

Cancellation prevents new work and sends at most one request to the original job of each pending preparation or submission, identified by its locator. Only a status returned by the provider establishes cancellation. resume(reanalyze=True) is a separate recovery, requested explicitly and implemented by the Method, that repeats the Method's classical analysis after an interruption. resume() without this argument and wait() do not repeat it. Recovery must keep the scientific settings, the saved samples, the RNG state and the exposure, and it cannot replace provider work whose outcome is unresolved.

Each RunData shares the immutable observations, preparation records and arrays of its Run. Result context is optional and belongs to the specific verification that uses it. A Method must supply its immutable saved state and its archive hooks. The Run never computes additional reference values only to save them. Loading a completed Run returns its saved Result without reanalysis. Run on a backend and Save, load and reanalyze results describe the public workflow.

Write order and crash windows

Every write that reserves work happens before that work starts, and every write that saves an outcome includes all of that outcome. In-memory state changes only after the commit to the run log succeeds, and it is restored when the commit fails.

A static Plan has a fixed list of experiments, each prepared and acquired once by prepare_static and execute_static. prepare_static prepares the first experiment, or all of them for prepare(plan, settings="all"), and execute_static prepares each remaining one when it reaches it. Before its first preparation, prepare_static checks the readout of every experiment against the backend target and the whole Plan against the Run's cumulative limits. On Aer it also checks, before that preparation, the largest known completion-metadata requirement of all the Plan's counts, exact Pauli scalar and amplitude settings. When the backend target refuses an experiment there, the error names the experiment and, for a counts readout, the shots per program that it requests, or for any other readout its readout kind.

One static acquisition passes through these commits, and a process can stop between any two of them:

  1. Workflow row. The experiment's runtime seed and the RNG position commit before anything is prepared, so a restart before the preparation charge prepares the experiment with that same seed.
  2. Preparation charge and RNG position. If the process stops after this commit and before the preparation record, reopening counts the circuit preparation against max_total_circuits. The static workflow does not rebuild a failed or interrupted preparation. The Run never repeats a preparation attempt on its own, since that would be new counted work that the caller did not request. Such a Run cannot complete, so execute_static raises PreparationNotRebuilt before it submits any further experiment. After prepare(plan, settings="all") this row can precede experiments that were never submitted. Before lowering or the backend makes an exact dense synthesis, a revision of this charge reserves its work against max_synthesis_work in its own commit, so a stop during the synthesis leaves that work counted. When the limit refuses the work, the revision records the refused amount instead and, in the same commit, removes the preparation from its static workflow item or checkpoint of the iteration state. The refused preparation stays counted, and because the commit removed it from the workflow item or checkpoint, the next call of the workflow or iteration code prepares the experiment again, unlike after a failed or interrupted preparation.
  3. Preparation record and native payload, with the cache row that holds the prepared Aer circuit in QPY, Qiskit's binary circuit format. If the process stops here, the preparation is complete on disk. A later resume submits the saved payload without lowering or transpiling again.
  4. Submission intent, reserved events and output reservation. This commit saves the submission intent, one event (ConsumptionEvent) with status reserved for each attempt, and the bytes reserved for the output. The backend is contacted only after this commit. If the process stops before an acknowledgement or outcome commits, reopening makes the reserved events uncertain with their shots and circuits still counted. An unacknowledged detached submission becomes uncertain. Its original job can be recovered only if the backend can reconcile the submission, that is, find the job by the original submission identity. An interrupted synchronous Aer submission with no locator becomes failed with results_consumed=True, because its in-process output is unavailable, and its pending output-byte reservation is released. If the uncancelled Run has no retrievable outcome and the Method cannot return a valid Result, resume() and wait() raise RunFailed at the recovery stage with status uncertain and the original attempt identity. The attempt stays uncertain and its work stays counted in the Run's exposure. A new Run is required to execute the Plan again. A synchronous backend has no acknowledgement step, so this window lasts until its outcome commits.
  5. Acknowledged locator (detached backends). If the process stops after this commit, the submission stays acknowledged and its events reserved, and the next refresh retrieves the original job by its locator.
  6. Outcome. For a synchronous acquisition, the completed event, the observation (ObservationChunk), the submission status and any arrays saved with the observation commit in one transaction, so reopening never finds one without the others. An observation of a circuit was checked against its preparation record when it was decoded and against its submission's job when it was saved, so reopening indexes it without checking it again.
  7. Collection ordinal. The Method's collection of the observation is a separate commit, so reopening restores the order in which the Method collected observations even when they completed in a different order.

A refresh of a detached job splits step 6. It first commits the provider's status and item associations on their own, so they survive a later decoding error. The observations then commit in a second transaction, which also sets the submission's results_consumed flag once the job is terminal. If the process stops between these two commits, the flag is still unset, so the next refresh reads the same job again and skips items already saved.

Remote compilation follows the same order, with the intent first. The intent of each upload or compilation commits before the SDK call. The backend adapter passes the new identity to an acknowledgement callback as soon as the SDK returns it, and the callback commits it before any further work that could fail. An intent without an acknowledged identity is resolved only by reconciliation with the original preparation identity.

Cache checkpoints write new files under unused names, commit their references, and then delete files that no row references. An interruption can leave uncommitted or superseded cache files. Reopening removes those that carry the reserved cache prefix before it counts the saved file bytes. A failed commit removes the provisional files, removes their bytes from the data count and restores the previous dictionary entries. This recovery covers an interrupted process and does not cover power loss.

Lifecycle rules

The lifecycle rules of a Run, with the failure each prevents, the code that keeps it and the test that checks it, are indexed in Design rationale. Two further rules belong to this page:

Rule Failure it prevents Code
Collection order is its own ordinal A reopened Method receiving observations in another order Run.collect, LocalJournal.commit
The native circuits of fully collected preparations can be released Memory held by native circuits that a long Run no longer uses Run.release_native, PreparedHandle._restore_native

Resource estimate bookkeeping

Estimate a construction directly

nwqlib.resources.estimate takes a SelectedConstruction, the backend-independent Program and chosen definitions that logical lowering accepts, and returns a WorkloadEstimate. It never reads native inputs, builds a circuit, transpiles, calls a backend or chooses another recipe. The following example estimates the HZH preparation of |1> that plan chooses for an ExpectationMethod configured with preparation_choice="hzh". The default X recipe has one operation. The block guide describes both recipes.

from nwqlib import Expectation, plan
from nwqlib.algorithms import ExpectationMethod
from nwqlib.operators import ingest_pauli
from nwqlib.problems import ingest_occupation
from nwqlib.resources import WorkloadEstimate, estimate

problem = Expectation(
    state=ingest_occupation("1", num_qubits=1),
    observable=ingest_pauli((("Z", 1.0),), num_qubits=1),
)
selected = plan(
    problem, method=ExpectationMethod(preparation_choice="hzh"), seed=7
)
workload = estimate(selected.construction)
operations = workload.quantity("operations")
assert operations.fact.value.numerator == 3
assert operations.lifecycle == "planned"
restored = WorkloadEstimate.model_validate_json(workload.model_dump_json())

Load resource records through their current concrete classes, as in the last line. Their serialized schema version and identifier describe the supplied construction and quantities. The compact JSON keeps every fact and content hash.

Evidence of each count

The evidence for each cost rule (ResourceLaw, a formula that gives the count of one metric) is kept in the estimate's shared source table. Arithmetic on counts produces a derived fact with declared evidence (status "declared"). It never produces witnessed evidence (status "witnessed"), which names the record of the computation that produced the value, such as a preparation record of a built circuit or an execution record. A user's assertion remains an assertion. A value extrapolated from observed source data becomes an empirical prediction even when another summand is asserted. The separate derivation_sources references identify the evidence used for the value, and sources also keeps supporting evidence from conformance checks. A literal count of one X gate therefore stays exact when an overlapping upper-bound cost rule is also checked against it. An estimated zero cost remains an estimate after a positive number of repetitions, while an exact multiplicity of zero removes even body work whose cost is unavailable. Every quantity that estimate returns has lifecycle "planned". Stage in core/records.py names the workflow stages of Limit declarations. Each resource quantity is identified by its metric, basis and source, so an upper bound on native CX gates stays planned and keeps its original source. A resource quantity does not imply a planning, preparation or execution stage. Counts of compiled circuits, submitted or observed work, and projections of physical error correction, magic-state factories or routing are not part of this estimate and come from later steps.

Counting rules for selected blocks

A SelectedDefinition may contain several cost rules (ResourceLaw) for one metric, for example one for each combination of control and adjoint flags, provided that at most one of them applies to a given call and resource context. A cost rule applies only when it matches the chosen control and adjoint flags, the concrete cost and call bindings, the metric basis, the precision and the synthesis choice. Through unbound_parameters, a cost rule can explicitly cover the whole accepted domain of named formal parameters, and every other argument must still match its fixed bindings. An empty unbound_parameters keeps matching at exactly one point. The covered names must be declared, distinct and disjoint from the fixed bindings. Coverage alone supplies no scientific proof. A call for which two cost rules of the same metric apply is rejected. Each applicable cost rule supplies its metric, and a metric that no cost rule supplies is counted from the definition's ordered primitive recipe. When the primitives also determine exactly a metric that a cost rule supplies, that exact count is kept and checked against the declared exact value or bound. Literal recipes are checked once per bounded context of the chosen definition even when cost rules supply all metrics. A definition described only by cost rules or by a native summary needs no traversal of a decomposition. Agreement of these scalar counts does not prove the chosen quantum action, and it does not turn source evidence into evidence witnessed by a preparation record of a built circuit.

The finite native CX adapters recognize the exact registered content hashes of the chosen block implementations for direct PREP, signed Pauli SELECT and Pauli readout. They reuse the registered CX bounds of those implementations (the functions that cost_law names), including outer-control rotations, sign diagonals and phases, and reject excessive integer growth before a shift or multiplication. Product PREP keeps its q single-qubit component calls through preparation_components and does not receive the generic bound for a q-qubit vector. An upper bound on CX gates supplies no missing T, rotation, depth or workspace facts.

Bases are selected_logical, cx, clifford_t and toffoli. T, Toffoli, CCZ, arbitrary-rotation, logical-depth, T-depth and non-Clifford-depth metrics are canonical. A Toffoli count and a T decomposition are alternative bases, and no count is converted automatically from one to the other or added across them. Unknown rotation precision or missing synthesis cannot imply a fixed T count. Cost rules for fault-tolerant bases can describe a conditional estimate without an executable block. The executable primitive gates are X, H, Z, S-adjoint, CX, multi-controlled Z and global phase. Control acts on each primitive, including global phase. Primitive arity and the concrete target widths of the chosen definitions are checked before counting. An effective one-qubit phase whose conventional value is 0, ±pi/2, ±pi, ±3pi/2 or ±2pi is classified as Clifford, and ±pi/4 is the T or T-inverse phase. These comparisons are exact, so an arbitrary angle close to one of these values is not rounded to it. In particular, controlled PREP of [i,0] is S on its control, with zero non-Clifford depth.

Composition, repetition and branches

Sequence and ordered primitive recipes are serial. Parallel is an explicit join of direct declared unitary/preserve calls on disjoint whole registers. It sums work and takes the maximum depth. Shared-wire calls and nonunitary effects are rejected by the Program check. Logical lowering rejects Parallel. Resource estimation does not imply a parallel compiler implementation.

Repeat multiplies work without expansion. The fold (resources/fold.py, the computation of resource counts from a Program's calls, using their cost rules, primitive recipes and registered CX bounds, without building circuits) keeps one result per context of a chosen definition and its call bindings and one per context of graph and register lifetimes, within the Program's existing work and integer limits. The context fixes the basis, precision and synthesis, and the content hash of the chosen definition fixes control, adjoint and recipe. There is no persistent global cache. Expression definitions reuse the bounded syntax tree of Constant, ParameterRef, add, multiply, ceildiv, min, max and comparison nodes with local references. JSON does not expand shared expressions. Arbitrary parsing, symbolic expansion, general range summation and automatic simplification are not supported. This subset uses exact integer arithmetic and binary64 real arithmetic, which can round, without a separate SymPy engine. When the fold enters a Program or the metadata of a chosen definition or context, it reserves room for a collection's children before pushing them on the traversal stack, so a rejected collection cannot first create a copy of its references that exceeds the limit. This limit bounds the graph traversal and does not bound memory use or CPU time.

A branch contributes, for each metric, the maximum over its alternatives, with upper-bound scope. No branch probability or model of the expected number of rounds is inferred. The cost of an adaptive body is known only when an explicit resource_envelope asserts a bound per round that holds uniformly over the history, and otherwise it remains unknown. The work and workspace of the adaptive policy remain separate missing costs. The settings of a MeasurementBatch are independent experiments. An explicit repetitions multiplies the body work, and without it the repeated work remains unknown. A terminal batch's observation_kind is counts, pauli_expectation, probabilities, estimated_observable, trajectory, or unknown (None). estimated_observable leaves shots unknown because the provider chooses them. An outer batch repeats its descendants and must have kind None. Its summed depths are an upper bound for running the whole workload serially. They do not claim that independent jobs have that causal depth, and they are not a depth per circuit. Costs and repetitions that depend on a batch axis stay unknown, because their total is a sum over the axis range, and the scalar expressions supported here exclude such range sums. Costs that do not depend on the axis are multiplied by its number of settings, computed from the compact axis description.

Trajectory readout items and bytes

For one declared trajectory, N_items = sum_k L_k. A Pauli point contributes its number of distinct requested labels. A probability marginal on q_k chosen wires contributes 2**q_k logical outcomes regardless of how many entries its sparse saved array eventually holds. An amplitude point contributes the dimension of its selected output declaration. A reduction contributes the output cardinality computed by its registered shape function from the validated parameters. Two distinct views at one boundary contribute both payloads. Positions are neither repetitions nor shots. An empty request is algebraic work with no measurement. core/planning.py::point_items computes this count, and assessment, readout_shape and readout_bytes use the same declared points. The saved data set aside for a trajectory is the sum of its point declarations, fixed observation headers and value payloads, plus one completion-metadata allowance, and the complete trajectory declaration is counted once, in its preparation record (_prepared_execution.trajectory_reservation). Before native preparation, the Run sums the amounts set aside for the points, each according to its representation (_prepared_execution._admit_readout). A dense or adaptively sparse probability array sets aside 8 * 2**q_k bytes, Pauli values the JSON size of their prototype, amplitude arrays their binary payload, and a reduction the JSON size of its registered output components (ReducedValues). The fixed header of a probability point covers its array manifests, scalar summaries, the growth of its saved-array rows and its payload-reference rows at their largest valid encoding. The completion-metadata allowance covers the job text and any bytes beyond that bound. With Aer's 36-character ASCII job identifier, probability and Pauli trajectories without a forecast require 58*K + 1113 metadata bytes for K points. The default limit of 65,536 bytes accepts 1,110 such points when their declarations, headers, binary payloads and execution workspace fit the other limits. An unsupported reduction or a reduction with unresolved output shape fails before native preparation. The data set aside for saving and the working memory of the execution are checked separately.

Register lifetimes and workspace

Register location and role specify, per location, the logical width of system, clean and dirty qubits. Allocate, Release and reuse determine the peaks. Quantum width is not converted into statevector bytes or into wires of a compiled circuit. Declared additional quantum workspace is included, and unknown clean or dirty requirements stay unknown. Classical registers remain live from their definition until the experiment ends. Outputs of a branch, or of a loop that may run zero times, may or may not be live and contribute a conservative storage bound. A guaranteed positive direct symbolic Repeat exports the same availability as IR. An empty Reset has no events or depth layer. A nonempty Reset has one layer. For a valid unresolved width that may be zero, its depth remains the symbolic min(1,width). Measure keeps its check that the result width is positive.

The fold of quantum register lifetimes keeps running sums of widths by role and location. Unresolved widths keep the multiplicities of their expressions, so Release removes the register's formal contribution without symbolic subtraction. Wire membership and equal lifetimes across branches and loops belong to the Program check. The fold does not copy the complete set of live wires at every Allocate or Release. One hundred one-bit registers therefore need only a compact running subtotal, and symbolic peaks keep their binding context.

The Workspace entries of a chosen definition give the bytes that one call needs at the same time. Parallel calls sum them per location, and serial calls reuse them. ResourceContext.resident holds the declared input, analysis, I/O, materialization or stored (purpose="stored") payloads that stay in memory for the whole workload. The fold allocates no entry. Missing block workspace or classical-stage workspace stays unknown. capacities keeps Limit declarations for a later assessment. This operation does not combine device capacities or claim feasibility. An 8 GiB device cannot host a declared 10 GiB peak merely because a second 8 GiB device exists.

Reachable definitions and the estimator's own work

defined_selections lists the identifiers of the chosen definitions that may be reached, excluding those under a Repeat or measurement batch known to run zero times. Its upper subtotal of construction work covers these distinct declarations. The list is distinct from the native definitions that lowering built, which its preparation record lists, from dynamic construction replay and from the hidden cache of base gates. work_units and evaluated_contexts report the fold's own bounded operation counts, which are neither CPU time nor measured memory. The fold's work limit grows in proportion to the Program's own checking work, as described in the engineering constants.

Pauli-mask storage example

tests/test_resource_fold.py::test_metadata_only_law_and_current_mask_storage, a metadata-only case with q=60 and M=10^6, declares the current raw storage of Pauli masks as two uint64 words and a complex128 coefficient per term, 32,000,000 bytes in total. It reads no payload. The figure excludes the workspace for merging terms, indices, copies and Python object overhead, and it does not measure the cost or throughput of reading the input.

Value domains

Concrete resource quantities are real and nonnegative. Exact cardinalities are integral. Exact expected_operations and estimated or upper-bound resource values may be fractional. These restrictions belong to resources, while generic Fact values remain usable for signed and complex quantities.

Execution accounting

Count-likelihood index

The count-likelihood check keeps, in the Run, an index built from the preparation records and the identities of the measurements. Work already committed and set aside keeps its fixed random streams unavailable for another independent sample, even if its result is uncertain or never collected. Completed observations update that index once, and reopening rebuilds it once when the check is next needed. The index is not saved as a second observation history, and it does not prove independence.

Limit amendments

Every increase of a limit appends an immutable LimitAmendment to run.limit_amendments. It records the complete old and new limits, its sequence number, a timezone-aware timestamp and the counters immediately before the commit, namely circuit preparations, circuit measurement attempts, explicitly prescribed raw shots, saved data bytes, pending output bytes and the dense synthesis work set aside against max_synthesis_work. The record refuses counters that exceed the old limits, counting saved and pending bytes together against max_data_bytes. Failed and unused preparations count, and so do set-aside and uncertain measurements. Host work and unknown provider-managed sampling do not become circuit or shot counts. Equal values change nothing. The new limit and its single history row commit together, and their metadata must fit the new data limit. When the check or the write fails, neither change takes effect. Recording a limit amendment uses saved bytes, so later writes can require a larger limit. run.trace and each Result's data keep the limits and the part of the amendment history that existed when they were captured, so raising the Run's limits later does not alter an earlier Result.

run.extend_limits(...) sets new cumulative limits and gives back no work already counted. Saving and reopening keep the ordered amendment history, both for a Run that started in memory and for a Run with a folder. To keep the limits and amendments it captured, a completed Result needs only a marker of the history length and the captured byte counter beside the saved Result, without another full trace or copy of the arrays. A reopened Result keeps its own identity and observations and is attached to the Run's trace as saved, apart from that marker and byte counter. Events and submissions that the Run revised after the Result, for example through a later run.cancel() and its refresh, appear there with their later status, and so does that cancellation request.

Completion-metadata allowance

The check of output bytes includes the encoded readout declaration, the declared numeric values and arrays, the application records (KernelApplication) that a classical routine declares, and ExecutionLimits.max_completion_metadata_bytes, which defaults to 65,536 bytes per completed attempt. This allowance covers variable provider annotations and undeclared application records of classical routines, but not the declaration and the declared records, which are counted already. A single-endpoint probability readout also uses it for the remaining fields of its observation, the manifests and summaries of its largest valid encoding and the rows written when its arrays are saved, and it is refused before lowering when those known costs exceed the allowance. A trajectory covers the manifests, summaries and saved-array rows of each probability point in the header bytes set aside for that point, so the allowance covers each point's job text and the completion rows. Raise it explicitly for providers that return larger metadata. A response beyond the configured limit can be rejected after measurement, and its work stays counted. Before native preparation, a static Plan on Aer checks the largest known completion-metadata requirement of all its counts, exact Pauli scalar and amplitude settings. Counts reserve the maximum decimal width of the returned-shot count both in the observation and in its completion event. Exact Pauli scalar readouts split their bytes into declaration, values and metadata in the same way. Amplitude readouts also cover their scalar summaries, their physical scale and the larger of two cases, saving the array or declaring it unavailable. These byte formulas are established for Aer, and another backend checks such readouts when it saves them. A failure to save that has already made a synchronous acquisition terminal cannot be cleared by raising a limit of that Run. Setting bytes aside allocates no output buffer.

Recovery of interrupted attempts

If an interrupted attempt has no retrievable outcome, resume() and wait() raise RunFailed with stage="recovery" and status="uncertain". This applies to interrupted classical routines and local Aer calls without a recoverable backend job. The error names the original attempt and the saved Run, and it keeps the recorded failure text, the observations and the counted work. For a reopened synchronous Aer attempt without a job locator (next paragraph), the recorded failure text is the one written on reopening, which states that the in-process result is unavailable. It does not assert that the underlying execution failed. A Method may still return a valid partial Result before this check. Remote work with a locator that can be refreshed, or with a supported way to reconcile the submission to that locator, stays pending. The order of the writes to the run log behind these recovery rules, and what a crash between any two of them leaves behind, is described in Write order and crash windows.

When an uncancelled Run reopens an interrupted synchronous Aer attempt with no saved job locator, its in-process result is unavailable. If the Method cannot return a valid Result from the saved data, both resume() and wait() raise RunFailed with stage="recovery", status="uncertain" and the original attempt identity in attempt. The exception includes the attempt record in trace, the saved Run directory and the Run's exposure. Reopening marks the submission failed with results_consumed=True to record that its output cannot be retrieved, and releases its pending output-byte reservation. The attempt stays uncertain, and its job, circuit and shot counts stay in the exposure. Inspect the saved evidence or cancel this Run. To execute the Plan again, start a new Run. Nothing is resubmitted in the original Run. Detached submissions keep that reservation while their original job can still supply output. NWQ-Sim can recover a lost launch acknowledgement from the original submission UUID even when no job locator was saved.

Report reader limits

Method discovery in the CLI and in Python

A missing or ambiguous registration raises an error, and discovery does not fall back to another registration. Discovery of the built-in Methods reads the same configuration as the Python exports. In Python, discovery returns the immutable registration records themselves, and a compact display does not replace them with strings.

Saved report reader

report PATH reads result.json through the archive JSON reader, checks the shared format and prints the saved metadata. The observations were joined to their preparation records and attempts when the Result was saved. Arrays, native circuits and caches of the iteration code are not loaded.

Saved run mechanics

QHD constrained-run directories

With solve_augmented_lagrangian(..., directory=path), every inner Run of the augmented-Lagrangian layer is created with prepare(..., directory=...) under iterations/<k>/run/, or iterations/<k>/levels/<z>/run/ with refinement, and has its own run log (Continue an interrupted run). The directory also holds problem.pickle and the outer record controller.json, which stores the arguments of the run, the configuration of the backend of every inner Run and the rounds and levels that have completed. A noisy Aer backend's model is saved once as noise-model.json, in the form that run.save uses (Aer). After each completed round or level, and once more when the run ends, the layer writes the new outer record to a temporary file and renames it over the previous one, so an interruption leaves one of the two complete records.

The outer record repeats no observation of the Runs, and its size grows linearly with the completed rounds and levels. It is rewritten after each of them, so the bytes written over a run grow with the square of their number, while the directory keeps one copy. The per-level tables.json files contain generated table-stage data. Their total saved size is the sum of their UTF-8 file lengths and grows with the number of saved levels and their table sizes. They belong to the outer layer and are not counted against the inner Runs' max_data_bytes. Their save and load memory is checked against the level Method's max_bytes.

In a refinement run directory of refine_box(..., directory=path), each level also writes its table-stage data once as levels/<z>/tables.json, which holds the search model's unscaled support tables, their evaluation count and, when it is rational, the refinement's constant C. A resumed level in progress reads these tables instead of evaluating them again, and the refinement takes C from the first level's file. Saving and loading a level file are checked against the level's QHD.max_bytes, with the earlier levels' Results counted as live data.

resume_augmented_lagrangian(path, backend=...) reads the constraint preprocessing and the check counts that the first call saved in controller.json and does not evaluate the setup's support tables and summand scans again. Resume then binds the model saved in the directory, with its errors and basis gates, to its own copy of that backend for the Runs it creates, as load_run binds a Run's saved model for the reopened Run, so every round is lowered to the Aer target of the original Runs. Resume reads the files that the layer wrote as they are, including the saved preprocessing and check counts, which it does not recompute, and does not detect an edit.

Ignored LCHS trotter_steps

With hamiltonian_evolution_backend set to dense_exact or qsp_block_encoding, LCHS does not use trotter_steps. Method validation sets that field to one before the content hash is computed and keeps a nondefault supplied value in the private attribute LCHS._ignored_trotter_steps (src/nwqlib/algorithms/lchs/method.py), which planning uses for its warning. The private record of that supplied value is not reconstructed by model_copy() or by loading the canonical Method.

ADAPT reanalysis of saved observations

During reanalysis, ADAPT checks each saved observation against its query. The Plan keeps only its most recent resolution, so a forward pass over Q distinct queries normally resolves Q points again, even when a query supplies several adjacent observations. Saving a quantum Result also derives the query constructions needed to check its saved data. These passes use the data already collected and do not run the quantum circuits again. The matrix stage validates each original query before assembly and passes one current matrix to projection. That temporary matrix is released after use and is not saved as another history, and restoring an interrupted projection rebuilds it from the original observations. Mutable Qiskit Gate definitions stay in the private Run cache for continuation and are excluded from public Result data.

Saved formats

Checks at save, load, analysis and first read

These checks of saved Results and Runs add to the checks that Saved folders are read-only lists for every load.

Check When
The Method's archive hook reconstructs its selected inputs and saved computation data. For example, LCHS restores its selected LCHSData parameters, PREP tensors and periodic Strang payload (LCHS guide) Every load
A counts observation requests at most 2**63 - 1 shots, the int64 maximum of the weights that ObservationChunk.histogram() returns, and each of its counts and their exact total are at most its requested shots When the observation is created and when it is loaded
The joins of observations to their preparation records and attempts, including the unit bound of exact probabilities When an observation is created and before a Result is saved. Loading does not repeat them
QHD's observed masses against the roundoff window of their executions (validate_analysis_masses) When QHD analyzes them, not when a Result is loaded
The block order and byte count of a reopened Run's saved array When the array is first read from the run log. Reopening registers each saved array without reading it

A trajectory readout is saved with the observation of every declared point, and their collection in the preparation record's point order must be the one its completed attempt names.

Run archive format

Current loaders support Run format 22 (nwqlib.run/22) and Result format 15 (nwqlib.result/15). In both, a classical routine declaration records application_bytes, the application record bytes that each invocation sets aside. Both formats keep the history of limit amendments. The Run format reserves the cache filename prefix for the Run. Each row of the run log stores the byte size of its JSON text in a column written with the text, and the size limits and saved-data totals read that column without reading the text. With each prepared Aer circuit, the format stores the prefix phases of its saved trajectory states and its thread cap. The run log keeps the initial limits and every later amendment.

run.json contains {format, selection, limits}: the format, the name of the selection file and the execution limits in force when the folder was written. The selection file, normally selection.json, holds the Method name and the record returned by the Method's archive hook. The built-in hooks put the Plan record in that record and hold or name the selected data there. The manifest has a fixed set of scalar fields. The header of the run log stores the original Plan identity, which is compared with the Plan restored by the Method hook. A Run writes the manifest as run.json.partial and then renames it to run.json, which replaces the file atomically within one filesystem, before it commits the header of its run log. A creation that a max_data_bytes refusal or an interruption stops before or during that write therefore leaves no run.json, and a folder with run.json holds a complete one. For a folder with run.sqlite and no run.json, load_run adds a note that the creation of the Run stopped before any preparation or measurement. The partial file stays and counts among the folder's saved file bytes. run.sqlite is the run log, which holds the current limits, the preparation and observation records, cache references and the completed Result when present. An observation row leaves out its readout declaration, which its preparation record stores, and reopening restores the declaration from that record, so a readout with many labels is stored once per preparation. The adjacent .lock file lets only one process at a time hold the Run open. These files and their referenced payloads form one Run, and run.json alone is not sufficient for continuation.

Arrays and trajectory observations

Probability readouts store their numerical values in binary arrays. The observation (ObservationChunk) contains the array manifests, the outcome layout and scalar summaries of the probabilities. Dense probabilities use outcome positions as indices. Sparse probabilities use sorted unique integer indices. The first selected probability qubit is least significant in its outcome index. Saved reports describe these arrays without loading their values. Counts keep one JSON count record per stored outcome. A trajectory readout saves one observation per point. Each names its point ID and resolved boundary, so a saved value maps to its body, point and preparation record after reload, and the point observations share the completion, preparation, shots and native work of their acquisition. A point observation stores only its own point's readout declaration and the content hash of the trajectory declaration, which the preparation record stores once.

A saved numerical array has one payload in the run log, committed with its manifest and completed observation. An in-memory Run saved later writes those same payload bytes and associations in one transaction, and no second NPY file is written for the array. Run loading registers each saved array without reading it. Its first use reads the saved blocks once per payload digest into one read-only buffer, checking block order and byte count without recomputing the digest or scanning the values, and each manifest gets a view of its own dtype and shape. An array first used after the Run is closed is read through a read-only connection to the run log, and an array already read stays readable. Run loading also registers the saved circuit file of each prepared circuit without decoding it, and the circuit is decoded at its first submission or inspection.

A reopened Run reads the bytes of an array already saved in the run log under the Run's data limit the first time the array is used, for example by run.artifacts.get(manifest) or ObservationChunk.histogram() of a probability observation, and keeps one copy of each payload for the Run's lifetime.

Loading a standalone Result checks each saved observation array against its manifest for shape, little-endian encoding (<c16 for complex128, <f8 for float64 or <u8 for uint64 outcome indices), byte count and contiguity. A reopened Run registers its arrays without reading them, and checks the shape, encoding, block order and byte count of each array when it is first read from the run log. A mismatch is rejected.

Plan inputs and method-cache arrays, which are not observation arrays, are saved as single files. An explicit Run copy or standalone Result save is a separate requested output and may contain its own payload. Copying a saved Run whose cache rows are all committed copies the files that those rows name under the same names, without decoding or encoding the cached objects again.

Result archive format

A saved Result directory contains result.json and its supporting payload files. The JSON object in result.json is {format, selection, data, result}, with format nwqlib.result/15. It is distinct from the human-readable dictionary returned by result.report(). That report is not a loadable archive. The selection entry of this object holds the Method name and the record returned by the Method's archive hook. The built-in hooks put the Plan record in that record and hold or name the selected data there. LCHS, for example, saves its node tables, the actions of its classical routine (host_actions) and SELECT angle tables in their own JSON file with NPY arrays. A Run folder uses its separate run.json and run-log format, described in Run archive format.

Circuit entries and Qiskit UCGate compatibility

Ordinary native entries use QPY, Qiskit's binary circuit format, and entries containing UCGate use the versioned NWQLIB-QPY-UC1 container around a QPY storage representation.

In Qiskit 2.6.0, a native UCGate can be written by QPY but cannot be loaded, because QPY passes the gate's matrix table as separate positional constructor arguments, whereas UCGate expects one list. This is a constructor reconstruction defect and not evidence of a reversed qubit order.

NWQLib stores an encountered UCGate as a compact QPY instruction carrying its matrix table and explicit reconstruction fields. A file prefix NWQLIB-QPY-UC1 identifies this versioned container. The reader restores the nominal number of qubits, existing simplified table, active controls, up_to_diagonal, label and any already materialized definition. Circuit phase and wire associations remain attached to their original circuit. Under this SDK's convention, the target is wire 0 and simplified control indices are one-based within the gate, from 1 through num_qubits - 1. Loading does not rerun multiplexor simplification or expand the table to all nominal controls. Saving does not synthesize a UCGate or construct its full matrix.

Use NWQLib's archive loaders for these .qpy entries, because the prefix makes them containers rather than standalone input for qiskit.qpy.load. Circuit files without this adaptation remain ordinary QPY. An old raw QPY entry affected by the upstream bug still reports its original SDK failure, with the entry path. The loader cannot infer missing reconstruction fields from it.

The small test_native_ucgate_qpy_limitation_canary test exercises raw QPY on one two-qubit UCGate, without simulation or synthesis. If native loading succeeds or its failure changes, reassess the codec against simplified controls, diagonal flags and executable round trips on that SDK.

Method record formats

QPE archives use qpe/<estimator>/8, which also store the selected polar base of a unitary input. QCELS keeps analysis source qpe.qcels.analysis version 3, and the other estimators use qpe.analysis version 3. Earlier development formats require their original source/environment.

The outer record of a QHD constrained run uses qhd.constrained_run/5, and that of a standalone refinement uses qhd.refinement_run/6. A saved constrained-result archive uses qhd.constrained/4, and a saved standalone refinement archive uses qhd.refinement/4, both with inner QHD Results in qhd/8. These format names belong to the layer and Method records, separately from the common Run and Result formats.

Backend adapter contract

Adapter rules

Every connection implements one adapter contract, stated in the module docstring of nwqlib.backends.connection. The shared execution code in nwqlib._prepared_execution saves the intent, locator and observations around each adapter call and relies on these rules.

Rule Failure it prevents Code
Readout, shots and batch shape are checked before preparation, upload or submission Provider work spent on a request that cannot produce the chosen observation each adapter's target_for and admit_batch
Intent and the submissions set aside are saved before one create request, and NWQLib never retries a create request. The IBM SDK's own safe retries are described in IBM Runtime A duplicate job after a lost acknowledgement submit_detached and each adapter's launch
A lost acknowledgement is matched only by the label written at launch. No match or several matches leave the intent uncertain and counted Binding another job, resubmitting, or refunding work whose outcome is unknown refresh_submissions and each reconcile
Refresh reads the original job, saves each completed item once and keeps earlier items when a later item fails Duplicate or lost observations refresh_submissions and each refresh
Cancellation is one request to the original job, confirmed only by later status, and the submissions stay counted Treating a request as a confirmed cancellation or a refund Run.cancel and each cancel
Measurement maps and bit order are translated in one decoder per adapter Reordered bits or mixed registers _decode for IBM, IonQ and Nexus, _submit_aer_execution for Aer, and NWQSimBackend._result with the runner for NWQ-Sim and Slurm
Gate-angle units are fixed at preparation IonQ native gates read in the wrong unit IonQBackend.prepare keeps native turns and lowers QIS gates in radians
Bytes that NWQLib reads or parses are checked against the byte limit before the read or parse Unbounded provider responses or saved payloads run.check_data calls in each adapter
The qualification notice states offline versus live evidence once per Run Reading an offline transport check as live device or site qualification each adapter's qualification_notice

Nexus preparation stages

Called on their own, the upload and compile methods of NexusBackend save none of the records that make execution recoverable, so the shared execution code records each of the following steps itself. Upload and compilation are preparation work, not quantum executions or measured shots.

Stage Saved before the outgoing request Acknowledgement / continuation
Local selection Preparation ID, snapshot, original observation and register maps, and NexusPreparationData with pytket Circuit.to_dict() JSON prepare_local replaces each dense UnitaryGate by its exact synthesis from the Run's synthesis cache (each distinct matrix synthesized once per Run object, its work set aside against the Run's cumulative max_synthesis_work), uses public Qiskit transpile to u/cx at optimization level 0, then invokes the public Qiskit-to-pytket converter once, without simulation
Upload Upload intent, exact local data, project, unique nwqlib:upload:<id> name upload returns public CircuitRef JSON. Save it before compilation
Compile Compile intent, acknowledged input reference, target/configuration, optimization level, nwqlib:compile:<id> name start_compile returns a JobLocator. Save it before refreshing
Compile pending Original locator and input reference refresh_compile returns BackendRefresh, with no repeated upload or compilation
Compile complete The input and output that the completed result associates refresh_compile returns NativePreparation holding the compiled CircuitRef in provider_options_json.compiled_program
Execute The accepted submission and the ordered list of programs and shots launch returns an execution locator immediately, and the shared execution code saves it

reconcile_preparation queries the original name and project. Compile-stage reconciliation also requires input_ref_json. reconcile checks the execution association. Both inspect at most two matches through the public paginated iterator. No match leaves the intent unresolved, and multiple matches raise. Neither case permits an automatic replacement upload, compile, or execution.

upload and start_compile require an acknowledge callback from the shared execution code. Upload calls it with the circuit ID and compile calls it with the JobLocator immediately after the SDK returns, before the serialization and limit checks that can fail. After an upload acknowledgement followed by serialization failure, restore_upload reads that same public circuit ID and never uploads again. restore_preparation parses the saved provider model. refresh_compile takes a load_data callback and invokes it only after successful terminal compilation and output association, so queued refreshes do not load the saved circuit JSON.

NWQ-Sim runner protocol

The runner protocol is nwqlib.nwqsim/3. Amplitude output uses a separate interleaved binary64 file, and a probability marginal a separate dense binary64 file of 2**k values, one per outcome of the k observed qubits. Bit zero of a marginal index is the first observed wire. For observed wires (0,...,k-1), a native basis index contributes to x & (2**k-1). Unobserved high bits are summed into that bin. The native index itself is the bin only for an identity-ordered full-register observation. The result names each file with its dtype, element count and bytes, the adapter checks them before reading it, and the runner removes the files of an attempt that ends without its terminal result. The U/CX request writer checks each compact JSON gate against the byte limit before encoding or writing it. A U gate costs at most 134+digits(q) bytes and a CX gate at most 38+digits(q0)+digits(q1), plus its list separator. The bytes of fixed request fields and the closing suffix are set aside separately. The writer validates finite binary64 parameters and verifies each actual encoded length against the bound.

With MPI, rank zero alone forms the histogram and writes it. It projects each outcome onto its classical bits in the sampler's outcome array, sorts that array and counts runs, so each distinct outcome is formatted once.

After a local launch intent loses its acknowledgement, the adapter checks the file metadata and the existing child process through can_reconcile, without a replacement launch or refund.

NWQ-Sim trajectories

For a CPU/SV trajectory, the adapter marks each point boundary of the bound body with a labeled barrier, lowers the body through its last point to U/CX once, and reads each boundary's native gate position from its barrier. Nothing after the last point runs. A view's tail is lowered once, and its inverse is the reversed, adjointed tail of that lowered sequence. The runner evolves the gates between consecutive boundaries with one NWQ-Sim call each, without resetting or renormalizing the state, and applies a view's inverse only when a later point follows. Pauli values are keyed by point and label, each marginal is a dense binary64 file and each saved state a complex128 file.

The trajectory's metadata check includes each point's upper bound on variable bytes and the net growth of the detached outcome rows. The provider-status row is committed first and credited when the outcome is saved. A new outcome adds 161–168 JSON bytes, or 92–99 when a forecast has already revised the event, and the check sets the maximum aside. Local job identifiers are 36-character UUIDs, so the JSON of the empty values of each point other than an amplitude point costs 58 bytes. Acknowledgement and status writes use the Run's cumulative data capacity while the output bytes remain set aside. The readouts of a restored preparation are checked against the bytes of their saved rows when they are saved. A single-endpoint probability readout, locally or through Slurm, adds the same detached outcome bytes, 168 or 99 with a forecast, to its metadata check, which refuses the readout before the runner starts when max_completion_metadata_bytes cannot hold it.