Program Batches

The divi.qprog.batch module provides powerful program batch capabilities for running multiple quantum programs in parallel, managing large-scale hyperparameter sweeps, and handling complex workflows.

Overview

Program batches in Divi enable you to:

  • Parallel Execution: Run multiple quantum programs simultaneously

  • Hyperparameter Sweeps: Systematically explore parameter spaces

  • Large Problem Decomposition: Break down complex problems into manageable subproblems

  • Progress Monitoring: Track execution across multiple programs with rich progress bars

Core Architecture

class ProgramBatch(backend)[source]

Bases: ABC

This abstract class provides the basic scaffolding for higher-order computations that require more than one quantum program to achieve its goal.

Each implementation of this class has to have an implementation of two functions:
  1. create_programs: This function generates the independent programs that

    are needed to achieve the objective of the job. The creation of those programs can utilize the instance variables of the class to initialize their parameters. The programs should be stored in a key-value store where the keys represent the identifier of the program, whether random or identificatory.

  2. aggregate_results: This function aggregates the results of the programs

    after they are done executing. This function should be aware of the different formats the programs might have (counts dictionary, expectation value, etc) and handle such cases accordingly.

__init__(backend)[source]
property total_circuit_count

Get the total number of circuits executed across all programs in the batch.

Returns:

Cumulative count of circuits submitted by all programs.

Return type:

int

property total_run_time

Get the total runtime across all programs in the batch.

Returns:

Cumulative execution time in seconds across all programs.

Return type:

float

property programs: dict

Get a copy of the programs dictionary.

Returns:

Copy of the programs dictionary mapping program IDs to

QuantumProgram instances. Modifications to this dict will not affect the internal state.

Return type:

dict

abstract create_programs()[source]
reset()[source]

Reset the batch to its initial state.

Clears all programs, stops any running executors, terminates listener threads, and stops progress bars. This allows the batch to be reused for a new set of programs.

Note

Any running programs will be forcefully stopped. Results from incomplete programs will be lost.

run(blocking=False)[source]

Execute all programs in the batch.

Starts all quantum programs in parallel using a thread pool. Can run in blocking or non-blocking mode.

Parameters:

blocking (bool, optional) – If True, waits for all programs to complete before returning. If False, returns immediately and programs run in the background. Defaults to False.

Returns:

Returns self for method chaining.

Return type:

ProgramBatch

Raises:

RuntimeError – If a batch is already running or if no programs have been created.

Note

In non-blocking mode, call join() later to wait for completion and collect results.

check_all_done()[source]

Check if all programs in the batch have completed execution.

Returns:

True if all programs are finished (successfully or with errors),

False if any are still running.

Return type:

bool

join()[source]

Wait for all programs in the batch to complete and collect results.

Blocks until all programs finish execution, aggregating their circuit counts and run times. Handles keyboard interrupts gracefully by attempting to cancel remaining programs.

Returns:

Returns False if interrupted by KeyboardInterrupt, None otherwise.

Return type:

bool or None

Raises:

RuntimeError – If any program fails with an exception, after cancelling remaining programs.

Note

This method should be called after run(blocking=False) to wait for completion. It’s automatically called when using run(blocking=True).

abstract aggregate_results()[source]

Usage:

from divi.qprog.batch import ProgramBatch
from divi.backends import ParallelSimulator

class MyBatch(ProgramBatch):
    def create_programs(self):
        # Create multiple quantum programs
        self.programs = {
            "program_1": VQE(...),
            "program_2": QAOA(...),
            "program_3": VQE(...)
        }

    def run(self):
        # Execute all programs in parallel
        return super().run()

# Run program batch
batch = MyBatch(backend=ParallelSimulator())
results = batch.run()

Workflows

VQE Hyperparameter Sweeps

class VQEHyperparameterSweep(ansatze, molecule_transformer, optimizer=None, max_iterations=10, **kwargs)[source]

Bases: ProgramBatch

Allows user to carry out a grid search across different values for the ansatz and the bond length used in a VQE program.

Initialize a VQE hyperparameter sweep.

Parameters:
  • ansatze (Sequence[Ansatz]) – A sequence of ansatz circuits to test.

  • molecule_transformer (MoleculeTransformer) – A MoleculeTransformer object defining the configuration for generating the molecule variants.

  • optimizer (Optimizer) – The optimization algorithm for the VQE runs.

  • max_iterations (int) – The maximum number of optimizer iterations for each VQE run.

  • **kwargs (Forwarded to parent class.)

