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
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 an empty basis set with a name
BasisSet basis_set("6-31G", AOType::Spherical);
// Add 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;
basis_set.add_shell(atom_index, orbital_type, exponents, coefficients);
// Add a shell with a single primitive
basis_set.add_shell(1, OrbitalType::S, 0.5, 1.0);
// Set molecular structure
basis_set.set_structure(structure);
import tempfile
from pathlib import Path
from qdk_chemistry.data import AOType, BasisSet, OrbitalType, Shell, Structure
# Create a spherical basis set from a single S shell
shell = Shell(0, OrbitalType.S, [1.0], [1.0])
basis_spherical = BasisSet("spherical example", [shell])
# Create a Cartesian basis set
basis_cartesian = BasisSet("6-31G", [shell], AOType.Cartesian)
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 atomic_orbital_type = basis_set.get_atomic_orbital_type();
// Get basis set name (returns std::string)
auto 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(3);
// 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(5);
size_t atom_index = basis_set.get_atom_index_for_atomic_orbital(5);
// 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);
// Validation
bool is_valid = basis_set.is_valid();
bool is_consistent = basis_set.is_consistent_with_structure();
# Load a water molecule structure from XYZ file
structure = Structure.from_xyz_file(
Path(__file__).parent / "../data/water.structure.xyz"
)
# Create shells consistent with structure
shells = [
Shell(0, OrbitalType.S, [1.0], [1.0]), # O 1s
Shell(1, OrbitalType.S, [1.0], [1.0]), # H 1s
Shell(2, OrbitalType.S, [1.0], [1.0]), # H 1s
]
basis = BasisSet("STO-3G", shells, structure)
# Access basic properties
print(f"Basis set name: {basis.get_name()}")
print(f"Atomic orbital type: {basis.get_atomic_orbital_type()}")
# Access shell information
print(f"Number of atomic orbitals: {basis.get_num_atomic_orbitals()}")
print(f"Number of shells: {basis.get_num_shells()}")
for ishell, shell in enumerate(basis.get_shells()):
print(
f"Shell {ishell}: type={shell.orbital_type}, l={shell.get_angular_momentum()}, "
f"num_primitives={shell.get_num_primitives()}"
)
print(f"Shells for atom 0: {basis.get_shells_for_atom(0)}")
print(f"Specific shell = {basis.get_shell(0)}")
# Get counts
print(f"num_shells = {basis.get_num_shells()}")
print(f"num_atomic_orbitals = {basis.get_num_atomic_orbitals()}")
print(f"num_atoms = {basis.get_num_atoms()}")
# Get mapping information
print(f"shell_index, m_quantum_number = {basis.get_atomic_orbital_info(0)}")
print(f"atom_index = {basis.get_atom_index_for_atomic_orbital(0)}")
# Get indices for specific atoms/orbitals
print(f"atomic_orbital_indices = {basis.get_atomic_orbital_indices_for_atom(0)}")
print(f"shell_indices = {basis.get_shell_indices_for_orbital_type(OrbitalType.P)}")
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_atomic_orbitals = shell.get_num_atomic_orbitals(AOType::Spherical);
int angular_momentum = shell.get_angular_momentum();
# Access individual shell properties
print(f"shell_atom_idx = {shell.atom_index}")
print(f"shell_orb_type = {shell.orbital_type}")
shell = basis.get_shell(0)
print(f"shell_l = {shell.get_angular_momentum()}")
print(f"shell_num_primitives = {shell.get_num_primitives()}")
print(f"shell_num_atomic_orbitals = {shell.get_num_atomic_orbitals()}")
# Access shell primitive data
print(f"shell_exponents = {shell.exponents}")
print(f"shell_coefficients = {shell.coefficients}")
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.json", "json");
basis_set.from_file("molecule.basis.json", "json");
// JSON serialization
basis_set.to_json_file("molecule.basis.json");
basis_set.from_json_file("molecule.basis.json");
// Direct JSON conversion
nlohmann::json j = basis_set.to_json();
basis_set.from_json(j);
// HDF5 serialization
basis_set.to_hdf5_file("molecule.basis.h5");
basis_set.from_hdf5_file("molecule.basis.h5");
# Create a new basis set with data
shells = [
Shell(0, OrbitalType.S, [1.0], [1.0]),
Shell(0, OrbitalType.P, [0.5], [1.0]),
]
basis_out = BasisSet("STO-3G", shells)
# Demonstrate and test JSON conversion
json_data = basis_out.to_json()
assert isinstance(json_data, str)
assert "STO-3G" in json_data
basis_in = BasisSet.from_json(json_data)
assert basis_in.get_name() == "STO-3G"
assert basis_in.get_num_shells() == 2
assert basis_in.get_num_atomic_orbitals() == 4
with tempfile.NamedTemporaryFile(
suffix=".basis_set.json", mode="w", delete=False
) as tmp:
filename = tmp.name
basis_out.to_json_file(filename)
basis_file = BasisSet.from_json_file(filename)
assert basis_file.get_name() == "STO-3G"
assert basis_file.get_num_shells() == 2
assert basis_file.get_num_atomic_orbitals() == 4
# Demonstrate and test HDF5 conversion
with tempfile.NamedTemporaryFile(suffix=".basis_set.h5", delete=False) as tmp:
filename = tmp.name
basis_out.to_hdf5_file(filename)
basis_in = BasisSet.from_hdf5_file(filename)
assert basis_in.get_name() == "STO-3G"
assert basis_in.get_num_shells() == 2
assert basis_in.get_num_atomic_orbitals() == 4
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
# Static utility functions for type conversions
print(f"orbital_str = {BasisSet.orbital_type_to_string(OrbitalType.D)}")
print(f"orbital_type = {BasisSet.string_to_orbital_type('f')}")
print(f"l_value = {BasisSet.get_angular_momentum(OrbitalType.P)}")
print(f"num_orbitals = {BasisSet.get_num_orbitals_for_l(2, AOType.Spherical)}")
print(f"basis_str = {BasisSet.atomic_orbital_type_to_string(AOType.Cartesian)}")
print(f"atomic_orbital_type = {BasisSet.string_to_atomic_orbital_type('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.
// Create a basis set from a predefined library (returns
// std::unique_ptr<BasisSet>)
auto basis_set = BasisSet::create("6-31G");
// List all available basis sets (returns std::vector<std::string>)
auto available_basis_sets = BasisSet::get_available_basis_sets();
// Check if a basis set exists in the library (returns bool)
bool has_basis = BasisSet::has_basis_set("cc-pvdz");
# TODO: add example of listing available basis sets in the library and loading one
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