🤖 AI text below 🤖
What
MonomialPropagator._bind builds a new Python list[float] on every call.
src/monoprop/monomial_propagator.py:602-612
def _bind(self, parameters: ParameterValues) -> list[float]:
if isinstance(parameters, Circuit):
parameters = parameters.parameters
if parameters is None:
return []
return [float(v) for v in parameters]
Why this is a problem
_bind is on every evaluation entry point — expectation_value, expectation_value_and_gradient,
gradient, contract_partially, evolved_operator, and, most importantly, inside the closures
returned by expectation_value_functional and expectation_value_and_gradient_functional:
fn = self._simulator.expectation_value_functional(pare_threshold)
return lambda parameters=None: fn(self._bind(parameters))
Those functionals exist precisely to be called in an optimizer loop. So each iteration allocates N
PyFloat objects plus a list, then throws them away — for a parameter vector that the caller almost
always already holds as a contiguous float64 ndarray.
Suggested fix
Pass a contiguous float64 ndarray straight through when the caller supplied one, and widen the
return annotation from list[float] to Sequence[float] | np.ndarray:
if isinstance(parameters, np.ndarray):
return np.ascontiguousarray(parameters, dtype=np.float64)
return [float(v) for v in parameters]
nanobind's stl/vector.h caster accepts any Python sequence, so the C++ signature
(const VecD &) is unchanged. To be clear about the size of the win: this removes the intermediate
Python objects, not the element-by-element conversion on the C++ side — list_caster still
iterates.
If the measured gain is small and the path matters, the larger version is to add an
nb::ndarray<const double, nb::ndim<1>, nb::c_contig> overload for the evaluation entry points in
src/monoprop/bindings/binder.h, which removes the per-element conversion too. That is a bigger
change to the binding surface and probably wants its own issue.
Verification
just bench serial before/after, comparing benches/results/REPORT.md; the functional-driven
benchmarks are the ones to watch.
just test — in particular the paths that pass lists, tuples and Circuit objects rather than
arrays, which must keep working unchanged.
Found by a code-reading review of the repository at 29a8050. No build tree was available, so the
analysis is from source inspection and should be confirmed by measurement.
🤖 AI text below 🤖
What
MonomialPropagator._bindbuilds a new Pythonlist[float]on every call.src/monoprop/monomial_propagator.py:602-612Why this is a problem
_bindis on every evaluation entry point —expectation_value,expectation_value_and_gradient,gradient,contract_partially,evolved_operator, and, most importantly, inside the closuresreturned by
expectation_value_functionalandexpectation_value_and_gradient_functional:Those functionals exist precisely to be called in an optimizer loop. So each iteration allocates
NPyFloatobjects plus a list, then throws them away — for a parameter vector that the caller almostalways already holds as a contiguous
float64ndarray.Suggested fix
Pass a contiguous
float64ndarray straight through when the caller supplied one, and widen thereturn annotation from
list[float]toSequence[float] | np.ndarray:nanobind's
stl/vector.hcaster accepts any Python sequence, so the C++ signature(
const VecD &) is unchanged. To be clear about the size of the win: this removes the intermediatePython objects, not the element-by-element conversion on the C++ side —
list_casterstilliterates.
If the measured gain is small and the path matters, the larger version is to add an
nb::ndarray<const double, nb::ndim<1>, nb::c_contig>overload for the evaluation entry points insrc/monoprop/bindings/binder.h, which removes the per-element conversion too. That is a biggerchange to the binding surface and probably wants its own issue.
Verification
just bench serialbefore/after, comparingbenches/results/REPORT.md; the functional-drivenbenchmarks are the ones to watch.
just test— in particular the paths that pass lists, tuples andCircuitobjects rather thanarrays, which must keep working unchanged.
Found by a code-reading review of the repository at
29a8050. No build tree was available, so theanalysis is from source inspection and should be confirmed by measurement.