Skip to content

Code tour

This page shows where each part of NWQLib lives in the source and in what order to read it. Follow one scientific Problem through its configured Method, the Plan it makes, the run that collects data and the Result. Inputs and initialization are defined in separate places, so two methods may target the same Eigenproblem with different trial states.

Before reading code, set up a development environment with Set up, test and build.

The Glossary defines the words that docstrings and these pages use in a narrow sense, such as Plan, Run, Program and preparation record. The terms used only by QHD and its augmented-Lagrangian and box-refinement layers are in the QHD guide.

Conventions gives the sign, phase, qubit-order, unit, normalization and error-quantity conventions shared across families, with the module that states each one.

Find the source and reason for a line of code

A developer reading an unfamiliar line usually needs to know which paper and equation it implements, which assumption makes that equation applicable, and why the code differs from the paper where it does. Look in these places, in this order.

  1. The docstring and nearby comments. A scientific module names the paper, its arXiv version and the equation, theorem or algorithm that each function implements, together with the assumptions the result needs and the units, normalization and shape of the output. A comment next to a rearranged formula gives the step that connects it to the paper. Where NWQLib departs from the paper, the comment states the departure and its reason. Where the paper itself contains an error, the comment gives the correction and the independent check behind it.
  2. The family guide's source map. Each guide under docs/algorithms/ has a section that lists the implemented steps with their source equations and the functions that implement them. Use it to go from an equation to its code. References links every source map, including those of the subroutine pages.
  3. The bibliography. References gives each citation with the paper version whose numbering the docstrings and source maps use. Equation numbers can change between arXiv versions, and occasionally between the PDF and HTML renderings of one version. Where the renderings are known to differ, the citation gives both numbers. Every page, docstring or comment that names a paper gives that paper's identifier in the same file, in the form Ding–Lin arXiv:2211.11973v2, or with a DOI or ISBN when the work has no arXiv version. References is the library's citation catalogue. The mathematics page also ends with the paper versions that its equation and theorem locators use, and References lists each of those papers too.
  4. The constants registry. The contribution policy requires every hardcoded tolerance, cap and default to have an entry in Engineering constants with its value, file, reason and the condition for revisiting it. Search that page for the constant's name or value.
  5. The design rationale. Records, the Run lifecycle, archives and backend adapters follow rules that no paper supplies. Design rationale names the failure each rule prevents, the invariant it keeps and a test that fails when the rule breaks.

To learn one family before changing it, read its guide from the problem definition to the source map, then the cited paper sections, then the modules listed under Follow a family in the order given there. Walk one expectation through the code shows the Plan, Run and Result lifecycle that every family shares. Maintenance lists the checks each kind of change needs.

Reading order

  1. src/nwqlib/scientist.py defines the public functions plan, compare, estimate, prepare, submit and solve and the functions that load saved results. search.py ranks a supplied finite set and can rescore its existing Comparison without replanning.
  2. problems/records.py, problems/inputs.py and operators/inputs.py define the mathematical inputs, the meaning of each output and the checked physical input data. Inputs keep coordinate, phase and normalization information, and each Method configures its own initialization.
  3. core/planning.py defines the shared Plan with its original Problem, Method, output, saved random-number-generator state, selected construction and reconstruction data. core/analysis.py defines Result and its attached immutable RunData. Reanalysis produces a new Result from that data.
  4. algorithms/protocol.py defines Method planning, execution, analysis and optional verification/archive hooks. algorithms/registry.py finds the trusted Method implementations that a caller names explicitly, and listing the available Methods does not load external implementations. Configured Method types supply their schemas.
  5. ir/ and blocks/ hold the Program's definitions, ordered calls, arguments and ports, and the selected bindings to backend code. ir/validation.py checks a Program before any other part of the library reads it, and the docstring of its checking class _Admission gives the qubit state, coherence-epoch and correlation model. blocks/selection.py binds each selected definition to trusted backend code by its exact content hash, and blocks/lowering.py builds the Qiskit circuit from that same definition body. resources/fold.py totals resources from the applicable analytical cost rules (ResourceLaw) of the Program without building circuits, and an unknown cost stays unknown.
  6. execution.py declares the observation, preparation-record, submission and limit records. _prepared_execution.py defines the Run with its preparation, submission and collection, the shared caches of backend objects and the cumulative exposure. Backend connections in backends/ take the selected readout and return the backend configuration that was used. _run_journal.py writes the Run's progress to its run log on disk.
  7. backends/assessment.py, profiles.py and telemetry.py keep each device-profile prediction associated with its original allocation and with the executions measured against it. inspection.py, called explicitly, inspects the backend circuits that were built, and formula estimates are not gate counts of optimized circuits.
  8. evidence/ defines scoped error facts, statistical models, criteria and explicit verification relations. Result.assess does not turn a component bound into a total error certificate. Missing evidence remains unknown.
  9. artifacts.py holds the immutable arrays that a Method requests. saved_evidence.py saves and restores Results and offers a reader for the metadata-only report. _choice_archive.py restores selected input data through known or explicitly supplied Method code, and _run_archive.py restores the execution frontier of a Run, the state from which it resumes. Loading does not replan, normalize arrays or perform scientific analysis.
  10. cli.py provides the commands algorithms, card, options, check-method and report. algorithms/authoring.py checks a Method on one case from its author, with an independently known answer and a deliberately wrong Result that the Method's checks must reject. tests/_hadamard_method.py, the test suite's reference external Method, implements a real Hadamard expectation using shared selected subroutines and Run.

