Factory pattern

QDK/Chemistry extensively uses the Factory pattern, a creational design pattern that provides an interface for creating objects without specifying their concrete classes. This document explains how and why QDK/Chemistry uses this pattern for algorithm instantiation.

Overview of the factory pattern

The factory pattern is a design pattern that encapsulates object creation logic. Instead of directly instantiating objects using constructors, clients request objects from a factory, typically by specifying a type identifier. This approach has several advantages:

Abstraction

Clients work with abstract interfaces rather than concrete implementations

Flexibility

The concrete implementation can be changed without affecting client code

Configuration

Objects can be configured based on runtime parameters

Extension

New implementations can be added without modifying existing code

Factory pattern in QDK/Chemistry

In QDK/Chemistry, algorithm classes are instantiated through factory classes rather than direct constructors. This design allows QDK/Chemistry to:

  • Support multiple implementations of the same algorithm interface

  • Configure algorithm instances based on settings

  • Load algorithm implementations dynamically at runtime

  • Isolate algorithm implementation details from client code

Factory classes in QDK/Chemistry

QDK/Chemistry provides factory infrastructure for each algorithm type. In Python, algorithm instantiation is managed through a centralized registry module rather than individual factory classes.

QDK/Chemistry Algorithm Factories

Algorithm

Algorithm Type (Python)

Factory Class (C++)

ScfSolver

"scf_solver"

ScfSolverFactory

Localizer

"orbital_localizer"

LocalizerFactory

ActiveSpaceSelector

"active_space_selector"

ActiveSpaceSelectorFactory

HamiltonianConstructor

"hamiltonian_constructor"

HamiltonianConstructorFactory

MCCalculator

"multi_configuration_calculator"

MultiConfigurationCalculatorFactory

MultiConfigurationScf

"multi_configuration_scf"

MultiConfigurationScfFactory

StabilityChecker

"stability_checker"

StabilityCheckerFactory

EnergyEstimator

"energy_estimator"

Python only

StatePreparation

"state_prep"

Python only

QubitMapper

"qubit_mapper"

Python only

Using factories

To create an algorithm instance, call the appropriate factory method with an optional implementation name. If no name is provided, the default implementation is used. See Discovering implementations below for how to list available implementations programmatically.

#include <qdk/chemistry.hpp>

using namespace qdk::chemistry::algorithms;
using namespace qdk::chemistry::data;

// Create a simple molecule
auto structure = Structure::from_xyz("2\n\nH 0.0 0.0 0.0\nH 0.0 0.0 1.4");

// Create a SCF solver using the default implementation
auto scf_solver = ScfSolverFactory::create();

// Create an orbital localizer using a specific implementation
auto localizer = LocalizerFactory::create("qdk_pipek_mezey");

// Configure the SCF solver and run
scf_solver->settings().set("basis_set", "cc-pvdz");
auto [E_scf, wfn] = scf_solver->run(structure, 0, 1);
from pathlib import Path
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import Structure

# Load H2 molecule from XYZ file
structure = Structure.from_xyz_file(Path(__file__).parent / "../data/h2.structure.xyz")

# Create a SCF solver using the default implementation
scf_solver = create("scf_solver")

# Create an orbital localizer using a specific implementation
localizer = create("orbital_localizer", "qdk_pipek_mezey")

# Configure the SCF solver and run
E_scf, wfn = scf_solver.run(
    structure, charge=0, spin_multiplicity=1, basis_or_guess="cc-pvdz"
)

Discovering implementations

QDK/Chemistry provides programmatic discovery of available algorithm types and their implementations. This is useful for exploring what’s available at runtime, building dynamic UIs, or debugging plugin loading.

Listing algorithm types and implementations

#include <iostream>
#include <qdk/chemistry.hpp>

using namespace qdk::chemistry::algorithms;

// List available SCF solver implementations
auto scf_methods = ScfSolverFactory::available();
std::cout << "Available SCF solvers:" << std::endl;
for (const auto& name : scf_methods) {
  std::cout << "  - " << name << std::endl;
}

