BasisSet
The BasisSet class in QDK/Chemistry represents a collection of basis functions used to describe the electronic structure of molecules.
It organizes atomic orbitals into shells and provides methods for managing, querying, and serializing basis set data.
Overview
In quantum chemistry, a basis set is a collection of mathematical functions used to represent molecular orbitals.
The BasisSet class in QDK/Chemistry uses a shell-based organization, where each shell contains basis functions with the same atom, angular momentum, and primitive Gaussian functions.
Key features of the BasisSet class include:
Shell-based storage for memory efficiency
Support for both spherical and Cartesian basis functions
Mapping between shells/basis functions and atoms
Mapping between shells/basis functions and orbital types
Basis set metadata (name, parameters)
Integration with molecular structure information
On-demand expansion of shells to individual basis functions
Usage
The BasisSet class is a fundamental component in quantum chemistry calculations, providing the mathematical foundation for representing molecular orbitals.
It’s typically used as input for SCF calculations and is usually created automatically when selecting a predefined basis set for a calculation.
Note
QDK/Chemistry provides a collection of predefined basis sets that can be accessed through the appropriate factory functions. For common calculations, you typically won’t need to construct basis sets manually.
Core concepts
Shells and primitives
A shell represents a group of basis functions that share the same atom, angular momentum, and primitive functions, but differ in magnetic quantum numbers. For example, a \(p\)-shell contains \(p_x, p_y, p_z\) functions.
Shells contain primitives, which are Gaussian functions defined by:
Exponent: Controls how diffuse or tight the function is
Coefficient: Controls the weight of the primitive in the contracted function
Orbital types
The BasisSet class supports various orbital types with different angular momentum:
S orbital (angular momentum \(l=0\)): 1 function per shell (spherical or Cartesian)
P orbital (angular momentum \(l=1\)): 3 functions per shell (spherical or Cartesian)
D orbital (angular momentum \(l=2\)): 5 functions (spherical) or 6 functions (Cartesian) per shell
F orbital (angular momentum \(l=3\)): 7 functions (spherical) or 10 functions (Cartesian) per shell
G, H, I orbitals: Higher angular momentum orbitals
Basis types
The BasisSet class supports two types of basis functions:
- Spherical
Uses spherical harmonics with \(2l+1\) functions per shell
- Cartesian
Uses Cartesian coordinates with \((l+1)(l+2)/2\) functions per shell
Loading from the basis set library
QDK/Chemistry provides a comprehensive library of predefined basis sets for convenience. This is the recommended approach for most calculations, as it ensures correct basis function definitions and supports a wide range of standard basis sets.
The library supports three methods for loading basis sets:
By basis set name: Apply the same basis set to all atoms
By element map: Use different basis sets for different elements
By atom index map: Use different basis sets for specific atoms
Note
If a basis set includes an ECP (Effective Core Potential), it will be automatically loaded. ECPs are commonly used to replace core electrons with pseudopotentials, particularly for heavy atoms.
// Create a water molecule structure
std::vector<Eigen::Vector3d> coords = {
{0.0, 0.0, 0.0}, {0.757, 0.586, 0.0}, {-0.757, 0.586, 0.0}};
std::vector<std::string> symbols = {"O", "H", "H"};
Structure structure(coords, symbols);
// Create basis sets from the library using basis set name
auto basis_from_name = BasisSet::from_basis_name("sto-3g", structure);
// Create basis sets from the library using element-based mapping
std::map<std::string, std::string> basis_map = {{"H", "sto-3g"},
{"O", "def2-svp"}};
auto basis_from_element = BasisSet::from_element_map(basis_map, structure);
// Create basis sets from the library using index-based mapping
std::map<size_t, std::string> index_basis_map = {
{0, "def2-svp"}, {1, "sto-3g"}, {2, "sto-3g"}}; // O at 0, H at 1 and 2
auto basis_from_index = BasisSet::from_index_map(index_basis_map, structure);
import numpy as np
from pathlib import Path
from qdk_chemistry.data import AOType, BasisSet, OrbitalType, Shell, Structure
# Load a water molecule structure from XYZ file
structure = Structure.from_xyz_file(
Path(__file__).parent / "../data/water.structure.xyz"
)
# Create basis sets from the library using basis set name
basis_from_name = BasisSet.from_basis_name("sto-3g", structure)
# Create basis sets from the library using element-based mapping
basis_map = {"H": "sto-3g", "O": "def2-svp"}
basis_from_element = BasisSet.from_element_map(basis_map, structure)
# Create basis sets from the library using index-based mapping
index_basis_map = {0: "def2-svp", 1: "sto-3g", 2: "sto-3g"} # O at 0, H at 1 and 2
basis_from_index = BasisSet.from_index_map(index_basis_map, structure)
See also
For a complete list of available basis sets, see the Supported Basis Sets documentation.
Creating a basis set
Note
In most cases, you should use the built-in basis set library rather than creating basis sets manually. Manual creation is primarily for advanced use cases or when working with custom basis sets not available in the library.
// Create a shell with multiple primitives
size_t atom_index = 0; // First atom
OrbitalType orbital_type = OrbitalType::P; // p orbital
Eigen::VectorXd exponents(2);
exponents << 0.16871439, 0.62391373;
Eigen::VectorXd coefficients(2);
coefficients << 0.43394573, 0.56604777;
Shell shell1(atom_index, orbital_type, exponents, coefficients);
// Add a shell with a single primitive
Shell shell2(1, OrbitalType::S, Eigen::VectorXd::Constant(1, 0.5),
Eigen::VectorXd::Constant(1, 1.0));
// Create a basis set from the shells
std::vector<Shell> shells = {shell1, shell2};
std::string name = "6-31G";
BasisSet basis_set(name, shells, structure, AOType::Spherical);
# Create a shell with multiple primitives
atom_index = 0 # First atom
orbital_type = OrbitalType.P # p orbital
exponents = np.array([0.16871439, 0.62391373])
coefficients = np.array([0.43394573, 0.56604777])
shell1 = Shell(atom_index, orbital_type, exponents, coefficients)
# Create a shell with a single primitive
shell2 = Shell(1, OrbitalType.S, [0.5], [1.0])
# Create a basis set from the shells
basis_set = BasisSet("6-31G", [shell1, shell2], structure, AOType.Spherical)
Accessing basis set data
Following the immutable design principle used throughout QDK/Chemistry, all getter methods return const references or copies of the data. This ensures that the basis set data remains consistent and prevents accidental modifications that could lead to inconsistent states.
Note
If you need to modify a basis set after creation, you should create a new BasisSet object with the desired changes rather than trying to modify an existing one.
// Get basis set type and name (returns AOType)
auto basis_atomic_orbital_type = basis_set.get_atomic_orbital_type();
// Get basis set name (returns std::string)
auto basis_name = basis_set.get_name();
// Get all shells (returns const std::vector<Shell>&)
auto all_shells = basis_set.get_shells();
// Get shells for specific atom (returns const std::vector<const Shell>&)
auto shells_for_atom = basis_set.get_shells_for_atom(0);
// Get specific shell by index (returns const Shell&)
const Shell& specific_shell = basis_set.get_shell(1);
// Get counts
size_t num_shells = basis_set.get_num_shells();
size_t num_atomic_orbitals = basis_set.get_num_atomic_orbitals();
size_t num_atoms = basis_set.get_num_atoms();
// Get atomic orbital information (returns std::pair<size_t, int>)
auto [shell_index, m_quantum_number] = basis_set.get_atomic_orbital_info(2);
size_t atom_index = basis_set.get_atom_index_for_atomic_orbital(2);
// Get indices for specific atoms or orbital types
// Returns std::vector<size_t>
auto atomic_orbital_indices =
basis_set.get_atomic_orbital_indices_for_atom(1);
// Returns std::vector<size_t>
auto shell_indices =
basis_set.get_shell_indices_for_orbital_type(OrbitalType::P);
// Returns std::vector<size_t>
auto shell_indices_specific =
basis_set.get_shell_indices_for_atom_and_orbital_type(0, OrbitalType::D);
# Get basis set type and name (returns AOType)
basis_atomic_orbital_type = basis_set.get_atomic_orbital_type()
# Get basis set name (returns str)
basis_name = basis_set.get_name()
# Get all shells (returns list[Shell])
all_shells = basis_set.get_shells()
# Get shells for specific atom (returns list[Shell])
shells_for_atom = basis_set.get_shells_for_atom(0)
# Get specific shell by index (returns Shell)
specific_shell = basis_set.get_shell(1)
# Get counts
num_shells = basis_set.get_num_shells()
num_atomic_orbitals = basis_set.get_num_atomic_orbitals()
num_atoms = basis_set.get_num_atoms()
# Get atomic orbital information (returns tuple[int, int])
shell_index, m_quantum_number = basis_set.get_atomic_orbital_info(2)
atom_index = basis_set.get_atom_index_for_atomic_orbital(2)
# Get indices for specific atoms or orbital types
# Returns list[int]
atomic_orbital_indices = basis_set.get_atomic_orbital_indices_for_atom(1)
# Returns list[int]
shell_indices = basis_set.get_shell_indices_for_orbital_type(OrbitalType.P)
# Returns list[int]
shell_indices_specific = basis_set.get_shell_indices_for_atom_and_orbital_type(
0, OrbitalType.D
)
Working with shells
The Shell structure contains information about a group of basis functions:
// Get shell by index (returns const Shell&)
const Shell& shell = basis_set.get_shell(0);
size_t atom_idx = shell.atom_index;
OrbitalType orb_type = shell.orbital_type;
// Get exponents (returns const Eigen::VectorXd&)
const Eigen::VectorXd& exps = shell.exponents;
// Get coefficients (returns const Eigen::VectorXd&)
const Eigen::VectorXd& coeffs = shell.coefficients;
// Get information from shell
size_t num_primitives = shell.get_num_primitives();
size_t num_aos = shell.get_num_atomic_orbitals(AOType::Spherical);
int angular_momentum = shell.get_angular_momentum();
# Get shell by index (returns Shell)
shell = basis_set.get_shell(0)
atom_idx = shell.atom_index
orb_type = shell.orbital_type
# Get exponents (returns np.ndarray)
exps = shell.exponents
# Get coefficients (returns np.ndarray)
coeffs = shell.coefficients
# Get information from shell
num_primitives = shell.get_num_primitives()
num_aos = shell.get_num_atomic_orbitals(AOType.Spherical)
angular_momentum = shell.get_angular_momentum()
Serialization
The BasisSet class supports serialization to and from JSON and HDF5 formats.
For detailed information about serialization in QDK/Chemistry, see the Serialization documentation.
Note
All basis set-related files require the .basis_set suffix before the file type extension, for example molecule.basis_set.json and h2.basis_set.h5 for JSON and HDF5 files respectively.
This naming convention is enforced to maintain consistency across the QDK/Chemistry ecosystem.
File formats
QDK/Chemistry supports multiple serialization formats for basis set data:
JSON format
JSON representation of a BasisSet has the following structure (showing simplified content):
{
"atoms": [
{
"atom_index": 0,
"shells": [
{
"coefficients": [0.1543289673, 0.5353281423, 0.4446345422],
"exponents": [3.425250914, 0.6239137298, 0.168855404],
"orbital_type": "s"
},
{
"coefficients": [0.1559162750, 0.6076837186],
"exponents": [0.7868272350, 0.1881288540],
"orbital_type": "p"
}
]
},
{
"atom_index": 1,
"shells": ["..."]
}
],
"basis_type": "spherical",
"name": "6-31G",
"num_atoms": 2,
"num_basis_functions": 9,
"num_shells": 3
}
HDF5 format
HDF5 representation of a BasisSet has the following structure (showing groups and datasets):
/
├── shells/ # Group
│ ├── atom_indices # Dataset: uint32, 1D Array of atom indices
│ ├── coefficients # Dataset: float64, 1D Array of orbital coefficients
│ ├── exponents # Dataset: float64, 1D Array of orbital exponents
│ ├── num_primitives # Dataset: uint32, 1D Array of number of primitives per orbital
│ └── orbital_types # Dataset: int32, 1D Array of orbital type per orbital
└── metadata/ # Group
└── name # Attribute: string value of the basis set name
// Generic serialization with format specification
basis_set.to_file("molecule.basis_set.json", "json");
auto basis_set_from_file =
BasisSet::from_file("molecule.basis_set.json", "json");
// JSON serialization
basis_set.to_json_file("molecule.basis_set.json");
auto basis_set_from_json_file =
BasisSet::from_json_file("molecule.basis_set.json");
// Direct JSON conversion
nlohmann::json j = basis_set.to_json();
auto basis_set_from_json = BasisSet::from_json(j);
// HDF5 serialization
basis_set.to_hdf5_file("molecule.basis_set.h5");
auto basis_set_from_hdf5 = BasisSet::from_hdf5_file("molecule.basis_set.h5");
# Generic serialization with format specification
basis_set.to_file("molecule.basis_set.json", "json")
basis_set = BasisSet.from_file("molecule.basis_set.json", "json")
# JSON serialization
basis_set.to_json_file("molecule.basis_set.json")
basis_set = BasisSet.from_json_file("molecule.basis_set.json")
# Direct JSON conversion
j = basis_set.to_json()
basis_set = BasisSet.from_json(j)
# HDF5 serialization
basis_set.to_hdf5_file("molecule.basis_set.h5")
basis_set = BasisSet.from_hdf5_file("molecule.basis_set.h5")
Utility functions
The BasisSet class provides several static utility functions:
// Convert orbital type to string (returns std::string)
std::string orbital_str =
BasisSet::orbital_type_to_string(OrbitalType::D); // "d"
// Convert string to orbital type (returns OrbitalType)
OrbitalType orbital_type =
BasisSet::string_to_orbital_type("f"); // OrbitalType::F
// Get angular momentum (returns int)
int l_value = BasisSet::get_angular_momentum(OrbitalType::P); // 1
// Get number of orbitals for angular momentum (returns int)
int num_orbitals =
BasisSet::get_num_orbitals_for_l(2, AOType::Spherical); // 5
// Convert basis type to string (returns std::string)
std::string basis_str = BasisSet::atomic_orbital_type_to_string(
AOType::Cartesian); // "cartesian"
// Convert string to basis type (returns AOType)
AOType atomic_orbital_type = BasisSet::string_to_atomic_orbital_type(
"spherical"); // AOType::Spherical
# Convert orbital type to string (returns str)
orbital_str = BasisSet.orbital_type_to_string(OrbitalType.D) # "d"
# Convert string to orbital type (returns OrbitalType)
orbital_type = BasisSet.string_to_orbital_type("f") # OrbitalType.F
# Get angular momentum (returns int)
l_value = BasisSet.get_angular_momentum(OrbitalType.P) # 1
# Get number of orbitals for angular momentum (returns int)
num_orbitals = BasisSet.get_num_orbitals_for_l(2, AOType.Spherical) # 5
# Convert basis type to string (returns str)
basis_str = BasisSet.atomic_orbital_type_to_string(AOType.Cartesian) # "cartesian"
# Convert string to basis type (returns AOType)
atomic_orbital_type = BasisSet.string_to_atomic_orbital_type(
"spherical"
) # AOType.Spherical
Predefined basis sets
QDK/Chemistry provides access to a library of standard basis sets commonly used in quantum chemistry calculations. These predefined basis sets can be easily loaded without having to manually specify the basis functions. For a complete list of available basis sets and their specifications, see the Supported Basis Sets documentation.
// Check supported basis sets
auto supported_basis_sets = BasisSet::get_supported_basis_set_names();
// Check supported elements for basis set
auto supported_elements =
BasisSet::get_supported_elements_for_basis_set("sto-3g");
# Check supported basis sets
supported_basis_sets = BasisSet.get_supported_basis_set_names()
# Check supported elements for basis set
supported_elements = BasisSet.get_supported_elements_for_basis_set("sto-3g")
Note
The basis set library includes popular basis sets such as STO-nG, Pople basis sets (3-21G, 6-31G, etc.), correlation-consistent basis sets (cc-pVDZ, cc-pVTZ, etc.), and more. The availability may depend on your QDK/Chemistry installation.
Further reading
The above examples can be downloaded as complete C++ and Python scripts.
Serialization: Data serialization and deserialization
Settings: Configuration settings for algorithms
Supported basis sets: List of pre-defined basis sets available in QDK/Chemistry