__init__(ansatze, molecule_transformer, optimizer=None, max_iterations=10, **kwargs)[source]

Initialize a VQE hyperparameter sweep.

Parameters:
  • ansatze (Sequence[Ansatz]) – A sequence of ansatz circuits to test.

  • molecule_transformer (MoleculeTransformer) – A MoleculeTransformer object defining the configuration for generating the molecule variants.

  • optimizer (Optimizer) – The optimization algorithm for the VQE runs.

  • max_iterations (int) – The maximum number of optimizer iterations for each VQE run.

  • **kwargs (Forwarded to parent class.)

create_programs()[source]

Create VQE programs for all combinations of ansatze and molecule variants.

Generates molecule variants using the configured MoleculeTransformer, then creates a VQE program for each (ansatz, molecule_variant) pair.

Note

Program IDs are tuples of (ansatz_name, bond_modifier_value).

aggregate_results()[source]

Find the best ansatz and bond configuration from all VQE runs.

Compares the final energies across all ansatz/molecule combinations and returns the configuration that achieved the lowest ground state energy.

Returns:

A tuple containing:
  • best_config (tuple): (ansatz_name, bond_modifier) of the best result.

  • best_energy (float): The lowest energy achieved.

Return type:

tuple

Raises:

RuntimeError – If programs haven’t been run or have empty losses.

visualize_results(graph_type='line')[source]

Visualize the results of the VQE problem.

Molecule Transformer

class MoleculeTransformer(base_molecule, bond_modifiers, atom_connectivity=None, bonds_to_transform=None, alignment_atoms=None)[source]

Bases: object

A class for transforming molecular structures by modifying bond lengths.

This class generates variants of a base molecule by adjusting bond lengths according to specified modifiers. The modification mode is detected automatically.

base_molecule

The reference molecule used as a template for generating variants.

Type:

qml.qchem.Molecule

bond_modifiers

A list of values used to adjust bond lengths. The class will generate one new molecule for each modifier in this list. The modification mode is detected automatically: - Scale mode: If all values are positive, they are used as scaling factors (e.g., 1.1 for a 10% increase). - Delta mode: If any value is zero or negative, all values are treated as additive changes to the bond length, in Ångstroms.

Type:

Sequence[float]

atom_connectivity

A sequence of atom index pairs specifying the bonds in the molecule. If not provided, a chain structure will be assumed e.g.: [(0, 1), (1, 2), (2, 3), …].

Type:

Sequence[tuple[int, int]] | None

bonds_to_transform

A subset of atom_connectivity that specifies the bonds to modify. If None, all bonds will be transformed.

Type:

Sequence[tuple[int, int]] | None

alignment_atoms

Indices of atoms onto which to align the orientation of the resulting variants of the molecule. Only useful for visualization and debugging. If None, no alignment is carried out.

Type:

Sequence[int] | None

base_molecule: Molecule
bond_modifiers: Sequence[float]
atom_connectivity: Sequence[tuple[int, int]] | None = None
bonds_to_transform: Sequence[tuple[int, int]] | None = None
alignment_atoms: Sequence[int] | None = None
generate()[source]
Return type:

dict[float, Molecule]

__init__(base_molecule, bond_modifiers, atom_connectivity=None, bonds_to_transform=None, alignment_atoms=None)

Graph Partitioning QAOA

class GraphPartitioningQAOA(graph, graph_problem, n_layers, backend, partitioning_config, initial_state='Recommended', aggregate_fn=<function linear_aggregation>, optimizer=None, max_iterations=10, **kwargs)[source]

Bases: ProgramBatch

Initializes the graph partitioning class.

Parameters:
  • graph (nx.Graph | rx.PyGraph) – The input graph to be partitioned.

  • graph_problem (GraphProblem) – The type of graph partitioning problem (e.g., EDGE_PARTITIONING).

  • n_layers (int) – Number of layers for the QAOA circuit.

  • backend (CircuitRunner) – Backend used to run quantum/classical circuits.

  • partitioning_config (PartitioningConfig) – the configuration of the partitioning as to the algorithm and

  • output. (expected)

  • initial_state ("Zeros", "Ones", "Superposition", "Recommended", optional) – Initial state for the QAOA algorithm. Defaults to “Recommended”.

  • aggregate_fn (optional) – Aggregation function to combine results. Defaults to linear_aggregation.

  • optimizer (optional) – Optimizer to use for QAOA. Defaults to Optimizers.MONTE_CARLO.

  • max_iterations (int, optional) – Maximum number of optimization iterations. Defaults to 10.

  • **kwargs – Additional keyword arguments passed to the QAOA constructor.

