Memory budget

Every run_job calculation runs through a pre-flight memory check that (a) reports the estimated peak memory in the text output and (b) aborts with an explanation if that estimate exceeds the machine’s available RAM. This is a guardrail against the most common catastrophic failure mode of a QC code, calculations that silently thrash to disk and freeze the host.

What you’ll see in the .out file

Job: RKS / PBE  basis=cc-pvdz
Atoms (bohr)
------------------------------------------------------
   1  Z=  8       0.00000000      0.00000000      0.00000000
   2  Z=  1       0.00000000      1.43000000     -0.98000000
   3  Z=  1       0.00000000     -1.43000000     -0.98000000
charge=0  multiplicity=1  n_electrons=10

vibe-qc estimates this calculation will require ~0.13 GB of memory:
    ERI tensor            0.00 GB
    Fock + density + 1e   0.00 GB
    DIIS history          0.00 GB
    MO workspace          0.00 GB
    DFT grid + chi        0.10 GB
    Python runtime + NumPy overhead   0.10 GB
Available on this machine: 24.0 GB. Proceeding.

  iter     energy (Ha)            dE          ||[F,DS]||   DIIS
  ...

The headline figure already carries a 1.5x default safety headroom over the sum of the per-category numbers. Set VIBEQC_MEMORY_HEADROOM=2.0 (or another value >= 1.0) to tune the factor for a site or scheduler wrapper.

When the estimate exceeds available RAM

vibe-qc estimates this calculation will require ~218.4 GB of memory:
    ERI tensor      186.0 GB
    ...
Available on this machine: 7.2 GB. ABORTING.

InsufficientMemoryError: Set `options.memory_override = True` (or pass
`memory_override=True` to `run_job`) to proceed anyway. Consider a
smaller basis or, once shipped in v0.6+, density fitting / on-disk
scratch.

Overriding the check

Pass memory_override=True to run_job:

from vibeqc import Molecule, run_job

mol = Molecule.from_xyz("large.xyz")
run_job(
    mol, basis="def2-tzvp", method="rhf",
    output="huge",
    memory_override=True,    # accept the risk of swap / freeze
)

The output then reads Proceeding (override) instead of Proceeding so anyone reading the log later knows what happened.

Estimators covered

Method

Dominant cost

Notes

RHF / UHF

Dense ERI tensor or direct-SCF shell-pair scratch

Direct SCF charges about 1 KB per shell pair

RKS / UKS

HF baseline + DFT grid, weights, coordinates, libxc scratch

meta-GGA adds tau buffers

MP2 / UMP2

OVOV tensor or DF auxiliary integrals and OV workspace

spin-channel storage for UMP2

CCSD / CCSD(T)

T1/T2 amplitudes, D1/D2 intermediates, triples workspace

DF auxiliary integrals are charged separately

DLPNO-MP2 / DLPNO-CCSD(T)

PAO/PNO domains, pair lists, auxiliary pair workspaces, triples domains

conservative local-domain estimate

CAS/CI/FCI

CI or determinant vectors, RDMs, MO integral transforms

exponential methods report their vector storage

Periodic GDF / GPW / GAPW

GDF Lpq factor cache or FFT-grid collocation/cache arrays

wired through run_periodic_job dry-run and live preflight

NEB uses a separate per-image-worker model and is intentionally not folded into the single-point estimator.

Reading the probe yourself

The cross-platform “how much memory is available right now” probe is exposed for scripting:

>>> import vibeqc
>>> vibeqc.available_memory_bytes() / 1024**3
24.3...

It tries psutil.virtual_memory().available first (install with pip install psutil to get the highest-quality number), then falls back to /proc/meminfo on Linux and os.sysconf on macOS. Returns 0 if all probes fail, check_memory treats that as “unknown” and silently proceeds rather than false-aborting on an unsupported platform.

Writing your own estimator

If you call the low-level SCF drivers (run_rhf, run_rks, …) directly instead of through run_job, you can invoke the estimator yourself:

from vibeqc import estimate_memory, check_memory

est = estimate_memory(mol, basis, method="rhf", options=rhf_options)
print(est)                       # human-readable block
check_memory(est)                # raises InsufficientMemoryError if over budget
# ... your own driver call ...

The estimator returns a MemoryEstimate dataclass with a by_category dict, so you can inspect exactly where the memory goes.