Trim (qiskit_addon_sqd.trim)

Trim SQD, a schedule that screens more configurations than it diagonalizes.

diagonalize_fermionic_hamiltonian() diagonalizes each subsampled batch and keeps the best result. Trim SQD instead treats the batches as candidates to be screened: each is diagonalized, trimmed down to the configurations carrying the largest weight, and the survivors of all the batches are merged and diagonalized together. The energy of that merged diagonalization is what the iteration reports, and its own trimmed output seeds the next one.

The point is that screening is cheaper than diagonalizing. Each batch is roughly the size of the merged subspace, so a set of candidates several times larger than the affordable subspace can be ranked for the cost of a few extra diagonalizations of that size.

This is expressed as a SubspacePolicy, so it describes the schedule and leaves the diagonalizations themselves to whatever sci_solver the caller passes.

class TrimPolicy(*, trim_ratio=0.1, carryover_threshold=0.0001, carryover_ratio=0.1, max_strings_per_trim=None, max_carryover=None)[source]

Bases: object

A SubspacePolicy that screens disjoint batches.

Each iteration diagonalizes every batch, keeps the highest-weight CI strings of each, merges them, and performs one further diagonalization over the merged subspace. That diagonalization’s energy is what the iteration reports, and its own trimmed CI strings seed the next one.

Pass an instance as the policy argument of diagonalize_fermionic_hamiltonian():

result = diagonalize_fermionic_hamiltonian(
    hcore,
    eri,
    bit_array,
    samples_per_batch=300,
    norb=norb,
    nelec=nelec,
    num_batches=5,
    policy=TrimPolicy(trim_ratio=0.1),
)

The cost of an iteration is num_batches + 1 diagonalizations rather than num_batches, and the merged subspace is bounded by what the batches retain.

The batches are drawn as one pool of samples_per_batch * num_batches bitstrings divided among them, so that no bitstring appears in more than one. Screening selects among the batches, so it is only as useful as the batches differ: batches drawn independently would overlap heavily, which shrinks the pool of distinct candidates and gives a bitstring appearing in several batches several chances to survive the trim. Because that follows from what this schedule does, it is not something to configure.

What this makes disjoint is the configurations. The per-spin CI string arrays derived from them can still share strings between batches, since two configurations may agree on one of their halves.

Note

A subspace here is spanned by the Cartesian product of a spin-alpha and a spin-beta CI string array, so trimming to a given number of configurations retains close to that many strings in each spin sector, whose product is larger. max_strings_per_trim and max_carryover bound the strings per sector, and the max_dim argument of diagonalize_fermionic_hamiltonian() bounds the subspace that the next iteration builds from them.

Note

Whether screening improves the result depends on the system and the scale. It is worth comparing against a run without it, which is what passing num_batches=1 amounts to: the single batch is diagonalized, trimmed and diagonalized again, with no cross-batch selection. Screening has the most to offer when the sampled configurations greatly outnumber what can be diagonalized at once, and the least when a subspace of the affordable size already captures most of the wavefunction.

Initialize the policy.

This policy performs two kinds of trim, and they are bounded separately. The screening trims shrink each batch of the first round, once per batch; the carryover trim shrinks the merged subspace to what seeds the next iteration. The carryover applies the same amplitude cutoff as StandardPolicy, then a fraction of what survives it.

Parameters:
  • trim_ratio (float) – The fraction of each batch’s CI strings, per spin sector, that survive screening and enter the merged subspace. Ranked by summed squared amplitude over the other spin sector.

  • carryover_threshold (float) – Amplitude cutoff for the CI strings that seed the next iteration, applied exactly as StandardPolicy applies it, with the same default. This bounds the weight discarded rather than the number of strings kept.

  • carryover_ratio (float) – The fraction of the strings surviving carryover_threshold that seed the next iteration, ranked by summed squared amplitude over the other spin sector. The cutoff runs first and the fraction narrows what it left, so the two bound different things: the cutoff the weight discarded, the fraction the count.

  • max_strings_per_trim (int | None) –

    Ceiling on the number of CI strings, per spin sector, that any one screening trim retains. Where it binds it lowers the effective ratio, so a trim keeps min(ceil(trim_ratio * len(strings)), max_strings_per_trim) strings in each sector. Defaults to None, leaving trim_ratio to decide.

    Reach for it when a ratio would retain more than intended: symmetrize_spin merges the two spin sectors into a single list and so roughly doubles what each batch offers, and a large samples_per_batch does the same.

    This is not a bound on a subspace. It applies once per batch, and the contributions are merged, so the merged subspace can hold up to num_batches times as many strings per sector – in practice close to that, since the batches have little in common. The max_dim argument of diagonalize_fermionic_hamiltonian() is what bounds a subspace.

  • max_carryover (int | None) – Ceiling on the number of CI strings, per spin sector, that seed the next iteration, applied together with carryover_ratio to the marginal-weight ranking of whatever survived carryover_threshold. Unlike max_strings_per_trim this governs a single trim of the merged subspace, so it bounds the carryover as a whole.

Note

Neither ceiling constrains a solver that selects its own carryover through carryover, whose selection is used as given.

Raises:

ValueError – A ratio was not in (0, 1], or a ceiling was less than one.

Parameters:
  • trim_ratio (float)

  • carryover_threshold (float)

  • carryover_ratio (float)

  • max_strings_per_trim (int | None)

  • max_carryover (int | None)

prepare_subspaces(request)[source]

Divide one pool of bitstrings among the batches, so that they are disjoint.

Parameters:

request (SubspaceRequest)

Return type:

list[tuple[ndarray, ndarray]]

refine(results, round_index)[source]

Merge what the batches retained, then stop after diagonalizing it.

The screening round’s results are trimmed to their highest-weight CI strings and merged into a single subspace, which the loop diagonalizes as the second round.

Parameters:
Return type:

list[tuple[ndarray, ndarray]] | None

select_carryover(result, *, symmetrize_spin=False)[source]

Carry over the highest-weight CI strings of the merged diagonalization.

The amplitude cutoff is applied exactly as StandardPolicy applies it, with the same default, and carryover_ratio and max_carryover then bound the count, which the cutoff alone does not. The treatment of symmetrize_spin is the same too: it ranks the two sectors together before merging them.

Parameters:
Return type:

tuple[ndarray, ndarray]

select_result(results)[source]

Report the merged diagonalization, which is the only result of the last round.

Parameters:

results (list[SCIResult])

Return type:

SCIResult