Skip to content

Async execution

Bethe uses Uni20's scheduler for independent calculations. It creates no thread pool of its own and never changes the global scheduler. Existing synchronous scans wait for the same operation exposed by the Async API.

For C++ build setup, start with embedding Bethe.

See app coverage for the execution and arbitrary-precision paths exposed by each frontend and their numerical limits.

State scans

#include <bethe/heisenberg_excitations.hpp>
#include <uni20/async/tbb_scheduler.hpp>

uni20::async::TbbScheduler scheduler(4);
bethe::RealExcitationOptions options;
options.count = 10;
options.execution.scheduler = &scheduler;
options.execution.max_in_flight = 8;
auto pending = bethe::heisenberg::real_excitations_async<double>(
    32, uni20::half_int{1}, options);
auto const& result = pending.get_wait(scheduler);

The periodic and open XXX and finite Lieb–Liniger APIs provide real_excitations_async. Other models using the shared real-root scanner also use continuous execution through their existing synchronous APIs. After preparing the ground reference, a bounded number of runners repeatedly solve candidates. There is no barrier between successive groups of candidates and no task graph proportional to family size. The top-K collector retains only the requested levels; ties and the first failed-state diagnostic use enumeration identity, independent of completion order. Final energy ordering and numerical convergence rules are unchanged.

max_in_flight limits active candidates, not scheduler threads. It defaults to 128. batch_size remains for clients that still use batches; both must be positive. Set BLAS/OpenMP thread limits to one when parallelizing many solves.

For finite Lieb–Liniger, include <bethe/lieb_liniger.hpp> or <bethe/lieb_liniger_open.hpp> and submit lieb_liniger::real_excitations_async(particles, length, c, padding, scan, solver) (or lieb_liniger::open::real_excitations_async for hard walls). These entry points own the physical parameters, label-window controls and solver options. The periodic result also retains its window and physical metadata. The same finite-family cutoff and top-K selection apply as in the blocking APIs. Independent MPFR scans can carry different precisions on the same scheduler; their solves use normal Uni20 dispatch, including CPU MPFR LU.

XXX two-spinon structure factor

two_spinon_structure_factor_async<Real>(sites, solver, budget, execution) owns the complete ground preparation, root solving, form-factor evaluation and ordered reduction. Each runner retains its candidate through form-factor evaluation before admitting another. A single immutable PreparedRaisingFormFactors<Real> snapshot is constructed before the runners start; it is not rebuilt per state. The synchronous entry point waits for this same operation.

All roots and factor diagnostics remain attached through sorting. Failed roots never enter the factor kernel, failed factors remain explicit diagnostics, and final spectral sums follow canonical energy order. The API retains the entire requested two-spinon family; the admission bound limits transient work, not the final result.

MPFR callers supply an explicit SolverOptions<uni20::mpreal> with finite uni20::Precision. The operation retains that precision in its prepared data, constants, empty momentum bins and reductions. Independent precisions may run on the same scheduler without changing ambient defaults.

Observable grids

evaluate_observable_grid_async(count, evaluate, collect, execution) returns uni20::async::Async<std::size_t> containing the completed point count. A bounded window retains active and completed-but-uncollected points until ordered collection releases their slots. The collector runs serially on the scheduler. The synchronous adapter instead collects on its calling thread; cooperative waits may execute a kernel there. See parallel grids.

Independent Kondo field responses also compose through this API. Capture the field/scale requests and numerical controls by value so they survive submission; return the complete kondo::State<Real>, including failed observables and work/error diagnostics. Every call has its own Euler series and quadrature scratch. No background preparation or global scheduler change is required. The frontend exposes this as --field-end, --points and --threads, collecting rows on its calling thread.

The Potts frontend uses this grid for its charged momenta after one vacuum solve. Charge conjugation reuses each result for both charge rows. Independent library calls to potts::ground_state and charged_one_hole can also be captured as owning requests in an Async grid: retain the full returned state, including auxiliary roots, labels, residual and convergence diagnostics. The root iterations themselves stay sequential.

Ownership, dependencies and cancellation

ASEP rate curves can share an owning asep::PreparedGap through the reader-dependent grid. Preparation solves one unit-rate TASEP state; each rate query owns its continuation scratch and returns its full diagnostics. The compiled example submits the consumer before preparation is ready, then waits on the final grid result. Numerical seed failure does not erase an independently valid analytic query.

