Skip to content

Parallel observable grids

An observable grid evaluates a scalar quantity at independent logical points. The model owns the physics and prepares its background; the shared bethe::evaluate_observable_grid adapter owns bounded admission and ordered collection. Uni20 supplies the scheduler. No output format or model formula belongs in the adapter.

Hubbard and Lieb–Liniger dispersion use this path. See the command-line examples. Thermodynamic XXX spectral grids also use this path, with ordered spectrum rows and final failure/threshold counts; see the frontend. Thermal Lieb–Liniger also uses this adapter for independent temperature solves in either ensemble, preserving each point's iteration and convergence diagnostics. Hubbard continuum edges also use this adapter for independent total momenta, retaining each point's inversion/search diagnostics. Sine-Gordon Bethe–Yang levels also use this adapter: each level owns an independent root search and repeated phase integrals. Other observables can adopt the same path after checking their independence and scratch ownership.

Using the library

#include <bethe/lieb_liniger_thermo.hpp>
#include <bethe/observable_grid.hpp>
#include <uni20/async/tbb_scheduler.hpp>

namespace model = bethe::lieb_liniger::thermo;
model::Solver<double> const background(4.0, 1.0);
// Validate the physical request and decide how to report background failure
// before opening output. This example samples the Type-I branch on [0, 6].
uni20::async::TbbScheduler scheduler(4);
std::vector<model::Point<double>> points;
bethe::evaluate_observable_grid(
    129,
    [&](std::size_t i) {
      return background.at_momentum(model::Branch::type_i, 6.0 * double(i) / 128.0);
    },
    [&](std::size_t i, auto point) {
      // Calling thread, increasing i: append a row or accumulate a moment here.
      points.push_back(std::move(point));
    },
    {.scheduler = &scheduler, .max_in_flight = 128});

The borrowed scheduler must outlive the synchronous call. An omitted scheduler uses Uni20's active scheduler (serial by default); the adapter never changes it. ExecutionOptions::max_in_flight bounds active and completed-but-uncollected points and defaults to 128. batch_size remains for other batched clients; both bounds must be positive. Logical indices are exactly [0, count), including empty and single-point grids. Frontends keep branches in their declared output order; a row's identity is its branch and logical point index. Hubbard dispersion flattens its selected branches into one logical grid, allowing evaluation across branch boundaries without changing row order. Lieb–Liniger currently evaluates each branch's grid in turn. In both cases the admission window bounds active and uncollected results; collected output can still grow with the grid.

Ownership, errors and timing

  • Evaluation may run concurrently. Share only immutable prepared data, use worker-local scratch, and return an owning value. Move-only results work. Do not change global precision settings or run dependent continuation steps as independent points.
  • Collection runs on the calling thread in increasing index order. Tables, diagnostics and floating-point reductions belong here, never in workers. Ordinary numerical failure/status values are passed through, not dropped.
  • As each result is collected, a new point may enter the bounded window. A slow early point or collector applies backpressure; there is no whole-batch join. Collection may retain more data; disable table retention for streaming output.
  • Execution errors and cancellation stop admission and drain every accepted task before propagating. Already collected prefixes remain, but a partial grid is never reported as successful completion. Numerical failure/status values are ordinary results.
  • The optional fifth argument, observe_wait(index, 1), scopes waiting for one point, excluding collection. It does not measure total computation: workers may compute while collection runs. Time the entire grid call for total costs. The frontends' CPU time includes background preparation and complete grid calls, including row output; CPU timing scope records this in metadata.

evaluate_observable_grid_async offers the same window policy with an owning Async result. Its collector is serialized on the scheduler, so use the synchronous adapter for caller-thread-only CLI output. See Async execution for ownership and cancellation.

When the background itself is not ready yet, prepared_observable_grid.hpp adds preparation as an Async result and a reader-dependent overload of the same grid. Several curves can share one prepared solver without copying its meshes; see shared backgrounds and the complete Lieb–Liniger example there. Threading leaves each scalar solver's arithmetic and stopping rules unchanged. Native-precision tests compare every field against independent scalar calls, using both a reverse-order scheduler and oneTBB, for binary64, native long double and binary128 when configured. CLI tests also compare row order, partial failures and non-retained file output across concurrency limits.

Performance measurements

Benchmarks by workload preserve the dated timings, commands, precision/provider configurations and whole-result comparisons.

Measuring scaling.

Initial measurement.

Hubbard branch scheduling.

Thermodynamic XXX streaming measurement.

Thermal Lieb–Liniger temperature scans.

Hubbard continuum frontend.

XYZ prepared bound branches.

Sine-Gordon Bethe–Yang level scans.

Finite-volume integral-equation sweeps.

Overlapping one vacuum/excited pair.

Kondo field-response scans.

Potts momentum scans.

Whole-process measurements.

ASEP shared seeds.

ASEP whole-process measurements.

TASEP and finite XYZ parameter sweeps.

Measurement on 28 October 2026.

Nested-model parameter sweeps.

Prepared ladder sweeps.

Biquadratic sector and bound-cluster scans.

Validation and measurement.