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:

  1. By basis set name: Apply the same basis set to all atoms

  2. By element map: Use different basis sets for different elements

  3. 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