VariationalQuantumAlgorithm

class VariationalQuantumAlgorithm(backend, optimizer=None, seed=None, progress_queue=None, early_stopping=None, **kwargs)[source]

Bases: ObservableMeasuringMixin, QuantumProgram

Base class for variational quantum algorithms.

This class provides the foundation for implementing variational quantum algorithms in Divi. It handles circuit execution, parameter optimization, and result management for algorithms that optimize parameterized quantum circuits to minimize cost functions.

Variational algorithms work by: 1. Generating parameterized quantum circuits 2. Executing circuits on quantum hardware/simulators 3. Computing expectation values of cost Hamiltonians 4. Using classical optimizers to update parameters 5. Iterating until convergence

Variables:
  • _losses_history – History of loss values during optimization.

  • _param_history (list[ndarray[tuple[Any, ...], dtype[double]]]) – Raw per-callback parameter batches; use param_history() to read copies with optional filtering.

  • _final_params – Final optimized parameters.

  • _best_params – Parameters that achieved the best loss.

  • _best_loss (float) – Best loss achieved during optimization.

  • _circuits – Generated quantum circuits.

  • _total_circuit_count (int) – Total number of circuits executed.

  • _total_run_time (float) – Total execution time in seconds.

  • _seed – Random seed for parameter initialization.

  • _rng – Random number generator.

  • _grouping_strategy – Strategy for grouping quantum operations.

  • _qem_protocol – Quantum error mitigation protocol.

  • _cancellation_event – Event for graceful termination.

  • _meta_circuit_factories (dict) – Lazily-built mapping of circuit names to MetaCircuit factories.

Initialize the VariationalQuantumAlgorithm.

This constructor is specifically designed for hybrid quantum-classical variational algorithms. The instance variables n_layers and n_params_per_layer must be set by subclasses, where: - n_layers is the number of layers in the quantum circuit. - n_params_per_layer is the number of parameters per layer.

For exotic variational algorithms where these variables may not be applicable, the _initialize_param_sets method should be overridden to generate the starting parameters for a fresh optimization run.

Parameters:
  • backend (CircuitRunner) – Quantum circuit execution backend.

  • optimizer (Optimizer | None) – The optimizer to use for parameter optimization. Defaults to MonteCarloOptimizer().

  • seed (int | None) – Random seed for parameter initialization. Defaults to None.

  • progress_queue (Queue | None) – Queue for progress reporting. Defaults to None.

  • early_stopping (EarlyStopping | None) – Early stopping controller. When provided, the optimization loop will be halted if any of the configured criteria are met (e.g. patience exceeded, gradient below threshold, cost variance settled). Defaults to None.

Keyword Arguments:
  • grouping_strategy (str) – Strategy for partitioning Hamiltonian terms into compatible measurement groups; one circuit is executed per group. Options: "qwc" (qubit-wise-commuting — most compact), "wires" (group by support wires), or None (one circuit per term). Defaults to "qwc".

  • shot_distribution (str or callable(), optional) –

    Focus the backend’s shot budget on the Hamiltonian terms that matter most. Without this option, every measurement group is sampled with the backend’s full shot count, even tiny terms with little impact on the final energy. With shot_distribution set, the same total budget is split across groups according to their importance — reducing variance without spending more shots.

    Available strategies:

    • "uniform" — equal split across groups.

    • "weighted" — proportional to per-group coefficient L1 norm; dominant Hamiltonian terms get more shots.

    • "weighted_random" — multinomial sample of the same probabilities; may drop more low-weight groups than the deterministic "weighted" for the same budget.

    • A callable (group_l1_norms, total_shots) -> per_group_shots for fully custom allocation.

    Example:

    vqe = MyVQA(
        backend=QiskitSimulator(shots=1000),
        shot_distribution="weighted",
    )
    vqe.run()
    

    Only valid when sampling is actually used. Setting it on a backend that computes expectation values analytically (grouping_strategy="_backend_expval") is rejected because shots are ignored in that mode. Defaults to None (every group receives the full shot budget).

  • precision (int) – Forwarded to QuantumProgram — decimal places for numeric parameter values in QASM conversion. Higher values produce longer QASM strings (more data sent to cloud backends); lower values trade resolution for compactness. Defaults to DEFAULT_PRECISION.

  • decode_solution_fn – Function to decode bitstrings into problem-specific solution representations. Called during final computation and when get_top_solutions(include_decoded=True) is used. The function should take a binary string (e.g., “0101”) and return a decoded representation (e.g., a list of indices, numpy array, or custom object). Defaults to lambda bitstring: bitstring (identity function).

Attributes Summary

best_loss

Get the best loss achieved so far.

best_params

Get a copy of the parameters that achieved the best (lowest) loss.

best_probs

Get normalized probabilities for the best parameters.

final_params

Get a copy of the final optimized parameters.

losses_history

Get a copy of the optimization loss history.

meta_circuit_factories

Get the meta-circuit factories used by this program.

min_losses_per_iteration

Get the minimum loss value for each iteration.

n_params_per_layer

Number of trainable parameters per ansatz layer.

stop_reason

Reason the optimization was stopped early, or None.

viz

Access visualization helpers for this variational program.

Methods Summary

get_expected_param_shape()

Get the expected shape for initial parameters.

get_top_solutions([n, min_prob, include_decoded])

Get the top-N solutions sorted by probability.

has_results()

Return True once the program has produced results.

load_state(checkpoint_dir, backend[, ...])

Load program state from a checkpoint directory.

param_history([mode])

Parameter vectors recorded at each optimization callback.

run([initial_params, ...])

Run the variational quantum algorithm.

sample_solution([params])

Run the final measurement and decode the solution.

save_state(checkpoint_config)

Save the program state to a checkpoint directory.

Attributes Documentation

best_loss

Get the best loss achieved so far.

Returns:

The best loss achieved so far.

Return type:

float

best_params

Get a copy of the parameters that achieved the best (lowest) loss.

Returns:

Copy of the best parameters. Modifications to this array

will not affect the internal state.

Return type:

npt.NDArray[np.float64]

best_probs

Get normalized probabilities for the best parameters.

This property provides access to the probability distribution computed by running measurement circuits with the best parameters found during optimization. The distribution maps bitstrings (computational basis states) to their measured probabilities.

The probabilities are normalized and have deterministic ordering when iterated (dictionary insertion order is preserved in Python 3.7+).

Returns:

Dictionary mapping parameter-set keys to

bitstring probability dictionaries. Bitstrings are binary strings (e.g., “0101”), values are probabilities in range [0.0, 1.0]. Returns an empty dict if final computation has not been performed.

Return type:

dict[str, dict[str, float]]

Raises:

RuntimeError – If attempting to access probabilities before running the algorithm with final computation enabled.

Note

To populate this distribution, you must run the algorithm with perform_final_computation=True (the default):

>>> program.run(perform_final_computation=True)
>>> probs = program.best_probs

Example

>>> program.run()
>>> probs = program.best_probs
>>> for bitstring, prob in probs.items():
...     print(f"{bitstring}: {prob:.2%}")
0101: 42.50%
1010: 31.20%
...
final_params

Get a copy of the final optimized parameters.

Returns:

Copy of the final parameters. Modifications to this array

will not affect the internal state.

Return type:

npt.NDArray[np.float64]

losses_history

Get a copy of the optimization loss history.

Each entry is a dictionary mapping parameter indices to loss values.

Returns:

Copy of the loss history. Modifications to this list

will not affect the internal state.

Return type:

list[dict]

meta_circuit_factories

Get the meta-circuit factories used by this program.

Returns:

Dictionary mapping circuit names to their

MetaCircuit factories.

Return type:

dict[str, MetaCircuit]

min_losses_per_iteration

Get the minimum loss value for each iteration.

Returns a list where each element is the minimum (best) loss value across all parameter sets for that iteration.

Returns:

List of minimum loss values, one per iteration.

Return type:

list[float]

n_params_per_layer

Number of trainable parameters per ansatz layer.

Used by the base class to compute the total parameter count as n_layers * n_params_per_layer.

stop_reason

Reason the optimization was stopped early, or None.

Returns:

The StopReason

that triggered early stopping, or None if optimization completed normally or has not been run yet.

Return type:

StopReason | None

viz

Access visualization helpers for this variational program.

The returned object exposes a thin convenience wrapper over the standalone divi.viz API, so scans can be written either as divi.viz.scan_1d(program, ...) or program.viz.scan_1d(...).

Returns:

Convenience wrapper bound to this program instance.

Return type:

ProgramViz

Methods Documentation

get_expected_param_shape()[source]

Get the expected shape for initial parameters.

Returns:

Shape (n_param_sets, n_layers * n_params_per_layer) that

initial parameters should have for this quantum program.

Return type:

tuple[int, int]

get_top_solutions(n=10, *, min_prob=0.0, include_decoded=False)[source]

Get the top-N solutions sorted by probability.

This method extracts the most probable solutions from the measured probability distribution. Solutions are sorted by probability (descending) with deterministic tie-breaking using lexicographic ordering of bitstrings.

Parameters:
  • n (int) – Maximum number of solutions to return. Must be non-negative. If n is 0 or negative, returns an empty list. If n exceeds the number of available solutions (after filtering), returns all available solutions. Defaults to 10.

  • min_prob (float) – Minimum probability threshold for including solutions. Only solutions with probability >= min_prob will be included. Must be in range [0.0, 1.0]. Defaults to 0.0 (no filtering).

  • include_decoded (bool) – Whether to populate the decoded field of each SolutionEntry by calling the decode_solution_fn provided in the constructor. If False, the decoded field will be None. Defaults to False.

Returns:

List of solution entries sorted by probability

(descending), then by bitstring (lexicographically ascending) for deterministic tie-breaking. Returns an empty list if no probability distribution is available or n <= 0.

Return type:

list[SolutionEntry]

Raises:
  • RuntimeError – If probability distribution is not available because optimization has not been run or final computation was not performed.

  • ValueError – If min_prob is not in range [0.0, 1.0] or n is negative.

Note

The probability distribution must be computed by running the algorithm with perform_final_computation=True (the default):

>>> program.run(perform_final_computation=True)
>>> top_10 = program.get_top_solutions(n=10)

Example

>>> # Get top 5 solutions with probability >= 5%
>>> program.run()
>>> solutions = program.get_top_solutions(n=5, min_prob=0.05)
>>> for sol in solutions:
...     print(f"{sol.bitstring}: {sol.prob:.2%}")
1010: 42.50%
0101: 31.20%
1100: 15.30%
0011: 8.50%
1111: 2.50%
>>> # Get solutions with decoding
>>> solutions = program.get_top_solutions(n=3, include_decoded=True)
>>> for sol in solutions:
...     print(f"{sol.bitstring} -> {sol.decoded}")
1010 -> [0, 2]
0101 -> [1, 3]
...
has_results()[source]

Return True once the program has produced results.

Return type:

bool

classmethod load_state(checkpoint_dir, backend, subdirectory=None, **kwargs)[source]

Load program state from a checkpoint directory.

Return type:

VariationalQuantumAlgorithm

param_history(mode='all_evaluated')[source]

Parameter vectors recorded at each optimization callback.

Parameters:

mode (Literal['all_evaluated', 'best_per_iteration']) –

Which rows to return for each iteration:

  • "all_evaluated" — full batch from the callback, shape (n_param_sets, n_params) per iteration (mirrors losses_history population layout).

  • "best_per_iteration" — single best member by loss for that iteration, shape (1, n_params) per iteration.

Returns:

One array per completed callback. Use numpy.vstack(...) for a 2D sample matrix (e.g. PCA).

Return type:

list[ndarray[tuple[Any, ...], dtype[double]]]

Raises:

RuntimeError – If internal loss and parameter histories are out of sync.

run(initial_params=None, perform_final_computation=True, checkpoint_config=None, **kwargs)[source]

Run the variational quantum algorithm.

The outputs are stored in the algorithm object and can be accessed via properties such as total_circuit_count, total_run_time, losses_history, and best_params.

Parameters:
  • initial_params (ndarray[tuple[Any, ...], dtype[double]] | None) – Optional initial parameter sets for a fresh optimization run. Must have shape (n_param_sets, n_layers * n_params_per_layer). Cannot be combined with a checkpoint-resumed optimizer state.

  • perform_final_computation (bool) – Whether to perform final computation after optimization completes. Typically, this step involves sampling with the best found parameters to extract solution probability distributions. Set this to False in warm-starting or pre-training routines where the final sampling step is not needed. Defaults to True.

  • checkpoint_config (CheckpointConfig | None) – Checkpoint configuration. If None, no checkpointing is performed.

  • **kwargs – Additional keyword arguments for subclasses.

Returns:

Returns self for method chaining.

Return type:

VariationalQuantumAlgorithm

sample_solution(params=None, **kwargs)[source]

Run the final measurement and decode the solution.

Called by run() (with params=None, falling back to self._best_params) after optimization completes. It can also be called directly with externally-provided params when you already have trained parameters (e.g. from a prior run(), a checkpoint, or external training) and only need to sample the circuit — skipping the EXPECTATION jobs that run() would otherwise dispatch during optimization.

When called with explicit params, this method does NOT mutate self._best_params or any optimizer state (optimize_result, _losses_history, _param_history, current_iteration). Only the measurement-side attributes are updated: _best_probs, _total_circuit_count, _total_run_time, and subclass-specific solution fields (e.g. solution_bitstring for QAOA, _eigenstate for VQE).

Parameters:
  • params (ndarray[tuple[Any, ...], dtype[double]] | None) – Optional parameter set to evaluate. Must have shape (n_layers * n_params_per_layer,) for a single set or (n_param_sets, n_layers * n_params_per_layer) for a batch. When None (the default), uses self._best_params.

  • **kwargs – Subclass-specific keyword arguments.

Returns:

Returns self for method chaining.

Return type:

VariationalQuantumAlgorithm

Raises:
  • ValueError – If params does not have the expected number of parameters per set.

  • RuntimeError – If params=None and self._best_params is empty (i.e. run() has not been called yet).

Note

Subclasses override this method to add their algorithm-specific decoding step. They should call super().sample_solution(params) to perform parameter validation and the measurement-pipeline dispatch, then read from self._best_probs to extract algorithm-specific solution state.

save_state(checkpoint_config)[source]

Save the program state to a checkpoint directory.

Return type:

Path