Define an Active Space (Complete Active Space)

Goal

Create a ground-state problem that uses a CAS(n, m) active space, where \(n\) active electrons are distributed among \(m\) active spatial orbitals, with all other electrons frozen.

The active space is added as a problem modifier: it reduces the Hamiltonian of the ground-state problem to the active orbitals only, and folds the contribution of the inactive orbitals into an inactive Fock operator and an inactive energy contribution.

Prerequisites

Steps

  1. Add the active space modifier to the ground-state problem builder

    import qrunch as qc
    
    problem_builder = (
        qc.problem_builder_creator()
        .ground_state()
        .standard()
        .add_problem_modifier()
        .active_space(
            number_of_active_spatial_orbitals=8,
            number_of_active_alpha_electrons=6,
        )
        .create()
    )
    

    This defines an active space where:

    • number_of_active_spatial_orbitals = 8

    • number_of_active_alpha_electrons = 6

    • The number of active beta electrons is inferred from the spin difference in the molecular configuration and number_of_active_alpha_electrons

    • In the case of an unrestricted calculation, the number of active spatial orbitals refers to alpha spin orbitals and the same number of beta spin orbitals will be included

    Example:

    • If spin difference = 2, the number of active beta electrons will be: active_beta = active_alpha - spin_difference = 6 - 2 = 4

    • Total active electrons = \(6_{\alpha} + 4_{\beta} = 10\) electrons in 8 spatial orbitals (16 active spin orbitals)

  2. Build the ground-state problem

    The ground-state problem is built as described in Construct a Ground State Energy Problem. It now also carries an “Inactive electrons energy” contribution, and the result of the ground-state energy calculation includes the energy of the non-active electrons (non_active_electrons_energy) in the total energy.

How Active Orbitals Are Selected

There are two main strategies for selecting active orbitals, and the selection is determined by the type of molecular orbitals used, i.e., whether they come from a mean-field calculation (Hartree-Fock or DFT) or from a correlated calculation (MP2 or CCSD).

Energy-Based Selection (Mean-Field Orbitals)

When the molecular orbitals come from a mean-field calculator (Hartree-Fock or DFT), they are canonical orbitals sorted by molecular orbital energy, and the active space is a contiguous energy window around the HOMO-LUMO gap. The number_of_active_alpha_electrons highest-lying occupied orbitals are made active, the remaining occupied orbitals are frozen, and the window is filled up with the lowest-lying virtual orbitals until number_of_active_spatial_orbitals is reached.

With mean-field orbitals, number_of_active_alpha_electrons must be given explicitly, since the integer-valued mean-field occupation numbers offer no basis for choosing a number of active occupied orbitals automatically.

Occupation-Based Selection (Correlated Natural Orbitals)

When the ground-state problem carries fractional (correlated) occupation numbers, for example MP2 or CCSD natural orbitals, the orbitals are ordered by how far their occupation deviates from the ideal reference values (fully occupied vs. virtual).

For restricted calculations and unrestricted calculations with shared spatial orbitals (spin_summed_natural_orbitals=True), the nominal full occupation is 2 and the deviation is:

\[d_i = \min(n_i, 2 - n_i)\]

for an orbital with total occupation \(n_i \in [0, 2]\). For unrestricted calculations with distinct per-spin orbitals (spin_summed_natural_orbitals=False), each spin channel \(\sigma \in \{\alpha, \beta\}\) is evaluated against a nominal full occupation of 1 with per-spin deviation \(d_i^\sigma = \min(n_i^\sigma, 1 - n_i^\sigma)\) (see Unrestricted Calculations below).

An orbital is considered strongly correlated when its deviation is large, i.e. when it is neither almost full nor almost empty.

Occupation numbers can also fall outside the physical range (\([0, 2]\) for spin-summed/restricted or \([0, 1]\) per spin). This happens because the MP2 and CCSD density matrices are not variational, so their eigenvalues are not guaranteed to be physical, and it is typically seen when the correlated method starts to break down for a strongly correlated system. Such anomalous orbitals signal strong multi-reference character, so the calculators place them right at the HOMO-LUMO border, where a contiguous active window reaches them first, and the automatic occupied count always selects them. A small tolerance (1e-4) is allowed before an occupation outside the physical range is treated as anomalous rather than as eigenvalue noise. A warning is logged when anomalous occupations occur.

To use this mode, choose a correlated molecular orbital calculator on the problem builder:

import qrunch as qc

problem_builder = (
    qc.problem_builder_creator()
    .ground_state()
    .standard()
    .choose_molecular_orbital_calculator()
    .moller_plesset_2()  # or .ccsd()
    .add_problem_modifier()
    .active_space(
        number_of_active_spatial_orbitals=8,
        number_of_active_alpha_electrons=6,
    )
    .create()
)