The root package and several subpackages, such as nwqlib.algorithms, nwqlib.backends and nwqlib.problems, import their exports on first use. The shared workflow functions call each Method's own hooks and do not duplicate the construction or iteration logic of any family. Saved Source descriptions import and run no code. Loading the archive of a built-in Method uses the list of known implementations, while loading an external Method requires the caller to pass its implementation.

Walk one expectation through the code

Use the Hadamard Method, whose complete implementation is tests/_hadamard_method.py. For the state supplied as (1, i) and observable Y, the expected normalized expectation is one. The Method adds one ancilla and uses the existing selected blocks.

  1. Check the input and choose the output. scientist.plan chooses the default NormalizedExpectation output and calls HadamardPauliExpectation.plan. That method checks one nonzero real Pauli coefficient and matching state dimension before selecting PREP and controlled signed-Pauli blocks. Plan._bind attaches the backend code of those blocks once. The reconstruction keeps the observable scale alpha=abs(c) and the ordered ancilla Pauli label, and declarations alone cannot recreate the bindings. See Compose blocks.
  2. Read costs or prepare the same construction. scientist.estimate calls the shared computation of resource totals (fold), which reads the selected Program and its cost rules without building a circuit. scientist.prepare instead creates a Run and invokes the Method's inherited prepare_static. blocks/lowering.py::_lower_qiskit builds the Qiskit circuit from the original calls, arguments and port order, and AerBackend.prepare compiles that logical circuit for Aer and inserts the requested readout. PreparedArtifact records this preparation itself, not a second build made for reference. See Run on a backend.
  3. Collect data through the Run. scientist.submit returns the Run after starting the work its Plan selected. The inherited execute_static submits the prepared item and collects a completed observation before analysis. _prepared_execution.py sets aside the work against the Run's limits and associates each observation with its preparation record, and the backend converts the result that its SDK returned. Exact readout, in which the simulator computes the expectation without shot sampling (shots=None), subject to numerical rounding, gives the ancilla Pauli mean, while a positive shot count gives the ancilla counts of those shots. Run.wait returns the scientific Result once available. See Execution and storage.
  4. Reconstruct and validate the quantity. HadamardPauliExpectation.analyze calls _ancilla_mean on those observations, then computes value = rec.alpha * mean. For A=cP, the controlled action already includes sign(c), so this multiplies the magnitude exactly once. HadamardExpectationResult.validate_plan checks the selected scientific relation, and validate_data ties the mean and the Result to the observation they were computed from. Result._attach checks the content hashes of the original Plan and of the observations before attaching RunData. Neither analysis nor its summary measures the state again.
  5. Save, reload and reuse. saved_evidence.save_result invokes the Method's save_archive hook and saves its Result and RunData. load_result(path, method=HadamardPauliExpectation) requires that explicit trusted implementation. Its load_archive restores the selected inputs and known block bindings, and the common loader checks the content hash of the original Plan. Result.analyze() then interprets the same data again. read_report follows the separate metadata-only path. A saved Run also uses _run_archive.py and _run_journal.py to restore its execution frontier, and a standalone Result does not hold that state. See Save, load and reanalyze results and Continue an interrupted run.

When changing this Method, place each change in the code that defines that part. Input checks and choices belong in plan, the gate action in the selected block, the interpretation of data in analyze, and the saved choices in its archive hooks. Adaptive methods such as ADAPT replace the static execution hook with their own iteration loop in src/nwqlib/algorithms/gcim/adapt_acquisition.py, and they still use the same preparation, submission and observation code. The author check supplies an independent expected value and a deliberately wrong Result that must be rejected, but it does not certify every input supported by a Method.

Follow a family

