Release Notes

Upcoming release (main)

New Features

  • The bitstrings_to_ci_strings_symmetrize_spin function has been added, which allows one to obtain a collection of CI strings (with alpha and beta determinants symmetrized) from a collection of bitstrings.

  • recover_configurations now performs its per-bitstring correction in parallel when the consumer compiles with OpenMP enabled. The library remains header-only and depends only on the C++ standard library; the parallel path is guarded by _OPENMP and the serial behavior is used otherwise.

  • recover_configurations validates all of its inputs before it corrects any bitstring. An unsatisfiable request – one whose target Hamming weight cannot be reached because too few eligible bits carry a nonzero weight – is now reported before any work begins rather than part way through it.

  • recover_configurations draws randomness from independent per-work-item streams rather than sequentially from the supplied generator, in serial and OpenMP builds alike. A counter-based engine – one whose set_counter accepts its counter type, such as the C++26 std::philox_engine – is keyed by bitstring index, so its result is identical for any number of threads and identical between a serial build and an OpenMP one. Any other engine must be seedable and is copied per thread; its results are valid, agree with a serial build when OpenMP runs on one thread, and depend on the thread count beyond that. A generator that is neither counter-based nor seedable is rejected at compile time under OpenMP but remains usable in a serial build.

    The counter-based tier is not specific to std::philox_engine. An engine qualifies by naming a counter type – either a static word_count from which the standard std::array<result_type, word_count> shape is derived, or a nested counter_type – and accepting it in set_counter. See Random number generation for the reproducibility guarantees of each tier.

Upgrade Notes

  • The weighted sampling-without-replacement used by recover_configurations now draws from a Fenwick tree rather than from a std::discrete_distribution with rejection of already-drawn indices. Each draw is O(log n) with no rejection and no rebuilding, and consumes exactly one variate from the supplied generator. The sampling scheme is unchanged – it remains the sequential scheme, drawing index i with probability proportional to its weight among those not yet drawn – but the specific bitstrings returned for a given seed differ from previous releases.

  • The numerical output of recover_configurations for a given seeded generator has changed. The returned unique bitstrings are now emitted in first-seen (input) order rather than in an unspecified order, and randomness is now drawn from per-work-item streams, so the specific bitstrings selected for a given seed differ. The returned distribution remains a valid refinement; only the selection and its ordering change.

  • A caller that catches the exception from an invalid request sees it raised earlier than before: the generator is no longer partially advanced, and a malformed bitstring is reported even when it follows bitstrings that would have corrected successfully.

  • recover_configurations leaves rng in a substantially different state than before, in a way that depends on which tier the generator falls into. Either way it first draws two values to form a base seed. A seedable engine is then re-seeded from that base seed. A counter-based engine is instead re-keyed and its counter repositioned for each bitstring, so on return it is left keyed to the base seed and positioned at the last bitstring’s substream rather than anywhere related to where it started – its original key is gone. In both cases the number of values consumed is unspecified. Callers who continue to draw from the same generator afterwards should not rely on its resulting state; pass a copy if the original state matters.

Bug Fixes

  • Fixed sampling without replacement returning an out-of-range index when the weight vector spanned a wide enough range of magnitudes that accumulating it lost the smaller weights entirely (for instance {1e-12, 1e12}). The index was used to subscript a parallel array, so the result was an out-of-bounds read and the flip of an arbitrary bit. Such a weight vector can now also exhaust its whole pool, where previously drawing from it could repeat an index or fail outright.

Other Notes

  • When the compiler asserts finite-math-only semantics (i.e., when __FINITE_MATH_ONLY__ is defined to a nonzero value, as it is under -ffinite-math-only and -ffast-math), the library now omits its now-redundant checks for NaN and infinite weight values.

  • The library will emit a compiler warning if __FAST_MATH__ is defined, as the use of -ffast-math breaks the IEEE 754 semantics that the library relies upon and is not supported. See Compilation flags for details.