ProgramEnsemble

class ProgramEnsemble(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.

Attributes Summary

programs

Get a copy of the programs dictionary.

total_circuit_count

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

total_run_time

Get the total runtime across all programs in the ensemble.

Methods Summary

aggregate_results()

Aggregate results from all programs in the ensemble after execution.

check_all_done()

Check if all programs in the ensemble have completed execution.

create_programs()

Generate and populate the programs dictionary for ensemble execution.

get_top_solutions([n, beam_width, ...])

Get the top-N global solutions from beam search aggregation.

join()

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

reset()

Reset the ensemble to its initial state.

run([blocking, batch_config])

Execute all programs in the ensemble.

sample_solution([params_per_program, ...])

Sample every sub-program's circuit with trained parameters.

Attributes Documentation

programs

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

total_circuit_count

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

Returns:

Cumulative count of circuits submitted by all programs.

Return type:

int

total_run_time

Get the total runtime across all programs in the ensemble.

Returns:

Cumulative execution time in seconds across all programs.

Return type:

float

Methods Documentation

abstractmethod aggregate_results()[source]

Aggregate results from all programs in the ensemble after execution.

This is an abstract method that must be implemented by subclasses. The base implementation performs validation checks: - Ensures programs have been created - Waits for any running programs to complete (calls join() if needed) - Verifies that all programs have completed execution (non-empty losses_history)

Subclasses should call super().aggregate_results() first, then implement their own aggregation logic to combine results from all programs. The aggregation should handle different result formats (counts dictionary, expectation values, etc.) as appropriate for the specific use case.

Return type:

Any

Returns:

The aggregated result, format depends on the subclass implementation.

Raises:

RuntimeError – If no programs exist, or if programs haven’t completed execution (empty losses_history).

check_all_done()[source]

Check if all programs in the ensemble have completed execution.

Returns:

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

False if any are still running.

Return type:

bool

abstractmethod create_programs()[source]

Generate and populate the programs dictionary for ensemble execution.

This method must be implemented by subclasses to create the quantum programs that will be executed as part of the ensemble. The method operates via side effects: it populates self._programs (or self.programs) with a dictionary mapping program identifiers to QuantumProgram instances.

Implementation Notes:
  • Subclasses should call super().create_programs() first to initialize the progress queue and validate that no programs already exist.

  • After calling super(), subclasses should populate self.programs or self._programs with their program instances.

  • Program identifiers can be any hashable type (e.g., strings, tuples). Common patterns include strings like “program_1”, “program_2” or tuples like (‘A’, 5) for partitioned problems.

Side Effects:
  • Populates self._programs with program instances.

  • Initializes self._queue for progress reporting. Sub-programs bind to this exact queue at construction, so run() reuses it rather than re-creating it. self._done_event is created later, per-run, by run()/sample_solution().

Raises:

RuntimeError – If programs already exist (should call reset() first).

Example

>>> def create_programs(self):
...     super().create_programs()
...     self.programs = {
...         "prog1": QAOA(...),
...         "prog2": QAOA(...),
...     }
get_top_solutions(n=10, *, beam_width=1, n_partition_candidates=None)[source]

Get the top-N global solutions from beam search aggregation.

Available on subclasses that use beam-search-based aggregation (e.g., PartitioningProgramEnsemble).

Parameters:
  • n (int) – Number of top solutions to return. Must be >= 1.

  • beam_width – Beam search width. Internally bumped to at least n so the beam retains enough candidates.

  • n_partition_candidates – Candidates per partition. Defaults to beam_width.

Returns:

Subclass-specific format. See subclass documentation.

Raises:

NotImplementedError – If the subclass does not override this method.

join()[source]

Wait for all programs in the ensemble 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).

reset()[source]

Reset the ensemble to its initial state.

Clears all programs, stops any running executors, terminates listener threads, and stops progress bars. This allows the ensemble 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, *, batch_config=BatchConfig(mode=<BatchMode.MERGED: 'merged'>, max_batch_size=None, max_concurrent_programs=None, _sort_programs=False))[source]

Execute all programs in the ensemble.

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

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

  • batch_config (BatchConfig) –

    Configuration for circuit batching. Controls whether submissions are merged and how. Defaults to BatchConfig() which merges all submissions via a wait-for-all barrier. Use BatchConfig(mode=BatchMode.OFF) to let each program submit independently, or BatchConfig(max_batch_size=50) to cap the number of circuits per merged backend call.

    The default barrier requires one executor thread per program, so it is capped at 256 programs. Larger ensembles must opt into max_batch_size (bounded pool, smaller merges), max_concurrent_programs (explicit pool size, ideal for cloud submission of one large merged job), or BatchMode.OFF.

Returns:

Returns self for method chaining.

Return type:

ProgramEnsemble

Raises:

RuntimeError – If an ensemble is already running, if no programs have been created, or if the ensemble exceeds 256 programs without an explicit batching strategy.

Note

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

sample_solution(params_per_program=None, *, blocking=False, batch_config=BatchConfig(mode=<BatchMode.MERGED: 'merged'>, max_batch_size=None, max_concurrent_programs=None, _sort_programs=False), suppress_strict_warning=False)[source]

Sample every sub-program’s circuit with trained parameters.

Runs only the final measurement step on each sub-program — no EXPECTATION jobs are dispatched. Two usage paths:

  • params_per_program=None: each sub-program uses its own _best_params (typically populated by a prior run() or a loaded checkpoint). A program with no trained parameters raises RuntimeError upfront with the program ID.

  • params_per_program={program_id: params, ...}: per-program parameter sets. Unknown program IDs raise ValueError. Program IDs that are present in the ensemble but missing from the dict fall back to that program’s own _best_params and emit a UserWarning listing the fallbacks (silence with suppress_strict_warning=True).

Mirrors run() for everything else — executor pool sizing, merged batching via _BatchCoordinator, progress UI, cancellation, and the blocking / non-blocking return contract. No optimizer state on any sub-program is mutated.

Parameters:
  • params_per_program (dict[Any, ndarray[tuple[Any, ...], dtype[double]]] | None) – Optional mapping from program ID to parameter set. See above for resolution semantics.

  • blocking (bool) – If True, waits for all programs to complete before returning. Defaults to False.

  • batch_config (BatchConfig) – Same semantics as run().

  • suppress_strict_warning (bool) – When True, silences the fallback warning emitted when params_per_program is missing entries for some programs.

Returns:

self for method chaining.

Return type:

ProgramEnsemble

Raises:
  • RuntimeError – If the ensemble has no programs, if it is already running, or if a sub-program has no parameters available (no dict entry and empty _best_params).

  • ValueError – If params_per_program contains unknown program IDs, or any resolved parameter set has the wrong shape for its sub-program’s n_layers * n_params_per_layer.

  • TypeError – If any sub-program is not a VariationalQuantumAlgorithm.