Each algorithm guide has a source map from implemented steps to their paper equations and the code that implements them. References lists those maps.

  • Expectation. algorithms/expectation.py implements finite Pauli selection, grouped exact readout, parity/count inference and optional readout calibration. evidence/binary.py and statistics.py keep the statistical assumptions chosen at planning.
  • Lanczos. src/nwqlib/algorithms/lanczos/method.py selects the signed Chebyshev walk and its readout table and analyzes moments, and its sibling readout.py reads signed outcomes from the histograms of the coherent SELECT-basis readout. Its numerical.py builds the raw Chebyshev Gram matrix and pencil from moments and defines the allocation and cutoff rules. src/nwqlib/_projected_eigensolver.py thresholds and solves the pencil, and workflow.py advances explicit sensitivity sampling through the common Run.
  • GCiM / ADAPT. Read src/nwqlib/algorithms/gcim/pencil.py and src/nwqlib/_projected_eigensolver.py for the H/S pencil and its solve, then fixed_basis.py, which selects normalized trial columns and interprets transition measurements. For ADAPT-GCiM continue with adapt_inputs.py (pool and commutators), adapt.py (one query space for every adaptive query), adapt_acquisition.py (basis chains, composition of the backend circuits and the resumable iteration loop drive_adapt), bounded_growth.py (the examined extensions, CX count bound, score and sampled eligibility of BoundedRitzGrowth), then optimization.py and adapt_verification.py. src/nwqlib/subroutines/fermionic_pool.py and fermionic_circuits.py define the generators and their exact circuits. The Table V test in tests/test_fermionic_pool.py compares every operator of the default spin-adapted pool with Table V of Zheng et al., arXiv:2312.07691v3, including signs and repeated indices.
  • LCHS. src/nwqlib/algorithms/lchs/method.py, and its sibling selection.py and primary_records.py, connect LinearDynamics to selected reconstruction and Result. plan_lchs routes the problem, and select_dense pads A and calls time_independent_terms.py, which applies the PSD rule and asks providers.py for finite nodes and coefficients. parameters.py fixes every SELECT and PREP product once. quantum.py assembles the PREP/SELECT/readout Program, and native.py supplies dense, product-formula, structured and QSP leaves. host.py evaluates the selected classical model, solution_error_budget.py propagates component bounds to the physical output, and the refinement and verification modules hold the explicit checks.
  • QPE. src/nwqlib/algorithms/qpe/method.py selects the input, time scale and Hadamard-test Program, and its sibling powers.py selects each controlled power with the bounds in src/nwqlib/subroutines/trotterization/error_budget.py. numerical.py holds the numerical routines of the four estimators with their paper equations and departures. controller.py advances RWPE through the common Run, and records.py ties selected queries and powers to their original inputs and Method. Verification is explicit and keeps its nominal spectral scope.
  • QLS. Read src/nwqlib/subroutines/qsp/phases.py for the Wx convention, phase solving and the sampled sup-norm bound, then inverse.py and shortcut.py in the same package for the two polynomial families, and evolution.py for the projector-phase circuit. src/nwqlib/algorithms/qls/method.py, and its sibling host_planning.py and primary_records.py, select the original LinearSystem, encoding, spectrum and polynomial. quantum.py writes the Program's named encoding, projector, phase, query and readout calls, the numerical and norm-search modules supply explicitly chosen classical models, and verification.py states the explicit allowances.
  • QHD. Start with the module docstring of src/nwqlib/algorithms/qhd/method.py, which maps the Method to Leng et al.'s Algorithm 1 (arXiv:2303.01471v1) and states each departure with its reason. Then read src/nwqlib/algorithms/qhd/schedules.py, grid.py, kinetic.py and potential.py for the discretized Hamiltonian, with objective.py preprocessing the objective for the one-hot compiler, then initial_state.py for the start vectors and their structured preparation, compiler.py for the step order and the running phase total (the sum of the identity phases that the compiled blocks omit), binary.py for the binary register, its QFT kinetic step and diagonal syntheses, and split_step.py for the classical split-step variant. resources.py, evolution_bounds.py and circuit_errors.py hold the rotation-count formula of the generated circuit, the budgeted T estimate, the per-circuit error sources and their bounds. method.py, its sibling records.py and the decoding module connect an ordered box objective to the selected finite model, its one-hot or binary circuit, the observed candidate and the most probable point. Explicit verification keeps grid and continuum/global-optimum claims distinct. The two layers that run a sequence of QHD Plans share _outer.py, which holds their inner planning streams, remaining limits, counts and point rules. constrained.py and constrained_records.py hold the augmented-Lagrangian layer for a ConstrainedOptimization, including support preprocessing, inequality representation selection, slack caps and error records, projected readout and the planning-only round-0 entry, and refinement.py and refinement_records.py hold box refinement with its search model and stall split. _durable.py holds the saved directory of both layers, with its outer record written to a temporary file and then renamed into place, the exclusive lock that lets one call at a time continue the directory, the backend of its inner Runs with a saved noise model, and the reopening of inner Runs by resume_augmented_lagrangian and resume_box_refinement.
  • Circuit subroutines. src/nwqlib/subroutines/_multiplexors.py implements the uniformly controlled rotations, phase diagonals and dependency-projected multiplexors that the other constructions reuse, together with their CX-count formulas. src/nwqlib/subroutines/state_preparation/direct.py builds PREP from them, src/nwqlib/subroutines/lcu/core.py composes PREP, SELECT and PREP†, and src/nwqlib/subroutines/block_encoding/core.py plans a multiplexed Pauli, banded or dense-dilation encoding and then builds exactly that plan. controlled in src/nwqlib/subroutines/qiskit_compat.py adds every control. It synthesizes a directly controlled UnitaryGate from its whole controlled matrix. Any other gate takes the route that the construction selects (dense control route). On the gate-wise route controlled replaces each dense unitary in the definition by its exact synthesis before Qiskit controls the gates, and on the whole-matrix route, which "auto" takes for one control, it synthesizes the controlled matrix of each dense unitary. src/nwqlib/subroutines/_dense_synthesis.py provides both syntheses, CX and one-qubit gates that equal the matrix to rounding.