// List available localizer implementations
auto localizer_methods = LocalizerFactory::available();
std::cout << "Available localizers:" << std::endl;
for (const auto& name : localizer_methods) {
  std::cout << "  - " << name << std::endl;
}

// List available Hamiltonian constructor implementations
auto ham_methods = HamiltonianConstructorFactory::available();
std::cout << "Available Hamiltonian constructors:" << std::endl;
for (const auto& name : ham_methods) {
  std::cout << "  - " << name << std::endl;
}

// List available multi-configuration calculator implementations
auto mc_methods = MultiConfigurationCalculatorFactory::available();
std::cout << "Available MC calculators:" << std::endl;
for (const auto& name : mc_methods) {
  std::cout << "  - " << name << std::endl;
}

// Show default implementation for each factory type
std::cout << "Default SCF solver: " << ScfSolverFactory::default_name()
          << std::endl;
std::cout << "Default localizer: " << LocalizerFactory::default_name()
          << std::endl;
std::cout << "Default Hamiltonian constructor: "
          << HamiltonianConstructorFactory::default_name() << std::endl;
std::cout << "Default MC calculator: "
          << MultiConfigurationCalculatorFactory::default_name() << std::endl;
from qdk_chemistry.algorithms import registry  # noqa: E402

# List all algorithm types and their implementations
all_algorithms = registry.available()
print(all_algorithms)
# Output: {'scf_solver': ['qdk', 'pyscf'], 'orbital_localizer': ['qdk_pipek_mezey', 'pyscf'], ...}

# List implementations for a specific algorithm type
scf_methods = registry.available("scf_solver")
print("Available SCF solvers:", scf_methods)
# Output: ['qdk', 'pyscf']

localizer_methods = registry.available("orbital_localizer")
print("Available localizers:", localizer_methods)
# Output: ['qdk_pipek_mezey', 'pyscf', ...]

# Show default implementations for each algorithm type
defaults = registry.show_default()
print("Defaults:", defaults)
# Output: {'scf_solver': 'qdk', 'orbital_localizer': 'qdk_pipek_mezey', ...}

Inspecting settings

Each algorithm implementation has configurable settings. You can discover available settings programmatically as shown below. For comprehensive documentation on working with settings, see Settings.

#include <iostream>
#include <qdk/chemistry.hpp>

using namespace qdk::chemistry::algorithms;

// Create a SCF solver and inspect its settings
auto scf = ScfSolverFactory::create("qdk");

// Print settings as a formatted table
std::cout << scf->settings().as_table() << std::endl;

// Or iterate over individual settings
for (const auto& key : scf->settings().keys()) {
  std::cout << key << ": " << scf->settings().get_as_string(key) << std::endl;
}
from qdk_chemistry.algorithms import registry  # noqa: E402

# Create a SCF solver and inspect its settings
scf = registry.create("scf_solver", "qdk")

# Print settings as a formatted table
registry.print_settings("scf_solver", "qdk")

# Or iterate over individual settings
for key in scf.settings().keys():
    print(f"{key}: {scf.settings().get(key)}")

Connection to the plugin system

The factory pattern serves as the foundation for QDK/Chemistry’s plugin system. Factories enable the registration and instantiation of plugin implementations that connect to external quantum chemistry programs.

Internally, QDK/Chemistry’s factories maintain a registry of creator functions mapped to implementation names. When a client requests an implementation by name, the factory looks up the appropriate creator function and instantiates the object with the necessary setup.

This design enables several key capabilities:

  • Seamless integration with external quantum chemistry packages

  • Runtime selection of specific implementations

  • Decoupling of plugin usage from implementation details

For detailed information about implementing custom plugins see the plugin documentation.

Further reading

  • Factory usage examples: C++ | Python

  • Settings: Configuration of algorithm instances

  • Plugins: Extending QDK/Chemistry with custom implementations