__init__(graph, graph_problem, n_layers, backend, partitioning_config, initial_state='Recommended', aggregate_fn=<function linear_aggregation>, optimizer=None, max_iterations=10, **kwargs)[source]

Initializes the graph partitioning class.

Parameters:
  • graph (nx.Graph | rx.PyGraph) – The input graph to be partitioned.

  • graph_problem (GraphProblem) – The type of graph partitioning problem (e.g., EDGE_PARTITIONING).

  • n_layers (int) – Number of layers for the QAOA circuit.

  • backend (CircuitRunner) – Backend used to run quantum/classical circuits.

  • partitioning_config (PartitioningConfig) – the configuration of the partitioning as to the algorithm and

  • output. (expected)

  • initial_state ("Zeros", "Ones", "Superposition", "Recommended", optional) – Initial state for the QAOA algorithm. Defaults to “Recommended”.

  • aggregate_fn (optional) – Aggregation function to combine results. Defaults to linear_aggregation.

  • optimizer (optional) – Optimizer to use for QAOA. Defaults to Optimizers.MONTE_CARLO.

  • max_iterations (int, optional) – Maximum number of optimization iterations. Defaults to 10.

  • **kwargs – Additional keyword arguments passed to the QAOA constructor.

create_programs()[source]

Creates and initializes QAOA programs for each partitioned subgraph.

The main graph is partitioned into node-based subgraphs according to the specified partitioning configuration. Each subgraph is relabeled with integer node labels for QAOA compatibility, and a reverse index map is stored for later result aggregation.

Each program is assigned a unique program ID, which is a tuple of:
  • An uppercase letter (A, B, C, …) corresponding to the partition index.

  • The number of nodes in the subgraph.

Example program ID: (‘A’, 5) for the first partition with 5 nodes.

The created QAOA programs are stored in the self.programs dictionary, keyed by their program IDs.

aggregate_results()[source]

Aggregates the results from all QAOA subprograms to form a global solution.

This method collects the final bitstring solutions from each partitioned subgraph’s QAOA program, using the aggregation function specified at initialization (e.g., linear or dominance aggregation). It reconstructs the global solution by mapping each subgraph’s solution back to the original node indices using the stored reverse index maps.

The final solution is stored in self.solution as a list of node indices assigned to the selected partition.

Raises:

RuntimeError – If no programs exist, if programs have not been run, or if results are incomplete.

Returns:

The list of node indices in the final aggregated solution.

Return type:

list[int]

draw_partitions(pos=None, figsize=(10, 8), node_size=300)[source]

Draw a NetworkX graph with nodes colored by partition.

Parameters:

posdict, optional

Node positions. If None, uses spring layout

figsizetuple, optional

Figure size (width, height)

node_sizeint, optional

Size of nodes

draw_solution()[source]

Visualizes the main graph with nodes highlighted according to the final aggregated solution.

If the solution has not yet been computed, this method calls aggregate_results() to obtain it.

Partitioning Configuration

class PartitioningConfig(max_n_nodes_per_cluster=None, minimum_n_clusters=None, partitioning_algorithm='spectral')[source]

Bases: object

Configuration for graph partitioning algorithms.

This class defines the parameters and constraints for partitioning large graphs into smaller subgraphs for quantum algorithm execution. It supports multiple partitioning algorithms and allows specification of size constraints.

max_n_nodes_per_cluster

Maximum number of nodes allowed in each cluster. If None, no upper limit is enforced. Must be a positive integer.

minimum_n_clusters

Minimum number of clusters to create. If None, no lower limit is enforced. Must be a positive integer.

partitioning_algorithm

Algorithm to use for partitioning. Options are: - “spectral”: Spectral partitioning using Fiedler vector (default) - “metis”: METIS graph partitioning library - “kernighan_lin”: Kernighan-Lin algorithm

Note

At least one of max_n_nodes_per_cluster or minimum_n_clusters must be specified. Both constraints cannot be None.

Examples

