Scientific computing workflows: input models, solvers, and reproduction

English · 简体中文

This chapter strings together the complete examples of QHAM, Carleman, LCHS, and CBMD, explaining which objects each step produces, which interfaces remain open, and which conclusions the results can support. The paper keeps the mechanism and representative experiments; full usage, input variants, and step-by-step walkthroughs continue to be maintained here.

Start by distinguishing three layers

Layer

What it decides

Typical objects

Mathematical input and approximation

PDE, discretization, truncation order, initial-state norm, physical output window

PolynomialPDE, QHAMPlan, PolynomialODE

Quantum-algorithm generation

which solver and internal simulation kernel to choose, which module calls to generate

QODESolver, linear_qode, carleman_qode

Implementation binding and execution

implement the open slots with gate tables, QRAM, or arithmetic, and supply run-time data

the RIR Program, Binding, memory snapshots

Replacing the solver usually requires regenerating the program; swapping a compatible implementation into an existing slot can use bind. Changing the public bit width or an alpha already used for assembly cannot be done by merely replacing a label. Identical mathematical conditions and interface compatibility are also two different things: a generator being able to provide a BE does not mean it satisfies the conditions of every QODE method.

QHAM: from the equation to the lifted linear input

Read the minimal QHAM tutorial first. What follows includes the actual example source directly, so imports, equations, data, and solver configuration always match the repository scripts.

QHAM assembly of the forced Burgers equation and two solver choices.
from functools import partial

from oracq.algorithms.common.hamiltonian import taylor_hamiltonian
from oracq.algorithms.qode.ode import linear_qode
from oracq.applications.qham import (
    Discretization,
    Field,
    Grid,
    Known,
    PolynomialPDE,
    QHAMPlan,
    qham_input_model,
    structured_fd_bindings,
)

u = Field("u")
pde = PolynomialPDE.from_equations(
    {"u": 0.1 * u.d("x", 2) - u * u.d("x") + Known("forcing")},
    label="forced_burgers",
)
plan = QHAMPlan(pde, order=2)
grid = Grid(("x",), (4,), (1.0,), boundary="periodic")
space = Discretization(pde, grid, {"forcing": [0.05, 0, -0.05, 0]})
initial = space.encode_fields({"u": [0.1, 0.2, 0, -0.1]})
bindings = structured_fd_bindings(space, initial)
qode_input = qham_input_model(plan, bindings, eta=-0.4)

# Schrödingerization accepts a general generator; the recovery interval and time approximation still need later validation.
solver = linear_qode(
    "schrodingerization", hamiltonian_function=partial(taylor_hamiltonian, degree=1)
)
solution = qode_input.solve(solver, 0.01)

# To switch to CBMD/LCHS instead, first apply an explicit global shift that makes the generator dissipative.
shifted = qode_input.dissipative_shift()
cbmd_solution = shifted.solve(
    linear_qode("cbmd", hamiltonian_function=partial(taylor_hamiltonian, degree=1)), 0.01
)

Read this code through the lifecycle of its objects:

  1. Field and Known build the unknown fields and the forcing terms referenced by name. The expression describes the field to be solved for; it does not apply an ordinary numeric function directly to quantum amplitudes.

  2. QHAMPlan(pde, order=2) fixes the finite HAM truncation and the quantum-adapted linearization rules. The plan can be saved, restored, and queried row by row; the full lifted matrix is never materialized up front.

  3. Grid and Discretization determine the periodic grid, the derivatives, and the known data. Boundary conditions belong to this layer; they are not guessed by the solver.

  4. structured_fd_bindings builds the port BEs from shifts, local coefficients, and multilinear contractions; qham_input_model then assembles the lifted generator and the initial state. The relative norms of the lifted initial state’s tensor blocks must be preserved; the blocks must not be normalized independently.

  5. linear_qode selects the linear-evolution method; partial(taylor_hamiltonian, degree=1) explicitly fixes this example’s approximate simulation kernel. solve produces a state oracle carrying signal and recovery information; it does not directly return a certified-converged classical PDE solution.

  6. dissipative_shift explicitly modifies the generator to meet another method’s preconditions. The corresponding growth-recovery factor must not be dropped, and this step must not be treated as a free way to raise the success rate.