For curves that depend on a prepared model, see shared backgrounds below. They use the same bounded grid and add a reader dependency on preparation.

Async entry points own their options and decay-copied callables. References captured inside callbacks remain borrowed. The scheduler and borrowed data must outlive completion, even if the result handle is discarded. Evaluators must permit concurrent calls; prepared data should be immutable and scratch local. No operation changes ambient arithmetic precision.

Pass pending.read() to a dependent Uni20 coroutine to consume a result without a host wait. Errors propagate through the result dependency. Destroying the handle does not cancel work; use ExecutionOptions::stop with a std::stop_source. A stop request prevents further admission and follow-up work at checkpoints, then waits for active numerical kernels to finish. Kernels are not interrupted mid-solve.

Scans throw ScanCancelled (derived from ExecutionCancelled); grids throw ExecutionCancelled. They do not return a partly completed family as success. Execution errors take precedence over cancellation and propagate only after owned work has drained. Numerical nonconvergence remains a normal diagnostic value. A grid may already have emitted an ordered prefix when it fails.

Argument and budget errors are checked before scientific work. A scheduler admission failure is an execution error, not a failed root or zero spectral weight. No scheduler-wide drain is used as the completion boundary for an individual operation.

Shared backgrounds

Include <bethe/prepared_observable_grid.hpp> to submit preparation and its dependent grids together. prepare_background_async(prepare, execution) runs the synchronous preparation callable once and returns its owning result as Async<Background>. The prepared-grid overload accepts a reader and calls evaluate(background const&, index) for each point:

auto background = bethe::prepare_background_async(
    [] { return model::Solver<double>(4.0, 1.0); }, execution);
auto particle = bethe::evaluate_observable_grid_async(
    background.read(), count,
    [pmax, count](model::Solver<double> const& solver, std::size_t i) {
      return solver.at_momentum(model::Branch::type_i,
                                pmax * double(i) / double(count - 1));
    }, collect_particle, execution);
auto hole = bethe::evaluate_observable_grid_async(
    background.read(), count,
    [pmax, count](model::Solver<double> const& solver, std::size_t i) {
      return solver.at_momentum(model::Branch::type_ii,
                                pmax * double(i) / double(count - 1));
    }, collect_hole, execution);

Here model is bethe::lieb_liniger::thermo, count is at least two, and pmax is within the type-II momentum range. Both curves are submitted without observing the background on the calling thread. They share one scheduler pool; the waiting coordinators do not reserve a fixed allocation of CPUs.

Each grid holds its reader until all admitted queries have finished, including failure cleanup. Destroying the original background handle is safe after readers are enrolled. No prepared meshes are copied per grid or per point. The prepared type must support concurrent const queries; mutable scratch belongs to each query. The maximum-in-flight limit applies independently to each grid, while the scheduler limits shared CPU concurrency. This adds composition; it does not establish a speedup over an already-prepared synchronous background.

Readiness is separate from numerical convergence. Lieb–Liniger's at_momentum already checks preparation status and returns explicit failed points when the background did not converge; a zero-energy endpoint remains a successful point. The generic adapter does not invent a convergence rule for other models. Their evaluators must preserve preparation diagnostics or reject the failed background. Preparation exceptions propagate to every dependent grid, including empty grids.

Cancelling one grid stops its queries without cancelling preparation or sibling grids. A grid waiting for preparation observes cancellation after that dependency finishes. Preparation itself checks its own stop token before and after its synchronous kernel; neither preparation nor point kernels are interrupted midway. Errors in an input dependency take precedence over a dependent grid's cancellation.

Buildable examples

See complete composition examples for shared Lieb–Liniger, Hubbard and ladder backgrounds, independent ground-state/nested/complex-root sweeps, pairing/central-spin queries and finite-volume equations. Their build commands, supported precisions and output-memory behavior are recorded there. Measured benchmarks keep timing evidence separate from contracts.

Ground-state parameter sweeps.

Independent TASEP and finite XYZ solves.

Owning nested-model requests.

Prepared ladder field and rung sweeps.

Matched pipeline benchmark.

Independent Richardson and central-spin solves.

Finite-volume integral equations.