Follow the shared machinery

The layers below have no paper source. Design rationale indexes the failure each mechanism prevents, the code responsible for it and the test that checks it.

  • Saved Run. The module docstring of src/nwqlib/_prepared_execution.py states the lifecycle rules and the crash windows of a static data collection. Then read src/nwqlib/_run_journal.py for atomic transitions and the file lock, src/nwqlib/_run_archive.py for the cache write, commit and delete order, and Execution and storage. The module docstring also covers prepare(plan, settings="all"), which prepares every experiment of a static Plan before any circuit runs, and PreparationNotRebuilt, which a Run raises instead of building again a local preparation whose work is counted against the limits but which has no preparation record.
  • Backends. The module docstring of src/nwqlib/backends/connection.py states the adapter contract that submit_detached and refresh_submissions rely on. Then follow one detached adapter end to end, for example src/nwqlib/backends/slurm.py with src/nwqlib/backends/nwqsim.py, through launch, reconciliation, refresh and result decoding. Backend adapter contract lists the provider rules.
  • Error evidence. A Method's Plan carries an error model listing its required error sources, and Result.facts supplies any evaluated bound. Result.assess combines them by the triangle inequality and a union bound and passes only on witnessed evidence of kind proved_relation or certified_bound. An explicit verify produces facts that preparation records support, bound to the options it was given. src/nwqlib/evidence/error_model.py defines these rules.

io/streaming.py writes the ordered elementary-gate recipes of supported blocks directly as QASM within the configured limits. io/materialization.py derives the construction's expansion counts, compares the construction_id of the writer's QasmWriteReceipt with the construction's content_id and refuses a mismatch, then checks those counts and the file's byte size before importing the file, which must be the unchanged output of the same writer call. Neither operation changes the original selected experiment.

Byte and work budgets

Many numerical functions take max_bytes, and some also take max_work or max_products. Before allocating, such a function computes from the input sizes alone the bytes of the arrays it will hold at the same time (its peak live arrays) and the number of scalar operations in its dominant steps. It compares these numbers with the limits and raises ValueError, naming the operation, the requirement and the limit, when either is exceeded. operators/access.py holds the shared checks _check_bytes and _check_products, and many modules make the same comparison inline.

The reason is that array sizes in this library grow as 2**q in the qubit count q, as D² or D³ in a dense dimension D, or polynomially in a degree or step count. A small change to an input can turn a computation of seconds into one that exhausts memory partway through. Checking the requirement first turns that failure into an immediate error, raised before the operation allocates anything and, for checks made during planning, before any preparation or submission. Work that runs after data collection but whose size the Plan alone fixes, such as the projected solve of Lanczos or FixedGCIM and the updates of the RWPE iteration, is checked during planning, so an undersized limit fails before any circuit runs. The caller can then raise the limit knowingly or choose a compact representation. The contribution policy states the general rule that the check precedes the work it limits, and Input cost controls give the size formula of each input operation.

The units are the same across the library. max_bytes counts bytes. Its default 10_000_000_000 (decimal 10 GB) is defined once in _limits.py and sized so that ordinary research inputs pass, as its registry entry explains. max_work and max_products count scalar operations according to a formula written next to the check, for example 8D³ for a dense SVD of dimension D or D² for a dense matrix-vector product. Their defaults, most of them 1_000_000_000, are registered in Engineering constants.

These limits cover only part of the cost. The byte count includes the arrays whose sizes the code knows before it allocates them and any other allocation that a formula names. It excludes other workspace inside NumPy, SciPy, LAPACK and Qiskit, other Python object overhead and the rest of the process, so passing the check does not guarantee that the process fits in memory. For example, the energy-shift relation check counts NumPy's sort buffers, and the circulant classification of a Pauli block-encoding plan counts its Python integers, Fraction wrappers and dictionary entries, as their entries in Engineering constants state. The work count is a planning quantity, not measured CPU time. Each limit applies to one operation. A Method that repeats an operation, for example once per iteration or query, accounts for the repetition separately, and a computation derived from another object scales its limit with that object, as the work limit of the resource total does.

Planning combines the requirements of phases whose selected dimensions are already known before starting those phases. Successive memory phases use the largest of their live-byte bounds, including data held across phases. A refusal distinguishes a complete known requirement from a conservative estimate or from a lower bound left by interrupted counting. Later phases can need more when their sizes depend on coefficients, selected ranks, spectra or observations. operators/access.py::refuse_known_need words such a refusal, QLS and QPE call it before their first dense phase, and QHD passes the same wording through its own planning check before it evaluates the initial state.

Comments next to the checks map each term of a formula to the arrays it counts, for example 16 * n for n complex128 entries or 8 * n for n float64 entries. When you add an array to such a function, add its bytes to the formula. When you add a function whose memory or work grows with its input, add a check before the first large allocation and register any new default.

Conventions and validation

  • Contribution policy states the rules for mathematical domains, numerical tolerances, and the scientific and resource distinctions.
  • Conventions collects the sign, phase, qubit-order, unit, normalization and error-quantity conventions shared across families, and each family's own frames and recoveries.
  • Maintenance explains the checks for each kind of change, example and notebook changes, and delivery procedures.
  • Algorithm guides map calculations to their source equations and describe current method controls and limitations. References catalogues the papers and their versions.
  • Design rationale indexes the mechanisms that have no paper source.
  • ENGINEERING_CONSTANTS.md records concrete operation bounds and their reasons.
  • ROADMAP.md describes current capabilities and separately identified open work.
  • docs/scripts/golden_snapshot.py and docs/scripts/mutation_probes.py support scoped behavior comparisons and discriminating mutations. Passing them does not replace independent scientific evidence.

Input relations

For the relations in Conventions and derivations, the named code writes each one out in its docstring or an adjacent comment.

Relation Justification Code location
Coordinate index k = sum_q b_q 2**q, qubit 0 rightmost Qiskit little-endian order operators/inputs.py (ORDER)
Stored row (x, z) means i**popcount(x & z) X**x Z**z, so both bits set is Y NWQLib encoding operators/_pauli.py (PauliTerms)
SparsePauliOp coefficient of the canonical label is coeff * (-i)**phase Qiskit phase convention operators/inputs.py (operator_input)
Dense matrix to Pauli coefficients c_P = tr(P M)/D by 2×2 block recursion Standard identity operators/_pauli.py (pauli_coefficients)
Pauli product phase and the action of a word on a basis state Standard Pauli algebra in the stored encoding PauliTerms.product, apply_terms
Commuting test Even symplectic product PauliTerms.group
Qubit-wise-commuting test Equal single-qubit Paulis on the shared support PauliTerms.group
a_j† = (prod_{k<j} Z_k)(X_j - iY_j)/2 Jordan–Wigner transformation operators/_fermion.py (_ladder_image)
Normal ordering of dΓ(B)²/2 Standard anticommutator algebra operators/df.py (_contract)
Spin orbital (p, σ) on mode 2p+σ NWQLib convention operators/df.py (_rows)
Pauli L1 norm and spectral enclosure Triangle inequality, unit-norm Pauli words operators/refinement.py (_pauli_values)
Sparse spectral enclosure and norm bound Gershgorin circle theorem operators/refinement.py (_sparse_values)

Search ranking

Step Documented rule Code location
Strict dominance, Pareto front and incomparable rows Read the ranking search.SearchSelection
Objective values read from the Plans, their resource totals and forecasts Objectives search._objective_value
Association of a selection with its Comparison, and rescoring without new work Rescore a comparison search._comparison_identity, search.SearchSelection.validate_comparison
Size controls checked before evaluation Finite search frontier envelope search.scan, search._frontier_size