High-level design
This document outlines the core architectural design principles of QDK/Chemistry, explaining the conceptual framework that guides the library’s organization and implementation. For a complete overview of QDK/Chemistry’s documentation, see the in-depth documentation index.
QDK/Chemistry is designed with a clear separation between data classes and algorithms. This design choice enables flexibility, extensibility, and maintainability of the codebase, while providing users with a consistent and intuitive API.
Separation of Data and Algorithms
QDK/Chemistry follows a design pattern that strictly separates:
Data Classes: Immutable containers that store and manage quantum chemical data
Algorithm Classes: Processors that operate on data objects to produce new data objects
This separation follows the principle of single responsibility and creates a clear flow of data through computational workflows.
![digraph DataFlow {
rankdir=LR;
bgcolor="#FAFAFA";
node [shape=box, style="rounded,filled", fontname="Arial", margin=0.3];
edge [color="#1976D2", penwidth=2];
Input [label="Input Data", fillcolor="#E8EAF6", color="#5C6BC0", penwidth=2, fontcolor="#3949AB"];
Algorithm [label="Algorithm", fillcolor="#E3F2FD", color="#42A5F5", penwidth=2, fontcolor="#1976D2"];
Output [label="Output Data", fillcolor="#E0F2F1", color="#26A69A", penwidth=2, fontcolor="#00796B"];
Input -> Algorithm;
Algorithm -> Output;
}](../../../_images/graphviz-81cb93437fff91546debeb5cb5e3d380016e084e.png)
Data classes
Data classes in QDK/Chemistry concretely represent intermediate quantities commonly encountered in quantum applications workflows. These classes are designed to be:
- Immutable
Once created, the core data cannot be modified
- Self-contained
Include all information necessary to represent the underlying quantum chemical quantity
- Serializable
Can be easily saved to and loaded from files
- Language-agnostic
Accessible through identical APIs in both C++ and Python
See the Data Classes documentation for further details on the availability and usage of QDK/Chemistry’s data classes.
Algorithm classes
Algorithm classes represent mutations on data, such as the execution of quantum chemical methods and generation of circuit components commonly found in quantum applications workflows. These classes are designed to be:
- Stateless
Their behavior depends only on their input data and configuration
- Configurable
Through a standardized
Settingsinterface- Conforming
Exposing a common interface for disparate implementations to enable a unified user experience.
- Extensible
Allowing new implementations to be added without modifying existing code
Programmatically, Algorithms are specified as abstract interfaces which can be specialized downstream through concrete implementations. This allows QDK/Chemistry to be expressed as a plugin architecture, for which algorithm implementations may be specified either natively within QDK/Chemistry or through established third-party quantum chemistry packages:
![digraph PluginArchitecture {
rankdir=LR;
bgcolor="#FAFAFA";
node [shape=box, style="rounded,filled", fontname="Arial", margin=0.3];
edge [color="#1976D2", penwidth=2];
Interface [label="QDK/Chemistry Interface\n(ScfSolver, Localizer,\nMCCalculator, etc.)", fillcolor="#E3F2FD", color="#42A5F5", penwidth=2, fontcolor="#1976D2"];
QDK [label="QDK Implementation\n(AutoCAS, etc.)", fillcolor="#E0F2F1", color="#26A69A", penwidth=2, fontcolor="#00796B"];
ThirdParty [label="Third-Party Interfaces\n(PySCF, etc.)", fillcolor="#F3E5F5", color="#AB47BC", penwidth=2, fontcolor="#7B1FA2"];
Interface -> QDK;
Interface -> ThirdParty;
}](../../../_images/graphviz-425f73d86fb873b40e9ee4489a05e57b826832b4.png)
This design allows users to benefit from specialized capabilities of “best-in-breed” software while maintaining a consistent user experience. See the Plugin System documentation for further details on how to contribute new algorithm implementations.
Further details on the availability and usage of QDK/Chemistry’s algorithm implementations can be found in the Algorithms documentation.
Factory pattern
QDK/Chemistry’s plugin architecture leverages a factory pattern for algorithm creation:
#include <qdk/chemistry.hpp>
using namespace qdk::chemistry::data;
using namespace qdk::chemistry::algorithms;
auto scf = ScfSolverFactory::create();
from pathlib import Path
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import Structure
scf_solver = create("scf_solver")
This pattern allows:
Extension of workflows by new Algorithm implementations without changing client code
Centralized management of dependencies and resources
Read more on QDK/Chemistry’s usage of this pattern in the Factory Pattern documentation.
Runtime configuration with settings
Algorithm configuration is managed through instances of Settings objects, which contain a type-safe data store of configuration parameters consistent between the python and C++ APIs:
std::cout << "Available settings: " << scf_solver.settings().get_summary()
<< std::endl;
scf_solver->settings().set("max_iterations", 100);
print(f"Available settings: {scf_solver.settings().get_summary()}")
scf_solver.settings().set("max_iterations", 100)
Read more on how one can configure, discover, and extend instances of Settings objects in the Settings documentation.
A complete dataflow example
A typical workflow in QDK/Chemistry demonstrates the data-algorithm separation:
int main() {
// Create molecular structure from an XYZ file
auto molecule = Structure::from_xyz_file("molecule.xyz");
// Configure and run SCF calculation
auto scf_solver = ScfSolverFactory::create();
scf_solver->settings().set("basis_set", "cc-pvdz");
auto [scf_energy, wfn_hf] = scf_solver->run(molecule, 0, 1);
// Select active space orbitals
auto active_selector = ActiveSpaceSelectorFactory::create("qdk_valence");
active_selector->settings().set("num_active_orbitals", 6);
active_selector->settings().set("num_active_electrons", 6);
auto active_wfn = active_selector->run(wfn_hf);
auto active_orbitals = active_wfn->get_orbitals();
// Create Hamiltonian with active space
auto ham_constructor = HamiltonianConstructorFactory::create();
auto hamiltonian = ham_constructor->run(active_orbitals);
// Run multi-configuration calculation
auto mc_solver = MultiConfigurationCalculatorFactory::create();
auto [mc_energy, wave_function] = mc_solver->run(hamiltonian, 3, 3);
return 0;
}
# Load a Structure from file (data classes in QDK/Chemistry are immutable by design)
structure = Structure.from_xyz_file(Path(__file__).parent / "../data/h2.structure.xyz")
# Configure and run SCF calculation
scf_solver = create("scf_solver")
scf_energy, scf_wavefunction = scf_solver.run(
structure, charge=0, spin_multiplicity=1, basis_or_guess="cc-pvdz"
)
# Select active space orbitals
active_space_selector = create(
"active_space_selector",
algorithm_name="qdk_valence",
)
active_space_selector.settings().set("num_active_orbitals", 2)
active_space_selector.settings().set("num_active_electrons", 2)
active_wfn = active_space_selector.run(scf_wavefunction)
active_orbitals = active_wfn.get_orbitals()
# Create Hamiltonian with active space
ham_constructor = create("hamiltonian_constructor")
hamiltonian = ham_constructor.run(active_orbitals)
mc = create("multi_configuration_calculator")
mc.settings().set("davidson_iterations", 300)
E_cas, wfn_cas = mc.run(
hamiltonian, n_active_alpha_electrons=1, n_active_beta_electrons=1
)
Further reading
The above examples can be downloaded as complete C++ and Python scripts.
Factory Pattern: Details on QDK/Chemistry’s implementation of the factory pattern
Settings: How to configure the execution behavior of algorithms through the Settings interface
Plugin system: QDK/Chemistry’s plugin system for extending functionality