>>> # Partition into clusters of at most 10 nodes
>>> config = PartitioningConfig(max_n_nodes_per_cluster=10)
>>> # Create at least 5 clusters using METIS
>>> config = PartitioningConfig(
...     minimum_n_clusters=5,
...     partitioning_algorithm="metis"
... )
>>> # Both constraints: clusters of max 8 nodes, min 3 clusters
>>> config = PartitioningConfig(
...     max_n_nodes_per_cluster=8,
...     minimum_n_clusters=3
... )
max_n_nodes_per_cluster: int | None = None
minimum_n_clusters: int | None = None
partitioning_algorithm: Literal['spectral', 'metis', 'kernighan_lin'] = 'spectral'
__init__(max_n_nodes_per_cluster=None, minimum_n_clusters=None, partitioning_algorithm='spectral')

QUBO Partitioning QAOA

class QUBOPartitioningQAOA(qubo, decomposer, n_layers, backend, composer=SplatComposer(), optimizer=None, max_iterations=10, **kwargs)[source]

Bases: ProgramBatch

Initialize a QUBOPartitioningQAOA instance for solving QUBO problems using partitioning and QAOA.

Parameters:
  • qubo (QUBOProblemTypes | BinaryQuadraticModel) – The QUBO problem to solve, provided as a supported type or a BinaryQuadraticModel. Note: Variable types are assumed to be binary (not Spin).

  • decomposer (hybrid.traits.ProblemDecomposer) – The decomposer used to partition the QUBO problem into subproblems.

  • n_layers (int) – Number of QAOA layers to use for each subproblem.

  • backend (CircuitRunner) – Backend responsible for running quantum circuits.

  • composer (hybrid.traits.SubsamplesComposer, optional) – Composer to aggregate subsamples from subproblems. Defaults to hybrid.SplatComposer().

  • optimizer (Optimizer, optional) – Optimizer to use for QAOA. Defaults to Optimizer.MONTE_CARLO.

  • max_iterations (int, optional) – Maximum number of optimization iterations. Defaults to 10.

  • **kwargs – Additional keyword arguments passed to the QAOA constructor.

__init__(qubo, decomposer, n_layers, backend, composer=SplatComposer(), optimizer=None, max_iterations=10, **kwargs)[source]

Initialize a QUBOPartitioningQAOA instance for solving QUBO problems using partitioning and QAOA.

Parameters:
  • qubo (QUBOProblemTypes | BinaryQuadraticModel) – The QUBO problem to solve, provided as a supported type or a BinaryQuadraticModel. Note: Variable types are assumed to be binary (not Spin).

  • decomposer (hybrid.traits.ProblemDecomposer) – The decomposer used to partition the QUBO problem into subproblems.

  • n_layers (int) – Number of QAOA layers to use for each subproblem.

  • backend (CircuitRunner) – Backend responsible for running quantum circuits.

  • composer (hybrid.traits.SubsamplesComposer, optional) – Composer to aggregate subsamples from subproblems. Defaults to hybrid.SplatComposer().

  • optimizer (Optimizer, optional) – Optimizer to use for QAOA. Defaults to Optimizer.MONTE_CARLO.

  • max_iterations (int, optional) – Maximum number of optimization iterations. Defaults to 10.

  • **kwargs – Additional keyword arguments passed to the QAOA constructor.

create_programs()[source]

Partition the main QUBO problem and instantiate QAOA programs for each subproblem.

This implementation: - Uses the configured decomposer to split the main QUBO into subproblems. - For each subproblem, creates a QAOA program with the specified parameters. - Stores each program in self.programs with a unique identifier.

Unique Identifier Format:
Each key in self.programs is a tuple of the form (letter, size), where:
  • letter: An uppercase letter (‘A’, ‘B’, ‘C’, …) indicating the partition index.

  • size: The number of variables in the subproblem.

Example: (‘A’, 5) refers to the first partition with 5 variables.

aggregate_results()[source]

Aggregate results from all QUBO subproblems into a global solution.

Collects solutions from each partitioned subproblem (both QAOA-optimized and trivial ones) and uses the hybrid framework composer to combine them into a final solution for the original QUBO problem.

Returns:

A tuple containing:
  • solution (np.ndarray): Binary solution vector for the QUBO problem.

  • solution_energy (float): Energy/cost of the solution.

Return type:

tuple

Raises:

RuntimeError – If programs haven’t been run or if final probabilities haven’t been computed.