number_of_active_alpha_electrons may also be omitted here, in which case it is determined automatically from the occupation numbers, as described below.

Automatic Number of Active Occupied Orbitals

number_of_active_alpha_electrons is optional. When it is left out, the number of active occupied orbitals is determined automatically from the occupation numbers: every occupied orbital whose deviation exceeds occupation_deviation_threshold is made active, and the rest of the active space is filled with the most strongly correlated virtual orbitals.

import qrunch as qc

problem_builder = (
    qc.problem_builder_creator()
    .ground_state()
    .standard()
    .choose_molecular_orbital_calculator()
    .ccsd()
    .add_problem_modifier()
    .active_space(number_of_active_spatial_orbitals=10)  # occupied/virtual split chosen automatically
    .create()
)

This requires fractional occupation numbers, i.e. MP2 or CCSD natural orbitals. If the problem carries no occupation numbers, or only integer-valued mean-field occupations, an error is raised asking you either to specify number_of_active_alpha_electrons explicitly or to switch to the MP2 or CCSD molecular orbital calculator.

Unrestricted Calculations

For unrestricted problems (e.g., open-shell systems or broken-symmetry states), the active space can be defined using either distinct per-spin orbitals or shared spatial orbitals. This is controlled by the spin_summed_natural_orbitals option on the MP2 and CCSD calculator options:

Per-spin natural orbitals (default, spin_summed_natural_orbitals=False)

The \(\alpha\) and \(\beta\) density matrices are diagonalized independently, giving distinct spatial orbitals and occupations in \([0, 1]\) for each spin channel.

  • The active window selects the same number of spatial orbitals for both spins, but allows the underlying \(\alpha\) and \(\beta\) orbitals to differ.

  • Sizing and validation automatically account for both spin channels, so unpaired electrons (SOMOs) and strongly correlated pairs in either spin channel are prioritized.

  • Thresholds configured in ActiveSpaceOptions are automatically scaled to the per-spin basis.

Shared spatial orbitals (spin_summed_natural_orbitals=True)

The \(\alpha\) and \(\beta\) densities are combined in the atomic orbital basis to produce a single, shared set of spatial natural orbitals with total occupations in \([0, 2]\) (ROHF-style).

  • Both spin channels share the exact same spatial orbitals; per-spin occupations reflect spin polarization (e.g., a singly occupied orbital has \(n^\alpha \approx 1.0, n^\beta \approx 0.0\)).

  • Recommended when downstream methods expect identical \(\alpha\) and \(\beta\) spatial orbitals.

  • Required when constructing consistent active spaces along a reaction path (see Construct a Reaction-Path Problem).

Tuning the Active Space with Options

You can pass a ActiveSpaceOptions instance to control the active-space reduction:

import qrunch as qc

options = qc.options.ActiveSpaceOptions(
    occupation_deviation_threshold=0.01,
    log_orbital_info=True,
)

problem_builder = (
    qc.problem_builder_creator()
    .ground_state()
    .standard()
    .choose_molecular_orbital_calculator()
    .moller_plesset_2()
    .add_problem_modifier()
    .active_space(number_of_active_spatial_orbitals=10, options=options)
    .create()
)

Option

Default

Description

occupation_deviation_threshold

0.02

Threshold on the occupation deviation. Used for ranking orbitals and, when number_of_active_alpha_electrons is None, for deciding how many occupied orbitals are active. Must be positive.

log_orbital_info

False

Log orbital energies and occupations for all orbitals (frozen occupied, active occupied, active virtual, frozen virtual). Useful when debugging an active-space choice.

Lowering occupation_deviation_threshold includes more orbitals, and raising it includes fewer. This matters most for the automatic occupied count, where too small a threshold can make the number of active occupied orbitals exceed number_of_active_spatial_orbitals.

Verify the Result

  • When you calculate the ground-state energy, the result object will include:

    • non_active_electrons_energy — the electronic energy contribution from electrons not included in the active space.

    • active_electrons_energy — the electronic energy contribution of the active electrons.

    • total_energy — the total energy of the system.

  • The problem itself carries the inactive contribution in problem.energy_contributions.inactive_electrons_energy.

  • At INFO log level, Qrunch reports whether occupation numbers were used to size the active occupied space, and the resulting number of active and inactive orbitals and electrons.

  • At DEBUG log level, Qrunch also reports the indices of the active occupied and active virtual orbitals, along with the occupation numbers at the active/inactive boundaries. This is useful for diagnosing discontinuities in the active space along a reaction path. Set log_orbital_info=True for a full orbital listing.

See Also

Next Step

You can use the active-space ground-state problem to calculate the ground-state energy: