pub fn solve_bracketed_batch<F>(
problems: &[RootProblem],
method: RootMethod,
settings: RootSettings,
backend: ComputeBackend,
f: F,
) -> RootBatchExpand description
Solve N independent bracketed root problems without a derivative, on the
chosen backend.
This is the entry point for the common case: a residual you can evaluate but
whose derivative you would rather not write, bracketed per lane. Both
RootMethods are globally convergent on a continuous, genuinely bracketed
residual.
§Arguments
problems— oneRootProblemper lane.problems[i]’s bracket is used withf(i, ·). Theguessfield is ignored.method—RootMethod::Brentunless you specifically want bisection’s fixed, function-independent cost.settings— tolerances and iteration cap; seeRootSettings.backend— requested execution backend. What actually runs isroot_batch_backend_forapplied to it: an unavailable backend degrades,Gpudegrades (no GPU kernel here yet), and a batch belowROOT_BATCH_MIN_PROBLEMSruns serially. None of those changes the answer.f— the residual.f(i, x)must return the value of lanei’s function at abscissax. It must be a pure deterministic function of its arguments — see the module-level “Determinism” section for what breaks if it is not. It is called from multiple threads on theCpuMultipath, hence theSyncbound; the bound is present in both feature builds so that enablingparallelnever changes a public signature.
§Returns
A RootBatch with one RootSolution per problem, in problem order. An
empty problems slice returns an empty batch and calls f zero times.
§Determinism
Bit-for-bit identical on ComputeBackend::Serial and
ComputeBackend::CpuMulti, at any thread count, for a pure f. Both
backends run the same per-lane kernel and no arithmetic crosses lanes.
§Non-convergence
Per lane, never swallowed. A lane whose bracket does not straddle a root
reports RootStatus::NotBracketed and a NaN iterate; a lane that
exhausts max_iterations reports RootStatus::MaxIterations and its best
iterate, which RootSolution::root still refuses to hand out as a root.
RootBatch::roots turns any failure into an Err.
§Verification
Methodology. The batch is checked against closed-form analytic roots, which
exist for the test residuals and so are an exact oracle rather than another
implementation. (x - 1)(x - 2)(x - 3) is solved on brackets isolating each
of its three known roots, and x^2 - k against sqrt(k); the pass criterion
is |x_computed - x_analytic| <= 1e-12 and status == Converged on every
lane.
Results, measured 2026-08-12 by bracketed_matches_analytic_cubic_roots and
brent_matches_analytic_sqrt in parallel/tests.rs, release build: worst
absolute error over the three cubic roots 0.000000e0 for both
RootMethod::Brent and RootMethod::Bisection — every lane recovered
1.0, 2.0 and 3.0 exactly — and worst absolute error over the 64 sqrt(k)
lanes 1.776357e-15. All lanes converged. Interpretation: the integer
cubic roots are exactly representable and both methods land on them bit for
bit; the sqrt(k) roots are irrational, and 1.78e-15 is 8 units in the last
place at k = 64, i.e. the rounding floor. The iteration contributes no
error of its own beyond the residual’s conditioning.
§Example
use outram_foam_basic_lib::compute::ComputeBackend;
use outram_foam_basic_lib::math::parallel::{
solve_bracketed_batch, RootMethod, RootProblem, RootSettings,
};
// Three lanes, each isolating one root of (x-1)(x-2)(x-3).
let cubic = |x: f64| (x - 1.0) * (x - 2.0) * (x - 3.0);
let problems = [
RootProblem::new(0.5, 1.5),
RootProblem::new(1.5, 2.5),
RootProblem::new(2.5, 3.5),
];
let batch = solve_bracketed_batch(
&problems,
RootMethod::Brent,
RootSettings::default(),
ComputeBackend::CpuMulti,
|_, x| cubic(x),
);
let roots = batch.roots().expect("all lanes converge");
for (got, want) in roots.iter().zip([1.0, 2.0, 3.0]) {
assert!((got - want).abs() < 1e-12, "got {got}, want {want}");
}