1.2. Adding a New Hamiltonian Block

Before starting an implementation, verify whether the block is already supported by looking at the pybest/featuredlists.py file. Check if your block string is registered under any of the following execution categories:

  • GeneralEffHamBlocks

  • GeneralEffHamCholeskyBlocks

  • GeneralEffHamSpecialBlocks

If your block is not listed in any of these featured lists, it means the block is currently not implemented, and you must follow these strict steps to integrate it into the dynamic loader.

1.2.1. Determine Directory and Create File

The directory structure relies on translating the block’s indices into an occupied/virtual string pattern.

Index Translation Mapping:

  • Occupied (o): i, j, k, l, m, n, o

  • Virtual (v): a, b, c, d, e, f, g, h

Steps:

  1. Calculate the occupied/virtual string based on the name of the new block (e.g., X_iajb translates to ovov).

  2. Find the corresponding directory under eff_ham/blocks/ (e.g., four_index/ovov/).

  3. Create a new Python file named exactly as your block but strictly in lowercase (e.g., X_iajb translates to x_iajb.py).

Note

Case Sensitivity & Spin: While the file name must be completely lowercase, the method names inside the file must retain their exact original casing. In PyBEST, uppercase and lowercase letters denote alpha and beta spins. Blocks like X_iajb and X_iAJB are completely different mathematical entities. Both of their getter methods would live inside the same lowercase x_iajb.py file.

1.2.2. Implement with the Correct Getter Protocol

The dispatcher enforces distinct calling conventions based on memory management constraints. You must adhere strictly to the naming conventions.

1.2.2.1. Helper Functions

You can always add helper functions (e.g., for calculating separate T1/T2 contributions) in the same file to keep your code clean. The dynamic loader will correctly bind them to the GeneralEffHam instance as long as the helper method’s name contains the exact block name.

For example, for the X_ajbc block, a helper named get_t1_t2_X_ajbc is perfectly valid and will be loaded automatically.

1.2.2.2. Protocol A: Dense Blocks (GeneralEffHamBlocks)

These are standard, cacheable blocks.

  • Naming: The method must be named get_{block_name}_block (retaining exact case for the block name).

  • Arguments: self, force_update (boolean).

  • Behavior: Compute and cache. The contract() runner handles the actual tensor math.

# File: blocks/two_index/vv/x_ad.py
def get_X_ad_block(self, force_update: bool = False):
    """Computes X_ad and caches it."""
    if "X_ad" in self._cache and not force_update:
        return self._cache.load("X_ad")

    # ... compute X_ad ...

    return X_ad

1.2.2.3. Protocol B: On-the-Fly Blocks (Cholesky & Special)

These blocks are too large to cache and compute directly into an output tensor.

Note

If you are implementing a Cholesky block (adding it to GeneralEffHamCholeskyBlocks), you always have to make two implementations:

  1. The Dense version following Protocol A (get_{block_name}_block).

  2. The Cholesky version following Protocol B below.

For Special Blocks (GeneralEffHamSpecialBlocks): These are inherently too large or complex to cache and bypass it entirely regardless of the active linear algebra factory. You only need to implement the Protocol B version below.

  • Naming: The method must be named get_{block_name}_block_cholesky (retaining exact case for the block name).

  • Arguments: self, operand2 (NIndex, usually b_vector), output (NIndex). Note: Special unary blocks may only accept self and output.

  • Behavior: Compute directly into the output vector using .contract(..., out=output). The output tensor is provided by the developer calling the API. The getter method does not allocate it; it must simply apply the correct contractions to populate or update the provided output object.

# File: blocks/four_index/ovov/x_iajb.py
def get_X_iAjb_sigma_block_cholesky(self, sigma):
    """Computes X_iAjb and applies it directly to the output 'sigma'."""
    e_oovv = self._ham.get("eri_oovv")
    t_2 = self.rcc_iodata.t_2

    # Compute directly into output
    e_oovv.contract("abcd,bdac->dac", t_2, factor=-1.0, out=sigma)

1.2.4. Route in BLOCK_LIBRARY

Open pybest/eff_ham/GeneralEffHam_library.py and add the block label to the dictionary under the specific reference method, flavor, and parameter state it belongs to.

BLOCK_LIBRARY = {
    "RCCSD": {
        "default": [],
        "flavor": {
            "RSF-CCSD": {
                "alpha": {
                    2: [
                        "X_im",
                        "X_ad",
                        # ...
                        "X_new",
                        "X_huge",
                        "X_special",  # <--- Register your new blocks here for alpha=2
                        # ...
                    ],
                },
            },
        },
    },
}