The original method is in the QHAM paper by Xue et al.. The derivation of the finite closure, the tensor-block weights, and the physical output is in the QHAM mathematical notes; general polynomials, forcing, and discretization restrictions are in the full implementation documentation.

Carleman: open coefficient ports and a replaceable linear solver

The Carleman route starts from the coefficient ports of a polynomial ODE and builds the tensor lifting according to the chosen cutoff. Its truncation object differs from QHAM’s; the two truncation orders must not be treated as one shared accuracy parameter.

Carleman example fragment; imports, grids, and the linear-solver configuration are in the full script.
# 6. Ordinary PDE -> F_p ports -> Carleman -> replaceable linear solver.
u = Field("u")
pde = PolynomialPDE.from_equations({"u": 0.1 * u.d("x", 2) - u * u.d("x")})
space = Discretization(pde, grid)
ports = structured_fd_bindings(space, space.encode_fields({"u": [0.1, 0.2, 0, -0.1]}))
concrete = polynomial_from_bindings(ports)
coefficient_slots, bindings = [], {}
for order, coefficient in concrete.coefficients:
    name = "BurgersF" + str(order)
    slot = abstract_block_encoding(
        name, coefficient.width, coefficient.signal_qubits, coefficient.alpha
    )
    coefficient_slots.append((order, slot))
    bindings[name] = coefficient.operation
initial_slot = abstract_state_prep(
    "BurgersInitial", concrete.width, concrete.initial.work_width
)
bindings["BurgersInitial"] = concrete.initial.operation
problem = PolynomialODE(
    concrete.width, tuple(coefficient_slots), initial_slot, concrete.initial_norm
)
recovery: dict[str, float] = {}
for method, solver in (
    ("schrodingerization", schrodinger),
    ("shifted_lchs", shifted_solver(lchs, recovery)),
):
    nonlinear_solver = partial(carleman_qode, linear_solver=solver, cutoff=2)
    state = qpde_solver(nonlinear_solver)(PDEInput(problem, "burgers"), time)
    records.append(
        write(
            "burgers_carleman_" + method,
            state,
            bindings,
            notes={
                "initial_norm": concrete.initial_norm,
                "cutoff": 2,
                **(recovery if method == "shifted_lchs" else {}),
            },
        )
    )

Here concrete.coefficients stores the coefficient BEs grouped by nonlinear degree. The loop establishes, for each coefficient, an open declaration with the same width, signal layout, and alpha, while keeping the matching implementation in bindings. BurgersInitial is likewise an open state-preparation slot; the original initial-state norm is carried separately by the host.

PolynomialODE therefore describes the mathematical input; carleman_qode performs the lifting; the passed-in linear_solver handles the lifted linear problem. The script uses Schrödingerization and an explicitly shifted LCHS respectively, and writes the recovery factors into the records. Running the generator twice does not mean the two methods agree in success probability, truncation error, or circuit cost.

Mathematical conventions and interfaces are on the Carleman page. Download the full input-model script with all the context.

LCHS: gate-table and QRAM bindings on the same open graph

An LCHS configuration contains a quadrature plan and a simulation kernel for the Hermitian branch. The Cauchy nodes and the first-order Taylor kernel in the example are concrete approximation choices, not a target accuracy that the language guarantees by default.

Without changing the caller, only the angle-database and initial-state bindings differ.
# 3. Same open graph, bound to gate / QRAM databases and state preparations respectively.
# The data is an integer encoding of rotation angles; A=diag(1, cos(pi/4)), G=-A.
angles = abstract_database("DiagonalAngles", 1, 2)
a = diagonal_block_encoding(angles, alpha=1.0)
state = lchs(scale(-1, a), initial, time)
records.append(
    write(
        "given_xor_gate_lchs",
        state,
        {
            "DiagonalAngles": gate_database(1, 2, [0, 1]).operation,
            "Initial": initial_gate.operation,
        },
    )
)
records.append(
    write(
        "given_xor_qram_lchs",
        state,
        {
            "DiagonalAngles": Binding(
                qram_database(1, 2).operation, {"table": "diagonal_angles"}
            ),
            "Initial": Binding(qram_state_prep(1, 2).operation, {"angles": "initial_angles"}),
        },
        memory={
            "diagonal_angles": [0, 1],
            "initial_angles": qram_state_angles([1, 1], 2),
        },
    )
)

DiagonalAngles stores rotation angle words, and diagonal_block_encoding interprets them as matrix coefficients. Initial is an independent initial-state preparation slot. The two write calls receive the same open state but choose the gate-table and QRAM implementations respectively; the latter must additionally supply the two tables in memory.

The script’s save_case saves the open, the first partially bound, and the final closed RIR separately, and produces modular backend text. Intermediate resource capture remains an explicit parameter; memory contents are never stuffed into the RIR. For a smaller standalone example see comparing implementations of an open program; method configuration and applicability conditions are on the LCHS page.

CBMD: reusing QHAM input while handling the dissipative precondition explicitly

QHAM ports that were already built are handed to CBMD through an explicit adaptation.
# ---- CBMD: QHAM open-coefficient input through a dissipative shift (input model reused across families) ------------
model = qham_input_model(plan, open_bindings, eta=-0.4)
state = model.dissipative_shift().solve(cbmd, 0.01)
records.append(
    write("cbmd_qham_coeff_qram", state, qram_dict, memory=qram_memory)
)

CBMD’s method originates in contour matrix decomposition by Wang et al.. This code reuses the QHAM plan and the open coefficient input instead of reinterpreting the PDE inside CBMD. The shift and recovery information produced by model.dissipative_shift() is a mathematical adaptation; only the final qram_dict and qram_memory constitute the implementation and run-time data binding.

Nodes, residues, the finite truncation, and the omitted terms are on the CBMD page. Do not infer from check().ok that the contour truncation error or the physical recovery error has been certified.

Input-model variants and reproduction

The input-model variant script provides structured ports, spectral constructions, and QRAM data paths for four families. It is a construction example, not a cross-method accuracy leaderboard.

PYTHONPATH=src python examples/general_qham.py
PYTHONPATH=src python examples/ode_input_models.py
PYTHONPATH=src python examples/input_models.py
PYTHONPATH=src python examples/research_workflow.py
PYTHONPATH=src python tools/build_qlss_comparison.py

Real-backend checks must use an interpreter with both pysparq and uniqc installed:

PYTHONPATH=src /path/to/backend/python examples/research_workflow.py --native
PYTHONPATH=src /path/to/backend/python tools/run_verification.py \
  --group oracles --group blockencoding --group arithmetic \
  --group mathfunc --group qham_qfvm --group ode

Numerical reports should explain three things separately: the implementation error of the circuits against an independent finite-precision reference, the method error of the finite precision or truncation relative to the original problem, and the post-selected success rate together with physical norm recovery. “The program exports”, “the backends agree with each other”, and “the method converges on the original PDE” are distinct conclusions. The full boundaries are in Applicability boundaries and validation status.

Format details, resource data, and extras

Detail to look up

Where it is maintained

RIR nodes, the full serialization text, and the OriginIR-ext grammar

RIR specification

Open declarations, batched binding, resource capture

Open IR

Intermediate representation and serialization of mathematical functions

Math IR

Toffoli/rotation/QRAM counts, generic scale sweeps

Resource estimation

QFVM inputs, Roe arithmetic, and the recovery chain

QFVM documentation

The wider algorithm catalog and validation status

Algorithm catalog, validation coverage

Classes, functions, parameter names, and returned objects

API reference

These pages carry the full implementation and tutorial detail. Non-core algorithms remain maintained in the library, but their count is not used as evidence of the framework’s superiority in the paper. The cross-framework code-size experiment scripts remain under tools/expressiveness/; they are not part of the evidence retained for the paper.