Source code for vibeqc.periodic_runner

"""High-level "run a periodic job" -- companion to :func:`run_job`.

Mirrors the molecular ``run_job`` API for periodic SCFs:

  - ``output.out``       text log (banner, system, basis, SCF trace,
                         energies, properties)
  - ``output.system``    runtime manifest (CPU, OS, libs, wall-time)
  - ``output.molden``    Γ-point MOs in MOLDEN format (when MOs exist)
  - ``output.xsf``       SCF density on a primitive-cell grid
                         (when ``write_density=True``)

Usage::

    from vibeqc import PeriodicSystem, BasisSet
    from vibeqc.periodic_runner import run_periodic_job

    sys_p = PeriodicSystem(...)
    basis = BasisSet(sys_p.unit_cell_molecule(), "sto-3g")
    run_periodic_job(
        sys_p, basis,
        method="RHF",
        output="output",
    )

Status: native GDF covers Gamma and multi-k RHF/ROHF/RKS/UHF/UKS. The
public RIJCOSX route keeps Gamma RHF on the dedicated periodic COSX
driver and routes true multi-k RHF/RKS/UHF/UKS through the native GDF
loop with ``k_exchange="cosx"``. The earlier PySCF-backed GDF spike is
retired because PySCF and CRYSTAL are external reference programs, not
in-process vibe-qc backends. Explicit ``jk_method="fft_poisson"`` is
retired as a public route; ``AUTO`` resolves to native GDF for
closed-shell jobs and BIPOLE for open-shell jobs.
"""

from __future__ import annotations

from dataclasses import replace
import math
import os
import time
import warnings
from contextvars import ContextVar
from functools import wraps
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Tuple, Union

import numpy as np

if TYPE_CHECKING:
    from ._vibeqc_core import BlochKMesh
    from .bands import BandStructure
    from .kpoints import KPoints

from ._vibeqc_core import (
    Atom,
    BasisSet,
    Crystal,
    D3BJParams,
    Functional,
    InitialGuess,
    PeriodicKSOptions,
    PeriodicRHFOptions,
    PeriodicSystem,
    SpaceGroup,
    SpinlockMode,
    attach_symmetry,
    direct_lattice_cells,
    get_num_threads,
    to_primitive,
)
from .banner import VIBEQC_VERSION, banner, enforce_runtime_pin_from_env, library_versions
from .occupations import (
    hartree_to_kelvin_temperature,
    resolve_smearing_temperature,
)
from .output import (
    Column,
    HeaderlessBlock,
    HeaderlessColumn,
    Level,
    ManifestUpdater,
    OutputChannel,
    OutputPlan,
    OutputWriter,
    Quantity,
    Table,
    active_policy,
    dry_run_manifest,
    flush,
    install_diagnostics_bridge,
    is_dry_run_estimate_requested,
    is_dry_run_requested,
    render_energy,
    render_energy_labeled,
    render_frequency,
    section_header,
    print_dry_run_summary,
    warn,
    write,
)
from .output._cpp_diagnostics import (
    _progress_handler as _CPP_PROGRESS_HANDLER_SLOT,
    install_progress_handler,
)
from .output.formats.scf_log import write_scf_trace
from .output._errors import (
    OutputFailureKind,
    warn_output_failure,
    warn_writer_failure,
)
from .output.citations import (
    citation_manifest_rows,
    format_bibtex,
    format_references_block,
    load_default_database,
    write_references_block,
)
from .output.formats.perf import _format_timing_summary
from .output.plan import _resolve_sidecar_request
from .structured_log import (
    StructuredLog,
    run_fingerprint,
)
from .structured_log import (
    structured_log as _structured_log_ctx,
)
from .pbc_gdf import (
    _gamma_dense_core_gdf_parity_held,
    run_pbc_gdf_rhf,
    run_pbc_gdf_uhf,
    run_pbc_gdf_uks,
)
from .periodic_aiccm2026dev_b import (
    _density_blocks_per_k,
    _resolve_backend as _resolve_aiccm2026dev_b_backend,
    _resolve_fitted_rsgdf_tail,
    _spin_density_blocks_per_k,
    cyclic_lattice_extension,
    inverse_bloch_transform,
    run_aiccm2026dev_b_rhf,
    run_aiccm2026dev_b_rks,
    run_aiccm2026dev_b_uhf,
    run_aiccm2026dev_b_uks,
)
from .periodic_convergence_auto import (
    ConvergenceStrategy,
    KnobResolution,
    classify_periodic_system,
    insulator_smearing_warning,
    resolve_convergence_strategy,
)
from .periodic_jk_method import (
    PeriodicJKMethod,
    describe_jk_method,
    pick_jk_method,
    validate_jk_method,
)
from .periodic_k_gdf import (
    _canonical_gdf_density_mixer,
    _expand_ibz_kmesh_to_full_bz,
    _gamma_kmesh_info,
    run_krhf_periodic_gdf,
    run_krks_periodic_gdf,
    run_kuhf_periodic_gdf,
    run_kuks_periodic_gdf,
)
from .periodic_rohf_gdf import run_krohf_periodic_gdf
from .periodic_screened_exchange import (
    reject_periodic_gdf_unsupported_functional,
)
from .output._stem_paths import stem_sibling


_PERIODIC_OUTPUT_WRITER: ContextVar[OutputWriter | None] = ContextVar(
    "vibeqc_periodic_output_writer",
    default=None,
)


def _periodic_output_lifecycle(func):
    """Finish or crash the job-scoped writer at the public API boundary."""

    @wraps(func)
    def wrapped(*args, **kwargs):
        writer_token = _PERIODIC_OUTPUT_WRITER.set(None)
        progress_token = _CPP_PROGRESS_HANDLER_SLOT.set(None)
        try:
            result = func(*args, **kwargs)
        except Exception:
            writer = _PERIODIC_OUTPUT_WRITER.get()
            if writer is not None and writer.status == "running":
                writer.crash()
            raise
        else:
            writer = _PERIODIC_OUTPUT_WRITER.get()
            if writer is not None and writer.status == "running":
                writer.finish()
            return result
        finally:
            try:
                _CPP_PROGRESS_HANDLER_SLOT.reset(progress_token)
            finally:
                _PERIODIC_OUTPUT_WRITER.reset(writer_token)

    return wrapped


from .periodic_rhf_gdf import (
    PeriodicRHFGDFResult,
    run_rhf_periodic_gamma_gdf,
)
from .progress import ProgressLogger, resolve_progress
from .smearing import SmearingOptions

# Sentinel distinguishing "smearing_temperature not given" (auto strategy
# may fill it) from the documented explicit values 0.0 / None (both mean
# "smearing off, by user choice").
_SMEARING_UNSET = object()

# Default-on finite-temperature smearing for periodic GFN2-xTB (k_B T in
# Hartree, about 316 K). A Gamma frontier of a metallic cell can cross on a
# lattice sweep and flip the hard-Aufbau occupations between SCC branches;
# the small default width keeps the SCC on one branch. Explicit
# smearing_temperature=0 restores exact zero-temperature Aufbau. Mirrors
# kPeriodicGFN2DefaultElectronicTemperature in the C++ periodic drivers.
_GFN2_PERIODIC_DEFAULT_SMEARING_HA = 0.001

# Sentinel distinguishing "bz_integration not given" from explicit None
# (which means "use smearing").
_BZ_INTEGRATION_UNSET = object()

_BOHR_TO_ANGSTROM = 0.529177210903

__all__ = ["run_periodic_job"]


def _periodic_xc_gradient_dry_run_estimate_bytes(
    system: PeriodicSystem,
    basis: BasisSet,
    *,
    method_upper: str,
    functional: str | None,
    lattice_cutoff_bohr: float | None = None,
) -> int | None:
    """Best-effort dry-run estimate for periodic RKS/UKS gradients."""
    if method_upper not in ("RKS", "UKS"):
        return None

    from .memory import estimate_periodic_xc_gradient

    opts = PeriodicKSOptions()
    grid = opts.grid
    n_atoms = len(system.unit_cell)
    n_grid_points = (
        n_atoms
        * int(getattr(grid, "n_radial", 75))
        * int(getattr(grid, "n_theta", 17))
        * int(getattr(grid, "n_phi", 36))
    )
    # RKS/UKS Ewald paths may enlarge the density cutoff to 18 bohr before
    # calling the analytic gradient. Charging at least that many cells keeps
    # vq placement from seeing the cheaper default 15-bohr stencil.
    cutoff_base = (
        float(lattice_cutoff_bohr)
        if lattice_cutoff_bohr is not None
        else float(getattr(opts.lattice_opts, "cutoff_bohr", 15.0))
    )
    cutoff = max(cutoff_base, 18.0)
    n_cells = len(direct_lattice_cells(system, cutoff))
    spin = 2 if method_upper == "UKS" else 1
    func = Functional(str(functional), spin)
    kind = getattr(getattr(func, "kind", None), "name", None)
    if kind is None:
        kind = str(getattr(func, "kind", ""))

    return estimate_periodic_xc_gradient(
        n_basis=int(basis.nbasis),
        n_atoms=n_atoms,
        n_grid_points=n_grid_points,
        n_cells=n_cells,
        functional_kind=str(kind),
        open_shell=method_upper == "UKS",
    ).total_bytes


def _basis_primitive_count(basis: BasisSet) -> int | None:
    try:
        return int(sum(len(sh.exponents) for sh in basis.shells()))
    except Exception:
        return None


def _periodic_gpw_gapw_estimate(
    system: PeriodicSystem,
    basis: BasisSet,
    *,
    resolved_jk: PeriodicJKMethod,
    method_upper: str,
    functional: str | None,
    cutoff_ha: float,
    kpoints: object,
):
    """Best-effort estimate for GPW/GAPW FFT-grid SCFs."""
    if resolved_jk not in (PeriodicJKMethod.GPW, PeriodicJKMethod.GAPW):
        return None

    from .memory import estimate_periodic_gpw_gapw
    from .periodic_gapw_grid import nx_for_axis

    lattice = np.asarray(system.lattice, dtype=float)
    n_grid_points = 1
    for axis_length in np.linalg.norm(lattice, axis=0):
        n_grid_points *= nx_for_axis(float(axis_length), float(cutoff_ha))

    functional_kind: str | None = None
    if method_upper in ("ROKS", "RKS", "UKS") and functional is not None:
        func = Functional(
            str(functional),
            2 if method_upper in ("ROKS", "UKS") else 1,
        )
        kind = getattr(getattr(func, "kind", None), "name", None)
        functional_kind = str(kind if kind is not None else getattr(func, "kind", ""))

    try:
        n_kpoints = _bloch_kmesh_size(_runner_bloch_kmesh(system, kpoints))
    except Exception:
        n_kpoints = 1

    compact_multik = False
    if n_kpoints > 1:
        try:
            from .periodic_gapw_j import _multik_gpw_is_molecular_limit

            compact_multik = not _multik_gpw_is_molecular_limit(system)
        except Exception:
            compact_multik = False

    n_soft_basis: int | None = None
    augmentation_active: bool | None = False
    if resolved_jk == PeriodicJKMethod.GAPW:
        n_soft_basis = int(basis.nbasis)
        augmentation_active = True
        try:
            from .periodic_gapw_augment import softened_basis

            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                soft_basis = softened_basis(basis, system)
            n_soft_basis = int(soft_basis.nbasis)
            full_prim = _basis_primitive_count(basis)
            soft_prim = _basis_primitive_count(soft_basis)
            if full_prim is not None and soft_prim is not None:
                augmentation_active = soft_prim != full_prim
        except Exception:
            augmentation_active = True

    return estimate_periodic_gpw_gapw(
        n_basis=int(basis.nbasis),
        n_grid_points=n_grid_points,
        route=resolved_jk.value,
        functional_kind=functional_kind,
        open_shell=method_upper in ("ROHF", "ROKS", "UHF", "UKS"),
        n_kpoints=n_kpoints,
        compact_multik=compact_multik,
        n_soft_basis=n_soft_basis,
        n_atoms=len(system.unit_cell),
        augmentation_active=augmentation_active,
        analytic_eri_one_centre=(
            resolved_jk == PeriodicJKMethod.GAPW
            and method_upper in ("RHF", "UHF")
        ),
    )


def _periodic_gpw_gapw_dry_run_estimate_bytes(
    system: PeriodicSystem,
    basis: BasisSet,
    *,
    resolved_jk: PeriodicJKMethod,
    method_upper: str,
    functional: str | None,
    cutoff_ha: float,
    kpoints: object,
) -> int | None:
    """Best-effort dry-run estimate for GPW/GAPW FFT-grid SCFs."""
    est = _periodic_gpw_gapw_estimate(
        system,
        basis,
        resolved_jk=resolved_jk,
        method_upper=method_upper,
        functional=functional,
        cutoff_ha=cutoff_ha,
        kpoints=kpoints,
    )
    return None if est is None else est.total_bytes


def _periodic_functional_needs_exchange(
    functional: str | None,
    *,
    open_shell: bool,
) -> bool:
    if functional is None:
        return False
    try:
        func = Functional(str(functional), 2 if open_shell else 1)
    except Exception:
        return False
    return bool(getattr(func, "is_hybrid", False)) or bool(
        getattr(func, "is_range_separated", False)
    )


def _periodic_gdf_aux_basis_size(
    system: PeriodicSystem,
    basis: BasisSet,
    aux_basis: str | None,
) -> int:
    if aux_basis:
        try:
            from .aux_basis import make_aux_basis_set

            return int(
                make_aux_basis_set(
                    system.unit_cell_molecule(),
                    aux_name=aux_basis,
                ).nbasis
            )
        except Exception:
            pass
    return max(1, 3 * int(basis.nbasis))


def _periodic_gdf_estimate(
    system: PeriodicSystem,
    basis: BasisSet,
    *,
    resolved_jk: PeriodicJKMethod,
    method_upper: str,
    functional: str | None,
    kpoints: object,
    aux_basis: str | None,
    rsgdf_ke_cutoff: float = 200.0,
    n_ibz_kpoints: int | None = None,
):
    if resolved_jk not in (PeriodicJKMethod.GDF, PeriodicJKMethod.RIJCOSX):
        return None

    from .memory import estimate_periodic_multik_gdf

    try:
        n_kpoints = _bloch_kmesh_size(_runner_bloch_kmesh(system, kpoints))
    except Exception:
        n_kpoints = 1
    n_aux = _periodic_gdf_aux_basis_size(system, basis, aux_basis)
    open_shell = method_upper in ("ROHF", "ROKS", "UHF", "UKS")
    need_k_pairs = method_upper in ("RHF", "ROHF", "UHF")
    if method_upper in ("RKS", "UKS"):
        need_k_pairs = _periodic_functional_needs_exchange(
            functional,
            open_shell=open_shell,
        )
    if resolved_jk == PeriodicJKMethod.RIJCOSX:
        need_k_pairs = True
    label_parts = [method_upper]
    if functional:
        label_parts.append(str(functional))
    label_parts.append(resolved_jk.value)
    route_label = " ".join(label_parts)
    # The space-group-reduced exchange build stores n_IBZ x n_k Lpq blocks,
    # so charging it n_k^2 would abort a run that fits (measured on NaCl
    # primitive / def2-SVP / def2-svp-jk at (4,4,4): the Lpq term is
    # 15.95 GB unreduced vs 1.99 GB at n_IBZ = 8) and would print an
    # estimate contradicting the cache actually allocated.
    # The wedge never reduces the diagonal-only cache.
    n_ibz = (
        int(n_ibz_kpoints)
        if (n_ibz_kpoints is not None and need_k_pairs)
        else None
    )
    estimate = estimate_periodic_multik_gdf(
        n_basis=int(basis.nbasis),
        n_aux=n_aux,
        n_kpoints=n_kpoints,
        need_k_pairs=need_k_pairs,
        open_shell=open_shell,
        n_ibz_kpoints=n_ibz,
    )
    # The default dense rsgdf fit build holds ONE (n_ao, n_ao, n_G)
    # complex128 AO-pair FT bundle in RAM while the Lpq factors are
    # assembled (the Schwarz-screened lean mode is opt-in, not the
    # default; see handovers/HANDOVER_GDF_FIT_SCREENING.md).  This
    # bundle, not the Lpq cache, is the peak on production basis sets:
    # Al2O3/def2-SVP at ke_cutoff=200 measured 41 GiB RSS against a
    # 0.40 GB preflight that missed it (2026-08-13).  Model it so the
    # fail-early gate tells the truth instead of OOM-killing the run
    # mid-setup.
    try:
        from .aux_basis import rsgdf_dense_g_mesh

        _g_mesh = rsgdf_dense_g_mesh(system, float(rsgdf_ke_cutoff))
        _pair_ft_bytes = (
            int(basis.nbasis) * int(basis.nbasis)
            * int(_g_mesh.shape[0]) * 16
        )
    except Exception:
        _pair_ft_bytes = 0
    if _pair_ft_bytes > 0:
        estimate.by_category[
            "GDF dense AO-pair FT bundle"
        ] = _pair_ft_bytes
    return SimpleNamespace(
        estimate=estimate,
        n_kpoints=n_kpoints,
        n_ibz_kpoints=n_ibz,
        route_label=route_label,
    )


# ============================================================
# Helpers -- symmetry reduction
# ============================================================


def _ibz_kpoint_count(system, kpoints) -> "int | None":
    """Size of the irreducible wedge of the resolved Monkhorst-Pack mesh.

    This is the number the space-group-reduced GDF exchange build
    (``ibz_native``) actually uses as its **bra** row count, so it is also
    what the memory preflight must charge: the reduced cache holds
    ``n_IBZ x n_k`` ``Lpq`` blocks, not ``n_k^2``
    (:func:`vibeqc.periodic_k_gdf._build_k_ibz_native`).

    Returns ``None`` -- never a guess -- when the wedge cannot be resolved
    (no attached symmetry, or a ``kpoints=`` spec that is not a
    reducible mesh). The caller decides whether that is a hard error
    (``symmetry_reduce_k=True`` was asked for) or simply "no reduction to
    report"; the driver's own :func:`_resolve_ibz_native_state` remains the
    authority on whether the wedge is usable at all.
    """
    if getattr(system, "symmetry", None) is None:
        return None
    try:
        from ._vibeqc_core import monkhorst_pack as _mp

        kmesh = _runner_bloch_kmesh(system, kpoints)
        mesh = getattr(kmesh, "mesh", None)
        if mesh is None and isinstance(kpoints, (list, tuple)):
            mesh = tuple(int(x) for x in kpoints)
        if mesh is None or len(tuple(mesh)) != 3:
            return None
        n_ibz = len(_mp(system, [int(x) for x in mesh], [0, 0, 0], True).kpoints)
    except Exception:
        return None
    return int(n_ibz) if n_ibz > 0 else None


# ============================================================
# Helpers -- ECP auto-attach
# ============================================================

_POB_SOURCE_DIR_BY_BASIS = {
    "pob-tzvp": "pob-TZVP",
    "pob-tzvp-rev2": "pob-TZVP-rev2",
    "pob-dzvp-rev2": "pob-DZVP-rev2",
}


#: Convention labels reported for a resolved Brillouin-zone mesh.
#: Which one applies is *measured* from the mesh, never assumed -- vibe-qc
#: has two, see :func:`kmesh_convention_of`.
KMESH_CONVENTION_GAMMA = "gamma-centred"
KMESH_CONVENTION_SHIFTED = "shifted (classical Monkhorst-Pack)"
KMESH_CONVENTION_UNKNOWN = "unresolved"

#: Backwards-compatible alias for the gamma-centred label.
KMESH_CONVENTION = KMESH_CONVENTION_GAMMA


def kmesh_convention_of(frac) -> str:
    """Name the convention of an already-resolved fractional k-list.

    Determined by whether Gamma is in the sampled set, because vibe-qc has
    **two** conventions reachable through the same ``kpoints=`` argument:

    * :func:`vibeqc.monkhorst_pack` (and the mesh-tuple form of
      ``run_periodic_job``) is always gamma-centred, ``is_shift=(0,0,0)``.
    * :meth:`vibeqc.KPoints.monkhorst_pack` applies the *classical*
      auto-shift, so it is gamma-centred for odd N and shifted by half a
      step for even N -- i.e. it matches ASE/GPAW where the tuple form
      does not.

    Measured 2026-08-02 on a cubic cell: for (2,2,2) and (4,4,4)
    ``vibeqc.monkhorst_pack`` contains Gamma and ``KPoints.monkhorst_pack``
    does not (``is_shift=(1,1,1)``); at (3,3,3) both contain it. So the
    convention cannot be inferred from the mesh tuple alone, and hardcoding
    one label would make the ``.out`` confidently wrong for the other API.
    """
    try:
        arr = np.asarray(frac, dtype=float)
        if arr.ndim != 2 or arr.shape[0] == 0:
            return KMESH_CONVENTION_UNKNOWN
        if bool(np.any(np.all(np.abs(arr) < 1e-10, axis=1))):
            return KMESH_CONVENTION_GAMMA
        return KMESH_CONVENTION_SHIFTED
    except Exception:
        return KMESH_CONVENTION_UNKNOWN


def _kmesh_fractional(system, kpoints_cart) -> "np.ndarray | None":
    """Fractional coordinates of Cartesian k-points, wrapped to [-1/2, 1/2).

    ``k_cart = frac @ B`` with ``B = 2 pi (A^-1)^T`` (rows = reciprocal
    vectors), so ``frac = k_cart @ B^-1``. Returns None if the lattice is
    unusable, since this feeds provenance output that must never be the
    reason a job fails.
    """
    try:
        a_mat = np.asarray(system.lattice, dtype=float)
        b_mat = 2.0 * np.pi * np.linalg.inv(a_mat).T
        frac = np.asarray(kpoints_cart, dtype=float) @ np.linalg.inv(b_mat)
        return (frac + 0.5) % 1.0 - 0.5
    except Exception:
        return None


def write_kmesh_convention(convention: str = KMESH_CONVENTION_UNKNOWN) -> None:
    """Write the Brillouin-zone convention statement for a resolved mesh.

    Split out of :func:`write_kmesh_line` so routes that report a k-point
    *count* rather than a mesh tuple (the full-k semiempirical route) can
    state the same thing without duplicating the wording.

    ``convention`` comes from :func:`kmesh_convention_of`, i.e. it is
    measured from the k-list rather than assumed. When it could not be
    resolved, say so instead of guessing -- a confidently wrong convention
    line is worse than none, since the whole point is to make cross-code
    comparison trustworthy.
    """
    write(f"    k-mesh convention  = {convention}\n")
    if convention == KMESH_CONVENTION_GAMMA:
        write(
            "                         Gamma is sampled; ASE/GPAW's mesh of\n"
            "                         the same name is disjoint for even N\n"
        )
    elif convention == KMESH_CONVENTION_SHIFTED:
        write(
            "                         Gamma is NOT sampled; this matches\n"
            "                         ASE/GPAW, not the mesh-tuple form\n"
        )
    else:
        write(
            "                         k-list unavailable here; confirm the\n"
            "                         sampling before any cross-code claim\n"
        )


def write_kmesh_line(
    mesh,
    *,
    suffix: str = "",
    system=None,
    kpoints_cart=None,
    label: str = "kpoints",
) -> None:
    """Write the k-mesh line together with the convention that defines it.

    KPOINT-CONVENTION-UNPRINTED: ``kpoints=(N, N, N)`` does **not** name the
    same point set in every code, and printing the tuple alone lets a
    cross-code comparison silently compare different Brillouin-zone
    samplings. The mesh-tuple form builds the **Gamma-centred** mesh
    ``{0, 1/N, ..., (N-1)/N}`` along each axis; ASE/GPAW's
    ``monkhorst_pack`` builds the **classical shifted** mesh, offset by half
    a step.

    The convention printed here is **measured from the resolved k-list**
    (:func:`kmesh_convention_of`), not inferred from ``mesh``, because
    ``kpoints=`` also accepts a :class:`vibeqc.KPoints` built with the
    classical auto-shift -- which is *shifted* at even N and so lands on
    the opposite convention from the identical-looking tuple. Assuming the
    label would make the ``.out`` confidently wrong for that caller.

    Measured 2026-08-02, cubic cell, fractional coordinates wrapped to
    [-1/2, 1/2), vibe-qc ``monkhorst_pack`` vs ``ase.dft.kpoints
    .monkhorst_pack``::

        mesh       n    shared
        (2,2,2)    8      0
        (3,3,3)   27     27      <- identical
        (4,4,4)   64      0
        (5,5,5)  125    125      <- identical
        (6,6,6)  216      0

    The two conventions therefore diverge for **even** N and agree exactly
    for **odd** N: for odd N the half-step offset wraps onto the same
    lattice, for even N it lands exactly between vibe-qc's points. That is
    what makes this a trap rather than a nuisance -- a cross-code check
    validated at (3,3,3) passes and gives false confidence in a (4,4,4)
    production run.

    Emits the mesh and convention at STANDARD level, and the resolved
    k-point list at VERBOSE, so the sampling is reconstructable from the
    ``.out`` alone.
    """
    write(f"    {label}            = {mesh}{suffix}\n")

    frac = None
    if system is not None:
        if kpoints_cart is None:
            # Resolve the mesh spec ourselves so every route gets the
            # k-list, not only the ones already holding a built mesh.
            try:
                kpoints_cart = _runner_bloch_kmesh(system, mesh).kpoints
            except Exception:
                kpoints_cart = None
        if kpoints_cart is not None:
            frac = _kmesh_fractional(system, kpoints_cart)
            if frac is not None and frac.ndim != 2:
                frac = None

    write_kmesh_convention(kmesh_convention_of(frac))
    if frac is None:
        return
    write(f"    k-points resolved  = {frac.shape[0]}\n")
    write("    k-points (fractional, wrapped to [-1/2, 1/2)):\n", Level.VERBOSE)
    for i, row in enumerate(frac):
        write(
            f"      {i:5d}  {row[0]:12.8f} {row[1]:12.8f} {row[2]:12.8f}\n",
            Level.VERBOSE,
        )


def _system_atomic_numbers(system) -> set[int]:
    return {int(atom.Z) for atom in system.unit_cell}


def _format_atomic_numbers(zs: Sequence[int]) -> str:
    try:
        from .basis_crystal import _ELEMENT_SYMBOLS as _symbols
    except Exception:
        _symbols = []

    labels: list[str] = []
    for z in sorted(int(z) for z in zs):
        sym = _symbols[z] if 0 <= z < len(_symbols) else f"Z={z}"
        labels.append(f"{sym}(Z={z})")
    return ", ".join(labels)


def _bundled_pob_source_atoms(name: str) -> list:
    """Return bundled per-element POB source records, if available.

    The checked-in source files cover all-electron POB elements (for example
    Ni/O in P16) and let runtime ECP resolution avoid any network access. The
    heavier POB ECP records are not bundled as source files in this tree; those
    still require the legacy Bredow fetcher and therefore fail closed below if
    they cannot be resolved.
    """
    source_dir_name = _POB_SOURCE_DIR_BY_BASIS.get(name.lower())
    if source_dir_name is None:
        return []
    source_dir = (
        Path(__file__).resolve().parent
        / "basis_library"
        / "sources"
        / source_dir_name
    )
    if not source_dir.is_dir():
        return []

    from .basis_crystal import parse_crystal_atom_basis_file

    atoms = []
    for path in sorted(source_dir.iterdir()):
        if not path.is_file() or path.name.startswith(".") or "_" not in path.name:
            continue
        atoms.append(parse_crystal_atom_basis_file(path))
    return atoms


def _resolve_ecp_data(system, basis) -> tuple:
    """Detect pob-TZVP-REV2 inline ECPs and build C++-ready data.

    When ``basis`` is a CRYSTAL-format basis (pob-TZVP-REV2 family)
    with heavy-element ECP blocks embedded in the basis files themselves,
    convert the parsed :class:`CrystalECP` data to the inline-primitive
    format the C++ periodic SCF drivers expect.

    Returns (ecp_primitive_blocks, ecp_home_centers, effective_charges,
    total_ncore) -- all empty/zero only when the requested POB atoms are
    genuinely all-electron. ECP-bearing POB atoms fail closed if the ECP
    records cannot be resolved; paper routes must not silently continue as
    all-electron calculations after missing runtime data.
    """
    name = getattr(basis, "name", "") or ""
    name_key = name.lower()
    if not name_key.startswith("pob"):
        return [], [], [], 0

    requested_zs = _system_atomic_numbers(system)
    local_atoms = _bundled_pob_source_atoms(name_key)
    local_by_z = {int(atom.Z): atom for atom in local_atoms}
    if requested_zs and requested_zs.issubset(local_by_z):
        from .basis_crystal import build_periodic_ecp_data

        return build_periodic_ecp_data(system, local_atoms)

    missing_local_zs = sorted(requested_zs.difference(local_by_z))
    try:
        from .basis_crystal import (
            build_periodic_ecp_data,
            fetch_bredow_basis_sets,
        )

        _, atoms_dict = fetch_bredow_basis_sets(
            names=[name_key], verbose=False, return_atoms=True
        )
        if not atoms_dict:
            raise RuntimeError("Bredow fetcher returned no parsed atom records")

        atom_list = atoms_dict.get(name_key, [])
        if not atom_list:
            raise RuntimeError(
                f"Bredow fetcher returned no records for basis {name!r}"
            )
        by_z = {int(atom.Z): atom for atom in atom_list}
        missing_fetched_zs = sorted(requested_zs.difference(by_z))
        if missing_fetched_zs:
            raise RuntimeError(
                "Bredow records do not include requested element(s): "
                f"{_format_atomic_numbers(missing_fetched_zs)}"
            )
        missing_ecp_zs = [
            z for z in missing_local_zs
            if not (by_z[z].has_ecp and by_z[z].ecp is not None)
        ]
        if missing_ecp_zs:
            raise RuntimeError(
                "requested POB heavy element record(s) did not include inline "
                f"ECP data: {_format_atomic_numbers(missing_ecp_zs)}"
            )

        return build_periodic_ecp_data(system, atom_list)
    except Exception as exc:
        raise RuntimeError(
            f"_resolve_ecp_data: basis {name!r} requires ECP source data for "
            f"{_format_atomic_numbers(missing_local_zs)}, but the data could "
            "not be resolved. The periodic SCF will not continue as an "
            f"all-electron calculation. Underlying error: {exc}"
        ) from exc


def _validate_smearing_dispatch(
    *,
    method: str,
    jk_method: PeriodicJKMethod,
    smearing_temperature: float,
    kpoints: object = None,
) -> None:
    """Fail fast when a selected backend cannot honour smearing.

    ``kpoints`` is the runner's k-mesh argument. The runner exposes
    open-shell smearing through the GDF drivers; the low-level multi-k Ewald
    UHF/UKS drivers also support per-spin smearing, but there is no current
    ``run_periodic_job`` Ewald dispatch route for them.
    """
    if float(smearing_temperature) <= 0.0:
        return
    if method in ("ROHF", "ROKS"):
        raise NotImplementedError(
            f"run_periodic_job: periodic {method} uses integer 2/1/0 "
            "occupations; electronic smearing is not implemented."
        )
    if jk_method == PeriodicJKMethod.BIPOLE:
        if method in ("RKS", "UHF", "UKS"):
            return
        raise NotImplementedError(
            "run_periodic_job: smearing_temperature > 0 is implemented "
            "for BIPOLE RKS/UHF/UKS, but BIPOLE RHF still requires integer "
            f"occupations. Got method={method!r}."
        )
    if method in ("UHF", "UKS"):
        # Open-shell smearing is wired on the runner's GDF drivers. The
        # exported multi-k Ewald UHF/UKS drivers also have per-spin smearing,
        # but run_periodic_job no longer exposes an Ewald dispatch branch.
        # BIPOLE UHF is accepted in the BIPOLE branch above; BIPOLE RHF
        # remains integer-occupation only.
        if jk_method in (PeriodicJKMethod.GDF, PeriodicJKMethod.RIJCOSX):
            return
        raise NotImplementedError(
            "run_periodic_job: open-shell (UHF/UKS) smearing_temperature > 0 "
            "is wired through run_periodic_job on the GDF drivers "
            "(jk_method='gdf', Gamma or multi-k) and on the multi-k "
            "RIJCOSX/COSX route. The exported multi-k "
            "Ewald UHF/UKS drivers support spin-resolved smearing directly; "
            "BIPOLE UHF supports spin-resolved smearing directly through "
            "this runner; BIPOLE RHF and the other runner routes do not. "
            f"Got method={method!r}, jk_method={jk_method.value!r}, "
            f"kpoints={'set' if kpoints is not None else 'None'}."
        )
    if jk_method == PeriodicJKMethod.RIJCOSX:
        if kpoints is not None:
            return
        raise NotImplementedError(
            "run_periodic_job: RIJCOSX smearing_temperature > 0 is wired "
            "only on the true multi-k COSX route. Pass kpoints= with at "
            "least two k-points, or omit smearing for the Gamma RIJCOSX "
            "RHF driver."
        )
    if jk_method not in (
        PeriodicJKMethod.GDF,
        PeriodicJKMethod.GPW,
        PeriodicJKMethod.GAPW,
    ):
        raise NotImplementedError(
            "run_periodic_job: smearing_temperature > 0 is currently "
            "wired through the GDF, GPW, GAPW, and multi-k RIJCOSX "
            "closed-shell RHF/RKS "
            f"paths; selected J/K method is {jk_method.value!r}."
        )


def _qvf_periodic_property_payload_supported(
    jk_method: PeriodicJKMethod,
) -> bool:
    """Return whether the generic QVF band-property rebuild is valid.

    The generic rebuild assembles its own fixed-cutoff Ewald/HF-like
    operator. That is neither the converged BIPOLE operator nor the executed
    finite-character AICCM2026DEV-B operator (and for KS jobs it also omits
    the XC potential), so those finite but unrelated DOS/PDOS/COOP/COHP and
    Mayer arrays must be omitted.
    """
    return jk_method not in (
        PeriodicJKMethod.BIPOLE,
        PeriodicJKMethod.AICCM2026DEV_B,
    )


def citation_scf_accelerator(
    density_mixer: Optional[str],
    density_mixer_kerker: bool,
) -> str:
    """Name the accelerator this run actually used, for the citation plan.

    `CitationDatabase.assemble` defaults `scf_accelerator` to ``"diis"``
    and the periodic runner never passed it, so a run that mixed with
    Anderson (optionally Kerker-preconditioned) cited Pulay's DIIS papers
    and nothing else -- not merely a missing citation but a WRONG one, on
    the surface users copy into a paper's Methods section
    (PERIODIC-CITES-DIIS-IT-DID-NOT-USE).

    `assemble` takes a single accelerator key, while a periodic run
    applies a density mixer AND, optionally, the Kerker preconditioner.
    The combination is expressed as a composed route key in
    ``database.toml`` (``anderson_kerker``, ``broyden_kerker``) rather
    than by widening `assemble`'s signature, which is IO-chat-owned
    (CLAUDE.md § 16); routes are the implementing chat's (§ 8).

    Mirrors the accepted values of :func:`_validate_density_mixer_dispatch`:
    ``None`` / ``""`` / ``"none"`` / ``"diis"`` all run the default
    Fock-DIIS route, and Kerker is only reachable with Anderson/Broyden.
    """
    key = None if density_mixer is None else str(density_mixer).strip().lower()
    if key in (None, "", "none", "diis"):
        return "diis"
    if density_mixer_kerker:
        return f"{key}_kerker"
    return key


def _validate_density_mixer_dispatch(
    *,
    density_mixer: Optional[str],
    density_mixer_depth: int,
    density_mixer_beta: float,
    density_mixer_kerker: bool,
    kerker_k0: float,
    kerker_strength: float,
    kerker_cutoff_ha: float,
    jk_method: Optional["PeriodicJKMethod"] = None,
    method: Optional[str] = None,
    kpoints: Optional[object] = None,
) -> None:
    """Validate high-level density-mixer requests and gate unsupported routes.

    Anderson/Broyden/Kerker are wired in the lower-level EWALD_3D RKS driver
    (`run_rks_periodic_scf` / `run_rks_periodic_multi_k_ewald3d`) and -- since
    the prompt-22 exposure -- in the closed-shell multi-k GDF driver
    (`run_krhf_periodic_gdf`), which `run_periodic_job` reaches via
    `jk_method="gdf"` + `method="RHF"/"RKS"` or via the public multi-k
    RIJCOSX route (`jk_method="rijcosx"`, same GDF loop with
    `k_exchange="cosx"`). All other runner routes (GPW/GAPW, BIPOLE,
    AICCM, open-shell GDF/RIJCOSX) still fail closed: they cannot honour
    these knobs without silently changing the SCF update scheme.
    """
    non_default_parameter = (
        int(density_mixer_depth) != 8
        or float(density_mixer_beta) != 0.5
        or float(kerker_k0) != 1.5
        or float(kerker_strength) != 1.0
        or float(kerker_cutoff_ha) != 120.0
    )
    if (
        density_mixer is None
        and not density_mixer_kerker
        and not non_default_parameter
    ):
        return

    mixer_key = None if density_mixer is None else str(density_mixer).strip().lower()
    if mixer_key in (None, "", "none"):
        if density_mixer_kerker or non_default_parameter:
            raise ValueError(
                "run_periodic_job: density_mixer_* and kerker_* options require "
                "density_mixer='anderson' or 'broyden'."
            )
        return
    if mixer_key == "diis":
        if density_mixer_kerker or non_default_parameter:
            raise ValueError(
                "run_periodic_job: density_mixer='diis' is the default Fock-DIIS "
                "route and does not accept density_mixer_* or kerker_* options."
            )
        return
    if mixer_key not in {"anderson", "broyden"}:
        raise ValueError(
            "run_periodic_job: density_mixer must be None, 'diis', 'anderson', "
            "or 'broyden'. Use density_mixer_kerker=True to add Kerker "
            "preconditioning to Anderson/Broyden."
        )
    method_u = (method or "").upper()
    if (
        jk_method is not None
        and jk_method in (PeriodicJKMethod.GDF, PeriodicJKMethod.RIJCOSX)
        and method_u in ("RHF", "RKS")
        and kpoints is not None
    ):
        # Supported: the closed-shell multi-k GDF driver honours
        # Anderson/Broyden density mixing with optional Kerker
        # preconditioning (ported from the EWALD_3D driver). Gamma-only
        # requests must pass kpoints=(1,1,1) explicitly -- the runner's
        # default-Gamma GDF path calls run_pbc_gdf_rhf, which has no
        # mixer plumbing, and silently dropping the knob is worse than
        # asking for the Nk=1 mesh.
        return
    raise NotImplementedError(
        "run_periodic_job: density_mixer="
        f"{density_mixer!r} is supported on the closed-shell GDF route "
        "(jk_method='gdf' or multi-k 'rijcosx', method='RHF'/'RKS', "
        "explicit kpoints=) and the "
        "lower-level multi-k EWALD_3D RKS drivers "
        "(vibeqc.run_rks_periodic_scf / run_rks_periodic_multi_k_ewald3d). "
        f"Got method={method!r}, jk_method="
        f"{jk_method.value if jk_method is not None else None!r}, "
        f"kpoints={'set' if kpoints is not None else 'None'}. For a "
        "Gamma-only mixed run pass kpoints=(1,1,1) explicitly. Use "
        "DIIS/FMIXING controls for the GPW/GAPW/BIPOLE/AICCM and "
        "open-shell routes."
    )


_COMPACT_MGGA_DENSITY_MIXER_PROFILES = {
    "covalent-insulator",
    "ionic-insulator",
    "metallic-candidate",
}


def _functional_is_scan_family(functional: Optional[str]) -> bool:
    key = (
        str(functional or "")
        .strip()
        .lower()
        .replace("-", "")
        .replace("_", "")
    )
    return key == "scan" or key.startswith("r2scan")


def _runner_bloch_kmesh(
    system: PeriodicSystem,
    kpoints: Optional[
        Union[Tuple[int, int, int], List[int], int, "KPoints", "BlochKMesh"]
    ],
):
    """Materialize the high-level runner's k-point input as a BlochKMesh."""
    from ._vibeqc_core import monkhorst_pack as _mp

    if kpoints is None:
        return _mp(system, [1, 1, 1])
    if hasattr(kpoints, "to_bloch_kmesh") or (
        hasattr(kpoints, "kpoints") and hasattr(kpoints, "weights")
    ):
        from .kpoints import as_bloch_kmesh

        return as_bloch_kmesh(kpoints)
    if isinstance(kpoints, (list, tuple)):
        mesh = list(kpoints)
    else:
        mesh = [kpoints, kpoints, kpoints]
    return _mp(system, [int(n) for n in mesh])


def _remap_kpoints_after_primitive_reduction(
    original_system: PeriodicSystem,
    primitive_system: PeriodicSystem,
    kpoints,
):
    """Rebuild an object-form MP mesh on the reduced reciprocal lattice.

    Tuple/integer mesh specifications are materialized only after cell
    reduction and therefore need no adjustment.  ``KPoints`` and raw
    ``BlochKMesh`` objects already contain Cartesian vectors, however; reusing
    them would sample the primitive Hamiltonian on the conventional-cell
    reciprocal lattice.  Only a verifiable Monkhorst-Pack object has an
    unambiguous reduced-cell meaning.  Arbitrary explicit/path objects fail
    closed rather than silently changing their physical k vectors.
    """
    if kpoints is None or isinstance(kpoints, (int, np.integer, list, tuple)):
        return kpoints

    from .kpoints import KPoints

    if isinstance(kpoints, KPoints):
        if (
            kpoints.kind != "monkhorst-pack"
            or kpoints.mesh is None
            or kpoints.shift is None
        ):
            raise NotImplementedError(
                "run_periodic_job: reduce_to_primitive/symmetry reduction "
                "cannot remap an explicit, path, database, or generalized "
                "KPoints object. Pass a Monkhorst-Pack KPoints object or a "
                "mesh tuple so the reciprocal mesh can be rebuilt on the "
                "primitive lattice."
            )
        remapped = KPoints.monkhorst_pack(
            primitive_system,
            kpoints.mesh,
            shift=kpoints.shift,
            symmetry=bool(np.asarray(kpoints.ir_mapping).size),
        )
        # Occupation/BZ choices belong to the sampling request. A convergence
        # ladder and its prose rationale are cell-specific, so do not claim
        # that a conventional-cell verification certifies the remapped mesh.
        remapped.smearing = kpoints.smearing
        remapped.bz_integration = kpoints.bz_integration
        remapped.citation_numerics = tuple(kpoints.citation_numerics)
        return remapped

    if hasattr(kpoints, "kpoints") and hasattr(kpoints, "weights"):
        mesh = tuple(int(x) for x in getattr(kpoints, "mesh", ()))
        shift = tuple(int(x) for x in getattr(kpoints, "is_shift", ()))
        if len(mesh) == 3 and len(shift) == 3:
            from ._vibeqc_core import monkhorst_pack as _mp

            reduced = bool(
                np.asarray(getattr(kpoints, "ir_mapping", []), dtype=int).size
            )
            reference = _mp(
                original_system,
                list(mesh),
                list(shift),
                reduced,
            )
            reference_points = np.asarray(reference.kpoints, dtype=float)
            supplied_points = np.asarray(kpoints.kpoints, dtype=float)
            reference_weights = np.asarray(reference.weights, dtype=float)
            supplied_weights = np.asarray(kpoints.weights, dtype=float)
            if (
                reference_points.shape == supplied_points.shape
                and reference_weights.shape == supplied_weights.shape
                and np.allclose(
                    reference_points,
                    supplied_points,
                    rtol=0.0,
                    atol=1.0e-12,
                )
                and np.allclose(
                    reference_weights,
                    supplied_weights,
                    rtol=0.0,
                    atol=1.0e-14,
                )
            ):
                return _mp(
                    primitive_system,
                    list(mesh),
                    list(shift),
                    reduced,
                )

    raise NotImplementedError(
        "run_periodic_job: reduce_to_primitive/symmetry reduction cannot "
        "prove that this object-form k-point list is a Monkhorst-Pack mesh. "
        "Pass a mesh tuple or KPoints.monkhorst_pack(...) so it can be "
        "rebuilt on the primitive reciprocal lattice."
    )


def _bloch_kmesh_size(kmesh) -> int:
    for attr in ("n_kpoints", "nkpts", "num_kpoints"):
        n = getattr(kmesh, attr, None)
        if isinstance(n, int):
            return int(n)
    for attr in ("kpoints_cart", "kpoints", "kpoints_frac"):
        pts = getattr(kmesh, attr, None)
        if pts is not None:
            return int(len(pts))
    return int(len(kmesh))


def _bloch_kmesh_full_size(kmesh) -> int:
    """Return the requested full-mesh size, including an IBZ parent mesh."""
    ir_mapping = np.asarray(
        getattr(kmesh, "ir_mapping", []), dtype=int
    ).reshape(-1)
    if ir_mapping.size > 0:
        return int(ir_mapping.size)
    stored_size = _bloch_kmesh_size(kmesh)
    mesh = getattr(kmesh, "mesh", None)
    if mesh is not None:
        try:
            full_size = int(np.prod(np.asarray(mesh, dtype=int)))
        except (TypeError, ValueError):
            full_size = 0
        # ``bloch_kmesh_from_lists`` assigns mesh=(1,1,1) to arbitrary
        # explicit lists because no MP parent is known. Do not let that
        # placeholder hide multiple stored k-points.
        if full_size > 0 and (stored_size <= 1 or full_size == stored_size):
            return full_size
    return stored_size


def _kmesh_contains_gamma(kmesh) -> bool:
    """Whether the requested Bloch mesh has an exact Γ point.

    Molden has no periodic representation: its ``[MO]`` block is one set of
    real coefficients over home-cell basis functions. k = 0 is the only
    k-point where the Bloch coefficients are real up to a global phase, so
    Γ membership -- not "the mesh is nothing but Γ" -- is what decides
    whether an orbital export can be produced at all. Every Γ-centred
    Monkhorst-Pack mesh qualifies regardless of subdivision; a shifted mesh
    does not.

    Uses the same |k| <= 1e-10 bohr^-1 test as
    :func:`_gamma_index_for_multi_k`, which performs the matching runtime
    selection against the converged result's own k list.
    """
    for attr in ("kpoints_cart", "kpoints"):
        pts = getattr(kmesh, attr, None)
        if pts is None:
            continue
        arr = np.asarray(pts, dtype=float).reshape(-1, 3)
        if arr.shape[0] == 0:
            return False
        return bool(np.min(np.linalg.norm(arr, axis=1)) <= 1.0e-10)
    return False


def _is_multik_kpoints(kpoints) -> bool:
    """Best-effort: does this ``kpoints`` request more than the Γ-point?

    Used to gate the Γ-only READ restart in run_periodic_job. The multi-k
    drivers carry their own defensive READ gate, so a miss here is caught
    downstream; this exists to give the clearer up-front error message.
    """
    if kpoints is None:
        return False
    if isinstance(kpoints, bool):
        return False
    if isinstance(kpoints, int):
        return kpoints > 1
    if isinstance(kpoints, (list, tuple)):
        vals = list(kpoints)
        if len(vals) == 3 and all(isinstance(v, int) for v in vals):
            return int(vals[0]) * int(vals[1]) * int(vals[2]) > 1
        return len(vals) > 1
    ir_mapping = np.asarray(
        getattr(kpoints, "ir_mapping", []), dtype=int
    ).reshape(-1)
    if ir_mapping.size > 1:
        return True
    mesh = getattr(kpoints, "mesh", None)
    if mesh is not None:
        try:
            if int(np.prod(np.asarray(mesh, dtype=int))) > 1:
                return True
        except (TypeError, ValueError):
            pass
    for attr in ("n_kpoints", "nkpts", "num_kpoints"):
        n = getattr(kpoints, attr, None)
        if isinstance(n, int):
            return n > 1
    kpts = getattr(kpoints, "kpoints", None)
    if kpts is not None:
        try:
            return len(kpts) > 1
        except TypeError:
            pass
    return True  # unknown explicit object -- assume multi-k (fail closed)


def _filter_bipole_restricted_open_convergence(
    strategy: ConvergenceStrategy,
    method: str,
) -> ConvergenceStrategy:
    """Remove AUTO aids the restricted-open corrected-Ewald engines ignore."""
    method_upper = str(method).upper()
    if method_upper not in ("ROHF", "ROKS"):
        return strategy

    knobs = dict(strategy.knobs)
    changed = False
    for name, reason in (
        (
            "fock_mixing",
            "the restricted-open corrected-Ewald engine does not implement "
            "Fock mixing",
        ),
        (
            "smearing_temperature",
            "the restricted-open corrected-Ewald engine uses fixed 2/1/0 "
            "occupations rather than finite-temperature smearing",
        ),
    ):
        resolution = knobs[name]
        if resolution.source == "auto" and float(resolution.value) != 0.0:
            knobs[name] = KnobResolution(
                0.0,
                "auto",
                f"capability-filtered to zero: {reason}",
            )
            changed = True
    if not changed:
        return strategy
    return ConvergenceStrategy(
        mode=strategy.mode,
        classification=strategy.classification,
        knobs=knobs,
    )


def _system_with_valid_default_multiplicity(
    system: PeriodicSystem,
) -> PeriodicSystem:
    """Return a copy with the lowest valid spin for an invalid default singlet.

    ``PeriodicSystem`` defaults to multiplicity 1, but an odd-electron unit
    cell needs an even multiplicity.  Basis construction already repairs its
    temporary ``Molecule``; this helper gives the SCF driver the same valid
    multiplicity without mutating the caller's system or overriding an
    explicit magnetic multiplicity.
    """
    n_electrons = int(system.n_electrons())
    multiplicity = int(system.multiplicity)
    if multiplicity != 1 or (n_electrons - multiplicity + 1) % 2 == 0:
        return system

    atoms = [
        Atom(int(atom.Z), [float(x) for x in atom.xyz])
        for atom in system.unit_cell
    ]
    normalized = PeriodicSystem(
        dim=int(system.dim),
        lattice=np.asarray(system.lattice, dtype=float),
        unit_cell=atoms,
        charge=int(system.charge),
        multiplicity=int(system.unit_cell_molecule().multiplicity),
    )
    normalized.symmetry = system.symmetry
    return normalized


def _validate_closed_shell_electron_count(
    system: PeriodicSystem,
    method_upper: str,
) -> None:
    """Reject an odd-electron cell selected for restricted RHF or KS."""
    if method_upper not in ("RHF", "RKS"):
        return
    try:
        n_electrons = int(system.n_electrons())
    except Exception:
        return
    if n_electrons % 2 == 0:
        return

    unrestricted_method = "UKS" if method_upper == "RKS" else "UHF"
    raise ValueError(
        f"method={method_upper!r} is a closed-shell (restricted) "
        f"method but the cell has an odd electron count ({n_electrons}); "
        "a restricted single determinant needs paired electrons. Use "
        f"method={unrestricted_method!r} (spin-polarised) — for an "
        "open-shell metal such as an odd-Z transition metal, add "
        "atomic_spins=... to seed the moments. If the cell should be "
        "neutral and even, check the charge / composition."
    )


def _reduce_system_to_primitive(
    system: PeriodicSystem,
    *,
    symprec: float = 1e-4,
) -> tuple[PeriodicSystem, SpaceGroup]:
    """Reduce a ``PeriodicSystem`` to its primitive cell via spglib.

    Returns ``(primitive_system, space_group)`` where ``space_group``
    is the symmetry analysis of the **original** cell. The primitive
    system carries its own symmetry analysis as well.

    Raises ``ValueError`` if the primitive cell is identical to the
    input (i.e. the input is already primitive), since no reduction
    is possible.
    """
    # Attach symmetry to the original cell first.
    attach_symmetry(system, symprec=symprec)
    sg_original = system.symmetry
    if sg_original is None:
        raise RuntimeError("attach_symmetry did not populate system.symmetry")

    # Build a Crystal from the PeriodicSystem so we can call to_primitive.
    L = np.asarray(system.lattice, dtype=float, order="F")
    n_atoms_in = len(system.unit_cell)

    # Fractional coordinates: r_frac = L^{-1} . r_cart
    inv_L = np.linalg.inv(L)
    frac_coords = np.empty((3, n_atoms_in), dtype=float, order="F")
    species = []
    for i, atom in enumerate(system.unit_cell):
        r_cart = np.array(atom.xyz, dtype=float)
        frac = inv_L @ r_cart
        frac_coords[:, i] = frac
        species.append(int(atom.Z))

    crystal_in = Crystal(L, frac_coords, species)
    crystal_prim = to_primitive(crystal_in, symprec=symprec)

    n_atoms_out = crystal_prim.n_atoms
    if n_atoms_out == n_atoms_in:
        raise ValueError(
            "reduce_to_primitive: input cell is already primitive "
            f"({n_atoms_in} atoms, space group "
            f"{sg_original.international_symbol} "
            f"(No. {sg_original.number})). "
            "Set reduce_to_primitive=False to skip reduction."
        )

    # Build a new PeriodicSystem from the primitive Crystal.
    prim_lattice = np.asarray(crystal_prim.lattice, dtype=float, order="F")
    prim_atoms = []
    for col in range(n_atoms_out):
        r_cart = prim_lattice @ np.asarray(
            crystal_prim.fractional_coords[:, col], dtype=float
        )
        prim_atoms.append(Atom(int(crystal_prim.species[col]), r_cart.tolist()))

    system_prim = PeriodicSystem(
        dim=system.dim,
        lattice=prim_lattice,
        unit_cell=prim_atoms,
        charge=system.charge,
        multiplicity=system.multiplicity,
    )
    # Reduction can change electron-count parity: four odd-Z FCC atoms are an
    # even-electron singlet, while their one-atom primitive cell is a doublet.
    system_prim = _system_with_valid_default_multiplicity(system_prim)
    # Attach symmetry to the primitive cell too.
    attach_symmetry(system_prim, symprec=symprec)

    return system_prim, sg_original


def _build_primitive_summary(
    system_in: PeriodicSystem,
    system_prim: PeriodicSystem,
    sg: SpaceGroup,
) -> str:
    """Human-readable summary of the cell reduction."""
    n_in = len(system_in.unit_cell)
    n_out = len(system_prim.unit_cell)
    ratio = n_in / n_out if n_out > 0 else 1.0
    L_in = np.asarray(system_in.lattice, dtype=float)
    L_out = np.asarray(system_prim.lattice, dtype=float)
    vol_in = float(abs(np.linalg.det(L_in)))
    vol_out = float(abs(np.linalg.det(L_out)))

    block = HeaderlessBlock(
        "Cell reduction (reduce_to_primitive=True)",
        [HeaderlessColumn("<")],
        body_indent=2,
    )
    block.add_row(
        f"space group      = {sg.international_symbol} "
        f"(No. {sg.number}), point group {sg.point_group}"
    )
    block.add_row(f"symmetry order   = {sg.order}")
    block.add_row(
        f"input cell       = {n_in} atoms, volume = {vol_in:.4f} bohr^3"
    )
    block.add_row(
        f"primitive cell   = {n_out} atoms, volume = {vol_out:.4f} bohr^3"
    )
    block.add_row(f"reduction factor = {ratio:.1f}x fewer atoms")
    if sg.equivalent_atoms:
        block.add_row(
            f"inequivalent     = {len(set(sg.equivalent_atoms))} atom type(s)"
        )
    return block.render(active_policy()) + "\n"


# ============================================================
# Helpers -- text output sections
# ============================================================


def _system_summary(system: PeriodicSystem) -> str:
    """Print lattice + atoms in bohr."""
    L = np.asarray(system.lattice, dtype=float)
    dim = int(system.dim)
    if dim == 1:
        measure_label = "periodic length"
        measure_unit = "bohr"
        measure = float(np.linalg.norm(L[:, 0]))
    elif dim == 2:
        measure_label = "periodic area"
        measure_unit = "bohr^2"
        measure = float(np.linalg.norm(np.cross(L[:, 0], L[:, 1])))
    else:
        measure_label = "cell volume"
        measure_unit = "bohr^3"
        measure = float(abs(np.linalg.det(L)))
    periodicity = HeaderlessBlock(
        "Periodicity",
        [HeaderlessColumn("<")],
        body_indent=2,
    )
    # These are label:value rows, but their historical contract is a
    # *minimum* 14-character label field rather than a table-wide aligned
    # column.  A longer runtime label ("periodic length") must not push the
    # equals signs on the other rows to the right.
    periodicity.add_row(f"{'dimensionality':<14s} = {dim}D")
    active_axes = ", ".join(f"a{i + 1}" for i in range(dim))
    periodicity.add_row(f"{'active axes':<14s} = {active_axes}")
    periodicity.add_row(
        f"{measure_label:<14s} = {measure:.4f} {measure_unit}"
    )

    lattice = HeaderlessBlock(
        "Lattice (bohr)",
        [
            HeaderlessColumn("<", min_width=2),
            HeaderlessColumn("<", min_width=1),
            HeaderlessColumn(">", min_width=14),
            HeaderlessColumn(">", min_width=14),
            HeaderlessColumn(">", min_width=14),
        ],
        body_indent=2,
        gutter=1,
    )
    # ``system.lattice`` columns are the Cartesian lattice vectors (C++
    # ``PeriodicSystem.lattice``: "Columns = Cartesian lattice vectors"), so
    # a{i+1} is column i -- ``L[:, i]``. Printing the rows (``L[i, :]``) would
    # display the transpose, which agrees with the true vectors only for a
    # symmetric lattice matrix and is wrong for a skewed / triclinic cell. The
    # measure lines above (periodic length/area) already read columns.
    for i in range(3):
        lattice.add_row(
            f"a{i + 1}",
            "=",
            f"{L[0, i]:14.8f}",
            f"{L[1, i]:14.8f}",
            f"{L[2, i]:14.8f}",
        )
    if dim < 3:
        supercell_volume = float(abs(np.linalg.det(L)))
        lattice.footer(f"embedding volume = {supercell_volume:.4f} bohr^3")

    atoms = HeaderlessBlock(
        f"Atoms (bohr) -- {len(system.unit_cell)} in unit cell",
        [
            HeaderlessColumn(">", min_width=4),
            HeaderlessColumn(">", min_width=5, gutter_after=3),
            HeaderlessColumn(">", min_width=14),
            HeaderlessColumn(">", min_width=14),
            HeaderlessColumn(">", min_width=14),
        ],
        body_indent=2,
        gutter=2,
    )
    for i, atom in enumerate(system.unit_cell, start=1):
        x, y, z = atom.xyz
        atoms.add_row(
            i,
            f"Z={atom.Z:3d}",
            f"{x:14.8f}",
            f"{y:14.8f}",
            f"{z:14.8f}",
        )
    atoms.footer(
        f"n_electrons = {system.n_electrons()}  "
        f"multiplicity = {system.multiplicity}"
    )
    return "\n\n".join(
        (
            periodicity.render(active_policy()),
            lattice.render(active_policy()),
            atoms.render(active_policy()),
        )
    ) + "\n"


def _basis_summary(basis: BasisSet) -> str:
    block = HeaderlessBlock(
        "Basis",
        [HeaderlessColumn("<")],
        body_indent=2,
    )
    block.add_row(f"name    = {basis.name}")
    block.add_row(f"nbasis  = {basis.nbasis}")
    block.add_row(f"nshells = {basis.nshells}")
    return block.render(active_policy()) + "\n\n"


def _parity_hold_summary(result) -> str:
    """Parity-hold notice for the ``.out``, appended after the shared SCF
    trace (IID 344).

    Returns ``""`` -- the common case -- unless the executing driver tagged
    the result backend ``+PARITY_HELD`` (the dense-core hold class,
    :func:`vibeqc.pbc_gdf._gdf_backend_with_parity_hold`), so ordinary runs
    emit nothing and golden outputs are unchanged. A held run states the
    hold in the ``.out`` next to the energy it qualifies instead of only in
    the ``.err`` sidecar: the absolute energy is known-wrong versus the
    external parity reference while energy differences may still be usable.
    """
    backend = str(
        getattr(result, "runtime_backend", None)
        or getattr(result, "backend", "")
        or ""
    )
    if "+PARITY_HELD" not in backend:
        return ""
    return (
        "    PARITY HELD: the absolute energy above is held for external\n"
        "    parity (dense-core class; see the .err warning for the\n"
        f"    remediation). Executing backend: {backend}.\n"
        "    Energy differences between runs of this same backend may\n"
        "    still cancel the held offset; do not quote the absolute\n"
        "    energy.\n\n"
    )


def _smearing_summary(result) -> str:
    """Finite-temperature (smearing) block, appended after the shared SCF
    trace + energy-component breakdown.

    Only the smearing-specific quantities live here now: kBT, electronic
    temperature, entropy, Helmholtz free energy, and the Fermi level. The
    iteration table, the ``converged in N iterations`` line, and the
    energy-component breakdown are produced by the shared
    :func:`vibeqc.output.formats.scf_log.format_scf_trace`, identical to the
    molecular path. Returns ``""`` for a zero-temperature (non-smeared)
    result, which is the common case.
    """
    smearing_T = float(getattr(result, "smearing_temperature", 0.0))
    if smearing_T <= 0.0:
        return ""
    policy = (
        active_policy()
        .with_spec("energy", width=20, precision=10)
        .with_spec("temperature", width=20, precision=3)
        .with_spec("dimensionless", width=20, precision=10)
    )
    energy_unit = policy.unit_of("energy")
    temperature_unit = policy.unit_of("temperature")
    block = HeaderlessBlock(
        "Finite-temperature (smearing)",
        [
            HeaderlessColumn(">", min_width=20),
            HeaderlessColumn(">", min_width=1),
            HeaderlessColumn(">", min_width=20),
        ],
        body_indent=2,
        gutter=1,
    )
    block.add_row(
        f"kBT_smearing ({energy_unit})", "=", Quantity(smearing_T, "energy")
    )
    block.add_row(
        f"T_elec ({temperature_unit})",
        "=",
        Quantity(
            hartree_to_kelvin_temperature(smearing_T),
            "temperature",
        ),
    )
    block.add_row(
        "entropy S/kB",
        "=",
        Quantity(float(getattr(result, "entropy", 0.0)), "dimensionless"),
    )
    block.add_row(
        f"free_energy ({energy_unit})",
        "=",
        Quantity(float(getattr(result, "free_energy", result.energy)), "energy"),
    )
    fermi_alpha = getattr(result, "fermi_level_alpha", None)
    fermi_beta = getattr(result, "fermi_level_beta", None)
    if fermi_alpha is not None or fermi_beta is not None:
        if fermi_alpha is not None:
            block.add_row(
                f"fermi_level_alpha ({energy_unit})",
                "=",
                Quantity(float(fermi_alpha), "energy"),
            )
        if fermi_beta is not None:
            block.add_row(
                f"fermi_level_beta ({energy_unit})",
                "=",
                Quantity(float(fermi_beta), "energy"),
            )
    else:
        block.add_row(
            f"fermi_level ({energy_unit})",
            "=",
            Quantity(float(getattr(result, "fermi_level", 0.0)), "energy"),
        )
    return block.render(policy) + "\n\n"


def _linear_dependence_summary(result) -> str:
    """The ``.out`` block for what orthogonalisation actually discarded.

    Emitted whenever the driver populated it, INCLUDING when nothing was
    discarded: "0 directions dropped" is the observation that rules
    linear dependence out as a cause of a surprising periodic energy, and
    its absence is what left several post-mortems unable to distinguish
    an ill-conditioned basis from a broken operator.
    """
    from .linear_dependence import format_periodic_linear_dependence

    summary = getattr(result, "linear_dependence", None)
    if summary is None:
        return ""
    return (
        section_header("Linear dependence", width=56)
        + format_periodic_linear_dependence(summary)
        + "\n"
    )


def _periodic_eigenvalue_channels(result):
    """Return ``(spin, energies, occupations, max_occ)`` result channels.

    Occupations are the only reliable frontier classifier: periodic orbital
    energies have an arbitrary gauge, so their sign cannot determine how many
    states are filled. Older restricted results may supply ``n_electrons``;
    that count is converted to explicit integer occupations as a compatibility
    fallback.
    """
    energies = getattr(result, "mo_energies", None)
    occupations = getattr(result, "occupations", None)
    method_label = str(getattr(result, "method", "") or "").strip().lower()
    if (
        method_label in ("rohf", "roks")
        and energies is not None
        and occupations is not None
        and not (
            isinstance(occupations, (list, tuple)) and len(occupations) == 0
        )
    ):
        # ROHF/ROKS expose alpha/beta aliases for their one restricted spatial
        # orbital set. Prefer the actual 2/1/0 restricted occupations rather
        # than treating those aliases as independent spin channels.
        return [(None, energies, occupations, 2.0)]

    alpha = getattr(result, "mo_energies_alpha", None)
    beta = getattr(result, "mo_energies_beta", None)
    if alpha is not None or beta is not None:
        channels = []
        for spin, energies, occ_name in (
            ("alpha", alpha, "occupations_alpha"),
            ("beta", beta, "occupations_beta"),
        ):
            if energies is None:
                continue
            occupations = getattr(result, occ_name, None)
            if occupations is None:
                return []
            if isinstance(energies, (list, tuple)):
                if not isinstance(occupations, (list, tuple)):
                    continue
                if len(energies) == 0 or len(energies) != len(occupations):
                    continue
            channels.append((spin, energies, occupations, 1.0))
        if channels:
            return channels

    if energies is not None and occupations is not None and not (
        isinstance(occupations, (list, tuple)) and len(occupations) == 0
    ):
        return [(None, energies, occupations, 2.0)]

    if energies is None:
        return []
    if occupations is None or (
        isinstance(occupations, (list, tuple)) and len(occupations) == 0
    ):
        n_electrons = int(getattr(result, "n_electrons", 0) or 0)
        if n_electrons <= 0 or n_electrons % 2:
            return []
        n_occ = n_electrons // 2
        energy_blocks = (
            list(energies)
            if isinstance(energies, (list, tuple))
            else [np.asarray(energies)]
        )
        occupations = []
        for eps in energy_blocks:
            n_orb = len(np.asarray(eps))
            if n_occ > n_orb:
                return []
            occ = np.zeros(n_orb, dtype=float)
            occ[:n_occ] = 2.0
            occupations.append(occ)
        if not isinstance(energies, (list, tuple)):
            occupations = occupations[0]
    return [(None, energies, occupations, 2.0)]


def _band_summary(result) -> str:
    """Print band extrema and gap for multi-k periodic jobs.

    Scans all k-point eigenstates and reports the valence-band
    maximum (VBM), conduction-band minimum (CBM), and the
    associated direct/indirect band gaps across the mesh.
    """
    channels = _periodic_eigenvalue_channels(result)
    if not channels:
        return ""
    first_energies = channels[0][1]
    if not isinstance(first_energies, (list, tuple)) or len(first_energies) <= 1:
        return ""
    n_k = len(first_energies)
    if any(
        not isinstance(energies, (list, tuple)) or len(energies) != n_k
        for _, energies, _, _ in channels
    ):
        return ""
    restricted_open = str(
        getattr(result, "method", "") or ""
    ).strip().lower() in ("rohf", "roks")
    # Absent on the lightweight result shims used by unit fixtures; a result
    # that does not carry convergence state is treated as converged.
    converged = bool(getattr(result, "converged", True))
    for _, _, occupations, max_occ in channels:
        if not isinstance(occupations, (list, tuple)) or len(occupations) != n_k:
            return ""
        for occupation_block in occupations:
            occ = np.asarray(occupation_block, dtype=float)
            if restricted_open:
                distance_to_integer_class = np.min(
                    np.abs(occ[..., None] - np.array([0.0, 1.0, 2.0])),
                    axis=-1,
                )
                fractional = np.any(distance_to_integer_class > 1e-8)
            else:
                fractional = np.any(
                    (occ > 1e-8) & (occ < max_occ - 1e-8)
                )
            if fractional:
                block = HeaderlessBlock(
                    "Band extrema (multi-k)",
                    [HeaderlessColumn("<")],
                    body_indent=2,
                    annotation_gutter=2,
                )
                # "fractionally occupied / smeared" is a statement about the
                # material. A non-converged SCF returns last-iteration
                # eigenvalues, and the fill attached to them is an artefact of
                # an unconverged Fock, not an occupation class -- reporting it
                # as one is how the 2D slab V_ne(k) defect was read as "h-BN
                # is band-overlapping" (GitLab #106, #85). Say what actually
                # happened instead.
                if converged:
                    block.add_row(
                        "occupation class = fractionally occupied / smeared",
                        annotation=(
                            "zero-temperature band edges and metallicity "
                            "not classified"
                        ),
                    )
                else:
                    block.add_row(
                        "SCF not converged - band edges not classified",
                        annotation=(
                            "fractional frontier occupations here are an "
                            "artefact of the unconverged Fock"
                        ),
                    )
                return block.render(active_policy()) + "\n"
    # Gather occupied/virtual indices per k-point from occupation thresholds.
    vbm = -1e300  # highest occupied energy across all k
    cbm = +1e300  # lowest unoccupied energy across all k
    vbm_k = -1
    cbm_k = -1
    direct_gap_min = +1e300
    direct_gap_min_k = -1

    for k_idx in range(n_k):
        occupied_energies = []
        virtual_energies = []
        for _, energies, occupations, max_occ in channels:
            if not isinstance(occupations, (list, tuple)) or len(occupations) != n_k:
                return ""
            eps = np.asarray(energies[k_idx], dtype=float)
            occ = np.asarray(occupations[k_idx], dtype=float)
            if eps.shape != occ.shape:
                return ""
            occ_mask = occ > 1e-8
            # A singly occupied ROHF/ROKS spatial orbital is an integer
            # occupation class, not a thermally fractional virtual state.
            virt_mask = (
                occ < 1e-8
                if restricted_open
                else occ < max_occ - 1e-8
            )
            occupied_energies.extend(eps[occ_mask].tolist())
            virtual_energies.extend(eps[virt_mask].tolist())
        # The VBM and CBM are independent maxima over the whole mesh, so a k
        # point contributes whichever half it has. Under one global Fermi
        # level (#85) a band-overlap mesh can leave a k point with NO occupied
        # states -- every band there sits above mu -- and skipping the whole k
        # would then hide its virtual states from the CBM search. On the
        # two-k reproducer that reported the CBM as +0.80 Ha at the occupied k
        # instead of -0.20 Ha at the empty one: a 1.300000-Ha "direct" gap in
        # place of the true 0.300000-Ha indirect one.
        ho = float(np.max(occupied_energies)) if occupied_energies else None
        lu = float(np.min(virtual_energies)) if virtual_energies else None
        if ho is not None and ho > vbm:
            vbm = ho
            vbm_k = k_idx
        if lu is not None and lu < cbm:
            cbm = lu
            cbm_k = k_idx
        # The DIRECT gap is a per-k quantity and is undefined at a k point
        # that has no occupied or no virtual state; only that half is skipped.
        # Never clamped at zero. A negative per-k direct gap means the
        # occupation classification placed a higher state below a lower
        # one at this k; that is a defect to surface, not to hide
        # (CLAUDE.md section 7).
        if ho is None or lu is None:
            continue
        direct_gap = lu - ho
        if direct_gap < direct_gap_min:
            direct_gap_min = direct_gap
            direct_gap_min_k = k_idx

    if vbm_k < 0 or cbm_k < 0:
        return ""

    # Reported unclamped. When the CBM lies *below* the VBM, the band
    # ordering across the mesh is inverted and the truthful report is the
    # negative number: clamping it to 0.000000 papers over exactly the
    # defect the reader needs to see (CLAUDE.md section 7) and silently
    # defeats any regression test written against this line. The hBN
    # sto-3g slab-GDF reproducer printed 0.000000 Ha while its true
    # CBM - VBM was -0.1201748057 Ha (-3.270 eV).
    indirect_gap = cbm - vbm
    block = HeaderlessBlock(
        "Band extrema (multi-k)",
        [HeaderlessColumn("<")],
        body_indent=2,
        annotation_gutter=2,
    )

    def _energy_pair(value: float, *, width: int, precision: int) -> str:
        return (
            f"{render_energy(value, width=width, precision=precision, unit='Ha')}"
            " Ha  ("
            f"{render_energy(value, width=8, precision=3, unit='eV')} eV)"
        )

    k_ref = getattr(result, "kpoints", None)
    if k_ref is not None and isinstance(k_ref, (list, tuple)):
        _fmt_k = lambda k: ", ".join(f"{x:+.4f}" for x in k)
        if 0 <= vbm_k < len(k_ref):
            block.add_row(
                f"VBM     = {_energy_pair(vbm, width=14, precision=10)}",
                annotation=f"at k = [{_fmt_k(k_ref[vbm_k])}]",
            )
        else:
            block.add_row(
                f"VBM     = {_energy_pair(vbm, width=14, precision=10)}",
                annotation=f"at k_idx = {vbm_k}",
            )
        if 0 <= cbm_k < len(k_ref):
            block.add_row(
                f"CBM     = {_energy_pair(cbm, width=14, precision=10)}",
                annotation=f"at k = [{_fmt_k(k_ref[cbm_k])}]",
            )
        else:
            block.add_row(
                f"CBM     = {_energy_pair(cbm, width=14, precision=10)}",
                annotation=f"at k_idx = {cbm_k}",
            )
    else:
        block.add_row(
            f"VBM     = {_energy_pair(vbm, width=14, precision=10)}",
            annotation=f"(k_idx = {vbm_k})",
        )
        block.add_row(
            f"CBM     = {_energy_pair(cbm, width=14, precision=10)}",
            annotation=f"(k_idx = {cbm_k})",
        )
    gap_type = "direct" if vbm_k == cbm_k else "indirect"
    block.add_row(
        f"gap ({gap_type}) = "
        f"{_energy_pair(indirect_gap, width=10, precision=6)}",
        annotation=(
            "negative: band ordering inverted across the mesh"
            if indirect_gap < 0.0
            else None
        ),
    )
    if direct_gap_min_k >= 0:
        block.add_row(
            "direct gap (min) = "
            f"{_energy_pair(direct_gap_min, width=10, precision=6)}",
            annotation=f"(k_idx = {direct_gap_min_k})",
        )
    else:
        # No k point carries both an occupied and a virtual state, so the
        # direct gap is undefined everywhere on the mesh. Say that rather than
        # rendering the +1e300 sentinel as a number (#85).
        block.add_row(
            "direct gap (min) = n/a",
            annotation=(
                "no k-point has both an occupied and a virtual state"
            ),
        )
    return block.render(active_policy()) + "\n"


def _mo_summary(result, n_show: int = 20) -> str:
    """Print HOCO/LUCO (crystal orbitals) + nearest energies.

    This function is only called for periodic jobs, so the frontier
    orbitals are crystalline orbitals, not molecular orbitals.
    """
    channels = _periodic_eigenvalue_channels(result)
    if not channels:
        return ""
    restricted_open = str(
        getattr(result, "method", "") or ""
    ).strip().lower() in ("rohf", "roks")
    policy = active_policy().with_spec("energy", width=18, precision=10)
    energy_unit = policy.unit_of("energy")
    rendered = []
    for spin, energies, occupations, max_occ in channels:
        if isinstance(energies, (list, tuple)):
            if len(energies) == 0 or not isinstance(occupations, (list, tuple)):
                continue
            eps = np.asarray(energies[0], dtype=float)
            occ = np.asarray(occupations[0], dtype=float)
            n_k = len(energies)
            k_suffix = f" at k-point 0 (of {n_k})"
        else:
            eps = np.asarray(energies, dtype=float)
            occ = np.asarray(occupations, dtype=float)
            k_suffix = ""
        if eps.shape != occ.shape or not occ.size:
            continue
        if restricted_open:
            distance_to_integer_class = np.min(
                np.abs(occ[..., None] - np.array([0.0, 1.0, 2.0])),
                axis=-1,
            )
            fractional = bool(np.any(distance_to_integer_class > 1e-8))
        else:
            fractional = bool(
                np.any((occ > 1e-8) & (occ < max_occ - 1e-8))
            )
        occ_mask = occ > 1e-8
        virt_mask = (
            occ < 1e-8
            if restricted_open
            else occ < max_occ - 1e-8
        )
        if fractional:
            homo_idx = -1
            lumo_idx = eps.size
            fermi_level = (
                getattr(result, f"fermi_level_{spin}", None)
                if spin is not None
                else None
            )
            if fermi_level is None:
                fermi_level = getattr(result, "fermi_level", None)
            centre = (
                int(np.searchsorted(eps, float(fermi_level)))
                if fermi_level is not None
                else eps.size // 2
            )
        else:
            homo_idx = (
                int(np.where(occ_mask)[0][-1]) if np.any(occ_mask) else -1
            )
            lumo_idx = (
                int(np.where(virt_mask)[0][0])
                if np.any(virt_mask)
                else eps.size
            )
            centre = (
                max(homo_idx + 1, 0)
                if homo_idx >= 0
                else min(lumo_idx, eps.size)
            )
        spin_suffix = f" ({spin})" if spin is not None else ""
        title = (
            f"Crystal orbital energies{spin_suffix} ({energy_unit})"
            f"{k_suffix} -- sorted low -> high"
        )
        if fractional:
            title += "; fractional occupations, frontier labels omitted"
        block = HeaderlessBlock(
            title,
            [
                HeaderlessColumn(">", min_width=4),
                HeaderlessColumn(">", min_width=18),
                HeaderlessColumn("<", min_width=12),
            ],
            body_indent=2,
            gutter=3,
            annotation_gutter=1,
        )
        half = n_show // 2
        lo = max(0, centre - half)
        hi = min(eps.size, centre + half)
        for i in range(lo, hi):
            marker = "HOCO" if i == homo_idx else "LUCO" if i == lumo_idx else None
            block.add_row(
                i + 1,
                Quantity(eps[i], "energy"),
                f"occ={occ[i]:8.5f}",
                annotation=marker,
            )
        rendered.append(block.render(policy))
    return "\n".join(rendered) + ("\n" if rendered else "")


def _optimized_geometry_summary(opt_result) -> str:
    """Render the final periodic optimization verdict once for every route."""
    block = HeaderlessBlock(
        "Optimized geometry",
        [HeaderlessColumn("<")],
        body_indent=2,
    )
    block.add_row(
        "E_final = "
        f"{render_energy_labeled(opt_result.energy, width=0, precision=10, sign=True)}"
    )
    block.add_row(f"n_iter  = {int(opt_result.n_iter)}")
    block.add_row(f"converged = {bool(opt_result.converged)}")
    return block.render(active_policy()) + "\n\n"


def _relax_periodic_gdf_atoms(
    system: PeriodicSystem,
    basis_name: str,
    rerun_gdf_scf,
    *,
    max_iter: int,
    conv_tol_grad: float,
):
    """Relax atomic positions on the GDF analytic-gradient objective.

    Mirrors :func:`vibeqc.bipole_optimize.relax_atoms` (scipy L-BFGS-B in
    fractional coordinates, system + basis rebuilt per candidate
    geometry, the same non-convergence penalty barrier and the same
    independent gradient gate) -- but the objective is
    ``rerun_gdf_scf(system, basis)``: the exact GDF driver call the
    runner's SCF dispatch executed, re-run with ``compute_gradient=True``.
    The relaxation therefore converges to a stationary point of the same
    surface the job reports (G-PBC-002 optimizer wiring); relaxing a GDF
    energy on BIPOLE forces lands on a stationary point of the wrong
    surface.

    One SCF per evaluation supplies energy *and* analytic gradient
    (``jac=True``), unlike the BIPOLE relaxer's separate objective /
    gradient callbacks.

    Fail-closed: an envelope the GDF gradient rejects surfaces the
    driver's ``NotImplementedError`` unchanged -- there is no silent
    fallback to another force objective.
    """
    from scipy.optimize import minimize

    from .bipole_optimize import (
        OptimizeResult,
        SCFNonConvergence,
        _atoms_to_flat,
        _flat_to_system,
    )
    from .molecular_optimize import _gradient_converged

    n_atoms = len(system.unit_cell)
    # Same recoverable line-search barrier as relax_atoms: a probe
    # geometry whose SCF fails returns a finite penalty (never used as a
    # real objective value) so the line search backs off; only a failure
    # at the initial geometry aborts.
    _PENALTY_HA = 1.0e6
    _had_good_eval = {"ok": False}

    def _energy_and_gradient(x: np.ndarray):
        sys2 = _flat_to_system(system, x)
        basis2 = BasisSet(sys2.unit_cell_molecule(), basis_name)
        result = rerun_gdf_scf(sys2, basis2)
        if not bool(getattr(result, "converged", True)):
            if not _had_good_eval["ok"]:
                raise SCFNonConvergence(
                    "run_periodic_job(optimize=True): the GDF SCF did not "
                    "converge at the initial geometry inside the optimizer "
                    "re-run; refusing to relax against a non-converged "
                    "objective. Increase max_iter, loosen conv_tol_energy, "
                    "or adjust the convergence aids."
                )
            return _PENALTY_HA, np.zeros(3 * n_atoms)
        _had_good_eval["ok"] = True
        grad_cart = getattr(result, "gradient", None)
        if grad_cart is None:
            raise NotImplementedError(
                "run_periodic_job(optimize=True): the dispatched GDF "
                "driver returned no analytic gradient despite "
                "compute_gradient=True; the GDF relaxation objective "
                "cannot proceed."
            )
        grad_cart = np.asarray(grad_cart, dtype=float)
        # dE/d(frac) = lattice^T . dE/d(cart), row per atom -- the same
        # conversion relax_atoms applies.
        grad_frac = grad_cart @ np.asarray(sys2.lattice, dtype=float)
        # Objective/gradient consistency: for a Fermi-Dirac smeared SCF
        # the analytic gradient is dA/dR of the Mermin free energy
        # A = E - T.S (the surface the smeared SCF converges on), so
        # the scalar objective must be result.free_energy, not
        # result.energy -- otherwise L-BFGS-B line-searches a surface
        # whose derivative it was not given. At T = 0 the drivers set
        # free_energy == energy, so this is only exercised when
        # smearing is active.
        smear_T = float(getattr(result, "smearing_temperature", 0.0) or 0.0)
        e_obj = (
            float(result.free_energy) if smear_T > 0.0
            else float(result.energy)
        )
        return e_obj, grad_frac.ravel()

    x0 = _atoms_to_flat(system)
    res = minimize(
        _energy_and_gradient,
        x0,
        method="L-BFGS-B",
        jac=True,
        options={"maxiter": int(max_iter), "gtol": float(conv_tol_grad)},
    )
    sys_opt = _flat_to_system(system, res.x)
    # Independent gradient gate (shared with relax_atoms /
    # optimize_molecule): scipy's success flag can trip on ftol at a
    # non-stationary geometry.
    converged, grad_max = _gradient_converged(
        bool(res.success), res.jac, float(conv_tol_grad)
    )
    write(
        f"\nAtomic relaxation: {res.nit} iters, "
        f"E = {res.fun:.8f} Ha, "
        f"max|grad| = {grad_max:.4e}, "
        f"converged={converged}\n"
    )
    return OptimizeResult(sys_opt, res.fun, res.jac, res.nit, converged)


def _is_lattice_matrix_set_like(obj) -> bool:
    return (
        hasattr(obj, "cells") and hasattr(obj, "blocks") and hasattr(obj, "set_block")
    )


def _real_block_for_periodic_output(
    block,
    *,
    label: str,
    abs_tol: float = 1.0e-10,
    rel_tol: float = 1.0e-7,
) -> np.ndarray:
    """Return a real matrix block for real scalar output artifacts."""
    arr = np.asarray(block)
    if np.iscomplexobj(arr):
        if arr.size:
            max_imag = float(np.max(np.abs(arr.imag)))
            max_real = float(np.max(np.abs(arr.real)))
        else:
            max_imag = 0.0
            max_real = 0.0
        limit = max(float(abs_tol), float(rel_tol) * max(max_real, 1.0))
        if max_imag > limit:
            raise ValueError(
                f"{label} has a non-negligible imaginary component "
                f"(max|Im|={max_imag:.2e}, max|Re|={max_real:.2e}, "
                f"tolerance={limit:.2e}); periodic density output is "
                "real-only and the k-point density fold is not "
                "time-reversal consistent"
            )
        arr = arr.real
    return np.ascontiguousarray(arr, dtype=float)


def _system_with_valid_unit_cell_multiplicity(
    system: PeriodicSystem,
) -> PeriodicSystem:
    """Return ``system`` or an output-only copy with a valid unit-cell spin."""
    unit = system.unit_cell_molecule()
    charge = int(getattr(unit, "charge", system.charge))
    multiplicity = int(getattr(unit, "multiplicity", system.multiplicity))
    if charge == int(system.charge) and multiplicity == int(system.multiplicity):
        return system
    atoms = [
        Atom(int(atom.Z), [float(x) for x in atom.xyz])
        for atom in system.unit_cell
    ]
    return PeriodicSystem(
        int(system.dim),
        np.asarray(system.lattice, dtype=float),
        atoms,
        charge=charge,
        multiplicity=multiplicity,
    )


def _fold_per_k_density_for_periodic_output(
    basis: BasisSet,
    system: PeriodicSystem,
    D_per_k: Sequence[np.ndarray],
    kpoints_cart,
    weights,
    lat_opts,
):
    """Inverse-Bloch fold per-k AO density matrices for real output grids."""
    from ._vibeqc_core import compute_overlap_lattice

    D_set = compute_overlap_lattice(basis, system, lat_opts)
    kpts = np.asarray(kpoints_cart, dtype=float).reshape(-1, 3)
    w_arr = np.asarray(weights, dtype=float).reshape(-1)
    if len(D_per_k) == 0:
        raise ValueError("SCF result has an empty density list")
    if kpts.shape[0] != len(D_per_k) or w_arr.shape[0] != len(D_per_k):
        raise ValueError(
            "per-k density output requires aligned density, k-point, and "
            f"weight lists; got {len(D_per_k)} densities, {kpts.shape[0]} "
            f"k-points, {w_arr.shape[0]} weights"
        )
    total_w = float(w_arr.sum())
    if total_w <= 0.0:
        raise ValueError("per-k density output requires positive k-point weights")
    if not np.isclose(total_w, 1.0, rtol=1.0e-9, atol=1.0e-12):
        w_arr = w_arr / total_w

    terms = _time_reversal_completed_density_terms(
        D_per_k,
        kpts,
        w_arr,
    )
    for cell_idx, cell in enumerate(D_set.cells):
        r_cart = np.asarray(cell.r_cart, dtype=float).reshape(3)
        P_g = np.zeros((basis.nbasis, basis.nbasis), dtype=np.complex128)
        for weight, k_cart, D in terms:
            phase = np.exp(-1j * float(np.dot(k_cart, r_cart)))
            P_g += float(weight) * phase * D
        D_set.set_block(
            cell_idx,
            _real_block_for_periodic_output(
                P_g,
                label=f"periodic density block g={tuple(cell.index)}",
            ),
        )
    return D_set


def _wrap_reciprocal_fractional(frac: np.ndarray) -> np.ndarray:
    """Wrap reciprocal fractional coordinates into [-0.5, 0.5)."""
    arr = np.asarray(frac, dtype=float)
    return arr - np.floor(arr + 0.5)


def _fractional_kpoints_for_output(
    system: PeriodicSystem,
    kpoints_cart: np.ndarray,
) -> np.ndarray:
    """Return reciprocal fractional k coordinates for output folding."""
    B = np.asarray(system.reciprocal_lattice(), dtype=float)
    return (np.linalg.pinv(B) @ np.asarray(kpoints_cart, dtype=float).T).T


def _time_reversal_completed_density_terms(
    D_per_k: Sequence[np.ndarray],
    kpoints_cart: np.ndarray,
    weights: np.ndarray,
) -> list[tuple[float, np.ndarray, np.ndarray]]:
    """Return density-fold terms symmetrized over time reversal.

    Every k point is folded as the explicit conjugate pair

        w/2 exp(-i k.R) D(k) + w/2 exp(+i k.R) D(k)^*

    so each lattice block of the fold is 2 Re(...) -- real to machine
    precision by construction. This both materializes the implicit -k
    partner of a symmetry-reduced mesh and time-reversal-symmetrizes an
    explicit +/-k mesh: the converged per-k densities satisfy
    D(-k) = D(k)^* only up to the per-k lattice-truncation asymmetry of
    the Fock build (~1e-6 relative; see
    handovers/HANDOVER_GDF_FIT_SCREENING.md "Open findings"), so trusting
    the explicit partner leaves a spurious imaginary residue of that size
    in the folded blocks. The symmetrized fold equals the real part of
    the pristine fold, which is exact for the time-reversal-symmetric
    Hamiltonians vibe-qc ships (no magnetic field / spin-orbit coupling).
    """
    kpts = np.asarray(kpoints_cart, dtype=float).reshape(-1, 3)
    w_arr = np.asarray(weights, dtype=float).reshape(-1)
    terms: list[tuple[float, np.ndarray, np.ndarray]] = []

    for weight, k_cart, D_k in zip(w_arr, kpts, D_per_k):
        D = np.asarray(D_k, dtype=np.complex128)
        D = 0.5 * (D + D.conj().T)
        half = 0.5 * float(weight)
        k = np.asarray(k_cart, dtype=float)
        terms.append((half, k, D))
        terms.append((half, -k, D.conj()))
    return terms


def _result_kpoints_cart(result) -> Optional[np.ndarray]:
    """Return result k-points in Cartesian bohr^-1 coordinates, if present."""
    kpts = getattr(result, "kpoints_cart", None)
    if kpts is None:
        kpts = getattr(result, "kpoints", None)
    if kpts is None:
        kmesh = getattr(result, "kmesh", None)
        if kmesh is not None:
            kpts = getattr(kmesh, "kpoints_cart", None)
            if kpts is None:
                kpts = getattr(kmesh, "kpoints", None)
    if kpts is None:
        return None
    arr = np.asarray(kpts, dtype=float)
    if arr.size == 0:
        return None
    return arr.reshape(-1, 3)


def _result_kpoint_weights(result) -> Optional[np.ndarray]:
    """Return result k-point weights, if present."""
    weights = getattr(result, "kpoint_weights", None)
    if weights is None:
        weights = getattr(result, "weights", None)
    if weights is None:
        kmesh = getattr(result, "kmesh", None)
        if kmesh is not None:
            weights = getattr(kmesh, "weights", None)
    if weights is None:
        return None
    arr = np.asarray(weights, dtype=float)
    if arr.size == 0:
        return None
    return arr.reshape(-1)


def _density_proxy_with_k_metadata(result, density) -> SimpleNamespace:
    """Proxy one spin density while preserving k metadata for output folds."""
    payload: dict[str, object] = {"density": density}
    for name in ("kpoints_cart", "kpoint_weights", "kpoints", "weights", "kmesh"):
        if hasattr(result, name):
            payload[name] = getattr(result, name)
    return SimpleNamespace(**payload)


def _gamma_index_for_multi_k(result, n_items: int) -> int:
    """Find the actual Gamma point for Gamma-only writer fallbacks."""
    if n_items <= 0:
        raise ValueError("multi-k result has no k-point data")
    if n_items == 1:
        return 0
    kpts = _result_kpoints_cart(result)
    if kpts is None:
        raise ValueError(
            "multi-k Gamma-only output requires k-point metadata; "
            "cannot assume the first k-point is Gamma"
        )
    if kpts.shape[0] != n_items:
        raise ValueError(
            "multi-k Gamma-only output requires aligned k-point metadata; "
            f"got {n_items} data blocks and {kpts.shape[0]} k-points"
        )
    norms = np.linalg.norm(kpts, axis=1)
    gamma_idx = int(np.argmin(norms))
    if float(norms[gamma_idx]) > 1.0e-10:
        raise ValueError(
            "multi-k Gamma-only output requires an explicit Gamma k-point; "
            f"closest |k|={float(norms[gamma_idx]):.2e} bohr^-1"
        )
    return gamma_idx


def _density_lattice_set_for_output(
    basis: BasisSet,
    system: PeriodicSystem,
    result,
    lat_opts,
):
    """Return a template-aligned LatticeMatrixSet for legacy DOS analysis."""
    D_attr = getattr(result, "density", None)
    if D_attr is None:
        raise ValueError("SCF result has no .density attribute")
    if _is_lattice_matrix_set_like(D_attr):
        from ._vibeqc_core import compute_overlap_lattice

        # SCF drivers may converge a density on a shorter lattice cutoff
        # than the fixed legacy DOS template. Build that target first,
        # then zero-extend the converged density by lattice-cell key. Never
        # use positional alignment: equal-radius cells have no ordering
        # contract across independently constructed lattice sets.
        D_set = compute_overlap_lattice(basis, system, lat_opts)
        target_cells = list(D_set.cells)
        target_blocks = list(D_set.blocks)
        if len(target_cells) != len(target_blocks):
            raise ValueError(
                "density output template received misaligned cells and blocks"
            )
        target_keys = [
            tuple(int(value) for value in cell.index) for cell in target_cells
        ]
        if len(set(target_keys)) != len(target_keys):
            raise ValueError("density output template received duplicate cells")

        source_cells = list(D_attr.cells)
        source_blocks = list(D_attr.blocks)
        if len(source_cells) != len(source_blocks):
            raise ValueError(
                "density lattice-set output received misaligned cells and blocks"
            )

        block_by_cell: dict[tuple[int, int, int], np.ndarray] = {}
        for cell, block in zip(source_cells, source_blocks):
            key = tuple(int(value) for value in cell.index)
            if key in block_by_cell:
                raise ValueError(
                    "density lattice-set output received duplicate cell "
                    f"{key}"
                )
            real_block = _real_block_for_periodic_output(
                block,
                label=f"periodic density block g={key}",
            )
            expected_shape = (int(D_set.nbf), int(D_set.nbf))
            if real_block.shape != expected_shape:
                raise ValueError(
                    "density lattice-set output block "
                    f"g={key} has shape {real_block.shape}, expected "
                    f"{expected_shape}"
                )
            block_by_cell[key] = real_block

        source_only = set(block_by_cell).difference(target_keys)
        if source_only:
            missing = sorted(source_only)
            raise ValueError(
                "density output template omits converged lattice cells: "
                f"{missing}"
            )

        zero = np.zeros((int(D_set.nbf), int(D_set.nbf)), dtype=float)
        for i, key in enumerate(target_keys):
            D_set.set_block(i, block_by_cell.get(key, zero))
        return D_set

    from ._vibeqc_core import compute_overlap_lattice

    if isinstance(D_attr, (list, tuple)):
        kpoints_cart = _result_kpoints_cart(result)
        weights = _result_kpoint_weights(result)
        has_complex_blocks = any(np.iscomplexobj(np.asarray(d)) for d in D_attr)
        if has_complex_blocks and (kpoints_cart is None or weights is None):
            raise ValueError(
                "complex per-k density output requires k-point and weight "
                "metadata so the density can be inverse-Bloch folded; "
                "refusing to real-project individual k blocks"
            )
        if kpoints_cart is not None and weights is not None:
            return _fold_per_k_density_for_periodic_output(
                basis,
                system,
                D_attr,
                kpoints_cart,
                weights,
                lat_opts,
            )
        if len(D_attr) == 0:
            raise ValueError("SCF result has an empty density list")
        D_set = compute_overlap_lattice(basis, system, lat_opts)
        D_home = sum(
            _real_block_for_periodic_output(
                d,
                label=f"periodic density k-point {i}",
            )
            for i, d in enumerate(D_attr)
        ) / len(D_attr)
    else:
        D_set = compute_overlap_lattice(basis, system, lat_opts)
        D_home = _real_block_for_periodic_output(
            D_attr,
            label="periodic density matrix",
        )
    zero = np.zeros_like(D_home)
    for i in range(len(D_set)):
        D_set.set_block(i, D_home if i == 0 else zero)
    return D_set


def _exact_lattice_density_set_for_grid_artifact(
    basis: BasisSet,
    system: PeriodicSystem,
    density,
    *,
    label: str,
):
    """Copy the returned SCF lattice density without changing its cell set.

    Density grids are a representation of the converged SCF state, so their
    lattice-cell domain must come from that state rather than from an
    independently generated overlap template.  The copy projects only
    numerically negligible imaginary residue and otherwise preserves every
    returned cell and block in source order.
    """
    if not _is_lattice_matrix_set_like(density):
        raise ValueError(
            f"{label} is not a returned lattice-cell density; exact periodic "
            "density artifacts require LatticeMatrixSet cells and blocks and "
            "will not reconstruct them from orbitals or a surrogate template"
        )

    nbf = int(basis.nbasis)
    if int(getattr(density, "nbf", nbf)) != nbf:
        raise ValueError(
            f"{label} has basis dimension {density.nbf}, expected {nbf}"
        )
    cells = list(density.cells)
    source = list(density.blocks)
    if not cells:
        raise ValueError(f"{label} has no lattice cells")
    if len(cells) != len(source):
        raise ValueError(
            f"{label} has {len(cells)} cells but {len(source)} blocks"
        )
    lattice = np.asarray(system.lattice, dtype=float)
    if lattice.shape != (3, 3) or not np.isfinite(lattice).all():
        raise ValueError(
            "exact periodic density artifacts require a finite 3x3 "
            "column-vector lattice"
        )

    expected_shape = (nbf, nbf)
    seen: set[tuple[int, int, int]] = set()
    blocks: list[np.ndarray] = []
    for cell, block in zip(cells, source):
        index = np.asarray(cell.index, dtype=float)
        if index.shape != (3,) or not np.isfinite(index).all():
            raise ValueError(
                f"{label} lattice-cell index must be a finite three-vector"
            )
        key = tuple(int(value) for value in index)
        if not np.array_equal(index, np.asarray(key, dtype=float)):
            raise ValueError(f"{label} contains a non-integral lattice cell {key}")
        if key in seen:
            raise ValueError(f"{label} contains duplicate lattice cell {key}")
        seen.add(key)
        r_cart = np.asarray(cell.r_cart, dtype=float)
        expected_r_cart = lattice @ np.asarray(key, dtype=float)
        if (
            r_cart.shape != (3,)
            or not np.isfinite(r_cart).all()
            or not np.allclose(
                r_cart,
                expected_r_cart,
                rtol=1.0e-12,
                atol=1.0e-10,
            )
        ):
            raise ValueError(
                f"{label} lattice cell {key} has Cartesian translation "
                "inconsistent with the column-vector primitive lattice"
            )
        real_block = _real_block_for_periodic_output(
            block,
            label=f"{label} block g={key}",
        )
        if real_block.shape != expected_shape:
            raise ValueError(
                f"{label} block g={key} has shape {real_block.shape}, "
                f"expected {expected_shape}"
            )
        if not np.isfinite(real_block).all():
            raise ValueError(f"{label} block g={key} contains non-finite values")
        blocks.append(real_block)

    from ._vibeqc_core import make_lattice_matrix_set

    return make_lattice_matrix_set(nbf, cells, blocks)


def _sum_exact_lattice_density_sets_for_grid_artifact(
    basis: BasisSet,
    *density_sets,
    label: str,
):
    """Add returned spin densities by exact lattice-cell key."""
    if not density_sets:
        raise ValueError("no exact spin density lattice sets supplied")

    ref_cells = list(density_sets[0].cells)
    ref_keys = [tuple(int(value) for value in cell.index) for cell in ref_cells]
    ref_key_set = set(ref_keys)
    if len(ref_key_set) != len(ref_keys):
        raise ValueError(f"{label} reference contains duplicate lattice cells")

    block_maps: list[dict[tuple[int, int, int], np.ndarray]] = []
    for spin_index, density_set in enumerate(density_sets):
        cells = list(density_set.cells)
        blocks = list(density_set.blocks)
        if len(cells) != len(blocks):
            raise ValueError(
                f"{label} channel {spin_index} has {len(cells)} cells but "
                f"{len(blocks)} blocks"
            )
        block_map = {
            tuple(int(value) for value in cell.index): np.asarray(block)
            for cell, block in zip(cells, blocks)
        }
        if len(block_map) != len(cells):
            raise ValueError(f"{label} channel {spin_index} has duplicate cells")
        if set(block_map) != ref_key_set:
            missing = sorted(ref_key_set.difference(block_map))
            extra = sorted(set(block_map).difference(ref_key_set))
            raise ValueError(
                f"{label} channels have different lattice-cell keys; "
                f"channel {spin_index} missing {missing} and adds {extra}"
            )
        block_maps.append(block_map)

    blocks = [
        _real_block_for_periodic_output(
            sum(block_map[key] for block_map in block_maps),
            label=f"{label} block g={key}",
        )
        for key in ref_keys
    ]
    if any(not np.isfinite(block).all() for block in blocks):
        raise ValueError(f"{label} contains non-finite summed density blocks")

    from ._vibeqc_core import make_lattice_matrix_set

    return make_lattice_matrix_set(int(basis.nbasis), ref_cells, blocks)


def _fit_exact_lattice_density_to_weighted_k_matrices(
    density_set,
    kpoints_cart: np.ndarray,
    kpoint_weights: np.ndarray,
    *,
    system: PeriodicSystem,
    bvk_mesh: Sequence[int],
    label: str,
) -> list[np.ndarray]:
    """Recover ``w_k P(k)`` from a returned BvK-periodic lattice density.

    This is a representation transform of the returned SCF density.  It uses
    one complete Born-von-Karman residue system (the exact inverse used by
    the BIPOLE Fock path), then refolds the recovered matrices onto *every*
    returned cell.  The transform is accepted only when that reconstruction
    agrees at a scale-aware floating-point tolerance; a truncated, corrupted,
    or non-BvK density therefore fails before any artifact is written.
    """
    kpts = np.asarray(kpoints_cart, dtype=float)
    weights = np.asarray(kpoint_weights, dtype=float).reshape(-1)
    if kpts.ndim != 2 or kpts.shape[1] != 3 or not np.isfinite(kpts).all():
        raise ValueError(
            f"{label} requires finite Cartesian k-points with shape (n_k, 3)"
        )
    if (
        weights.shape != (kpts.shape[0],)
        or not np.isfinite(weights).all()
        or np.any(weights <= 0.0)
    ):
        raise ValueError(
            f"{label} requires one finite positive weight per k-point"
        )
    if not np.isclose(float(weights.sum()), 1.0, rtol=0.0, atol=1.0e-12):
        raise ValueError(
            f"{label} k-point weights sum to {float(weights.sum()):.16g}, "
            "not one"
        )
    mesh = tuple(int(value) for value in bvk_mesh)
    if len(mesh) != 3 or any(value < 1 for value in mesh):
        raise ValueError(
            f"{label} requires a verified three-axis BvK mesh; got {mesh}"
        )
    if int(np.prod(mesh)) != kpts.shape[0]:
        raise ValueError(
            f"{label} BvK mesh {mesh} contains {int(np.prod(mesh))} points "
            f"but the returned SCF metadata contains {kpts.shape[0]}"
        )
    uniform_weight = 1.0 / float(kpts.shape[0])
    if not np.allclose(weights, uniform_weight, rtol=0.0, atol=2.0e-13):
        raise ValueError(
            f"{label} BvK inversion requires uniform Monkhorst-Pack weights; "
            f"got {weights.tolist()}"
        )
    dim = max(0, min(3, int(getattr(system, "dim", 3))))
    lattice = np.asarray(system.lattice, dtype=float)
    if lattice.shape != (3, 3) or not np.isfinite(lattice).all():
        raise ValueError(f"{label} requires a finite 3x3 primitive lattice")
    fractional = (lattice.T @ kpts.T).T / (2.0 * np.pi)
    residues: list[np.ndarray] = []
    metadata_tolerance = 2.0e-10
    for axis, period in enumerate(mesh):
        wrapped = fractional[:, axis] - np.floor(fractional[:, axis])
        if axis >= dim:
            centered = ((fractional[:, axis] + 0.5) % 1.0) - 0.5
            if period != 1 or not np.allclose(
                centered,
                0.0,
                rtol=0.0,
                atol=metadata_tolerance,
            ):
                raise ValueError(
                    f"{label} has non-Gamma sampling on nonperiodic axis {axis}"
                )
            residues.append(np.zeros(kpts.shape[0], dtype=int))
            continue
        scaled = float(period) * fractional[:, axis]
        fractional_offsets = scaled - np.floor(scaled)
        distance_unshifted = np.minimum(
            fractional_offsets,
            1.0 - fractional_offsets,
        )
        is_unshifted = bool(np.all(distance_unshifted <= metadata_tolerance))
        is_half_shifted = bool(
            np.all(np.abs(fractional_offsets - 0.5) <= metadata_tolerance)
        )
        if not (is_unshifted or is_half_shifted):
            raise ValueError(
                f"{label} k-points are not a supported Monkhorst-Pack "
                f"character set on axis {axis}"
            )
        offset = 0.5 if is_half_shifted else 0.0
        residues.append(
            np.mod(np.rint(scaled - offset).astype(int), period)
        )
    residue_tuples = list(zip(*(axis.tolist() for axis in residues)))
    if len(set(residue_tuples)) != int(np.prod(mesh)):
        raise ValueError(
            f"{label} k-points do not contain every BvK residue of mesh "
            f"{mesh} exactly once"
        )
    cells = list(density_set.cells)
    blocks = [np.asarray(block, dtype=float) for block in density_set.blocks]
    if not cells or len(cells) != len(blocks):
        raise ValueError(f"{label} has misaligned or empty lattice blocks")
    expected_shape = (int(density_set.nbf), int(density_set.nbf))
    if any(block.shape != expected_shape for block in blocks):
        raise ValueError(f"{label} contains a block with the wrong basis shape")
    blocks_by_key = {
        tuple(int(value) for value in cell.index): block
        for cell, block in zip(cells, blocks)
    }
    if len(blocks_by_key) != len(cells):
        raise ValueError(f"{label} contains duplicate lattice cells")
    block_scale = max(1.0, max(float(np.max(np.abs(block))) for block in blocks))
    transpose_tolerance = 5.0e-11 * block_scale
    for key, block in blocks_by_key.items():
        inverse_key = tuple(-value for value in key)
        inverse_block = blocks_by_key.get(inverse_key)
        if inverse_block is None:
            raise ValueError(
                f"{label} lacks inverse lattice cell {inverse_key} required "
                "by a real time-reversal-symmetric density"
            )
        transpose_error = float(np.max(np.abs(inverse_block - block.T)))
        if transpose_error > transpose_tolerance:
            raise ValueError(
                f"{label} has a pre-refold residual in D(-g)=D(g).T at "
                f"cell {key}; max residual {transpose_error:.3e}"
            )

    from .pbc_bipole_common import bvk_torus_density_matrices

    recovered = [
        np.asarray(matrix, dtype=np.complex128)
        for matrix in bvk_torus_density_matrices(density_set, kpts, mesh)
    ]
    if len(recovered) != kpts.shape[0]:
        raise ValueError(
            f"{label} BvK inverse returned {len(recovered)} matrices for "
            f"{kpts.shape[0]} k-points"
        )
    for index, matrix in enumerate(recovered):
        if matrix.shape != expected_shape or not np.isfinite(matrix).all():
            raise ValueError(
                f"{label} recovered matrix k={index} is not finite with "
                f"shape {expected_shape}"
            )

    wrapped_fractional = fractional - np.floor(fractional)
    for index, (qpoint, matrix) in enumerate(zip(wrapped_fractional, recovered)):
        pair_distances = np.max(
            np.abs(
                ((wrapped_fractional + qpoint + 0.5) % 1.0) - 0.5
            ),
            axis=1,
        )
        pair_index = int(np.argmin(pair_distances))
        if float(pair_distances[pair_index]) > metadata_tolerance:
            raise ValueError(
                f"{label} k-point {index} has no time-reversal partner"
            )
        pair = recovered[pair_index]
        pair_scale = max(1.0, float(np.max(np.abs(matrix))))
        time_reversal_error = float(np.max(np.abs(pair - matrix.conj())))
        if time_reversal_error > 5.0e-10 * pair_scale:
            raise ValueError(
                f"{label} recovered k-point densities violate time reversal "
                f"at k={index}; max residual {time_reversal_error:.3e}"
            )
        matrix_scale = max(1.0, float(np.max(np.abs(matrix))))
        hermitian_error = float(np.max(np.abs(matrix - matrix.conj().T)))
        if hermitian_error > 5.0e-11 * matrix_scale:
            raise ValueError(
                f"{label} recovered matrix k={index} is not Hermitian; "
                f"max residual {hermitian_error:.3e}"
            )

    # The inverse/refold consists of O(n_k) complex sums.  This bound is
    # deliberately close to floating-point noise while allowing different
    # BLAS accumulation orders on supported platforms.
    tolerance = max(
        5.0e-12,
        256.0
        * np.finfo(float).eps
        * max(1, kpts.shape[0])
        * block_scale,
    )
    worst_error = 0.0
    worst_cell: tuple[int, int, int] | None = None
    for cell, actual in zip(cells, blocks):
        r_cart = np.asarray(cell.r_cart, dtype=float)
        predicted = np.zeros(expected_shape, dtype=float)
        for weight, kpoint, matrix in zip(weights, kpts, recovered):
            phase = float(np.dot(kpoint, r_cart))
            predicted += float(weight) * (
                np.cos(phase) * matrix.real + np.sin(phase) * matrix.imag
            )
        error = float(np.max(np.abs(predicted - actual)))
        if error > worst_error:
            worst_error = error
            worst_cell = tuple(int(value) for value in cell.index)
    if worst_error > tolerance:
        raise ValueError(
            f"{label} is not exactly representable by its returned BvK "
            f"k-point metadata: refold residual {worst_error:.3e} at cell "
            f"{worst_cell} exceeds {tolerance:.3e}"
        )
    return [float(weight) * matrix for weight, matrix in zip(weights, recovered)]


def _validated_periodic_density_electron_count(
    basis: BasisSet,
    system: PeriodicSystem,
    density_set,
    *,
    expected_electrons: float,
    tolerance: float = 5.0e-5,
) -> float:
    """Validate ``sum_g D(g):S(g)`` on the exact returned cell domain."""
    from ._vibeqc_core import compute_overlap_lattice_explicit

    cells = list(density_set.cells)
    overlap = compute_overlap_lattice_explicit(basis, system, cells)
    charge = float(
        sum(
            np.einsum(
                "ij,ij->",
                np.asarray(density_block, dtype=float),
                np.asarray(overlap_block, dtype=float),
            )
            for density_block, overlap_block in zip(
                density_set.blocks,
                overlap.blocks,
            )
        )
    )
    _require_artifact_electron_count(
        charge,
        expected_electrons,
        tolerance=tolerance,
        label="returned periodic density sum_g D(g):S(g)",
    )
    return charge


def _require_artifact_electron_count(
    actual: float,
    expected: float,
    *,
    tolerance: float,
    label: str,
) -> None:
    if not np.isfinite(float(expected)):
        raise ValueError(f"{label} expected electron count must be finite")
    if not np.isfinite(float(tolerance)) or float(tolerance) <= 0.0:
        raise ValueError(f"{label} tolerance must be finite and positive")
    if not np.isfinite(actual) or abs(float(actual) - float(expected)) > float(
        tolerance
    ):
        raise ValueError(
            f"{label}={float(actual):.10f}, expected {float(expected):.10f} "
            f"within {float(tolerance):.1e}"
        )


def _converged_periodic_density_grid_for_artifact(
    evaluate_at_radius,
    *,
    lattice_bohr: np.ndarray,
    grid_shape: tuple[int, int, int],
    analytic_electrons: float,
    label: str,
    max_image_radius: int = 3,
    charge_tolerance: float = 5.0e-3,
    image_l1_tolerance: float = 1.0e-5,
    image_max_tolerance: float = 1.0e-6,
) -> tuple[np.ndarray, object]:
    """Certify an artifact grid against AO-image and charge convergence."""
    lattice = np.asarray(lattice_bohr, dtype=float)
    if lattice.shape != (3, 3) or not np.isfinite(lattice).all():
        raise ValueError("periodic density certification requires a finite 3x3 lattice")
    volume = float(abs(np.linalg.det(lattice)))
    if not np.isfinite(volume) or volume <= 0.0:
        raise ValueError("periodic density certification requires nonzero cell volume")
    if len(grid_shape) != 3 or any(int(value) < 1 for value in grid_shape):
        raise ValueError("periodic density certification grid shape must be positive")
    if not np.isfinite(float(analytic_electrons)):
        raise ValueError("periodic density analytic electron count must be finite")
    if int(max_image_radius) < 2:
        raise ValueError(
            "periodic density certification requires max_image_radius >= 2"
        )
    for name, value in (
        ("charge_tolerance", charge_tolerance),
        ("image_l1_tolerance", image_l1_tolerance),
        ("image_max_tolerance", image_max_tolerance),
    ):
        if not np.isfinite(float(value)) or float(value) <= 0.0:
            raise ValueError(f"periodic density {name} must be finite and positive")
    dV = volume / float(np.prod(grid_shape))
    previous: np.ndarray | None = None
    last_l1 = float("inf")
    last_max = float("inf")
    for radius in range(1, int(max_image_radius) + 1):
        rho_raw, metadata = evaluate_at_radius(radius)
        rho = np.asarray(rho_raw, dtype=float)
        if rho.shape != tuple(grid_shape) or not np.isfinite(rho).all():
            raise ValueError(
                f"{label} evaluator returned invalid grid shape {rho.shape}; "
                f"expected finite {tuple(grid_shape)}"
            )
        if previous is not None:
            delta = rho - previous
            last_l1 = float(np.sum(np.abs(delta)) * dV)
            last_max = float(np.max(np.abs(delta)))
            if (
                radius == int(max_image_radius)
                and last_l1 <= float(image_l1_tolerance)
                and last_max <= float(image_max_tolerance)
            ):
                grid_electrons = float(np.sum(rho) * dV)
                if (
                    not np.isfinite(grid_electrons)
                    or abs(grid_electrons - float(analytic_electrons))
                    > float(charge_tolerance)
                ):
                    raise ValueError(
                        f"{label} primitive-cell grid integral="
                        f"{grid_electrons:.10f}, expected "
                        f"{float(analytic_electrons):.10f} within "
                        f"{float(charge_tolerance):.1e}; decrease "
                        "density_spacing_bohr to refine the artifact grid"
                    )
                return rho, metadata
        previous = rho

    raise ValueError(
        f"{label} did not converge its periodic AO basis translations "
        f"through image radius {int(max_image_radius)}: successive-grid "
        f"L1={last_l1:.3e} electrons (limit "
        f"{float(image_l1_tolerance):.1e}), max|delta rho|={last_max:.3e} "
        f"electron/bohr^3 (limit {float(image_max_tolerance):.1e})"
    )


def _converged_lattice_density_grid_for_artifact(
    basis: BasisSet,
    system: PeriodicSystem,
    density_set,
    *,
    grid_shape: tuple[int, int, int],
    spacing_bohr: float,
    expected_electrons: float,
    max_image_radius: int = 3,
    charge_tolerance: float = 5.0e-3,
    image_l1_tolerance: float = 1.0e-5,
    image_max_tolerance: float = 1.0e-6,
) -> np.ndarray:
    """Evaluate and certify a returned lattice density for an artifact.

    The finite AO-image sum is increased through radius three.  An artifact is
    emitted only when successive grids agree both pointwise and in integrated
    absolute density, and the final numerical primitive-cell integral agrees
    with the exact lattice trace on the same returned cells.
    """
    from .periodic_density import evaluate_periodic_density_on_grid

    analytic_electrons = _validated_periodic_density_electron_count(
        basis,
        system,
        density_set,
        expected_electrons=expected_electrons,
    )

    def _evaluate(radius: int) -> tuple[np.ndarray, None]:
        rho, resolved_shape = evaluate_periodic_density_on_grid(
            basis,
            system,
            density_set,
            grid_shape=grid_shape,
            spacing_bohr=spacing_bohr,
            ao_image_radius=radius,
        )
        if tuple(resolved_shape) != tuple(grid_shape):
            raise ValueError(
                "periodic density evaluator changed the requested artifact "
                f"grid shape from {tuple(grid_shape)} to {tuple(resolved_shape)}"
            )
        return rho, None

    rho, _ = _converged_periodic_density_grid_for_artifact(
        _evaluate,
        lattice_bohr=np.asarray(system.lattice, dtype=float),
        grid_shape=grid_shape,
        analytic_electrons=analytic_electrons,
        label="periodic density artifact",
        max_image_radius=max_image_radius,
        charge_tolerance=charge_tolerance,
        image_l1_tolerance=image_l1_tolerance,
        image_max_tolerance=image_max_tolerance,
    )
    return rho


def _converged_bvk_density_grid_for_artifact(
    basis: BasisSet,
    system: PeriodicSystem,
    density_set,
    kpoints_cart: np.ndarray,
    kpoint_weights: np.ndarray,
    bvk_mesh: Sequence[int],
    *,
    grid_shape: tuple[int, int, int],
    spacing_bohr: float,
    expected_electrons: float,
    max_image_radius: int = 3,
    charge_tolerance: float = 5.0e-3,
    image_l1_tolerance: float = 1.0e-5,
    image_max_tolerance: float = 1.0e-6,
    component_density_sets: Sequence[object] | None = None,
) -> np.ndarray:
    """Evaluate a refold-certified returned BvK density via Bloch AOs."""
    from .periodic_density import evaluate_weighted_k_density_on_grid

    analytic_electrons = _validated_periodic_density_electron_count(
        basis,
        system,
        density_set,
        expected_electrons=expected_electrons,
    )
    components = list(component_density_sets or (density_set,))
    weighted_components = [
        _fit_exact_lattice_density_to_weighted_k_matrices(
            component,
            kpoints_cart,
            kpoint_weights,
            system=system,
            bvk_mesh=bvk_mesh,
            label=(
                "returned periodic density"
                if len(components) == 1
                else f"returned periodic spin density channel {index}"
            ),
        )
        for index, component in enumerate(components)
    ]
    weighted_density = [
        sum(component[k_index] for component in weighted_components)
        for k_index in range(len(weighted_components[0]))
    ]

    def _evaluate(radius: int) -> tuple[np.ndarray, None]:
        rho, resolved_shape = evaluate_weighted_k_density_on_grid(
            basis,
            system,
            weighted_density,
            kpoints_cart,
            grid_shape=grid_shape,
            spacing_bohr=spacing_bohr,
            ao_image_radius=radius,
        )
        if tuple(resolved_shape) != tuple(grid_shape):
            raise ValueError(
                "weighted-k density evaluator changed the requested artifact "
                f"grid shape from {tuple(grid_shape)} to {tuple(resolved_shape)}"
            )
        return rho, None

    rho, _ = _converged_periodic_density_grid_for_artifact(
        _evaluate,
        lattice_bohr=np.asarray(system.lattice, dtype=float),
        grid_shape=grid_shape,
        analytic_electrons=analytic_electrons,
        label="periodic BvK density artifact",
        max_image_radius=max_image_radius,
        charge_tolerance=charge_tolerance,
        image_l1_tolerance=image_l1_tolerance,
        image_max_tolerance=image_max_tolerance,
    )
    return rho


def _converged_gamma_density_grid_for_artifact(
    basis: BasisSet,
    system: PeriodicSystem,
    density: np.ndarray,
    overlap: np.ndarray,
    lattice_bohr: np.ndarray,
    grid_shape: tuple[int, int, int],
    *,
    expected_electrons: float,
    max_image_radius: int = 3,
    charge_tolerance: float = 5.0e-3,
    image_l1_tolerance: float = 1.0e-5,
    image_max_tolerance: float = 1.0e-6,
) -> tuple[np.ndarray, np.ndarray]:
    """Evaluate and certify an actual returned Gamma density matrix."""
    expected_shape = (int(basis.nbasis), int(basis.nbasis))
    D = _real_block_for_periodic_output(
        density,
        label="periodic Gamma density",
    )
    S = _real_block_for_periodic_output(
        overlap,
        label="periodic Gamma overlap",
    )
    if D.shape != expected_shape or S.shape != expected_shape:
        raise ValueError(
            "exact Gamma density artifacts require returned density and "
            f"overlap matrices of shape {expected_shape}; got D{D.shape}, "
            f"S{S.shape}"
        )
    if not np.isfinite(D).all() or not np.isfinite(S).all():
        raise ValueError(
            "exact Gamma density artifacts require finite returned density "
            "and overlap matrices"
        )
    analytic_electrons = float(np.einsum("ij,ij->", D, S))
    _require_artifact_electron_count(
        analytic_electrons,
        expected_electrons,
        tolerance=5.0e-5,
        label="returned Gamma density D:S",
    )

    def _evaluate(radius: int) -> tuple[np.ndarray, np.ndarray]:
        return _evaluate_density_matrix_on_lattice_grid(
            D,
            basis,
            lattice_bohr,
            grid_shape,
            system=system,
            ao_image_radius=radius,
        )

    rho, voxel_vectors = _converged_periodic_density_grid_for_artifact(
        _evaluate,
        lattice_bohr=lattice_bohr,
        grid_shape=grid_shape,
        analytic_electrons=analytic_electrons,
        label="Gamma density artifact",
        max_image_radius=max_image_radius,
        charge_tolerance=charge_tolerance,
        image_l1_tolerance=image_l1_tolerance,
        image_max_tolerance=image_max_tolerance,
    )
    return rho, np.asarray(voxel_vectors, dtype=float)


def _exact_periodic_density_grid_artifact(
    basis: BasisSet,
    system: PeriodicSystem,
    result,
    *,
    lattice_bohr: np.ndarray,
    grid_shape: tuple[int, int, int],
    spacing_bohr: float,
    gamma_only: bool,
    expected_electrons: float,
    bvk_mesh: Sequence[int] | None = None,
) -> tuple[np.ndarray, np.ndarray]:
    """Evaluate the returned SCF density without reconstructing its state."""
    lattice_arr = np.asarray(lattice_bohr, dtype=float)
    system_lattice = np.asarray(system.lattice, dtype=float)
    if (
        lattice_arr.shape != (3, 3)
        or not np.isfinite(lattice_arr).all()
        or not np.allclose(lattice_arr, system_lattice, rtol=0.0, atol=1.0e-12)
    ):
        raise ValueError(
            "periodic density artifact lattice must equal the system's finite "
            "column-vector primitive lattice"
        )
    if not np.isfinite(float(expected_electrons)):
        raise ValueError("periodic density artifact electron count must be finite")
    voxel_vectors = lattice_arr.T / np.asarray(
        grid_shape,
        dtype=float,
    )[:, None]
    has_alpha = hasattr(result, "density_alpha")
    has_beta = hasattr(result, "density_beta")
    if has_alpha != has_beta:
        raise ValueError(
            "periodic density artifact received only one spin density channel"
        )
    if has_alpha:
        densities = [result.density_alpha, result.density_beta]
        labels = ["periodic alpha density", "periodic beta density"]
    else:
        returned = getattr(result, "density", None)
        if returned is None:
            raise ValueError("SCF result has no returned periodic density")
        densities = [returned]
        labels = ["periodic density"]

    lattice_flags = [_is_lattice_matrix_set_like(item) for item in densities]
    if any(lattice_flags) and not all(lattice_flags):
        raise ValueError(
            "periodic density artifact received different alpha/beta density "
            "representations"
        )
    if all(lattice_flags):
        exact = [
            _exact_lattice_density_set_for_grid_artifact(
                basis,
                system,
                item,
                label=label,
            )
            for item, label in zip(densities, labels)
        ]
        total = exact[0]
        if len(exact) == 2:
            total = _sum_exact_lattice_density_sets_for_grid_artifact(
                basis,
                *exact,
                label="periodic spin density",
            )
        kpoints_cart = _result_kpoints_cart(result)
        weights = _result_kpoint_weights(result)
        metadata_count = sum(
            item is not None for item in (kpoints_cart, weights, bvk_mesh)
        )
        if metadata_count not in (0, 3):
            raise ValueError(
                "exact periodic lattice density artifacts require aligned "
                "kpoints_cart, kpoint_weights, and verified BvK mesh metadata"
            )
        if metadata_count == 3:
            assert kpoints_cart is not None and weights is not None
            # Legacy Gamma-local BIPOLE densities are a finite-support real-
            # space representation, not a BvK-periodic inverse fold.  Their
            # exact grid remains the direct returned-block contraction.
            gamma_local = bool(
                kpoints_cart.shape == (1, 3)
                and float(np.linalg.norm(kpoints_cart[0])) <= 1.0e-12
                and tuple(int(value) for value in bvk_mesh) == (1, 1, 1)
                and all(
                    tuple(int(value) for value in cell.index) == (0, 0, 0)
                    or not np.any(np.asarray(block) != 0.0)
                    for cell, block in zip(total.cells, total.blocks)
                )
            )
            if not gamma_local:
                return (
                    _converged_bvk_density_grid_for_artifact(
                        basis,
                        system,
                        total,
                        kpoints_cart,
                        weights,
                        bvk_mesh,
                        grid_shape=grid_shape,
                        spacing_bohr=spacing_bohr,
                        expected_electrons=expected_electrons,
                        component_density_sets=exact,
                    ),
                    voxel_vectors,
                )
        live_blocks = sum(
            bool(np.any(np.asarray(block) != 0.0)) for block in total.blocks
        )
        if metadata_count == 0 and live_blocks > 128:
            raise ValueError(
                "exact periodic density artifact has no verified BvK k-point "
                f"metadata and {live_blocks} nonzero returned lattice blocks; "
                "refusing the non-scalable direct grid evaluation"
            )
        return (
            _converged_lattice_density_grid_for_artifact(
                basis,
                system,
                total,
                grid_shape=grid_shape,
                spacing_bohr=spacing_bohr,
                expected_electrons=expected_electrons,
            ),
            voxel_vectors,
        )

    if not gamma_only:
        raise ValueError(
            "exact true-multi-k periodic density artifacts require a returned "
            "lattice-cell density; refusing to reconstruct one from per-k "
            "matrices"
        )
    matrices = [np.asarray(item) for item in densities]
    overlap = np.asarray(getattr(result, "overlap", None))
    if any(matrix.ndim != 2 for matrix in matrices) or overlap.ndim != 2:
        raise ValueError(
            "exact Gamma density artifacts require returned rank-2 density "
            "and overlap matrices"
        )
    return _converged_gamma_density_grid_for_artifact(
        basis,
        system,
        sum(matrices),
        overlap,
        lattice_bohr,
        grid_shape,
        expected_electrons=expected_electrons,
    )


def _sum_lattice_density_sets_for_output(
    basis: BasisSet,
    system: PeriodicSystem,
    lat_opts,
    *density_sets,
    label: str,
):
    """Add spin-resolved lattice density sets for real output artifacts."""
    if not density_sets:
        raise ValueError("no density lattice sets supplied")
    ref = density_sets[0]
    ref_cells = list(ref.cells)
    ref_blocks = list(ref.blocks)
    if len(ref_cells) != len(ref_blocks):
        raise ValueError("spin density lattice set has misaligned cells and blocks")
    ref_keys = [tuple(int(value) for value in cell.index) for cell in ref_cells]
    for other in density_sets[1:]:
        other_cells = list(other.cells)
        other_blocks = list(other.blocks)
        if len(other_cells) != len(other_blocks):
            raise ValueError(
                "spin density lattice set has misaligned cells and blocks"
            )
        other_keys = [
            tuple(int(value) for value in cell.index) for cell in other_cells
        ]
        if other_keys != ref_keys:
            raise ValueError(
                "spin density lattice sets have different keyed cell order"
            )

    from vibeqc.pbc_bipole_common import _copy_lattice_with_blocks

    blocks = []
    for i, parts in enumerate(zip(*(list(ds.blocks) for ds in density_sets))):
        cell = ref_cells[i]
        combined = sum(np.asarray(part) for part in parts)
        blocks.append(
            _real_block_for_periodic_output(
                combined,
                label=f"{label} block g={tuple(cell.index)}",
            )
        )
    # BUG 37 fix: the legacy template from compute_overlap_lattice may include
    # additional cells (e.g. in odd-electron DOS preparation).
    # fill_missing=True zero-fills those cells instead of raising.
    return _copy_lattice_with_blocks(
        basis,
        system,
        lat_opts,
        ref.cells,
        blocks,
        fill_missing=True,
    )


def _evaluate_density_matrix_on_lattice_grid(
    density: np.ndarray,
    basis: BasisSet,
    lattice_bohr: np.ndarray,
    shape: tuple[int, int, int],
    *,
    system: PeriodicSystem | None = None,
    ao_image_radius: int = 1,
) -> tuple[np.ndarray, np.ndarray]:
    """Evaluate a one-cell AO density matrix on a lattice-spanning grid.

    ``PeriodicSystem.lattice`` stores lattice vectors as columns. QVF grids
    store per-voxel vectors as rows, so the returned ``voxel_vectors`` are
    ``lattice.T / shape`` and ``shape[i] * voxel_vectors[i]`` exactly spans
    lattice vector ``i``.
    """
    L_bohr = np.asarray(lattice_bohr, dtype=float)
    if L_bohr.shape != (3, 3):
        raise ValueError("periodic QVF lattice grid requires a 3x3 lattice")
    nx, ny, nz = (int(shape[0]), int(shape[1]), int(shape[2]))
    if min(nx, ny, nz) < 1:
        raise ValueError("periodic QVF lattice grid shape must be positive")
    voxel_vectors = L_bohr.T / np.array([nx, ny, nz], dtype=float)[:, None]
    D = _real_block_for_periodic_output(density, label="periodic QVF density")
    if D.ndim != 2:
        raise ValueError(
            "periodic QVF density matrix must be rank 2; "
            f"got shape {D.shape!r}"
        )

    n_points = nx * ny * nz
    rho_flat = np.empty(n_points, dtype=float)
    chunk_size = 200_000
    for start in range(0, n_points, chunk_size):
        stop = min(start + chunk_size, n_points)
        linear = np.arange(start, stop, dtype=np.int64)
        ix = linear // (ny * nz)
        iy = (linear // nz) % ny
        iz = linear % nz
        frac = np.column_stack(
            [
                ix.astype(float) / float(nx),
                iy.astype(float) / float(ny),
                iz.astype(float) / float(nz),
            ]
        )
        points = frac @ L_bohr.T
        chi = _evaluate_ao_for_periodic_qvf(
            basis,
            system,
            points,
            image_radius=ao_image_radius,
        )
        rho_flat[start:stop] = np.einsum("mi,ij,mj->m", chi, D, chi)
    return rho_flat.reshape((nx, ny, nz)), voxel_vectors


def _evaluate_ao_for_periodic_qvf(
    basis: BasisSet,
    system: PeriodicSystem | None,
    points: np.ndarray,
    *,
    image_radius: int,
) -> np.ndarray:
    """Evaluate AO values with image sums on periodic axes for QVF grids."""
    from ._vibeqc_core import evaluate_ao

    if system is None or int(image_radius) <= 0:
        return evaluate_ao(basis, points)

    import itertools

    from .ewald_j import get_shifted_basis

    dim = max(0, min(3, int(getattr(system, "dim", 3))))
    ranges = [
        range(-int(image_radius), int(image_radius) + 1) if axis < dim else (0,)
        for axis in range(3)
    ]
    chi = evaluate_ao(basis, points)
    for shift in itertools.product(*ranges):
        if shift == (0, 0, 0):
            continue
        shifted = get_shifted_basis(basis, system, shift)
        chi += evaluate_ao(shifted, points)
    return chi


def _evaluate_orbital_on_lattice_grid(
    coefficients: np.ndarray,
    basis: BasisSet,
    lattice_bohr: np.ndarray,
    shape: tuple[int, int, int],
    *,
    system: PeriodicSystem | None = None,
    ao_image_radius: int = 1,
) -> tuple[np.ndarray, np.ndarray]:
    """Evaluate one AO coefficient vector on a lattice-spanning grid."""
    L_bohr = np.asarray(lattice_bohr, dtype=float)
    if L_bohr.shape != (3, 3):
        raise ValueError("periodic QVF lattice grid requires a 3x3 lattice")
    nx, ny, nz = (int(shape[0]), int(shape[1]), int(shape[2]))
    if min(nx, ny, nz) < 1:
        raise ValueError("periodic QVF lattice grid shape must be positive")
    voxel_vectors = L_bohr.T / np.array([nx, ny, nz], dtype=float)[:, None]
    coeff = np.asarray(coefficients, dtype=np.complex128).reshape(-1)
    if coeff.shape[0] != int(basis.nbasis):
        raise ValueError(
            "periodic QVF orbital coefficient length "
            f"{coeff.shape[0]} does not match basis size {int(basis.nbasis)}"
        )

    n_points = nx * ny * nz
    psi_flat = np.empty(n_points, dtype=np.complex128)
    chunk_size = 200_000
    for start in range(0, n_points, chunk_size):
        stop = min(start + chunk_size, n_points)
        linear = np.arange(start, stop, dtype=np.int64)
        ix = linear // (ny * nz)
        iy = (linear // nz) % ny
        iz = linear % nz
        frac = np.column_stack(
            [
                ix.astype(float) / float(nx),
                iy.astype(float) / float(ny),
                iz.astype(float) / float(nz),
            ]
        )
        points = frac @ L_bohr.T
        chi = _evaluate_ao_for_periodic_qvf(
            basis,
            system,
            points,
            image_radius=ao_image_radius,
        )
        psi_flat[start:stop] = chi @ coeff
    psi = _real_block_for_periodic_output(
        psi_flat.reshape((nx, ny, nz)),
        label="periodic QVF orbital grid",
    )
    return psi, voxel_vectors


def _aiccm_b_qvf_translations(mesh: tuple[int, int, int]) -> list[tuple[int, int, int]]:
    return [
        (int(i), int(j), int(k))
        for i in range(int(mesh[0]))
        for j in range(int(mesh[1]))
        for k in range(int(mesh[2]))
    ]


def _aiccm_b_qvf_supercell_system(
    system: PeriodicSystem,
    mesh: tuple[int, int, int],
) -> PeriodicSystem:
    """Build the visual BvK supercell used by χ-CCM-B QVF output."""
    lattice = np.asarray(system.lattice, dtype=float)
    mesh_arr = np.asarray(mesh, dtype=float)
    n_cells = int(np.prod(mesh))
    super_lattice = lattice * mesh_arr[None, :]
    atoms: list[Atom] = []
    for translation in _aiccm_b_qvf_translations(mesh):
        shift = lattice @ np.asarray(translation, dtype=float)
        for atom in system.unit_cell:
            position = np.asarray(atom.xyz, dtype=float) + shift
            atoms.append(Atom(int(atom.Z), position.tolist()))
    return PeriodicSystem(
        int(system.dim),
        super_lattice,
        atoms,
        charge=int(system.charge) * n_cells,
        multiplicity=n_cells * (int(system.multiplicity) - 1) + 1,
    )


def _aiccm_b_residue_density_blocks_for_qvf(
    density_like,
    mesh: tuple[int, int, int],
    *,
    label: str,
) -> dict[tuple[int, int, int], np.ndarray]:
    """Fold lattice-set density aliases to one block per cyclic residue."""
    residues = _aiccm_b_qvf_translations(mesh)
    if not _is_lattice_matrix_set_like(density_like):
        if tuple(mesh) != (1, 1, 1):
            raise ValueError(
                f"{label} is not a lattice matrix set for χ-CCM-B mesh {mesh!r}"
            )
        return {
            (0, 0, 0): _real_block_for_periodic_output(
                density_like,
                label=label,
            )
        }

    buckets: dict[tuple[int, int, int], list[np.ndarray]] = {
        residue: [] for residue in residues
    }
    mesh_arr = np.asarray(mesh, dtype=int)
    for cell, block in zip(density_like.cells, density_like.blocks):
        index = np.asarray(cell.index, dtype=int)
        residue = tuple(int(x) for x in np.mod(index, mesh_arr))
        if residue in buckets:
            buckets[residue].append(
                _real_block_for_periodic_output(
                    block,
                    label=f"{label} residue {residue}",
                )
            )
    missing = [residue for residue, blocks in buckets.items() if not blocks]
    if missing:
        raise ValueError(
            f"{label} is missing cyclic density residues {missing!r}"
        )
    return {
        residue: np.mean(np.stack(blocks, axis=0), axis=0)
        for residue, blocks in buckets.items()
    }


def _aiccm_b_density_alias_can_fold_directly(
    density_like,
    mesh: tuple[int, int, int],
) -> bool:
    if _is_lattice_matrix_set_like(density_like):
        return True
    return tuple(mesh) == (1, 1, 1) and np.asarray(density_like).ndim == 2


def _aiccm_b_effective_electrons_for_qvf(result) -> int:
    diagnostics = getattr(result, "aiccm2026dev_b", None)
    value = getattr(result, "effective_n_electrons", None)
    if value is None and diagnostics is not None:
        value = getattr(diagnostics, "effective_electron_count", None)
    if value is None:
        raise TypeError(
            "χ-CCM-B QVF density folding requires an effective electron count"
        )
    return int(value)


def _aiccm_b_spin_occupations_for_qvf(
    system: PeriodicSystem,
    effective_electrons: int,
) -> tuple[int, int]:
    two_s = int(system.multiplicity) - 1
    alpha_twice = int(effective_electrons) + two_s
    beta_twice = int(effective_electrons) - two_s
    if alpha_twice < 0 or beta_twice < 0 or alpha_twice % 2 or beta_twice % 2:
        raise ValueError(
            "χ-CCM-B QVF density folding requires the effective electron "
            "count and multiplicity to define integer alpha/beta occupations"
        )
    return alpha_twice // 2, beta_twice // 2


def _aiccm_b_residue_density_blocks_from_k_for_qvf(
    result,
    system: PeriodicSystem,
    mesh: tuple[int, int, int],
) -> dict[tuple[int, int, int], np.ndarray]:
    """Fold stored B k-density blocks to one block per cyclic residue."""
    effective_electrons = _aiccm_b_effective_electrons_for_qvf(result)
    if hasattr(result, "density_alpha") and hasattr(result, "density_beta"):
        n_alpha, n_beta = _aiccm_b_spin_occupations_for_qvf(
            system,
            effective_electrons,
        )
        alpha = _spin_density_blocks_per_k(result, "alpha", n_alpha)
        beta = _spin_density_blocks_per_k(result, "beta", n_beta)
        if len(alpha) != len(beta):
            raise ValueError("χ-CCM-B QVF alpha/beta density block counts differ")
        density_k = [a + b for a, b in zip(alpha, beta)]
    else:
        if int(system.multiplicity) != 1 or int(effective_electrons) % 2:
            raise ValueError(
                "χ-CCM-B QVF restricted density folding requires a singlet "
                "record with an even effective electron count"
            )
        density_k = _density_blocks_per_k(result, effective_electrons)

    kpoints_frac = np.asarray(getattr(result, "kpoints_frac"), dtype=float).reshape(
        -1,
        3,
    )
    weights = np.asarray(getattr(result, "kpoint_weights"), dtype=float).reshape(-1)
    if len(density_k) != kpoints_frac.shape[0] or weights.shape != (
        kpoints_frac.shape[0],
    ):
        raise ValueError(
            "χ-CCM-B QVF density folding requires matching density, k-point, "
            "and weight counts"
        )
    residues = _aiccm_b_qvf_translations(mesh)
    blocks = inverse_bloch_transform(density_k, kpoints_frac, residues, weights)
    imaginary_residual = float(np.max(np.abs(blocks.imag))) if blocks.size else 0.0
    if imaginary_residual > 1.0e-7:
        raise NotImplementedError(
            "χ-CCM-B QVF density folding would discard a non-real density "
            f"residue ({imaginary_residual:.3e})"
        )
    return {
        residue: np.ascontiguousarray(block, dtype=float)
        for residue, block in zip(residues, blocks.real)
    }


def _aiccm_b_full_density_matrix_for_qvf(
    result,
    system: PeriodicSystem,
    mesh: tuple[int, int, int],
) -> np.ndarray:
    """Expand χ-CCM-B residue density blocks to a full BvK AO matrix."""

    def full_from_blocks(blocks_by_residue: dict[tuple[int, int, int], np.ndarray]):
        residues = _aiccm_b_qvf_translations(mesh)
        nbf = int(next(iter(blocks_by_residue.values())).shape[0])
        full = np.zeros((len(residues) * nbf, len(residues) * nbf), dtype=float)
        for origin_index, origin in enumerate(residues):
            row = slice(origin_index * nbf, (origin_index + 1) * nbf)
            for target_index, target in enumerate(residues):
                delta = tuple(
                    (int(target[axis]) - int(origin[axis])) % int(mesh[axis])
                    for axis in range(3)
                )
                col = slice(target_index * nbf, (target_index + 1) * nbf)
                full[row, col] = blocks_by_residue[delta]
        return full

    if hasattr(result, "density_alpha") and hasattr(result, "density_beta"):
        if _aiccm_b_density_alias_can_fold_directly(
            result.density_alpha,
            mesh,
        ):
            alpha = _aiccm_b_residue_density_blocks_for_qvf(
                result.density_alpha,
                mesh,
                label="periodic QVF alpha density",
            )
            beta = _aiccm_b_residue_density_blocks_for_qvf(
                result.density_beta,
                mesh,
                label="periodic QVF beta density",
            )
            return full_from_blocks(alpha) + full_from_blocks(beta)
        return full_from_blocks(
            _aiccm_b_residue_density_blocks_from_k_for_qvf(
                result,
                system,
                mesh,
            )
        )
    density_like = getattr(result, "density", None)
    if _aiccm_b_density_alias_can_fold_directly(density_like, mesh):
        density = _aiccm_b_residue_density_blocks_for_qvf(
            density_like,
            mesh,
            label="periodic QVF density",
        )
    else:
        density = _aiccm_b_residue_density_blocks_from_k_for_qvf(
            result,
            system,
            mesh,
        )
    return full_from_blocks(density)


def _aiccm_b_qvf_gamma_orbital_grids(
    result,
    super_basis: BasisSet,
    super_system: PeriodicSystem,
    lattice_bohr: np.ndarray,
    shape: tuple[int, int, int],
    mesh: tuple[int, int, int],
) -> list[dict[str, object]]:
    """Return Γ-character HOMO/LUMO grids in the χ-CCM-B BvK supercell."""
    mo_coeffs = getattr(result, "mo_coeffs", None)
    if not isinstance(mo_coeffs, (list, tuple)) or not mo_coeffs:
        return []
    if hasattr(result, "mo_coeffs_alpha") or hasattr(result, "mo_coeffs_beta"):
        return []

    kfrac = getattr(result, "kpoints_frac", None)
    if kfrac is None:
        return []
    kfrac_arr = np.asarray(kfrac, dtype=float).reshape(-1, 3)
    if kfrac_arr.shape[0] != len(mo_coeffs):
        raise ValueError(
            "aiccm2026dev-b QVF orbital grids require aligned kpoints_frac "
            f"({kfrac_arr.shape[0]}) and mo_coeffs ({len(mo_coeffs)})"
        )
    wrapped = _wrap_reciprocal_fractional(kfrac_arr)
    gamma_idx = int(np.argmin(np.linalg.norm(wrapped, axis=1)))
    if float(np.linalg.norm(wrapped[gamma_idx])) > 1.0e-12:
        raise ValueError(
            "aiccm2026dev-b QVF orbital grids require an explicit Gamma "
            "character in the finite mesh"
        )

    C_gamma = np.asarray(mo_coeffs[gamma_idx], dtype=np.complex128)
    if C_gamma.ndim != 2:
        raise ValueError(
            "aiccm2026dev-b QVF Gamma coefficient block must be rank 2; "
            f"got shape {C_gamma.shape!r}"
        )
    n_ao_cell, n_bands = C_gamma.shape
    n_cells = int(np.prod(np.asarray(mesh, dtype=int)))
    if int(super_basis.nbasis) != n_cells * n_ao_cell:
        raise ValueError(
            "aiccm2026dev-b QVF supercell basis size does not match "
            f"mesh*AO count: {int(super_basis.nbasis)} != {n_cells}*{n_ao_cell}"
        )

    diag = getattr(result, "aiccm2026dev_b", None)
    n_electrons = getattr(result, "effective_n_electrons", None)
    if n_electrons is None and diag is not None:
        n_electrons = getattr(diag, "effective_electron_count", None)
    try:
        n_occ = int(n_electrons) // 2
    except Exception:
        n_occ = 0
    indices: list[int] = []
    if 0 < n_occ <= n_bands:
        indices.append(n_occ - 1)
    if n_occ < n_bands:
        indices.append(n_occ)
    if not indices and n_bands:
        indices.append(0)

    energies = getattr(result, "mo_energies", None)
    if isinstance(energies, (list, tuple)) and len(energies) > gamma_idx:
        eps_gamma = np.asarray(energies[gamma_idx], dtype=float).reshape(-1)
    elif energies is not None:
        eps_gamma = np.asarray(energies, dtype=float).reshape(-1)
    else:
        eps_gamma = np.zeros(n_bands, dtype=float)

    translations = _aiccm_b_qvf_translations(mesh)
    norm = 1.0 / np.sqrt(float(n_cells))
    out: list[dict[str, object]] = []
    for band_index in indices:
        column = np.array(C_gamma[:, band_index], dtype=np.complex128, copy=True)
        if column.size:
            pivot = int(np.argmax(np.abs(column)))
            if abs(column[pivot]) > 0.0:
                column *= np.exp(-1j * float(np.angle(column[pivot])))
        coeff = np.zeros(int(super_basis.nbasis), dtype=np.complex128)
        for cell_index, _translation in enumerate(translations):
            start = cell_index * n_ao_cell
            stop = start + n_ao_cell
            coeff[start:stop] = norm * column
        psi, voxel_vectors = _evaluate_orbital_on_lattice_grid(
            coeff,
            super_basis,
            lattice_bohr,
            shape,
            system=super_system,
        )
        if band_index == n_occ - 1:
            role = "HOMO"
        elif band_index == n_occ:
            role = "LUMO"
        else:
            role = f"band {band_index}"
        out.append(
            {
                "label": f"chi-CCM Gamma {role}",
                "data": psi,
                "origin": np.zeros(3, dtype=float),
                "span": voxel_vectors,
                "band_index": int(band_index),
                "energy_eh": (
                    float(eps_gamma[band_index])
                    if band_index < eps_gamma.shape[0]
                    else 0.0
                ),
                "occupation": 2.0 if band_index < n_occ else 0.0,
                "spin": "both",
                "component": "real",
            }
        )
    return out


def _json_safe_qvf_value(value):
    """Convert small diagnostic payloads to JSON-safe Python values."""
    from dataclasses import asdict, is_dataclass

    if is_dataclass(value):
        return _json_safe_qvf_value(asdict(value))
    if isinstance(value, np.ndarray):
        return _json_safe_qvf_value(value.tolist())
    if isinstance(value, np.generic):
        return value.item()
    if isinstance(value, dict):
        return {
            str(key): _json_safe_qvf_value(item)
            for key, item in value.items()
        }
    if isinstance(value, (list, tuple)):
        return [_json_safe_qvf_value(item) for item in value]
    return value


def _real_gamma_qvf_vendor_sections(result) -> list[dict[str, object]]:
    """QVF vendor metadata for the real-Γ direct-route control (EXPERIMENTAL).

    Carries the :class:`~vibeqc.periodic_ccm_real_gamma.CCMRealGammaConvention`
    record verbatim -- the 2d convention fields (``exchange_q0`` +
    applicability, the D86 executed values, ``lattice_vector_convention``,
    the screened-exchange assembly triple for HSE-class runs).
    """
    convention = getattr(result, "real_gamma", None)
    if convention is None:
        return []
    payload = {
        "schema_version": 1,
        "method_selector": "real-gamma",
        "prose_name": "neutral fitted-torus real-Gamma control",
        # D89/D90 terminology, load-bearing: a representation control of
        # the neutral fitted-torus Hamiltonian; not the union-and-weight
        # Γ-CCM construction, and not construction evidence for it.
        "ccm_construction": "none (representation control)",
        "evaluation_representation": "real supercell-Gamma eigenproblem",
        "family": "neutral fitted-torus BvK control",
        "result_backend": getattr(result, "backend", None),
        "electronic_method": getattr(result, "functional", None),
        "experimental": True,
        "real_gamma_convention": _json_safe_qvf_value(convention),
    }
    return [
        {
            "id": "x_vibeqc_real_gamma_convention",
            "kind": "x_vibeqc.real_gamma_convention",
            "member": "convention",
            "label": "real-Gamma neutral fitted-torus convention",
            "payload": _json_safe_qvf_value(payload),
        }
    ]


def _aiccm_b_qvf_vendor_sections(result) -> list[dict[str, object]]:
    """Return first-party QVF vendor metadata sections for χ-CCM-B output."""
    diagnostics = getattr(result, "aiccm2026dev_b", None)
    convention = getattr(diagnostics, "finite_torus_convention", None)
    if convention is None:
        return []
    exact_exchange_assembly = getattr(
        diagnostics,
        "exact_exchange_assembly",
        None,
    )
    if exact_exchange_assembly is None:
        raise RuntimeError(
            "χ-CCM-B QVF convention metadata requires the versioned "
            "exact-exchange assembly"
        )
    payload = {
        "schema_version": 2,
        "method_selector": "aiccm2026dev-b",
        "prose_name": "chi-CCM",
        "ccm_approach": convention.ccm_approach,
        "ccm_construction": convention.ccm_construction,
        "evaluation_representation": convention.evaluation_representation,
        # Retained verbatim from vendor schema v1.  The exact machine identity
        # is the evaluation_representation field above.
        "representation": "finite-character Gamma-centred character mesh",
        "family": "variational finite-BvK-torus CCM",
        "result_backend": getattr(result, "backend", None),
        "backend": getattr(diagnostics, "backend", None),
        "electronic_method": getattr(diagnostics, "electronic_method", None),
        "mesh": _json_safe_qvf_value(getattr(diagnostics, "mesh", None)),
        "n_cyclic_cells": getattr(diagnostics, "n_cyclic_cells", None),
        "finite_torus_convention": _json_safe_qvf_value(convention),
        "exact_exchange_assembly": _json_safe_qvf_value(
            exact_exchange_assembly
        ),
    }
    return [
        {
            "id": "x_vibeqc_aiccm2026dev_b_convention",
            "kind": "x_vibeqc.aiccm2026dev_b_convention",
            "member": "convention",
            "label": "chi-CCM-B finite-torus convention",
            "payload": _json_safe_qvf_value(payload),
        }
    ]


def _gamma_qvf_wannier_center_sections(
    localization_result,
) -> list[dict[str, object]]:
    """Wannier-centre overlay sections from a Γ-point localization.

    Takes a :class:`~vibeqc.periodic_localise.PeriodicWannierResult`.

    **Uses ``centers``, not ``centroids``.** Both are on the result and the
    difference matters here: ``centroids`` is the true ``<i|r|i>`` and is,
    per its own docstring, "faithful only for orbitals that do not wrap the
    cell boundary" -- the same cell-face truncation that keeps localized
    coefficients out of periodic QVF entirely. ``centers`` is the
    Mulliken-charge-weighted centre, approximate but PBC-robust, so it stays
    meaningful for exactly the orbitals a reader most wants to locate: the
    ones straddling a face.

    Descriptors only. No coefficients are emitted, which is what makes this
    section safe for periodic output at all.
    """
    centers = np.asarray(getattr(localization_result, "centers"), dtype=float)
    spreads = np.asarray(getattr(localization_result, "spreads"), dtype=float)
    centers = centers.reshape((-1, 3))
    spreads = spreads.reshape((-1,))
    if centers.shape[0] != spreads.shape[0]:
        raise ValueError(
            "Wannier centre count does not match spread count"
        )

    method = str(getattr(localization_result, "method", "") or "localized")
    bohr2_to_ang2 = _BOHR_TO_ANGSTROM * _BOHR_TO_ANGSTROM
    entries: list[dict[str, object]] = [
        {
            "center": [float(v) * _BOHR_TO_ANGSTROM for v in center_bohr],
            "spread": float(spread_bohr2) * bohr2_to_ang2,
            "label": f{method} Wannier {index + 1}",
        }
        for index, (center_bohr, spread_bohr2) in enumerate(
            zip(centers, spreads)
        )
    ]
    if not entries:
        return []
    return [
        {
            "id": "x_ccm_wannier_centers",
            "kind": "x_ccm.wannier_centers",
            "member": "centers",
            "label": f"Γ-point Wannier centres ({method})",
            "payload": {"centers": _json_safe_qvf_value(entries)},
        }
    ]


def _aiccm_b_qvf_wannier_center_sections(
    localization_result,
) -> list[dict[str, object]]:
    """Return vibe-view Wannier-centre overlay sections for χ-CCM-B output."""

    entries: list[dict[str, object]] = []
    bohr2_to_ang2 = _BOHR_TO_ANGSTROM * _BOHR_TO_ANGSTROM

    def append_block(block, *, prefix: str = "") -> None:
        centers = np.asarray(getattr(block, "centers_bohr"), dtype=float)
        spreads = np.asarray(getattr(block, "spreads_bohr2"), dtype=float)
        centers = centers.reshape((-1, 3))
        spreads = spreads.reshape((-1,))
        if centers.shape[0] != spreads.shape[0]:
            raise ValueError(
                "χ-CCM-B Wannier centre count does not match spread count"
            )
        for index, (center_bohr, spread_bohr2) in enumerate(zip(centers, spreads)):
            label = (
                f"{prefix}Wannier {index + 1}"
                if prefix
                else f"Wannier {index + 1}"
            )
            entries.append(
                {
                    "center": [
                        float(value) * _BOHR_TO_ANGSTROM
                        for value in center_bohr
                    ],
                    "spread": float(spread_bohr2) * bohr2_to_ang2,
                    "label": f"χ-CCM-B {label}",
                }
            )

    if hasattr(localization_result, "alpha"):
        append_block(localization_result.alpha, prefix="alpha ")
        beta = getattr(localization_result, "beta", None)
        if beta is not None:
            append_block(beta, prefix="beta ")
    else:
        append_block(localization_result)

    if not entries:
        return []
    return [
        {
            "id": "x_ccm_wannier_centers",
            "kind": "x_ccm.wannier_centers",
            "member": "centers",
            "label": "χ-CCM-B Wannier centres",
            "payload": {"centers": _json_safe_qvf_value(entries)},
        }
    ]


def _qvf_extensions_with(
    extensions: dict[str, object] | None,
    namespace: str,
) -> dict[str, object]:
    """Return a QVF extension map containing ``namespace``."""
    merged = dict(extensions or {})
    merged.setdefault(namespace, {"version": "1.0", "critical": False})
    return merged


# ============================================================
# Main entry point
# ============================================================


def _has_valid_mo_coeffs(result) -> bool:
    """Check whether result.mo_coeffs is non-empty (array or list)."""
    mc = getattr(result, "mo_coeffs", None)
    if mc is None:
        return False
    if isinstance(mc, (list, tuple)):
        return len(mc) > 0 and hasattr(mc[0], "size") and mc[0].size > 0
    return hasattr(mc, "size") and mc.size > 0


def _has_valid_sidecar_mo_coeffs(result) -> bool:
    """Return whether a restricted or unrestricted result has MO data."""
    if _has_valid_mo_coeffs(result):
        return True

    def _nonempty(value) -> bool:
        if value is None:
            return False
        if isinstance(value, (list, tuple)):
            return bool(value) and hasattr(value[0], "size") and value[0].size > 0
        return hasattr(value, "size") and value.size > 0

    return _nonempty(getattr(result, "mo_coeffs_alpha", None)) and _nonempty(
        getattr(result, "mo_coeffs_beta", None)
    )


def _should_prepare_periodic_qvf_wavefunction(output_qvf: bool, result) -> bool:
    """Gate QVF wavefunction data for restricted and unrestricted results."""
    return bool(output_qvf) and _has_valid_sidecar_mo_coeffs(result)


def _mark_legacy_gamma_gdf_parity_hold(
    result,
    system: PeriodicSystem,
    plog: ProgressLogger,
) -> bool:
    """Tag dense-core Gamma GDF fallback results as held for PySCF parity."""
    if not _gamma_dense_core_gdf_parity_held(system, "rsgdf"):
        return False
    backend = str(getattr(result, "backend", "") or "")
    if "+PARITY_HELD" not in backend:
        result.backend = (
            f"{backend}+PARITY_HELD" if backend else "PARITY_HELD"
        )
    msg = (
        "run_periodic_job: Gamma-only GDF legacy fallback absolute-energy "
        "parity is HELD for dense-core ionic cells. The P01 MgO/STO-3G "
        "audit found matched Ewald nuclear terms but a large electronic "
        "GDF gauge offset versus PySCF; keep this row held until the "
        "electronic gauge is resolved."
    )
    warnings.warn(msg, RuntimeWarning, stacklevel=3)
    plog.info("  WARNING: " + msg)
    return True


def _gamma_proxy_for_multi_k(result) -> _GammaProxy:
    """Wrap a multi-k result to expose Γ-point (k=0) MOs for molden/etc."""
    mo_coeffs = result.mo_coeffs
    if isinstance(mo_coeffs, (list, tuple)):
        gamma_idx = _gamma_index_for_multi_k(result, len(mo_coeffs))
    else:
        gamma_idx = 0
    return _kpoint_proxy_for_multi_k(result, gamma_idx)


def _is_unrestricted_orbital_result(result) -> bool:
    """Whether a result carries split alpha/beta orbitals instead of MOs."""
    return getattr(result, "mo_coeffs", None) is None and (
        getattr(result, "mo_coeffs_alpha", None) is not None
    )


def _gamma_orbital_proxy(result):
    """Wrap the Γ block of any periodic result in single-k result shape.

    Restricted results delegate to :func:`_gamma_proxy_for_multi_k`.
    Unrestricted (UHF/UKS) periodic drivers store one ``(nbf, nmo)`` block
    per k-point under ``mo_coeffs_alpha`` / ``mo_coeffs_beta`` and carry no
    ``mo_coeffs`` at all, so they need their own slice: without it the
    Γ-only writers receive the whole per-k list and see a 3-D array.

    Both paths reach :func:`_gamma_index_for_multi_k`, which locates the
    real Γ entry from the result's k-point metadata and refuses a mesh that
    has none rather than exporting an arbitrary k-block as if it were Γ.
    """
    if not _is_unrestricted_orbital_result(result):
        return _gamma_proxy_for_multi_k(result)

    coeffs_alpha = result.mo_coeffs_alpha
    coeffs_beta = getattr(result, "mo_coeffs_beta", None)
    if coeffs_beta is None:
        raise ValueError(
            "unrestricted periodic Γ-only output requires both "
            "mo_coeffs_alpha and mo_coeffs_beta"
        )
    if isinstance(coeffs_alpha, (list, tuple)):
        if len(coeffs_beta) != len(coeffs_alpha):
            raise ValueError(
                "unrestricted periodic Γ-only output requires matching "
                f"alpha/beta k-point counts; got {len(coeffs_alpha)} alpha "
                f"and {len(coeffs_beta)} beta blocks"
            )
        gamma_idx = _gamma_index_for_multi_k(result, len(coeffs_alpha))
    else:
        gamma_idx = 0

    def _at_gamma(value):
        if isinstance(value, (list, tuple)):
            if gamma_idx >= len(value):
                raise ValueError(
                    "unrestricted periodic result has per-k metadata for "
                    f"fewer k-points than orbital blocks; requested index "
                    f"{gamma_idx}, got {len(value)} blocks"
                )
            return value[gamma_idx]
        return value

    def _optional_at_gamma(attr: str):
        value = getattr(result, attr, None)
        if value is None:
            return None
        if isinstance(value, (list, tuple)) and not value:
            return None
        return _at_gamma(value)

    return _UnrestrictedGammaProxy(
        mo_coeffs_alpha=_at_gamma(coeffs_alpha),
        mo_coeffs_beta=_at_gamma(coeffs_beta),
        mo_energies_alpha=_at_gamma(result.mo_energies_alpha),
        mo_energies_beta=_at_gamma(result.mo_energies_beta),
        occupations_alpha=_optional_at_gamma("occupations_alpha"),
        occupations_beta=_optional_at_gamma("occupations_beta"),
        density_alpha=getattr(result, "density_alpha", None),
        density_beta=getattr(result, "density_beta", None),
        overlap=_overlap_at_kpoint(result, gamma_idx),
    )


def _qvf_wavefunction_proxy_for_multi_k(result, system) -> tuple[object, list[float]]:
    """Return a result proxy and fractional k point for QVF wavefunction.gto.

    QVF can carry one selected complex Bloch wavefunction. Prefer Γ when the
    SCF k list contains it, preserving older archives; otherwise use the first
    k point and record its reciprocal fractional coordinate.
    """
    mo_coeffs = getattr(result, "mo_coeffs", None)
    if not isinstance(mo_coeffs, (list, tuple)):
        return result, [0.0, 0.0, 0.0]
    n_items = len(mo_coeffs)
    if n_items <= 0:
        raise ValueError("multi-k result has no k-point data")
    kpts = _result_kpoints_cart(result)
    if kpts is None:
        if n_items == 1:
            return _kpoint_proxy_for_multi_k(result, 0), [0.0, 0.0, 0.0]
        raise ValueError(
            "multi-k QVF wavefunction output requires k-point metadata"
        )
    if kpts.shape[0] != n_items:
        raise ValueError(
            "multi-k QVF wavefunction output requires aligned k-point metadata; "
            f"got {n_items} data blocks and {kpts.shape[0]} k-points"
        )
    norms = np.linalg.norm(kpts, axis=1)
    gamma_idx = int(np.argmin(norms))
    idx = gamma_idx if float(norms[gamma_idx]) <= 1.0e-10 else 0
    frac = _wrap_reciprocal_fractional(
        _fractional_kpoints_for_output(system, kpts)
    )
    return _kpoint_proxy_for_multi_k(result, idx), [float(x) for x in frac[idx]]


def _overlap_at_kpoint(result, idx: int):
    """Return the single-k overlap block a Gamma-only writer can use as S.

    Periodic drivers carry ``overlap`` as one Hermitian block per k-point.
    The Molden writer needs S(k=0) as a metric when it re-expresses a
    degenerate block on a real basis; handing it the whole per-k list would
    silently drop it back to the identity metric.
    """
    overlap = getattr(result, "overlap", None)
    if isinstance(overlap, (list, tuple)):
        if idx >= len(overlap):
            return None
        return overlap[idx]
    return overlap


def _kpoint_proxy_for_multi_k(result, idx: int) -> _GammaProxy:
    """Wrap one k-point block of a multi-k result in single-k result shape."""
    mo_coeffs = result.mo_coeffs
    mo_energies = result.mo_energies
    occupations = getattr(result, "occupations", None)
    if isinstance(occupations, (list, tuple)):
        if len(occupations) == 0:
            occupations = None
        elif idx < len(occupations):
            occupations = occupations[idx]
        else:
            raise ValueError(
                "multi-k result has occupation metadata for fewer k-points "
                f"than orbital blocks; requested index {idx}, got "
                f"{len(occupations)} occupation blocks"
            )
    density = result.density
    return _GammaProxy(
        mo_coeffs=mo_coeffs[idx]
        if isinstance(mo_coeffs, (list, tuple))
        else mo_coeffs,
        mo_energies=mo_energies[idx]
        if isinstance(mo_energies, (list, tuple))
        else mo_energies,
        occupations=occupations,
        density=density[idx]
        if isinstance(density, (list, tuple))
        else density,
        overlap=_overlap_at_kpoint(result, idx),
    )


class _GammaProxy:
    """Duck-typed result wrapping Γ-point (k=0) of a multi-k result."""

    __slots__ = ("mo_coeffs", "mo_energies", "occupations", "density", "overlap")

    def __init__(self, *, mo_coeffs, mo_energies, occupations, density, overlap):
        self.mo_coeffs = mo_coeffs
        self.mo_energies = mo_energies
        self.occupations = occupations
        self.density = density
        self.overlap = overlap


class _UnrestrictedGammaProxy:
    """Duck-typed UHF/UKS result wrapping the Γ block of a periodic result.

    Mirrors :class:`_GammaProxy` for the split-spin attribute names the
    Molden writer dispatches on. The spin densities are passed through
    unsliced: they are real-space ``LatticeMatrixSet`` objects that already
    span the full k-mesh, not per-k lists.
    """

    __slots__ = (
        "mo_coeffs_alpha",
        "mo_coeffs_beta",
        "mo_energies_alpha",
        "mo_energies_beta",
        "occupations_alpha",
        "occupations_beta",
        "density_alpha",
        "density_beta",
        "overlap",
    )

    def __init__(
        self,
        *,
        mo_coeffs_alpha,
        mo_coeffs_beta,
        mo_energies_alpha,
        mo_energies_beta,
        occupations_alpha,
        occupations_beta,
        density_alpha,
        density_beta,
        overlap,
    ):
        self.mo_coeffs_alpha = mo_coeffs_alpha
        self.mo_coeffs_beta = mo_coeffs_beta
        self.mo_energies_alpha = mo_energies_alpha
        self.mo_energies_beta = mo_energies_beta
        self.occupations_alpha = occupations_alpha
        self.occupations_beta = occupations_beta
        self.density_alpha = density_alpha
        self.density_beta = density_beta
        self.overlap = overlap


class _GapwMultiKRunnerProxy:
    """Duck-typed proxy wrapping a ``GpwMultiKScfResult`` for the runner.

    ``GpwMultiKScfResult`` stores per-k data under ``mo_coeffs_k`` /
    ``mo_energies_k`` / ``occupations_k``, but the runner's output code
    (molden, MO summary, density) expects ``mo_coeffs`` / ``mo_energies`` /
    ``occupations`` as per-k list-shaped attributes. This proxy delegates
    those names to the ``*_k`` equivalents and passes all other attribute
    access through to the original result.
    """

    __slots__ = ("_inner",)

    def __init__(self, inner):
        self._inner = inner

    @property
    def mo_coeffs(self):
        return list(self._inner.mo_coeffs_k)

    @property
    def mo_energies(self):
        return list(self._inner.mo_energies_k)

    @property
    def occupations(self):
        return list(self._inner.occupations_k)

    @property
    def e_electronic(self) -> float:
        bd = getattr(self._inner, "breakdown", None)
        if bd is not None:
            return float(
                getattr(bd, "e_kinetic", 0.0)
                + getattr(bd, "e_nuclear_attraction", 0.0)
                + getattr(bd, "e_hartree", 0.0)
                + getattr(bd, "e_xc", 0.0)
                + getattr(bd, "e_hf_exchange", 0.0)
            )
        return 0.0

    @property
    def e_nuclear(self) -> float:
        bd = getattr(self._inner, "breakdown", None)
        if bd is not None:
            return float(getattr(bd, "e_nuclear_repulsion", 0.0))
        return 0.0

    @property
    def e_xc(self) -> float:
        bd = getattr(self._inner, "breakdown", None)
        if bd is not None:
            return float(getattr(bd, "e_xc", 0.0))
        return 0.0

    def __getattr__(self, name):
        # Delegate all other attribute lookups to the inner result.
        # Properties (mo_coeffs etc.) are handled by the class descriptors
        # and never reach __getattr__.
        return getattr(self._inner, name)


class _SemiempiricalKPointRunnerProxy:
    """Expose native k-point semiempirical results to runner summaries."""

    __slots__ = ("_inner", "_kmesh")

    def __init__(self, inner, kmesh):
        self._inner = inner
        self._kmesh = kmesh

    @property
    def mo_energies(self):
        return list(getattr(self._inner, "eps_per_k"))

    @property
    def occupations(self):
        return list(getattr(self._inner, "occupations_per_k"))

    @property
    def kpoints(self):
        return list(getattr(self._kmesh, "kpoints"))

    @property
    def converged(self):
        return bool(getattr(self._inner, "converged", True))

    @property
    def iterations(self):
        return getattr(self._inner, "n_iter", None)

    def __getattr__(self, name):
        return getattr(self._inner, name)


def _plan_periodic_semiempirical_method(
    method: str,
    system: PeriodicSystem,
    *,
    kpoints: object | None,
):
    """Return a validated semiempirical route plan, or ``None``."""
    from vibeqc.semiempirical.routes import (
        BOUNDARY_PERIODIC_GAMMA,
        BOUNDARY_PERIODIC_K,
        is_semiempirical_method,
        plan_periodic_semiempirical_route,
    )

    if not is_semiempirical_method(method):
        return None
    boundary = (
        BOUNDARY_PERIODIC_K
        if kpoints is not None
        else BOUNDARY_PERIODIC_GAMMA
    )
    return plan_periodic_semiempirical_route(
        method,
        system,
        boundary=boundary,
    )


def _run_periodic_semiempirical_engine(
    system: PeriodicSystem,
    route_plan,
    *,
    max_iter: int,
    conv_tol: float,
    kpoints: object | None = None,
    smearing_temperature_hartree: float | None = None,
):
    """Run one basis-free periodic semiempirical backend.

    ``smearing_temperature_hartree`` is the resolved electronic temperature
    (k_B T in Hartree). For GFN2-xTB, ``None`` leaves the native default-on
    frontier smearing in place; a numeric value (including 0.0 for exact
    zero-temperature Aufbau) is forwarded explicitly. Other backends treat
    ``None`` as 0.0.
    """
    method_key = route_plan.method_key
    resolved_smearing = float(
        0.0 if smearing_temperature_hartree is None else smearing_temperature_hartree
    )
    if method_key in ("dftb0", "scc_dftb"):
        from vibeqc._vibeqc_core import semiempirical as _se

        open_shell = route_plan.spin == "unrestricted"
        if route_plan.boundary == "periodic_k":
            if open_shell:
                raise NotImplementedError(
                    "full k-point periodic DFTB routes are closed-shell only; "
                    "unrestricted k-point DFTB/SCC-DFTB is not implemented."
                )
            from vibeqc.semiempirical.periodic import _as_bloch_kmesh

            kmesh = _as_bloch_kmesh(system, kpoints)

        from vibeqc.semiempirical.parameters import default_parameters

        params = default_parameters()
        # Loudly flag placeholder repulsive pairs before any number is
        # produced (issue #306): fixed-geometry differences stay valid,
        # absolute energies/EOS fits for such systems are not chemistry.
        from vibeqc.semiempirical.dftb0 import warn_placeholder_repulsives

        warn_placeholder_repulsives(
            params,
            [atom.Z for atom in system.unit_cell],
            route=f"periodic {method_key}",
        )
        if route_plan.boundary == "periodic_k":
            occupation_options = _se.KPointOccupationOptions()
            occupation_options.smearing_temperature = resolved_smearing
            if method_key == "dftb0":
                result = _se.run_dftb0_kpoints(
                    system,
                    params,
                    kmesh,
                    15.0,
                    occupation_options,
                )
            else:
                opts = _se.SCCOptions()
                opts.max_iter = int(max_iter)
                opts.conv_tol_charge = float(conv_tol)
                opts.use_diis = True
                result = _se.run_scc_dftb_kpoints(
                    system,
                    params,
                    kmesh,
                    opts,
                    15.0,
                    occupation_options,
                    # Issue #342: receive the record even when unconverged.
                    # The established output-stage guard downstream writes
                    # the fatal diagnostic into the .out, marks the .system
                    # manifest crashed, and then raises; an engine-level
                    # raise here would lose that evidence (the d70f4335b
                    # lesson).
                    allow_unconverged=True,
                )
            return _SemiempiricalKPointRunnerProxy(result, kmesh)

        if method_key == "dftb0":
            if open_shell:
                return _se.run_udftb0_gamma(system, params)
            return _se.run_dftb0_gamma(system, params)

        opts = _se.PeriodicSCCOptions()
        opts.max_iter = int(max_iter)
        opts.conv_tol_charge = float(conv_tol)
        opts.use_diis = True
        if open_shell:
            return _se.run_uscc_dftb_gamma(system, params, opts)
        return _se.run_scc_dftb_gamma(system, params, opts)

    if method_key == "gfn2_xtb":
        from vibeqc._vibeqc_core.semiempirical.xtb import (
            XTBSccOptions,
            run_gfn2_xtb_gamma,
        )
        from vibeqc.semiempirical.methods.gfn2_params import load_gfn2_params

        opts = XTBSccOptions()
        opts.max_iter = int(max_iter)
        opts.conv_tol_charge = float(conv_tol)
        if smearing_temperature_hartree is not None:
            # Assignment marks the temperature explicit: 0.0 keeps exact
            # zero-temperature Aufbau. None leaves the native default-on
            # frontier smearing (0.001 Ha) in place.
            opts.electronic_temperature = float(smearing_temperature_hartree)
        return run_gfn2_xtb_gamma(system, load_gfn2_params(), opts)

    if method_key == "pm6":
        from vibeqc.semiempirical.methods.periodic_pm6 import run_pm6_gamma

        return run_pm6_gamma(
            system,
            max_iter=int(max_iter),
            conv_tol=float(conv_tol),
        )

    if method_key in ("om1", "om2", "om3"):
        from vibeqc.semiempirical.methods.periodic_omx import run_omx_gamma

        return run_omx_gamma(
            system,
            variant=method_key,
            max_iter=int(max_iter),
            conv_tol=float(conv_tol),
        )

    raise ValueError(f"unknown periodic semiempirical method {method_key!r}")


def _periodic_semiempirical_attr(result: object, *names: str) -> object | None:
    for name in names:
        value = getattr(result, name, None)
        if value is not None:
            return value
    return None


def _run_periodic_semiempirical_job(
    system: PeriodicSystem,
    *,
    route_plan,
    output: Union[str, os.PathLike],
    dry_run: bool,
    record_hostname: bool,
    citations: bool,
    write_xyz_file: bool,
    write_poscar_file: bool,
    write_xsf_structure_file: bool,
    write_cif_file: bool,
    write_molden_file: bool,
    write_density: bool,
    write_population_file: bool,
    output_qvf: bool,
    jk_method: Union[str, "PeriodicJKMethod"],
    aux_basis: Optional[str],
    smearing: Optional[SmearingOptions],
    smearing_temperature: Union[float, str, None],
    smearing_unit: str,
    smearing_method: str,
    smearing_metallic: Optional[bool],
    smearing_band_gap_hartree: Optional[float],
    dispersion: Optional[Union[str, bool, "D3BJParams"]],
    optimize: bool,
    optimize_max_iter: int,
    optimize_conv_tol_grad: float,
    optimize_cell_requested: bool,
    hessian: bool,
    tddft: bool,
    coop_cohp: bool,
    band_structure: object | None,
    qvf_wannier_centers: bool,
    kpoints: object | None,
    checkpoint_qvf: Union[str, os.PathLike, None],
    dft_plus_u: object | None,
    atomic_spins: object | None,
    spinlock: str | None,
    read_from: object | None,
    restart_from: Union[str, os.PathLike, None],
    functional: str | None,
    max_iter: int,
    conv_tol_energy: float,
):
    """Public ``run_periodic_job`` branch for basis-free SE methods."""
    from vibeqc.semiempirical.routes import BOUNDARY_PERIODIC_K

    method_key = route_plan.method_key
    full_k_route = route_plan.boundary == BOUNDARY_PERIODIC_K
    full_k_optimizer = full_k_route and method_key in ("dftb0", "scc_dftb")
    gfn2_gamma_route = method_key == "gfn2_xtb" and not full_k_route
    if functional is not None:
        raise ValueError(
            f"run_periodic_job: functional={functional!r} is not used with "
            f"basis-free semiempirical method={method_key!r}."
        )
    unsupported: list[str] = []
    if optimize and not full_k_optimizer:
        unsupported.append("optimize")
    if optimize_cell_requested and not full_k_optimizer:
        unsupported.append("optimize_cell")
    if hessian:
        unsupported.append("hessian")
    if tddft:
        unsupported.append("tddft")
    if coop_cohp:
        unsupported.append("coop_cohp")
    if band_structure is not None:
        unsupported.append("band_structure")
    if qvf_wannier_centers:
        unsupported.append("qvf_wannier_centers")
    if write_density:
        unsupported.append("write_density")
    jk_label = (
        jk_method.value if isinstance(jk_method, PeriodicJKMethod) else str(jk_method)
    ).strip().lower()
    if jk_label not in ("auto", ""):
        unsupported.append("jk_method")
    if aux_basis is not None:
        unsupported.append("aux_basis")
    smearing_requested = (
        smearing is not None
        or smearing_temperature is not _SMEARING_UNSET
        or smearing_metallic is not None
        or smearing_band_gap_hartree is not None
    )
    if smearing_requested and not full_k_route and not gfn2_gamma_route:
        unsupported.append("smearing")
    if full_k_route or gfn2_gamma_route:
        if smearing is not None:
            if not isinstance(smearing, SmearingOptions):
                raise TypeError(
                    "run_periodic_job: smearing must be a SmearingOptions "
                    "instance."
                )
            if smearing.flavor not in ("fermi-dirac", "mermin"):
                raise NotImplementedError(
                    "periodic semiempirical routes support only Fermi-Dirac / "
                    "Mermin smearing."
                )
        if smearing_metallic is not None or smearing_band_gap_hartree is not None:
            unsupported.append("smearing_auto")
    if dispersion not in (None, False):
        unsupported.append("dispersion")
    if (not full_k_route) and kpoints is not None:
        unsupported.append("kpoints")
    if checkpoint_qvf is not None:
        unsupported.append("checkpoint_qvf")
    if dft_plus_u:
        unsupported.append("dft_plus_u")
    if atomic_spins is not None:
        unsupported.append("atomic_spins")
    if spinlock is not None:
        unsupported.append("spinlock")
    if read_from is not None:
        unsupported.append("read_from")
    if restart_from is not None:
        unsupported.append("restart_from")
    if unsupported:
        opts = ", ".join(sorted(unsupported))
        route_desc = (
            "full k-point single-point routes"
            if full_k_route
            else "Gamma-point single-point routes"
        )
        raise NotImplementedError(
            "run_periodic_job: basis-free periodic semiempirical methods are "
            f"{route_desc}; unsupported option(s): {opts}."
        )

    if full_k_route and kpoints is None:
        raise ValueError(
            "full k-point periodic semiempirical routes require kpoints=."
        )
    if full_k_route:
        from vibeqc.semiempirical.periodic import _as_bloch_kmesh

        _as_bloch_kmesh(system, kpoints)
    if optimize_cell_requested and not optimize:
        raise ValueError(
            "run_periodic_job: optimize_cell=True requires optimize=True for "
            "basis-free periodic semiempirical routes."
        )

    smearing_temperature_hartree = 0.0
    smearing_source = "explicit"
    smearing_reason = ""
    if full_k_route:
        if smearing is not None:
            smearing_temperature_hartree = (
                float(smearing.temperature) if bool(smearing.enabled) else 0.0
            )
            smearing_source = smearing.source
            smearing_reason = smearing.reason
        else:
            smearing_resolution = resolve_smearing_temperature(
                0.0
                if smearing_temperature is _SMEARING_UNSET
                else smearing_temperature,
                unit=smearing_unit,
                method=smearing_method,
                metallic=False,
                band_gap_hartree=None,
                n_electrons=system.n_electrons(),
            )
            if smearing_resolution.method not in ("fermi-dirac", "mermin"):
                raise NotImplementedError(
                    "full k-point semiempirical DFTB routes currently support "
                    "only Fermi-Dirac / Mermin smearing."
                )
            smearing_temperature_hartree = float(smearing_resolution.temperature)
            smearing_source = smearing_resolution.source
            smearing_reason = smearing_resolution.reason
    elif gfn2_gamma_route:
        # Periodic GFN2-xTB smears by default. An explicit numeric 0.0 (or
        # None / "off") restores exact zero-temperature Aufbau; leaving the
        # argument unset (or "auto") selects the default width.
        if smearing is not None:
            smearing_temperature_hartree = (
                float(smearing.temperature) if bool(smearing.enabled) else 0.0
            )
            smearing_source = smearing.source
            smearing_reason = smearing.reason
        elif smearing_temperature is _SMEARING_UNSET or (
            isinstance(smearing_temperature, str)
            and str(smearing_temperature).strip().lower().replace("_", "-")
            == "auto"
        ):
            smearing_temperature_hartree = _GFN2_PERIODIC_DEFAULT_SMEARING_HA
            smearing_source = "auto"
            smearing_reason = (
                "periodic GFN2-xTB default frontier smearing; pass "
                "smearing_temperature=0 for exact zero-temperature Aufbau"
            )
        else:
            smearing_resolution = resolve_smearing_temperature(
                smearing_temperature,
                unit=smearing_unit,
                method=smearing_method,
                metallic=False,
                band_gap_hartree=None,
                n_electrons=system.n_electrons(),
            )
            if smearing_resolution.method not in ("fermi-dirac", "mermin"):
                raise NotImplementedError(
                    "periodic GFN2-xTB supports only Fermi-Dirac / Mermin "
                    "smearing."
                )
            smearing_temperature_hartree = float(smearing_resolution.temperature)
            smearing_source = smearing_resolution.source
            smearing_reason = smearing_resolution.reason
    if optimize and smearing_temperature_hartree > 0.0:
        raise NotImplementedError(
            "run_periodic_job: full k-point periodic DFTB optimization is "
            "zero-temperature only; finite-temperature free-energy derivative "
            "semantics are still gated."
        )
    if optimize:
        if int(optimize_max_iter) < 0:
            raise ValueError("run_periodic_job: optimize_max_iter must be non-negative.")
        if (
            not np.isfinite(float(optimize_conv_tol_grad))
            or float(optimize_conv_tol_grad) <= 0.0
        ):
            raise ValueError(
                "run_periodic_job: optimize_conv_tol_grad must be finite and positive."
            )

    output_stem = Path(os.fspath(output))
    output_stem.parent.mkdir(parents=True, exist_ok=True)
    out_path = stem_sibling(output_stem, ".out")
    basis_label = "<basis-free>"
    plan = OutputPlan.from_run_job_kwargs(
        output=output_stem,
        method=method_key,
        basis=basis_label,
        functional=None,
        write_molden_file=False,
        write_xyz=write_xyz_file,
        write_poscar=write_poscar_file,
        write_xsf_structure=write_xsf_structure_file,
        write_cif=write_cif_file,
        write_density_xsf=False,
        write_population=False,
        citations=citations,
        crash_dump=False,
        output_qvf=output_qvf,
        job_kind="periodic_scf",
    )
    if dry_run or is_dry_run_requested():
        dry_run_manifest(plan, record_hostname=record_hostname)
        return None

    t_start = time.perf_counter()
    _output_writer = OutputWriter(plan, record_hostname=record_hostname)
    _PERIODIC_OUTPUT_WRITER.set(_output_writer)
    # Re-register progress handler with THIS writer's manifest.
    install_progress_handler(
        lambda fields: _output_writer.update_progress(**fields)
    )
    try:
        result = _run_periodic_semiempirical_engine(
            system,
            route_plan,
            max_iter=max_iter,
            conv_tol=conv_tol_energy,
            kpoints=kpoints,
            smearing_temperature_hartree=smearing_temperature_hartree,
        )
        parameter_identity = getattr(result, "parameter_identity", None)
        parameter_sha256 = getattr(result, "parameter_sha256", None)
        if (parameter_identity is None) != (parameter_sha256 is None):
            raise RuntimeError(
                "periodic semiempirical result has incomplete parameter "
                "identity provenance"
            )
        if parameter_identity is not None:
            parameter_identity = str(parameter_identity)
            parameter_sha256 = str(parameter_sha256)
            if (
                not parameter_identity
                or len(parameter_sha256) != 64
                or any(
                    character not in "0123456789abcdef"
                    for character in parameter_sha256
                )
            ):
                raise RuntimeError(
                    "periodic semiempirical result has invalid parameter "
                    "identity provenance"
                )
            _output_writer.update_run_fields(
                {
                    "parameter_identity": parameter_identity,
                    "parameter_sha256": parameter_sha256,
                }
            )
        if full_k_route:
            # Issue #426: the measured band edges + gaps are result-struct
            # fields on every k-route record (convention documented on the
            # native KPointBandEdges); carry them into the .system [run]
            # section through the existing extension point, mirroring the
            # IID 344 backend/parity_held surfacing. NaN is not a valid
            # manifest float -- a non-finite value means the edge does not
            # exist in the model space (or an unconverged diagnostics
            # record measured nothing) and is recorded as the explicit
            # string "not-measured", never silently dropped.
            _gap_fields = {}
            for _gap_name in (
                "indirect_gap",
                "direct_gap",
                "valence_band_max",
                "conduction_band_min",
                "gap_above_fermi_manifold",
            ):
                _gap_value = getattr(result, _gap_name, None)
                if _gap_value is None:
                    continue
                _gap_value = float(_gap_value)
                _gap_fields[_gap_name] = (
                    _gap_value if math.isfinite(_gap_value) else "not-measured"
                )
            # The structural gapless flag travels with the numbers it
            # qualifies: a screen must be able to read "metallic" without
            # inferring it from the sign of a float (sec8-r4 F1).
            _is_metallic = getattr(result, "is_metallic", None)
            if _is_metallic is not None:
                _gap_fields["is_metallic"] = bool(_is_metallic)
            if _gap_fields:
                _output_writer.update_run_fields(_gap_fields)
        t_total = time.perf_counter() - t_start

        cite_block_text = ""
        bibtex_content = ""
        cite_manifest_rows: list[dict[str, Any]] = []
        if citations:
            try:
                citation_route_plan = replace(
                    route_plan,
                    electronic_temperature=smearing_temperature_hartree,
                )
                refs = load_default_database().assemble(
                    basis="",
                    functional=None,
                    **citation_route_plan.citation_assemble_kwargs,
                )
                cite_manifest_rows = citation_manifest_rows(refs)
                bibtex_content = format_bibtex(refs)
                _output_writer.dispatch_role(
                    "citations",
                    citations=refs,
                    raise_on_error=True,
                )
                cite_block_text = format_references_block(refs)
            except Exception as exc:
                warn(
                    f"citation emission failed: {type(exc).__name__}: {exc}",
                )

        nonconverged_error: str | None = None
        with OutputChannel.to_file(out_path):
            write(banner() + "\n\n")
            write(
                f"  Job: PERIODIC {method_key.upper()}  "
                "basis=<basis-free>\n"
            )
            write(
                "  Route: "
                + (
                    "full k-point periodic semiempirical"
                    if full_k_route
                    else "Gamma-point periodic semiempirical"
                )
                + "\n\n"
            )
            write(_system_summary(system))
            write(section_header("Semiempirical options", width=56))
            write(f"    method              = {method_key}\n")
            if full_k_route:
                write(f"    boundary            = {route_plan.boundary}\n")
                n_kpoints = _periodic_semiempirical_attr(result, "n_kpoints")
                if n_kpoints is not None:
                    write(f"    kpoints             = {int(n_kpoints)}\n")
                    write_kmesh_convention()
                write("    kpoint_occupations  = fermi-dirac\n")
                if smearing_temperature_hartree > 0.0 or smearing_source != "explicit":
                    write(f"    smearing_source     = {smearing_source}\n")
                    if smearing_reason:
                        write(f"    smearing_reason     = {smearing_reason}\n")
                write(
                    "    smearing_temperature = "
                    f"{smearing_temperature_hartree}\n"
                )
                if smearing_temperature_hartree > 0.0:
                    write(
                        "    smearing_temperature_K = "
                        f"{hartree_to_kelvin_temperature(smearing_temperature_hartree)}\n"
                    )
            elif gfn2_gamma_route and smearing_temperature_hartree > 0.0:
                write("    occupations         = fermi-dirac\n")
                write(f"    smearing_source     = {smearing_source}\n")
                if smearing_reason:
                    write(f"    smearing_reason     = {smearing_reason}\n")
                write(
                    "    smearing_temperature = "
                    f"{smearing_temperature_hartree}\n"
                )
                write(
                    "    smearing_temperature_K = "
                    f"{hartree_to_kelvin_temperature(smearing_temperature_hartree)}\n"
                )
            write(f"    max_iter            = {int(max_iter)}\n")
            write(f"    conv_tol            = {float(conv_tol_energy)}\n\n")
            write(
                "  Basis-free route: Molden orbitals and population "
                "analysis are inapplicable (no Gaussian AO wavefunction).\n"
            )
            skipped = []
            if skipped:
                write(
                    "  Basis-free route: skipped unavailable artefacts "
                    f"({', '.join(skipped)}).\n\n"
                )
            else:
                write("\n")

            write(section_header("Results", width=56))
            energy = float(getattr(result, "energy"))
            write(
                "    total energy        = "
                f"{render_energy_labeled(energy, width=0, precision=10, sign=True)}\n"
            )
            result_smearing_t = float(
                getattr(result, "smearing_temperature", 0.0) or 0.0
            )
            if full_k_route or result_smearing_t > 0.0:
                free_energy = float(getattr(result, "free_energy", energy))
                if free_energy != energy:
                    write(
                        "    free energy         = "
                        f"{render_energy_labeled(free_energy, width=0, precision=10, sign=True)}\n"
                    )
            converged = _periodic_semiempirical_attr(result, "converged")
            if converged is not None:
                write(f"    converged           = {bool(converged)}\n")
            n_iter = _periodic_semiempirical_attr(result, "n_iter", "iterations")
            if n_iter is not None:
                write(f"    iterations          = {int(n_iter)}\n")
            if converged is not None and not bool(converged):
                n_iter_text = (
                    f"{int(n_iter)} iterations"
                    if n_iter is not None
                    else "the allowed iterations"
                )
                nonconverged_error = (
                    f"{method_key.upper()} periodic semiempirical SCF did not "
                    f"converge after {n_iter_text}; refusing to mark the "
                    "calculation complete."
                )
            n_basis = _periodic_semiempirical_attr(result, "n_basis")
            if n_basis is not None:
                write(f"    semiempirical basis = {int(n_basis)} functions\n")
            if parameter_identity is not None:
                write(f"    parameter identity  = {parameter_identity}\n")
                write(f"    parameter sha256    = {parameter_sha256}\n")
            write("\n")
            if full_k_route:
                write(_smearing_summary(result))
                write(_band_summary(result))
                write(_mo_summary(result))
            elif gfn2_gamma_route:
                write(_smearing_summary(result))
            if nonconverged_error is not None:
                write(f"\n  FATAL: {nonconverged_error}\n")
                flush()

            if citations and cite_block_text:
                write_references_block(block=cite_block_text)
                flush()

        _output_writer.record(out_path, wall_time_s=t_total)
        if nonconverged_error is not None:
            raise RuntimeError(nonconverged_error)

        if write_xyz_file:
            _output_writer.dispatch_role(
                "geometry",
                only_format="extended-xyz",
                system=system,
                energy_ha=float(getattr(result, "energy")),
                comment=f"vibe-qc periodic {method_key}",
                raise_on_error=True,
            )
        if write_poscar_file:
            _output_writer.dispatch_role(
                "geometry",
                only_format="poscar",
                system=system,
                comment=f"vibe-qc periodic {method_key}",
                raise_on_error=True,
            )
        if write_xsf_structure_file:
            _output_writer.dispatch_role(
                "geometry",
                only_format="xsf",
                system=system,
                raise_on_error=True,
            )
        if write_cif_file:
            _output_writer.dispatch_role(
                "geometry",
                only_format="cif",
                system=system,
                comment=f"vibe-qc periodic {method_key}",
                raise_on_error=True,
            )

        if optimize:
            from vibeqc.semiempirical.periodic import (
                evaluate_periodic_energy_gradient,
                evaluate_periodic_kpoint_energy_gradient_stress,
                optimize_cell,
                optimize_periodic_positions,
            )
            from vibeqc.semiempirical.routes import (
                plan_periodic_semiempirical_route,
            )

            opt_properties = (
                ("energy", "gradient", "stress")
                if optimize_cell_requested
                else ("energy", "gradient")
            )
            opt_route_plan = plan_periodic_semiempirical_route(
                method_key,
                system,
                boundary=BOUNDARY_PERIODIC_K,
                properties=opt_properties,
            )

            def opt_energy(candidate):
                candidate_result = _run_periodic_semiempirical_engine(
                    candidate,
                    route_plan,
                    max_iter=max_iter,
                    conv_tol=conv_tol_energy,
                    kpoints=kpoints,
                    smearing_temperature_hartree=0.0,
                )
                if not bool(getattr(candidate_result, "converged", True)):
                    n_iter = _periodic_semiempirical_attr(
                        candidate_result, "n_iter", "iterations"
                    )
                    suffix = (
                        f" after {int(n_iter)} iterations"
                        if n_iter is not None
                        else ""
                    )
                    raise RuntimeError(
                        f"{method_key.upper()} full-k periodic optimization "
                        f"SCF did not converge{suffix}."
                    )
                return float(getattr(candidate_result, "energy"))

            def opt_derivatives(candidate):
                return evaluate_periodic_energy_gradient(
                    method_key,
                    candidate,
                    kpoints=kpoints,
                    cutoff_bohr=15.0,
                    max_iter=max_iter,
                    conv_tol_charge=conv_tol_energy,
                    _return_result=True,
                )

            def opt_cell_derivatives(candidate):
                return evaluate_periodic_kpoint_energy_gradient_stress(
                    method_key,
                    candidate,
                    kpoints=kpoints,
                    cutoff_bohr=15.0,
                    max_iter=max_iter,
                    conv_tol_charge=conv_tol_energy,
                )

            with OutputChannel.to_file(out_path, mode="a"):
                gradient_policy = active_policy().with_spec("gradient", width=0)
                gradient_tolerance = gradient_policy.render(
                    Quantity(float(optimize_conv_tol_grad), "gradient")
                )
                gradient_unit = gradient_policy.unit_of("gradient")
                write(section_header("Geometry optimization", width=56))
                write("    optimizer           = ASE BFGSLineSearch\n")
                write(
                    "    cell                = "
                    f"{'variable' if optimize_cell_requested else 'fixed'}\n"
                )
                write(f"    max_iter            = {int(optimize_max_iter)}\n")
                write(
                    "    gradient_tolerance  = "
                    f"{gradient_tolerance} {gradient_unit}\n\n"
                )
                flush()

            if optimize_cell_requested:
                opt_result = optimize_cell(
                    system,
                    opt_energy,
                    derivatives_fn=opt_cell_derivatives,
                    gradient_tolerance_ha_bohr=float(optimize_conv_tol_grad),
                    max_steps=int(optimize_max_iter),
                    return_result=True,
                    route_plan=opt_route_plan,
                )
            else:
                opt_result = optimize_periodic_positions(
                    system,
                    opt_energy,
                    derivatives_fn=opt_derivatives,
                    gradient_tolerance_ha_bohr=float(optimize_conv_tol_grad),
                    max_steps=int(optimize_max_iter),
                    route_plan=opt_route_plan,
                )
            if parameter_identity is not None and (
                opt_result.parameter_identity != parameter_identity
                or opt_result.parameter_sha256 != parameter_sha256
            ):
                raise RuntimeError(
                    "periodic semiempirical optimization used a different "
                    "immutable parameter snapshot than its initial energy"
                )
            with OutputChannel.to_file(out_path, mode="a"):
                write("\n" + _optimized_geometry_summary(opt_result))
                write(_system_summary(opt_result.system))
                if not opt_result.converged:
                    write(
                        "\n  FATAL: full-k periodic semiempirical geometry "
                        "optimization did not converge; refusing to mark the "
                        "calculation complete.\n"
                    )
                flush()
            _output_writer.record(out_path)
            if not opt_result.converged:
                raise RuntimeError(
                    "full-k periodic semiempirical geometry optimization did "
                    "not converge; refusing to mark the calculation complete."
                )
            if write_xyz_file:
                _output_writer.dispatch_role(
                    "geometry",
                    runtime_path=stem_sibling(output_stem, ".opt.xyz"),
                    runtime_format="extended-xyz",
                    runtime_description="Optimized periodic semiempirical geometry.",
                    system=opt_result.system,
                    energy_ha=float(opt_result.energy),
                    raise_on_error=True,
                )
            if write_poscar_file:
                _output_writer.dispatch_role(
                    "geometry",
                    runtime_path=stem_sibling(output_stem, ".opt.POSCAR"),
                    runtime_format="poscar",
                    runtime_description="Optimized periodic semiempirical POSCAR geometry.",
                    system=opt_result.system,
                    comment=f"vibe-qc optimized periodic {method_key}",
                    raise_on_error=True,
                )
            result = opt_result

        t_total = time.perf_counter() - t_start
        if citations and cite_block_text:
            _output_writer.set_citations(cite_manifest_rows)
        if output_qvf:
            from vibeqc.output.formats.qvf import (
                assemble_run_record,
                terminal_run_status,
            )

            # --- Population summary for QVF atom_properties ----------
            # Semiempirical methods carry native Mulliken charges when the
            # engine exposes them (DFTB SCC variants, GFN2-xTB).  Build a
            # population summary so the QVF archive carries at least
            # atom_properties rather than being an empty shell.
            _qvf_pop = None
            try:
                _native_charges = _periodic_semiempirical_attr(
                    result, "charges"
                )
                if _native_charges is not None:
                    from vibeqc.output.formats.population import (
                        compute_native_mulliken_population_summary,
                    )

                    _qvf_pop = compute_native_mulliken_population_summary(
                        _native_charges,
                        system.unit_cell_molecule(),
                        method_key,
                    )
            except Exception:
                pass  # optional enrichment; empty QVF is still valid

            # Symmetry data for structure.symmetry (semiempirical).
            _qvf_symmetry_se = None
            try:
                _sg = getattr(qvf_system, "symmetry", None)
                if _sg is not None:
                    _qvf_symmetry_se = {
                        "space_group_number": int(getattr(_sg, "number", 0)),
                        "space_group_symbol": str(
                            getattr(_sg, "international_symbol", "")
                        ),
                        "point_group": str(getattr(_sg, "point_group", "")),
                    }
            except Exception:
                pass

            qvf_system = getattr(result, "system", None) or system
            _output_writer.dispatch_role(
                "qvf",
                atomic=True,
                record_hostname=record_hostname,
                system=qvf_system,
                result=result,
                method=method_key,
                basis=basis_label,
                wall_seconds=t_total,
                bibtex_content=bibtex_content,
                population_summary=_qvf_pop,
                job_spec={
                    "job_type": "periodic",
                    "method": method_key,
                    "basis": basis_label or "",
                    **(
                        {
                            "options": {
                                "parameter_identity": parameter_identity,
                                "parameter_sha256": parameter_sha256,
                            }
                        }
                        if parameter_identity is not None
                        else {}
                    ),
                },
                symmetry_data=_qvf_symmetry_se,
                run_record=assemble_run_record(
                    plan, wall_seconds=t_total
                ),
                run_status=terminal_run_status(result),
                raise_on_error=True,
            )
        _output_writer.finish(wall_seconds=t_total)
        return result
    except Exception:
        _output_writer.crash(wall_seconds=time.perf_counter() - t_start)
        raise


def _finalize_periodic_checkpoint(
    _checkpointer: object,
    result: object,
    system: object,
    method_upper: str,
    basis_name: str,
    functional: object,
) -> None:
    """Single terminal-checkpoint finalize callsite (AST contract).

    ``tests/test_periodic_runner_bipole_callsites.py`` pins the module to
    exactly one direct ``_checkpointer.finalize`` call whose status
    argument is ``_checkpoint_terminal_run_status(result)``. Every
    terminal checkpoint (converged, the fail-closed SCF gate, and
    optimization) therefore routes through this helper and settles to the
    same status the QVF job-container finalizer derives: ``"failed"``
    only for a falsy ``result.converged``.
    """
    from .output.formats.qvf import (
        terminal_run_status as _checkpoint_terminal_run_status,
    )

    _final_system = getattr(result, "system", None) or system
    _checkpointer.finalize(
        _checkpoint_terminal_run_status(result),
        system=_final_system,
        result=result,
        method=method_upper,
        basis=basis_name,
        functional=functional,
    )


[docs] @_periodic_output_lifecycle def run_periodic_job( system: PeriodicSystem, basis: Optional[BasisSet], *, method: str = "RHF", functional: Optional[str] = None, jk_method: Union[str, "PeriodicJKMethod"] = "auto", # Plane-wave grid cutoff (Hartree) for GPW / GAPW routes. # Default 300 Ha ≈ 600 Ry gives ~10⁻⁴ Ha grid convergence on # compact contracted Gaussians. Ignored for GDF / BIPOLE routes. cutoff_ha: float = 300.0, aux_basis: Optional[str] = None, # GDF backend selection for jk_method="gdf". ``None`` selects rsgdf # (the per-path default for the multi-k + Γ open-shell drivers AND, since # 2026-06-15, the Γ closed-shell RHF path: a plain dim=3 neutral Γ RHF # now routes through run_pbc_gdf_rhf with exxdiv='ewald' -- PySCF µHa # parity -- instead of the legacy molecular-limit gamma driver, which is # kept only for RKS / dim<3 / charged / smeared / symmetry runs). Set to # "compcell" / "rsgdf" / "mdf" to choose explicitly; "mdf" (Mixed # Density Fitting) closes the all-electron Gaussian-DF floor. All three # explicit values route Γ closed-shell RHF through run_pbc_gdf_rhf. # rsgdf_ke_cutoff controls the reciprocal-space auxiliary mesh used by the # range-separated GDF backend. rsgdf_tail_ke_cutoff optionally completes # the exact high-|G| shell for GDF and fitted aiccm2026dev-b routes; the # latter transport is diagnostic and does not by itself qualify D93. # mdf_ke_cutoff is the modest residual-mesh cutoff used only by # gdf_method="mdf". gdf_method: Optional[str] = None, rsgdf_ke_cutoff: float = 200.0, rsgdf_tail_ke_cutoff: Optional[float] = None, mdf_ke_cutoff: float = 40.0, # Electron-repulsion representation for jk_method="aiccm2026dev-b": # corrected-gauge direct four-centre, pair-resolved RI, or RIJCOSX. aiccm_backend: str = "four_center", # Primary B-stream finite-size control: number of primitive lattice # vectors in each cyclic supercell direction. The equivalent k net is # derived, never chosen independently. ``aiccm_wigner_seitz_shells=s`` # is the odd-cluster shorthand N=2s+1 (s complete layers per side). aiccm_lattice_extension: Optional[Union[int, Sequence[int]]] = None, aiccm_wigner_seitz_shells: Optional[Union[int, Sequence[int]]] = None, # B-stream-only space-group mode. ``diagnostic`` constructs and checks # the finite-torus subgroup and k orbits without changing the SCF; # ``integrals`` fails closed until petite-list parity is established. aiccm_symmetry: str = "off", aiccm_symmetry_require_full_group: bool = False, output: Union[str, os.PathLike] = "output", use_diis: bool = True, # Diagonalisation solver for the SCF procedure. # "dense" -- NumPy/ScaLAPACK dense eigh (default). # "davidson" -- block-Davidson iterative solver. # "lobpcg" -- LOBPCG iterative solver (handled by Python SCF loop). solver: str = "dense", # Convergence strategy: "auto" fills any convergence knob the user # did not set, from a cheap pre-SCF classification of the system # (ionic / covalent / metallic / molecular-limit); "off" keeps the # plain defaults. Omitted (None) -> auto unless any explicit knob # below is given. The chosen strategy, its per-knob values, and the # classification reasons are stated in the .out file. v1 scope: # jk_method="bipole" and jk_method="gdf" (other routes fall back # to plain defaults). convergence: Optional[str] = None, # None = not given (auto strategy may choose); a float (incl. 0.0) # is an explicit user choice that auto never overrides. damping: Optional[float] = None, # Explicitly control the B selector's underlying SCF dynamic-damping # controller. ``None`` preserves the option-struct default; ``False`` # disables adaptive damping updates. Other J/K selectors fail closed on an # explicit value until their dispatches implement the same contract. dynamic_damping: Optional[bool] = None, fmixing_percent: Optional[float] = None, fock_mixing: Optional[float] = None, density_mixer: Optional[str] = None, density_mixer_depth: int = 8, density_mixer_beta: float = 0.5, density_mixer_kerker: bool = False, kerker_k0: float = 1.5, kerker_strength: float = 1.0, kerker_cutoff_ha: float = 120.0, smearing: Optional[SmearingOptions] = None, smearing_temperature: Union[float, str, None] = _SMEARING_UNSET, # type: ignore[assignment] smearing_unit: str = "hartree", smearing_method: str = "fermi-dirac", smearing_metallic: Optional[bool] = None, smearing_band_gap_hartree: Optional[float] = None, # BZ integration backend. None / "smearing" (temperature-broadening, # the default), or "gilat" (parameter-free Gilat-Raubenheimer net, # T=0 tetrahedron-family integrator). When not given (sentinel), # auto-reads from a KPoints.recommend() result's .bz_integration # attribute. Explicit user args win over KPoints metadata. bz_integration: Optional[str] = _BZ_INTEGRATION_UNSET, diis_start_iter: int = 2, diis_subspace_size: int = 8, max_iter: int = 80, conv_tol_energy: float = 1e-7, initial_guess: str = "SAD", write_molden_file: bool | None = None, write_density: bool = False, density_spacing_bohr: float = 0.2, write_xyz_file: bool = True, write_poscar_file: bool = False, write_xsf_structure_file: bool = True, write_cif_file: bool = True, write_population_file: bool | None = None, citations: bool = True, dry_run: bool = False, memory_override: bool = False, gapw_molecular_limit: bool = False, record_hostname: bool = True, progress: Union[bool, ProgressLogger, None] = None, verbose: Optional[int] = None, # --- Dispersion --------------------------------------------------- dispersion: Optional[Union[str, bool, "D3BJParams"]] = None, dispersion_backend: str = "auto", dispersion_cutoff_bohr: float = 50.0, # --- BIPOLE-specific --------------------------------------------- # Direct-lattice cutoff (bohr) for BIPOLE Fock and nuclear sums. # ``cutoff_ha`` is a plane-wave grid cutoff and remains GPW/GAPW-only. bipole_cutoff_bohr: Optional[float] = None, bipole_nuclear_cutoff_bohr: Optional[float] = None, bipole_exact_zone_bohr: Optional[float] = None, # M5 production default for the erfc SR internal ket-image ball. # ``None`` restores the historical unpadded traversal. sr_image_precision: Optional[float] = 1e-6, # M4b (pair-resolved truncation): QQR-style separation-aware # screening for the BIPOLE SR erfc J/K build # (LatticeSumOptions.sr_range_screening). The M5 precision path # enables it automatically; this explicit flag also permits screened # historical/unpadded diagnostics. Ignored by non-BIPOLE routes. sr_range_screening: bool = False, ewald_omega: Optional[float] = None, ewald_precision: float = 1e-8, use_oda: bool = False, oda_trust_lambda_max: float = 1.0, use_mom: bool = False, # None = not given (auto strategy may choose); a float (incl. 0.0) # is an explicit user choice that auto never overrides. level_shift: Optional[float] = None, # --- Multi-k (GDF, RIJCOSX, and BIPOLE) ------------------------- kpoints: Optional[ Union[Tuple[int, int, int], List[int], int, "KPoints", "BlochKMesh"] ] = None, # --- Cell reduction ----------------------------------------------- reduce_to_primitive: bool = False, symmetry_precision: float = 1e-4, symmetry: Union[bool, str] = False, symmetry_stabilize: bool = False, symmetry_reduce_fock: Optional[bool] = None, # Space-group reduction of the multi-k GDF exchange build (the driver's # ``ibz_native``). Exchange is the one n_k^2 term: under this flag K is # built only at the irreducible-wedge bras and symmetry-transported to # the rest of each star, so the dense Lpq cache holds n_IBZ x n_k blocks # instead of n_k^2. Everything else -- diagonalisation at every k, the # occupations, the energy expression, the result shape -- is unchanged, # so the reduction is exact rather than an approximation. Requires an # attached symmetry model (pass symmetry='attach'), a true multi-k # Monkhorst-Pack mesh, exact exchange, and a symmorphic space group; # every one of those fails closed rather than silently running full-BZ. symmetry_reduce_k: bool = False, # --- BIPOLE multipole far-field ---------------------------------- # Fail-closed: the dormant Saunders 1992 quartet-level bipolar # far-field (G6) prototype does not preserve the exact route's # three-translation Fock domain, so an explicit True raises # NotImplementedError here and in every direct driver # (reject_bipole_quartet_far_field in pbc_bipole_common.py). # False -- the default -- selects the exact four-centre traversal, # the only supported route (the retired G1 cell-level path never # engages). The dipolar far-field accuracy remains uncertified: # the 2026-08-13 LiH measurement puts the exact-vs-far-field error # at 6.3e-2 Ha at 18 bohr. use_multipole_far_field: bool = False, multipole_l_max: int = 2, # --- BIPOLE exchange convention (option (b), 2026-06-10) ---------- # None = auto: the Ewald exchange split (corrected gauge -- full # Bloch density, split K with the exxdiv G=0 correction, no # spheropole term) is ON for 3D Γ-only RHF BIPOLE runs and OFF # otherwise (multi-k pends the q!=0 LR-exchange channels). Pass # False to force the legacy Γ-locality gauge (needed e.g. to # combine the multipole far-field branch with Γ sampling, or for # the analytic-gradient preview). 'exchange_exxdiv' picks the K=0 # convention: 'ewald' (probe-charge Madelung; PySCF-equivalent # default) or 'none'. use_exchange_ewald_split: Optional[bool] = None, exchange_exxdiv: str = "ewald", # --- Geometry optimization ---------------------------------------- optimize: bool = False, optimize_max_iter: int = 30, optimize_conv_tol_grad: float = 1e-4, optimize_cell: bool = False, # QVF visualisation archive (v1). output_qvf: bool = True, # Optional vibe-view overlay: localize χ-CCM-B occupied orbitals and # embed x_ccm.wannier_centers in the QVF archive. qvf_wannier_centers: bool = False, # Opt-in live QVF checkpointing for vibe-view hot-reload. When # ``checkpoint_qvf`` is set, a running snapshot is atomically # (re)written there as the SCF climbs -- every ``checkpoint_every`` # cycles -- carrying ``provenance.run_status="running"`` and a # monotonic ``provenance.checkpoint.seq``; the terminal snapshot is # labeled ``"converged"`` / ``"failed"``. ``checkpoint_every=0`` # keeps the start + end frames but no per-iteration cadence. checkpoint_qvf: Optional[Union[str, os.PathLike]] = None, checkpoint_every: int = 0, # Harmonic vibrational frequencies via finite-difference Hessian. hessian: bool = False, # Partial Hessian: atom indices to hold fixed (e.g. bulk-like bottom # slab layers) so only the unfrozen atoms are displaced -- 6M instead # of 6N SCFs. Frozen atoms anchor the cell, so the frequencies are # vibrational-only (no gas-phase trans/rot). See HessianFDOptions. hessian_frozen_indices: Optional[List[int]] = None, # Pre-computed band structure to include in QVF (vibe-view bands plot). band_structure: Optional["BandStructure"] = None, # Compute COOP/COHP bonding analysis (requires output_qvf=True; # refused pre-SCF otherwise, IID 195). coop_cohp: bool = False, # TD-DFT excited states (Gamma-point only, TDA). tddft: bool = False, tddft_n_states: int = 5, # DOS/PDOS/COOP k-mesh dimensions for QVF output (default [8,8,8]). # For 1D/2D systems, consider e.g. [32,1,1] or [12,12,1]. dos_kmesh: Optional[Sequence[int]] = None, # --- DFT+U (Dudarev rotationally-invariant) ------------------------ # Shipped on main (see docs/user_guide/dft_plus_u.md): # - RHF +U: Γ-only, via vibeqc.run_rhf_periodic_gamma # (multi-k RHF +U raises NotImplementedError). # - RKS +U: Γ and multi-k both supported, via vibeqc.run_rks_periodic. # Multi-k (Increment 4c) uses the k-averaged AO occupation matrix # and adds S(k) V_AO S(k) per k in cpp/src/periodic_scf.cpp. # - UHF / UKS +U: Γ GPW and Γ/multi-k BIPOLE are supported via # their open-shell drivers. # - GDF +U: queued (GDF Γ-only driver has no +U hook yet). dft_plus_u: Optional[List["HubbardSite"]] = None, # --- ATOMSPIN: broken-symmetry magnetic seed (UHF/UKS only) ------- # Per-atom spin tag in unit-cell atom order: +1 (majority alpha), # -1 (majority beta), 0 (unpolarised). Seeds an AFM / ferrimagnetic # g=0 spin pattern (Bloch-sums to a broken-symmetry D(k)). Supported # on the Γ UHF/UKS Ewald / GDF / BIPOLE drivers and the multi-k UHF/UKS # Ewald drivers. # Requires the SAD guess (the default). See docs/roadmap.md Sec.G2. atomic_spins: Optional[List[int]] = None, # --- READ: restart from a prior SCF ------------------------------- # Gamma periodic restarts use the prior g=0 cell density, projected onto # this cell's basis. Closed-shell multi-k GDF/GPW/GAPW restarts accept an # in-memory prior result and rebuild the per-k Bloch density blocks from # its coefficients/occupations. File-backed multi-k READ stays fail-closed # until QVF carries all-k wavefunction data. See docs/roadmap.md Sec.G2. read_from: Optional[object] = None, # --- SPINLOCK: broken-symmetry magnetic convergence (UHF/UKS only) - # "pattern_hold" holds the seeded (ATOMSPIN) occupied set by maximum # overlap (MOM) for ``spinlock_iterations`` cycles, then releases -- # protecting an AFM seed from collapsing to the symmetric solution. # "spin_schedule" runs a two-phase SCF: converge at locked # n_alpha-n_beta = ``spinlock_value`` for ``spinlock_iterations`` cycles, # then release to the multiplicity target. PATTERN_HOLD is supported on # the Γ-Ewald, GDF, BIPOLE and multi-k UKS Ewald drivers; SPIN_SCHEDULE # on the Γ-Ewald, Γ-GDF and Γ-BIPOLE drivers (unsupported # (driver, mode) pairs fail closed). See docs/roadmap.md Sec.G2. spinlock: Optional[str] = None, spinlock_value: int = 0, spinlock_iterations: int = 0, # Restart from a previous GPW/GAPW calculation (.npz file written # by ``save_gpw_result``). Warm-starts the SCF from the saved density. restart_from: Optional[Union[str, os.PathLike]] = None, ): """Run a periodic SCF job and write the standard output files. Mirrors :func:`vibeqc.run_job` but for periodic systems. Gamma RHF/RKS GDF is the default when no k-mesh is specified. ROHF/GDF is available by explicit backend selection at Gamma and on a full Monkhorst-Pack mesh. Multi-k RHF/RKS GDF, multi-k RHF/RKS/UHF/UKS RIJCOSX, and all four BIPOLE methods are available via ``kpoints``. These routes accept a mesh tuple/list, a scalar mesh size, :class:`KPoints`, or a native :class:`BlochKMesh`. .. warning:: ``kpoints=(N, N, N)`` builds the **Gamma-centred** mesh ``{0, 1/N, ..., (N-1)/N}`` per axis. ASE/GPAW read the same-looking argument as the **classical shifted** mesh, offset by half a step. The two agree exactly for **odd** N and are *disjoint* for **even** N (measured 2026-08-02: ``(3,3,3)``/``(5,5,5)`` share every point, ``(2,2,2)``/``(4,4,4)``/``(6,6,6)`` share none), so a cross-code comparison validated at an odd mesh can still be sampling a different Brillouin zone at an even one. The resolved convention and k-point count are printed in the ``.out``; pass ``VIBEQC_OUTPUT_LEVEL=verbose`` for the full k-list. Parameters ---------- system, basis Periodic system + AO basis. For basis-free periodic semiempirical methods (`dftb0`, `scc_dftb`, `gfn2_xtb`, `pm6`, `om1`, `om2`, `om3`), pass `basis=None`; the runner dispatches before any Gaussian-basis setup. method ``"RHF"``, ``"ROHF"``, ``"ROKS"``, ``"UHF"``, ``"RKS"`` or ``"UKS"``. Periodic ROHF/ROKS are maintained-preview 3D routes: ROHF runs on native GDF at Gamma or on full Monkhorst-Pack meshes (``jk_method="gdf"``), on Gamma-only GPW (``jk_method="gpw"``), or on the BIPOLE-route corrected-Ewald-exchange EWALD_3D engine (``jk_method="bipole"`` -- the AUTO default for ROHF -- at Gamma and on full Monkhorst-Pack meshes), while pure-DFT ROKS is GPW-only (Gamma or true multi-k). Unsupported backends, hybrids on the multi-k ROKS route, smearing, gradients, and response properties fail closed. Closed-shell RHF / RKS default to the Gamma or multi-k GDF path depending on ``kpoints``. With ``jk_method="bipole"`, the RHF/ROHF/RKS/UHF/UKS methods dispatch through the BIPOLE Gamma or multi-k route. With ``jk_method="rijcosx"``, Gamma RHF uses the dedicated RIJCOSX driver and true multi-k meshes use the GDF/COSX backend for RHF/RKS/UHF/UKS. functional XC functional for ``method="RKS"`` or ``method="UKS"``. output Path stem; produces ``{output}.out``, ``{output}.system``, ``{output}.molden``, ``{output}.xsf`` (when ``write_density``). band_structure Optional :class:`BandStructure` pre-computed by :func:`vibeqc.band_structure` (or ``_hcore``). When given together with ``output_qvf=True``, the band structure is embedded in the QVF archive so vibe-view can render an interactive Plotly band-structure plot. Compute it before calling this function -- the same workflow used for matplotlib plotting with :func:`vibeqc.plot.band_structure_figure`. coop_cohp When ``True``, compute COOP and COHP bonding analysis and embed ``dos.coop`` + ``dos.cohp`` sections in the QVF archive. Requires ``output_qvf=True``: the archive is the analysis's only sink, so ``coop_cohp=True`` with ``output_qvf=False`` raises ``ValueError`` before SCF rather than silently skipping the analysis. Uses the same DOS k-mesh as the total/projected DOS (``[8,8,8]``). The Hcore matrix (T + V) needed for COHP is computed independently within the DOS/COHP k-mesh block; no additional user input is required. tddft When True, compute TD-DFT vertical excitation energies via the Tamm-Dancoff approximation (TDA) at the Gamma point. Writes excitation energies, oscillator strengths, and dominant transitions to the .out file. Requires ``_has_valid_mo_coeffs(result)`` (true for Γ-only and multi-k results). Not embedded in QVF yet. tddft_n_states Number of excited states to compute when ``tddft=True``. Default 5. dos_kmesh Override the DOS/PDOS/COOP k-mesh dimensions. Default ``[8, 8, 8]``. For 1D systems use e.g. ``[32, 1, 1]``; for 2D use ``[12, 12, 1]``. Only used when ``output_qvf=True``. qvf_wannier_centers When True for ``jk_method="aiccm2026dev-b"``, localize the occupied finite-torus space with the B-owned Wannier gauge and embed an ``x_ccm.wannier_centers`` vendor section in the QVF archive for vibe-view's centre overlay. Requires ``output_qvf=True``. aux_basis Optional auxiliary basis for ``jk_method="gdf"`` and the RI-J part of ``jk_method="rijcosx"``. If omitted, vibe-qc chooses the current native-GDF default for ``basis.name``. gapw_molecular_limit Declare that ``jk_method="gapw"`` is describing an isolated molecule or atom in a vacuum-padded single-Gamma cell. Required for RHF/UHF so the method-aware one-centre default can select the validated fit-free analytic augmentation without guessing from geometry. Compact crystals should use GDF or BIPOLE. Rejected for RKS/UKS, whose method-aware default remains the block DFT functional. High-level GAPW HF geometry/cell optimization and Hessians fail closed until derivatives of the same fit-free energy are implemented. bipole_cutoff_bohr, bipole_nuclear_cutoff_bohr Direct-lattice cutoff radii in bohr for ``jk_method="bipole"``. ``bipole_cutoff_bohr`` controls the electronic BIPOLE Fock sums; when ``bipole_nuclear_cutoff_bohr`` is omitted, the BIPOLE route keeps the nuclear/Ewald real-space cutoff no longer than the electronic J/K cutoff so neutral-cell cancellation stays in the corrected Ewald gauge. Corrected-gauge RHF/RKS/UHF/UKS runs measure Bloch-overlap fold drift before the first Fock build. Drift above ``1e-2`` is the established unreliable-support regime and raises before SCF; increase this cutoff until the reported drift is below ``1e-4`` for quantitative work. Intermediate drift retains a truncation note. These controls are separate from ``cutoff_ha``, which is a GPW/GAPW plane-wave grid cutoff. convergence Convergence-strategy selector. ``"auto"`` classifies the system from cheap pre-SCF signals (composition electronegativity spread, cell volume, vacuum axes, electron parity) into a profile -- ionic-insulator, covalent-insulator, metallic-candidate or molecular-limit -- and fills every convergence knob the user did not set (Fermi-Dirac smearing, FMIXING, level shift, damping) with profile defaults grounded in measured behaviour. MgO-class ionic cells get FMIXING 30 % with integer occupations; smearing is never selected automatically for an insulating profile because it can converge a wrong-energy metallic basin. ``"off"``/``"none"`` keeps the plain defaults. Omitted (``None``): auto applies **only when no explicit convergence knob is given** -- any explicit ``damping=`` / ``fmixing_percent=`` / ``fock_mixing=`` / ``level_shift=`` / smearing input switches to fully-manual mode and nothing is auto-filled. Either way the ``.out`` file carries a "Convergence strategy" block stating the mode (AUTO default / AUTO requested / manual / off), the per-knob values with their provenance, and the classification reasons. Explicit knobs are never overridden. Scope: applied on ``jk_method="bipole"`` and ``"gdf"``; other routes run with plain defaults and label the block accordingly. use_diis, damping, dynamic_damping, fmixing_percent, fock_mixing, diis_start_iter, diis_subspace_size, max_iter, conv_tol_energy SCF controls forwarded to the periodic driver. ``dynamic_damping`` is a χ-CCM-B-only override. ``None`` preserves that selector's option default, while ``False`` disables adaptive damping updates. Supplying it explicitly with any other ``jk_method`` fails closed. It is separate from the generic ``convergence="auto"`` explicit-knob detection; χ-CCM-B campaign callers pin their generic convergence controls independently. ``fmixing_percent`` mirrors CRYSTAL's ``FMIXING`` keyword: the percentage of the previous Fock/KS matrix mixed into the matrix diagonalised on the next cycle. It is separate from density damping. ``fock_mixing`` is the same knob on the fractional 0.0-1.0 scale; pass only one spelling. ``density_mixer`` / ``density_mixer_kerker`` expose the periodic Anderson/Broyden/Kerker API surface on the closed-shell multi-k GDF and RIJCOSX routes. The lower-level Ewald RKS drivers also support these mixers directly. Other routes fail closed on active requests instead of silently ignoring them or changing electrostatic gauge. Compact 3D closed-shell RKS/GDF SCAN/r2SCAN-family jobs with an explicit ``kpoints=`` mesh select Anderson density mixing by default unless ``density_mixer=`` or ``convergence="off"`` is set. ``smearing`` accepts the new :class:`vibeqc.SmearingOptions` surface. The legacy ``smearing_temperature`` may be a numeric electronic ``k_B T`` (interpreted via ``smearing_unit``), ``"auto"``, ``"metal"``, ``"small-gap"``, ``"debug"``, ``"none"`` / ``"off"``, or ``None``. ``smearing_method`` selects ``"fermi-dirac"`` (default), ``"mermin"`` (Mermin finite-temperature free-energy functional), ``"methfessel-paxton"`` or ``"marzari-vanderbilt"`` (all implemented). ``smearing_metallic`` and ``smearing_band_gap_hartree`` guide the conservative ``"auto"`` guess. initial_guess ``"SAD"`` (default) or ``"HCORE"``. write_molden_file Emit ``{output}.molden`` of the Γ-point MOs (using the unit-cell molecule + basis as the molecular target). ``None`` (the default) enables it for exact single-Γ Gaussian-basis SCF routes and for Gamma-containing BIPOLE/GDF meshes whose result metadata locates that block. It is inapplicable for basis-free and shifted meshes. Explicit ``True`` on an inapplicable route fails before calculation. write_population_file Emit the population-summary text/JSON pair. Exact single-Γ routes use the molecular analysis; multi-k BIPOLE uses its lattice-density population and χ-CCM uses its finite-torus population. ``None`` is capability-aware auto mode, ``True`` is a guaranteed request, and ``False`` disables the pair. write_density Emit ``{output}.xsf`` with the SCF density on a primitive-cell grid (XSF works for any lattice; cube is orthorhombic-only). density_spacing_bohr Grid spacing for the XSF density. Default 0.2 bohr. solver Diagonalisation solver. ``"dense"`` (default) uses NumPy/ScaLAPACK dense eigh. ``"davidson"`` uses the block-Davidson iterative solver (``opts.use_davidson = True``). ``"lobpcg"`` also sets ``use_davidson = True``; the Python SCF loop then detects the LOBPCG preference and dispatches through the Python solver stack instead of dense or Davidson diagonalisation. hessian When True, compute harmonic vibrational frequencies for the unit-cell molecule via finite-difference Hessian. Frequencies and IR intensities printed to .out and embedded in QVF for vibe-view. Default False. Cost: ~6N SCF evaluations for the unit cell. """ enforce_runtime_pin_from_env() # Fail fast on a molecular system. Passing a Molecule here used to die # deep in the setup with "'Molecule' object has no attribute 'lattice'"; # say what the user actually needs instead. if not hasattr(system, "lattice") or not hasattr(system, "unit_cell"): raise TypeError( f"run_periodic_job: system must be a PeriodicSystem (a cell " f"with a lattice); got {type(system).__name__}. Molecules have " f"no lattice or Brillouin zone -- k-points, band structures, " f"and DOS meshes are periodic-boundary-condition features. Use " f"vibeqc.run_job(...) for molecular calculations, or build a " f"PeriodicSystem (set .lattice / .unit_cell / .dim) for a " f"crystal." ) semiempirical_route = _plan_periodic_semiempirical_method( method, system, kpoints=kpoints, ) if semiempirical_route is not None: _basis_free_reason = ( "basis-free periodic semiempirical results do not expose the " "Gaussian AO wavefunction required by this writer." ) write_molden_file = _resolve_sidecar_request( write_molden_file, supported=False, option="write_molden_file", method=semiempirical_route.method_key, caller="run_periodic_job", unavailable_reason=_basis_free_reason, ) write_population_file = _resolve_sidecar_request( write_population_file, supported=False, option="write_population_file", method=semiempirical_route.method_key, caller="run_periodic_job", unavailable_reason=_basis_free_reason, ) return _run_periodic_semiempirical_job( system, route_plan=semiempirical_route, output=output, dry_run=dry_run, record_hostname=record_hostname, citations=citations, write_xyz_file=write_xyz_file, write_poscar_file=write_poscar_file, write_xsf_structure_file=write_xsf_structure_file, write_cif_file=write_cif_file, write_molden_file=write_molden_file, write_density=write_density, write_population_file=write_population_file, output_qvf=output_qvf, jk_method=jk_method, aux_basis=aux_basis, smearing=smearing, smearing_temperature=smearing_temperature, smearing_unit=smearing_unit, smearing_method=smearing_method, smearing_metallic=smearing_metallic, smearing_band_gap_hartree=smearing_band_gap_hartree, dispersion=dispersion, optimize=optimize, optimize_max_iter=optimize_max_iter, optimize_conv_tol_grad=optimize_conv_tol_grad, optimize_cell_requested=optimize_cell, hessian=hessian, tddft=tddft, coop_cohp=coop_cohp, band_structure=band_structure, qvf_wannier_centers=qvf_wannier_centers, kpoints=kpoints, checkpoint_qvf=checkpoint_qvf, dft_plus_u=dft_plus_u, atomic_spins=atomic_spins, spinlock=spinlock, read_from=read_from, restart_from=restart_from, functional=functional, max_iter=max_iter, conv_tol_energy=conv_tol_energy, ) method_upper = method.upper() if method_upper not in ("RHF", "ROHF", "ROKS", "RKS", "UHF", "UKS"): # A leading "K" is the PySCF spelling for the multi-k driver class # (KRHF/KRKS/KUHF), and vibe-qc's own result type is named # PeriodicKRHFGDFResult, so `method="KRHF"` is a natural thing to # reach for. It has never been a run_periodic_job method: k-point # sampling is not a *method*, it is the `kpoints` argument, and every # method below is multi-k whenever kpoints is set. Name the # replacement rather than dead-ending on the unsupported list. if method_upper.startswith("K") and method_upper[1:] in ( "RHF", "ROHF", "ROKS", "RKS", "UHF", "UKS", ): raise NotImplementedError( f"run_periodic_job: method={method!r} not supported; k-point " f"sampling is not a separate method. Use " f"method={method_upper[1:]!r} and pass the mesh explicitly, " f"e.g. kpoints=(4, 4, 4). Every method runs multi-k when " f"`kpoints` is set (the default is the Gamma point)." ) raise NotImplementedError( f"run_periodic_job: method={method!r} not supported. " f"Supported: RHF, ROHF, ROKS, RKS, UHF, UKS." ) if basis is None: raise TypeError( f"run_periodic_job: method={method_upper!r} requires a BasisSet. " "Pass basis=None only for basis-free periodic semiempirical " "methods such as 'scc_dftb' or 'pm6'." ) if method_upper in ("RHF", "ROHF", "UHF") and functional is not None: raise ValueError( f"functional={functional!r} given but method={method_upper}; " f"use method='RKS' or 'UKS' for KS calculations" ) if method_upper in ("ROKS", "RKS", "UKS") and functional is None: raise ValueError(f"method={method_upper!r} requires functional=...") # Fail early for an input cell that is already odd. The same validation # runs after optional primitive reduction because reduction can change # electron-count parity. _validate_closed_shell_electron_count(system, method_upper) if atomic_spins is not None and method_upper not in ("UHF", "UKS"): # ATOMSPIN is an open-shell broken-symmetry seed; reject the # closed-shell misuse early (before the dry-run manifest / setup). raise ValueError( "atomic_spins (ATOMSPIN) is an open-shell broken-symmetry seed; " f"it requires method='UHF' or 'UKS', got {method_upper!r}." ) if atomic_spins is not None and len(atomic_spins) != len(system.unit_cell): raise ValueError( "run_periodic_job: atomic_spins length " f"({len(atomic_spins)}) must match the input-cell atom count " f"({len(system.unit_cell)})." ) # SPINLOCK: resolve the string mode to a SpinlockMode (open-shell only). # The selected driver fails closed if it does not implement the mode. _spinlock_mode = SpinlockMode.OFF if spinlock is not None: if method_upper not in ("UHF", "UKS"): raise ValueError( "spinlock is an open-shell broken-symmetry convergence aid; " f"it requires method='UHF' or 'UKS', got {method_upper!r}." ) _spinlock_map = { "spin_schedule": SpinlockMode.SPIN_SCHEDULE, "pattern_hold": SpinlockMode.PATTERN_HOLD, "off": SpinlockMode.OFF, } _spinlock_key = str(spinlock).strip().lower().replace("-", "_") if _spinlock_key not in _spinlock_map: raise ValueError( f"unknown spinlock={spinlock!r} (valid: 'spin_schedule', " "'pattern_hold')." ) _spinlock_mode = _spinlock_map[_spinlock_key] if _spinlock_mode != SpinlockMode.OFF and int(spinlock_iterations) <= 0: raise ValueError( "spinlock requires spinlock_iterations > 0 (the number of " "locked/held SCF cycles before release)." ) # READ restart validations, early (before the dry-run manifest / setup). _is_read_request = initial_guess.strip().upper() in ("READ", "MOREAD", "COREAD") if optimize_cell and not optimize: raise ValueError( "run_periodic_job: optimize_cell=True requires optimize=True; " "the cell step is part of the geometry-optimization workflow." ) _read_multik_request = _is_read_request and _is_multik_kpoints(kpoints) if read_from is not None and not _is_read_request: raise ValueError( "read_from is only used with initial_guess='read' (or 'moread' / " "'coread'); set initial_guess to request a READ restart." ) if _read_multik_request: if method_upper not in ("RHF", "RKS"): raise NotImplementedError( "multi-k periodic READ restart is currently closed-shell " "only (RHF/RKS). Open-shell multi-k READ needs per-spin " "per-k Bloch densities." ) if read_from is None: raise ValueError( "multi-k periodic initial_guess='read' needs an in-memory " "multi-k source result or a .qvf archive with all-k Bloch " "restart data." ) if isinstance(read_from, (str, os.PathLike)): _read_suffix = Path(os.fspath(read_from)).suffix.lower() if _read_suffix != ".qvf": raise NotImplementedError( "multi-k periodic READ restart from non-QVF file sources " "is not implemented: a multi-k restart needs all per-k " "complex Bloch coefficients and occupations." ) # Resolve the J/K method (AUTO -> concrete pick) and validate the # combination of method x lattice x basis. This is intentionally # done before any expensive setup so user errors fire early. resolved_jk = pick_jk_method( jk_method, lattice=np.asarray(system.lattice, dtype=float), basis_name=basis.name, n_atoms=len(system.unit_cell), scf_method=method_upper, dim=int(system.dim), ) validate_jk_method( resolved_jk, lattice=np.asarray(system.lattice, dtype=float), basis_name=basis.name, ) if ( resolved_jk == PeriodicJKMethod.GAPW and not isinstance(gapw_molecular_limit, (bool, np.bool_)) ): raise TypeError( "run_periodic_job: gapw_molecular_limit must be bool; " f"got {type(gapw_molecular_limit).__name__}." ) if ( resolved_jk == PeriodicJKMethod.GAPW and method_upper in ("RKS", "UKS") and gapw_molecular_limit ): raise ValueError( "run_periodic_job: gapw_molecular_limit applies only to GAPW " "RHF/UHF. GAPW RKS/UKS resolves one_centre='auto' to the " "legacy block DFT functional." ) if ( resolved_jk == PeriodicJKMethod.GAPW and method_upper in ("RHF", "UHF") and not gapw_molecular_limit ): raise NotImplementedError( "run_periodic_job: GAPW RHF/UHF requires " "gapw_molecular_limit=True. The fit-free analytic one-centre " "default is validated only for a vacuum-padded single-Gamma " "molecule/atom, and the legacy block fallback is known wrong " "for H2O-class bonded cores. Use GDF/BIPOLE for compact " "crystals." ) if ( resolved_jk == PeriodicJKMethod.GAPW and method_upper in ("RHF", "UHF") and (optimize or optimize_cell or hessian) ): requested_derivatives = [] if optimize: requested_derivatives.append("optimize") if optimize_cell: requested_derivatives.append("optimize_cell") if hessian: requested_derivatives.append("hessian") raise NotImplementedError( "run_periodic_job: GAPW RHF/UHF " f"{', '.join(requested_derivatives)} requires derivatives of " "the same fit-free analytic one-centre energy. Those derivatives " "are not implemented; the generic periodic optimizers use a " "different BIPOLE Hamiltonian and the generic Hessian uses the " "molecular unit-cell Hamiltonian." ) if ( dynamic_damping is not None and resolved_jk != PeriodicJKMethod.AICCM2026DEV_B ): raise NotImplementedError( "run_periodic_job: explicit dynamic_damping is currently " "implemented only for jk_method='aiccm2026dev-b'; other " "periodic dispatches do not all preserve this option" ) _requested_bloch_kmesh = _runner_bloch_kmesh(system, kpoints) _requested_kmesh_size = _bloch_kmesh_full_size(_requested_bloch_kmesh) _requested_true_multik = kpoints is not None and _requested_kmesh_size > 1 _requested_gamma_only = ( kpoints is None or _gamma_kmesh_info(system, _requested_bloch_kmesh) is not None ) _jk_requested_label = ( jk_method.value if isinstance(jk_method, PeriodicJKMethod) else str(jk_method) ).strip().lower() _jk_method_explicit = _jk_requested_label not in ("auto", "") # AUTO must never change the Coulomb Hamiltonian only after setup merely # to find a +U implementation. Resolve that execution plan now, before # BIPOLE capability guards and the dry-run manifest. In 3D, the exact # BIPOLE drivers support +U for RHF/RKS/UHF/UKS. A true low-dimensional # BIPOLE Coulomb model does not exist and therefore fails closed. _auto_bipole_dftu_fallback = bool( dft_plus_u and not _jk_method_explicit and resolved_jk not in ( PeriodicJKMethod.BIPOLE, PeriodicJKMethod.GPW, PeriodicJKMethod.GAPW, ) and method_upper in ("RHF", "RKS", "UHF", "UKS") and not ( resolved_jk == PeriodicJKMethod.GDF and method_upper in ("RHF", "RKS") and _requested_true_multik ) and not ( resolved_jk == PeriodicJKMethod.RIJCOSX and method_upper in ("RHF", "RKS") and _requested_true_multik ) ) if _auto_bipole_dftu_fallback: if int(system.dim) != 3: raise NotImplementedError( "run_periodic_job: AUTO cannot preserve the selected " f"{resolved_jk.value!r} low-dimensional Coulomb Hamiltonian " "with DFT+U. The available BIPOLE +U implementation is " "3D-only, so this request fails before dry-run/SCF." ) resolved_jk = PeriodicJKMethod.BIPOLE # These checks must follow AUTO +U route planning. Otherwise a request # that resolves to BIPOLE only through the fallback can bypass the # finalized route's k-mesh and restart capabilities during dry-run. if ( resolved_jk == PeriodicJKMethod.BIPOLE and _requested_kmesh_size == 1 and not _requested_gamma_only ): raise NotImplementedError( "run_periodic_job: BIPOLE does not support a lone non-Gamma " "twist. Its one-point path is Gamma-specific; use the Gamma " "point or a complete Monkhorst-Pack mesh." ) if ( resolved_jk == PeriodicJKMethod.BIPOLE and use_exchange_ewald_split is True and _requested_true_multik ): _split_ir = np.asarray( getattr(_requested_bloch_kmesh, "ir_mapping", []), dtype=int ).reshape(-1) _split_mesh = tuple( int(x) for x in getattr(_requested_bloch_kmesh, "mesh", (1, 1, 1)) ) _split_stored = _bloch_kmesh_size(_requested_bloch_kmesh) if _split_ir.size == 0 and int(np.prod(_split_mesh)) != _split_stored: raise NotImplementedError( "run_periodic_job: use_exchange_ewald_split=True at multi-k " "requires a complete Monkhorst-Pack mesh carrying its BvK " "dimensions. An ad-hoc explicit k-point list cannot execute " "the corrected exchange gauge." ) if ( resolved_jk == PeriodicJKMethod.BIPOLE and method_upper in ("UHF", "UKS") and _spinlock_mode == SpinlockMode.SPIN_SCHEDULE and _requested_true_multik ): raise NotImplementedError( "run_periodic_job: BIPOLE SPIN_SCHEDULE is Gamma-only because " "its phase-2 restart cannot reconstruct the complete multi-k " "spin density. Use spinlock='pattern_hold' or a Gamma mesh." ) if resolved_jk == PeriodicJKMethod.BIPOLE and solver != "dense": raise NotImplementedError( "run_periodic_job: jk_method='bipole' currently supports only " "solver='dense'. The direct BIPOLE SCF loops do not execute " f"solver={solver!r}, so the request fails before dry-run/SCF." ) def _explicit_unit_kmesh(value: object) -> bool: if isinstance(value, (int, np.integer)): return int(value) == 1 if isinstance(value, (list, tuple)): return tuple(int(x) for x in value) == (1, 1, 1) return False # Match the actual GPW/GAPW branch predicates, including their historical # choice to send explicit RKS (1,1,1) through the multi-k implementation. _gpw_multik_dispatch = bool( resolved_jk == PeriodicJKMethod.GPW and kpoints is not None and not ( method_upper != "RKS" and _explicit_unit_kmesh(kpoints) ) ) _gapw_multik_dispatch = bool( resolved_jk == PeriodicJKMethod.GAPW and kpoints is not None and not ( method_upper != "RKS" and _explicit_unit_kmesh(kpoints) ) ) if dft_plus_u and _gpw_multik_dispatch and method_upper != "RKS": raise NotImplementedError( "run_periodic_job: multi-k GPW DFT+U is currently implemented " "only for RKS; this request fails before dry-run/SCF." ) if dft_plus_u and _gapw_multik_dispatch and method_upper != "RKS": raise NotImplementedError( "run_periodic_job: multi-k GAPW DFT+U is currently implemented " "only for RKS; this request fails before dry-run/SCF." ) if ( dft_plus_u and method_upper == "RKS" and (_gpw_multik_dispatch or _gapw_multik_dispatch) and _periodic_functional_needs_exchange(functional, open_shell=False) ): _backend = "GPW" if _gpw_multik_dispatch else "GAPW" raise NotImplementedError( f"run_periodic_job: multi-k {_backend} RKS DFT+U does not " "implement hybrid or range-separated exact exchange. Use a " "pure functional such as LDA/PBE, or select a backend with a " "per-k exchange builder; this request fails before dry-run/SCF." ) if method_upper in ("ROHF", "ROKS"): _restricted_open_backends = ( PeriodicJKMethod.GPW, PeriodicJKMethod.BIPOLE, ) if method_upper == "ROHF": _restricted_open_backends += (PeriodicJKMethod.GDF,) if resolved_jk not in _restricted_open_backends: _available = "jk_method='gpw' and jk_method='bipole'" _gated = "GDF, GAPW, and the other" if method_upper == "ROHF": _available += ", plus jk_method='gdf'" _gated = "GAPW and the other" raise NotImplementedError( f"run_periodic_job: periodic {method_upper} is currently " f"wired for {_available} " "(Gamma and full Monkhorst-Pack meshes) on the " "maintained-preview 3D routes" f"; got jk_method={resolved_jk.value!r}. {_gated} " "restricted-open-shell backends remain gated." ) if int(system.dim) != 3: raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/" f"{resolved_jk.value} currently requires a " f"3D cell; got dim={system.dim}. The 1D/2D Coulomb gauges " "remain gated." ) if resolved_jk == PeriodicJKMethod.BIPOLE: _kpoints_kind = getattr(kpoints, "kind", None) if _kpoints_kind is not None and str(_kpoints_kind) != "monkhorst-pack": raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/bipole " "requires a complete Monkhorst-Pack mesh; explicit, " "band-path, generalized-regular, and database k-point " "lists do not carry the BvK torus needed by this route." ) _ro_ir_mapping = np.asarray( getattr(_requested_bloch_kmesh, "ir_mapping", []), dtype=int ).reshape(-1) _ro_stored_nk = _bloch_kmesh_size(_requested_bloch_kmesh) _ro_mesh = tuple( int(x) for x in getattr(_requested_bloch_kmesh, "mesh", (1, 1, 1)) ) if _ro_ir_mapping.size == 0 and int(np.prod(_ro_mesh)) != _ro_stored_nk: raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/bipole " "requires a complete Monkhorst-Pack mesh carrying its " f"dimensions; got mesh={_ro_mesh} for {_ro_stored_nk} " "stored k-points." ) if _requested_kmesh_size == 1 and not _kmesh_contains_gamma( _requested_bloch_kmesh ): raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/bipole does " "not support a lone non-Gamma twist. Use the Gamma point " "or a complete Monkhorst-Pack mesh." ) if ( method_upper == "ROHF" and resolved_jk == PeriodicJKMethod.GPW and (not _requested_gamma_only or _requested_kmesh_size != 1) ): raise NotImplementedError( "run_periodic_job: periodic ROHF/GPW is Gamma-only " "with one k point. Multi-k restricted-open-shell GPW needs " "per-k exact exchange and remains gated; multi-k ROHF is " "available via jk_method='bipole'." ) if ( method_upper == "ROKS" and _requested_kmesh_size == 1 and not _requested_gamma_only ): raise NotImplementedError( f"run_periodic_job: periodic ROKS/{resolved_jk.value} " "supports either the Gamma route or a true multi-k mesh. A " "single non-Gamma k point is not a Monkhorst-Pack mesh and " "has no maintained restricted-open-shell route." ) if dft_plus_u: raise NotImplementedError( "run_periodic_job: DFT+U is not implemented for periodic " f"{method_upper}/{resolved_jk.value}." ) if optimize or optimize_cell or hessian: raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/{resolved_jk.value} " "gradients, geometry " "optimization, cell optimization, and Hessians are not " "implemented." ) if tddft: raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/{resolved_jk.value} " "TD-DFT is not " "implemented." ) if coop_cohp: raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/{resolved_jk.value} " "COOP/COHP analysis is " "not validated." ) if restart_from is not None or read_from is not None or _is_read_request: raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/{resolved_jk.value} " "restart densities are " "not implemented; the route requires paired alpha/beta " "densities in one restricted-orbital gauge." ) if solver != "dense": raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/{resolved_jk.value} " "currently " "supports only " "solver='dense'." ) # Molden orbitals: the writable object is the Gamma block, which a # Gamma-centred mesh of any subdivision carries. Both routes below hand # back the k-point metadata `_gamma_index_for_multi_k` needs to locate # that block instead of assuming the first k-point is Gamma. # # GDF was briefly excluded here. Its Gamma block left a degenerate # frontier orbital complex after global-phase removal, which the writer # refused -- and because `plan.py` declares `.molden` a *guaranteed* # artefact, that refusal aborted finalization rather than dropping a # sidecar. The cause was not the route: the residual tracks degeneracy # exactly on both routes (non-degenerate orbitals sit at 1e-16 while # the degenerate Ne 2p pair sits at 1.1e-05 on BIPOLE and 3.9e-03 on # GDF), because one global phase per column cannot undo a rotation # *between* columns. The writer now re-expresses degenerate blocks on a # real basis of their own span, which is exact, so both routes export # S-orthonormal real orbitals to machine precision. The optional-row ask # in HANDOVER_OUTPUT_LOGGER.md still stands: it makes a genuine refusal # drop the sidecar instead of failing the job. _multik_gamma_orbital_routes = ( PeriodicJKMethod.BIPOLE, PeriodicJKMethod.GDF, ) _molden_sidecar_supported = _requested_gamma_only or ( resolved_jk in _multik_gamma_orbital_routes and _kmesh_contains_gamma(_requested_bloch_kmesh) ) _molden_sidecar_reason = ( "periodic Molden export writes the Gamma-block orbitals, so the " "k-mesh must contain an exact Gamma point and the route must carry " "k-point metadata locating it. A shifted mesh has no Gamma block, " "and orbitals at k != 0 are complex -- Molden's [MO] block holds " "only real coefficients." ) write_molden_file = _resolve_sidecar_request( write_molden_file, supported=_molden_sidecar_supported, option="write_molden_file", method=method_upper.lower(), caller="run_periodic_job", unavailable_reason=_molden_sidecar_reason, ) # Population: BIPOLE analyses the real-space lattice density and the # full SCF k-mesh (compute_bipole_population_summary), while χ-CCM uses # its dedicated finite-torus density and overlap contractions # (compute_aiccm2026dev_b_population_summary). Both are crystal # populations and need no Gamma restriction. Every other route falls # back to the molecular analysis of a single Bloch block, which is only # a population at Gamma. _population_sidecar_supported = _requested_gamma_only or resolved_jk in ( PeriodicJKMethod.BIPOLE, PeriodicJKMethod.AICCM2026DEV_B, ) _population_sidecar_reason = ( "the selected multi-k route has only a molecular Gamma-block " "population proxy. Full-k population output is implemented for " "BIPOLE through its lattice-density analysis and for " "aiccm2026dev-b through its finite-torus analysis; other routes " "require an exact single-Gamma result." ) write_population_file = _resolve_sidecar_request( write_population_file, supported=_population_sidecar_supported, option="write_population_file", method=method_upper.lower(), caller="run_periodic_job", unavailable_reason=_population_sidecar_reason, ) if _read_multik_request and resolved_jk not in ( PeriodicJKMethod.GDF, PeriodicJKMethod.RIJCOSX, PeriodicJKMethod.GPW, PeriodicJKMethod.GAPW, ): raise NotImplementedError( "multi-k periodic READ from in-memory results is wired for " "closed-shell GDF, RIJCOSX, GPW, and GAPW routes only; the " f"selected jk_method={resolved_jk.value!r} has no per-k density " "restart hook yet." ) if resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: from .periodic.exchange_convention import BVK_EWALD, exchange_q0_label _requested_b_exchange_q0 = exchange_q0_label(exchange_exxdiv) if _requested_b_exchange_q0 != BVK_EWALD: raise ValueError( "jk_method='aiccm2026dev-b' fixes the finite-torus " "exchange_q0 convention to 'bvk-ewald'; " f"exchange_exxdiv={exchange_exxdiv!r} requests " f"{_requested_b_exchange_q0!r}, which the χ-CCM-B drivers " "do not implement. Omit exchange_exxdiv or pass 'ewald'." ) if method_upper not in ("RHF", "RKS", "UHF", "UKS"): raise NotImplementedError( "jk_method='aiccm2026dev-b' implements RHF, RKS, UHF, and " f"UKS; got method={method_upper!r}" ) if dft_plus_u: raise NotImplementedError( "jk_method='aiccm2026dev-b' does not yet implement DFT+U" ) if optimize: raise NotImplementedError( "jk_method='aiccm2026dev-b' does not yet implement analytic " "χ-CCM-B nuclear gradients, so periodic geometry optimization " "is disabled. Use aiccm2026dev_b_gradient_status(result) to " "inspect the declared finite-torus convention and open " "gradient terms." ) if hessian: raise NotImplementedError( "jk_method='aiccm2026dev-b' does not yet implement χ-CCM-B " "force constants. The generic periodic Hessian path would " "differentiate a non-B unit-cell model, so it is disabled." ) if coop_cohp: raise NotImplementedError( "run_periodic_job: aiccm2026dev-b COOP/COHP analysis is not " "implemented with the converged finite-character " "Hamiltonian. The generic QVF property path rebuilds a " "fixed-cutoff Ewald/HF-like surrogate, so this request " "fails before SCF." ) if resolved_jk == PeriodicJKMethod.AICCM2026DEV_A_REAL_GAMMA: # EXPERIMENTAL: the neutral fitted-torus real-Γ control via the # per-unit-cell adapter (periodic_ccm_real_gamma). D89 terminology: # a representation control of the neutral fitted-torus Hamiltonian, # NOT the union-and-weight Γ-CCM construction. if method_upper not in ("RHF", "RKS", "UHF", "UKS"): raise NotImplementedError( "jk_method='real-gamma' implements RHF, RKS, UHF, and UKS; " f"got method={method_upper!r}. Double hybrids run via the " "API driver " "vibeqc.periodic.ccm.direct.run_ccm_double_hybrid_direct." ) if int(system.dim) != 3: raise NotImplementedError( "jk_method='real-gamma' requires a 3-D periodic system " f"(dim=3); got dim={int(system.dim)}. The direct-torus " "route fails closed for dim<3 by design (no gauge-" "consistent neutral-torus Hamiltonian; see " "vibeqc.periodic.ccm.direct)." ) if dft_plus_u: raise NotImplementedError( "jk_method='real-gamma' does not implement DFT+U" ) if optimize and optimize_cell: raise NotImplementedError( "jk_method='real-gamma': variable-cell relaxation is not " "implemented -- the route has no analytic stress, and its " "relaxation is fixed-lattice (unit-cell positions only). " "Use optimize=True with optimize_cell=False." ) # Smearing needs no extra guard here: _validate_smearing_dispatch # already refuses this route generically (zero-temperature Aufbau # supercell-Γ SCF), so a smeared optimize never reaches dispatch. if hessian: raise NotImplementedError( "jk_method='real-gamma' does not implement force constants. " "The generic periodic Hessian path would differentiate a " "different unit-cell model, so it is disabled." ) # Smearing rejects through _validate_smearing_dispatch's generic # not-wired branch (zero-temperature Aufbau supercell-Γ SCF only). if resolved_jk == PeriodicJKMethod.BIPOLE and int(system.dim) < 3: # Fail fast at the API level: the supported exact Ewald-J split relies # on a 3-D Ewald/Madelung lattice sum that is undefined for a true # 1-D/2-D Coulomb problem. raise NotImplementedError( f"jk_method='bipole' requires a 3-D periodic system (dim=3); got " f"dim={int(system.dim)}. The exact Ewald-J route uses a 3-D " "Ewald/Madelung lattice sum that is not a low-dimensional Coulomb " "model. For a 1-D wire use jk_method='auto' or 'gdf'; for a 2-D " "surface use jk_method='auto'/'slab_ewald_2d', or explicit GDF " "for its supported closed-shell slab envelope." ) if resolved_jk == PeriodicJKMethod.BIPOLE: if optimize_cell: raise NotImplementedError( "run_periodic_job: BIPOLE variable-cell optimization is " "unavailable. The historical strain convention and coupled " "atom/cell convergence were not certified on one terminal " "geometry. Use optimize=True with optimize_cell=False for " "fixed-cell atomic relaxation." ) unsupported_post_scf = [] if hessian: unsupported_post_scf.append("hessian") if tddft: unsupported_post_scf.append("tddft") if coop_cohp: unsupported_post_scf.append("coop_cohp") if unsupported_post_scf: raise NotImplementedError( "run_periodic_job: BIPOLE " + ", ".join(unsupported_post_scf) + " is not implemented with the converged periodic " "Hamiltonian. The previous generic post-SCF paths rebuilt " "a molecular or fixed-cutoff surrogate operator, so these " "requests now fail before SCF." ) if (optimize or optimize_cell) and _is_read_request: raise NotImplementedError( "run_periodic_job: BIPOLE optimization cannot preserve a " "READ restart across displaced geometries. Run the restart " "single point first, then begin optimization from a " "geometry-defined SAD/HCORE/ATOMSPIN guess." ) _dispersion_disabled = dispersion in (None, False) or ( isinstance(dispersion, str) and dispersion.strip().lower() in ("", "none", "false") ) if (optimize or optimize_cell) and not _dispersion_disabled: raise NotImplementedError( "run_periodic_job: BIPOLE optimization with periodic " "dispersion is not implemented. The optimizer does not yet " "differentiate or add the selected dispersion correction at " "each displaced geometry, so the combination fails before " "the initial SCF instead of optimizing a different surface." ) _attached_symmetry_requested = bool( symmetry not in (False, None, "off", "false", "none") or reduce_to_primitive or getattr(system, "symmetry", None) is not None ) if (optimize or optimize_cell) and ( symmetry_stabilize or symmetry_reduce_fock is True or ( symmetry_reduce_fock is None and _attached_symmetry_requested ) ): raise NotImplementedError( "run_periodic_job: BIPOLE optimization with Fock symmetry " "stabilization/reduction is not implemented. Atomic or cell " "displacements can break the input symmetry, and the " "optimizer has no symmetry-constrained coordinate space." ) if (optimize or optimize_cell) and resolved_jk not in ( PeriodicJKMethod.BIPOLE, PeriodicJKMethod.GDF, PeriodicJKMethod.AICCM2026DEV_A_REAL_GAMMA, ): raise NotImplementedError( "run_periodic_job: geometry optimization is not wired to the " f"executed {resolved_jk.value!r} Hamiltonian. Only BIPOLE, the " "captured analytic-gradient GDF route, and the real-Γ " "direct-torus route (EXPERIMENTAL, parity-verified per run) " "preserve their single-point objective at every displaced " "geometry; this request fails before SCF instead of falling " "through to BIPOLE forces." ) if ( resolved_jk == PeriodicJKMethod.BIPOLE and (symmetry_stabilize or symmetry_reduce_fock is True) and symmetry in (False, None, "off", "false", "none") and getattr(system, "symmetry", None) is None ): raise ValueError( "run_periodic_job: explicit BIPOLE Fock symmetry requires an " "attached symmetry model. Pass symmetry='attach' (or attach " "validated operations to the PeriodicSystem) before requesting " "symmetry_stabilize/symmetry_reduce_fock." ) # --- Space-group-reduced multi-k GDF exchange (symmetry_reduce_k) --- # Every precondition fails closed. A silently-ignored performance flag # is worse than a refusal: the run looks reduced, costs the full n_k^2, # and the memory line the flag exists to demonstrate would be a lie. if symmetry_reduce_k: if resolved_jk != PeriodicJKMethod.GDF: raise NotImplementedError( "run_periodic_job: symmetry_reduce_k is wired to the " "multi-k GDF exchange build (jk_method='gdf'); got " f"{resolved_jk.value!r}. The reduction acts on the " "per-(k_i,k_j) cderi cache, which only the fitted-GDF " "exchange builds -- RIJCOSX/COSX assembles K in real space " "and never touches it, and the plane-wave routes have no " "such cache at all." ) if int(system.dim) != 3: raise NotImplementedError( "run_periodic_job: symmetry_reduce_k is a bulk (dim=3) " f"route; got dim={int(system.dim)}. The wedge comes from " "the 3-D space group of the crystal, and the slab / " "polymer GDF paths run their own truncated-metric gauge." ) if not _requested_true_multik: raise NotImplementedError( "run_periodic_job: symmetry_reduce_k needs a true multi-k " "Monkhorst-Pack mesh to reduce; this job samples " f"{_requested_kmesh_size} k-point(s). At Gamma the wedge is " "the whole mesh and there is nothing to save." ) if method_upper not in ("RHF", "RKS", "UHF", "UKS"): raise NotImplementedError( "run_periodic_job: symmetry_reduce_k is implemented on the " "closed-shell (RHF/RKS) and unrestricted (UHF/UKS) multi-k " f"GDF drivers; got method={method_upper!r}. The " "spin-restricted open-shell GDF driver has no ibz_native " "argument surface (see periodic_rohf_gdf.py)." ) if symmetry in (False, None, "off", "false", "none") and ( getattr(system, "symmetry", None) is None ): raise ValueError( "run_periodic_job: symmetry_reduce_k requires an attached " "symmetry model -- there is no irreducible wedge without " "one. Pass symmetry='attach' (or attach validated " "operations to the PeriodicSystem) alongside " "symmetry_reduce_k=True." ) if optimize or optimize_cell: raise NotImplementedError( "run_periodic_job: symmetry_reduce_k cannot be combined " "with geometry optimization. The wedge-native SCF converges " "the k-transported exchange, whose analytic gradient is not " "the captured single-point objective (periodic_k_gdf.py " "rejects compute_gradient=True with ibz_native=True), and " "displacements can break the input symmetry outright." ) if ( resolved_jk == PeriodicJKMethod.BIPOLE and method_upper in ("UHF", "UKS") and (symmetry_stabilize or symmetry_reduce_fock is True) ): _broken_symmetry_controls = [] if atomic_spins is not None: _broken_symmetry_controls.append("atomic_spins") if use_mom: _broken_symmetry_controls.append("use_mom") if _spinlock_mode != SpinlockMode.OFF: _broken_symmetry_controls.append("spinlock") if dft_plus_u: _broken_symmetry_controls.append("dft_plus_u") if _is_read_request: _broken_symmetry_controls.append("READ") if initial_guess.strip().upper() == "PATOM": _broken_symmetry_controls.append("PATOM") if _broken_symmetry_controls: raise NotImplementedError( "run_periodic_job: explicit BIPOLE Fock symmetry cannot " "be combined with an electronically broken or unverified " "state control (" + ", ".join(_broken_symmetry_controls) + "). Structural symmetry alone does not prove spin-density " "covariance; disable Fock symmetry for this state." ) _bipole_cutoff_bohr = ( None if bipole_cutoff_bohr is None else float(bipole_cutoff_bohr) ) _bipole_nuclear_cutoff_bohr = ( None if bipole_nuclear_cutoff_bohr is None else float(bipole_nuclear_cutoff_bohr) ) if _bipole_cutoff_bohr is not None or _bipole_nuclear_cutoff_bohr is not None: if resolved_jk != PeriodicJKMethod.BIPOLE: raise NotImplementedError( "run_periodic_job: bipole_cutoff_bohr and " "bipole_nuclear_cutoff_bohr apply only to " "jk_method='bipole'. Use cutoff_ha for GPW/GAPW grid " "routes, rsgdf_ke_cutoff/rsgdf_tail_ke_cutoff for GDF, " "or select jk_method='bipole'." ) if _bipole_cutoff_bohr is not None and _bipole_cutoff_bohr <= 0.0: raise ValueError( "run_periodic_job: bipole_cutoff_bohr must be positive " f"(bohr); got {bipole_cutoff_bohr!r}." ) if ( _bipole_nuclear_cutoff_bohr is not None and _bipole_nuclear_cutoff_bohr <= 0.0 ): raise ValueError( "run_periodic_job: bipole_nuclear_cutoff_bohr must be " f"positive (bohr); got {bipole_nuclear_cutoff_bohr!r}." ) if _bipole_nuclear_cutoff_bohr is None: _bipole_nuclear_cutoff_bohr = _bipole_cutoff_bohr _bipole_exact_zone_bohr = ( None if bipole_exact_zone_bohr is None else float(bipole_exact_zone_bohr) ) if _bipole_exact_zone_bohr is not None: # BIPOLE-EXACT-ZONE increment 1: restricted exact bielectronic # zone (RHF/RKS drivers). Deeper validation (zone < cutoff, # corrected split + padded SR path, no SYM3b reduction) happens # in resolve_bipole_exact_zone inside the driver. if resolved_jk != PeriodicJKMethod.BIPOLE: raise NotImplementedError( "run_periodic_job: bipole_exact_zone_bohr applies only to " "jk_method='bipole'." ) if method_upper not in ("RHF", "RKS", "UHF", "UKS"): raise NotImplementedError( "run_periodic_job: bipole_exact_zone_bohr is wired for " "the RHF/RKS/UHF/UKS BIPOLE drivers; ROHF/ROKS route to " "the EWALD_3D engine, which has no exact-zone knob. Omit " "it for those methods." ) if _bipole_exact_zone_bohr <= 0.0: raise ValueError( "run_periodic_job: bipole_exact_zone_bohr must be positive " f"(bohr); got {bipole_exact_zone_bohr!r}." ) _rijcosx_closed_shell_multik = ( resolved_jk == PeriodicJKMethod.RIJCOSX and method_upper in ("RHF", "RKS") and _requested_true_multik ) if ( rsgdf_tail_ke_cutoff is not None and resolved_jk == PeriodicJKMethod.AICCM2026DEV_B ): _resolve_fitted_rsgdf_tail( _resolve_aiccm2026dev_b_backend(aiccm_backend), gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, where="run_periodic_job", ) if ( rsgdf_tail_ke_cutoff is not None and resolved_jk != PeriodicJKMethod.GDF and resolved_jk != PeriodicJKMethod.AICCM2026DEV_B and not _rijcosx_closed_shell_multik ): raise NotImplementedError( "run_periodic_job: rsgdf_tail_ke_cutoff is currently supported only " "with jk_method='gdf', fitted jk_method='aiccm2026dev-b', or " "closed-shell true multi-k jk_method='rijcosx'. Other J/K routes " "would ignore the high-|G| RSGDF tail correction, so this " "combination fails closed." ) _executed_jk_method = resolved_jk.value if not dft_plus_u: _dft_plus_u_route = "none" elif resolved_jk == PeriodicJKMethod.BIPOLE: _dft_plus_u_route = ( f"bipole_{method_upper.lower()}_" + ("multi_k" if _requested_true_multik else "gamma") ) elif resolved_jk == PeriodicJKMethod.GDF and _requested_true_multik: _dft_plus_u_route = f"gdf_{method_upper.lower()}_multi_k" elif resolved_jk == PeriodicJKMethod.RIJCOSX and _requested_true_multik: _dft_plus_u_route = f"rijcosx_{method_upper.lower()}_multi_k" elif resolved_jk == PeriodicJKMethod.GPW: _dft_plus_u_route = ( f"gpw_{method_upper.lower()}_" + ("multi_k" if _gpw_multik_dispatch else "gamma") ) elif resolved_jk == PeriodicJKMethod.GAPW: _dft_plus_u_route = ( f"gapw_{method_upper.lower()}_" + ("multi_k" if _gapw_multik_dispatch else "gamma") ) else: _dft_plus_u_route = "none" _dftu_native_routes = ( PeriodicJKMethod.BIPOLE, PeriodicJKMethod.GPW, PeriodicJKMethod.GAPW, ) _dftu_gdf_native_route = ( dft_plus_u and resolved_jk == PeriodicJKMethod.GDF and method_upper in ("RHF", "RKS") and _requested_true_multik ) if resolved_jk == PeriodicJKMethod.GDF and int(system.dim) == 2: unsupported_slab_gdf: list[str] = [] if method_upper not in ("RHF", "RKS"): unsupported_slab_gdf.append("open-shell method") # optimize=True is supported since 2026-07-30 (G-PBC-002 § 6 # rung 5): the slab dispatch below runs through the GDF # optimizer-objective capture, so the relaxation re-runs the # identical slab driver with compute_gradient=True. Only # variable-cell relaxation stays closed (no slab GDF analytic # stress exists). if optimize_cell: unsupported_slab_gdf.append( "variable-cell optimization (no slab GDF analytic stress)" ) if hessian: unsupported_slab_gdf.append("Hessian") if tddft: unsupported_slab_gdf.append("TDDFT/response") if dft_plus_u: unsupported_slab_gdf.append("DFT+U") if symmetry_stabilize or symmetry_reduce_fock: unsupported_slab_gdf.append("SCF symmetry reduction") if restart_from is not None or read_from is not None or _is_read_request: unsupported_slab_gdf.append("density restart") if unsupported_slab_gdf: raise NotImplementedError( "run_periodic_job: slab GDF currently supports closed-shell " "RHF/RKS single-point and atomic-relaxation calculations " "on a full Gamma-centered mesh only; unsupported " "option(s): " + ", ".join(unsupported_slab_gdf) + "." ) if resolved_jk == PeriodicJKMethod.GDF and optimize and optimize_cell: # No GDF analytic stress exists, and mixing a BIPOLE-stress cell # step into a GDF energy surface would relax the wrong objective # (the silent-objective-swap bug the GDF optimizer wiring fixes). raise NotImplementedError( "run_periodic_job: optimize_cell=True is not implemented on " "the GDF route -- there is no GDF analytic stress. For " "variable-cell relaxation use a route with a certified stress " "and coupled atom/cell convergence implementation." ) if resolved_jk == PeriodicJKMethod.GDF and optimize: _gdf_dispersion_disabled = dispersion in (None, False) or ( isinstance(dispersion, str) and dispersion.strip().lower() in ("", "none", "false") ) if not _gdf_dispersion_disabled: raise NotImplementedError( "run_periodic_job: GDF optimization with periodic dispersion " "is not implemented. The captured optimizer reruns the bare " "GDF Hamiltonian and does not add or differentiate D3 at each " "candidate geometry, so this combination fails before SCF " "instead of optimizing a different surface." ) _dftu_rijcosx_native_route = ( dft_plus_u and resolved_jk == PeriodicJKMethod.RIJCOSX and method_upper in ("RHF", "RKS") and _requested_true_multik ) if ( dft_plus_u and _jk_method_explicit and resolved_jk not in _dftu_native_routes and not _dftu_gdf_native_route and not _dftu_rijcosx_native_route ): raise NotImplementedError( "run_periodic_job: dft_plus_u with explicit " f"jk_method={_jk_requested_label!r} is not wired. Supported " "explicit +U routes are jk_method='bipole', Gamma " "jk_method='gpw' for RHF/RKS/UHF/UKS, multi-k GPW RKS, " "closed-shell jk_method='gapw', and closed-shell multi-k " "jk_method='gdf'/'rijcosx'. Omit jk_method to let AUTO choose " "a supported backend, or choose one explicitly." ) # --- Cell reduction (symmetry) ------------------------------------ system_original_info: Optional[str] = None # Resolve the 'symmetry' kwarg into a reduction flag. _sym_val = str(symmetry).lower() if isinstance(symmetry, str) else "" _do_reduce = ( reduce_to_primitive or symmetry is True or _sym_val in ("auto", "reduce") ) _do_attach = _do_reduce or _sym_val == "attach" if _do_attach and system.symmetry is None: attach_symmetry(system, symprec=symmetry_precision) if _do_reduce: try: system_prim, sg_input = _reduce_system_to_primitive( system, symprec=symmetry_precision, ) except ValueError: # Already primitive -- just attach symmetry and continue if system.symmetry is None: attach_symmetry(system, symprec=symmetry_precision) else: if system.symmetry is None: attach_symmetry(system, symprec=symmetry_precision) if dft_plus_u: raise NotImplementedError( "run_periodic_job: dft_plus_u cannot be combined with " "primitive-cell reduction yet. Hubbard atom indices " "refer to the input cell, and no symmetry-aware mapping " "to primitive equivalent sites is defined. Reduce the " "structure first and specify +U on that primitive cell, " "or disable reduction." ) if atomic_spins is not None: raise NotImplementedError( "run_periodic_job: atomic_spins cannot be combined with " "primitive-cell reduction yet. The tags refer to input-" "cell atom order, and no symmetry-aware mapping to " "primitive equivalent sites is defined. Reduce the " "structure first and tag that primitive cell, or disable " "reduction." ) system_original_info = _build_primitive_summary( system, system_prim, sg_input, ) basis = BasisSet(system_prim.unit_cell_molecule(), basis.name) kpoints = _remap_kpoints_after_primitive_reduction( system, system_prim, kpoints, ) system = system_prim # Cover an already-primitive odd-electron cell as well as the reduced one. # Lower-level SCF drivers read PeriodicSystem.multiplicity directly, not # the auto-repaired Molecule used to construct the basis. system = _system_with_valid_default_multiplicity(system) _validate_closed_shell_electron_count(system, method_upper) # --- Resolve the irreducible wedge (symmetry_reduce_k) ------------- # Resolved HERE: after primitive reduction has settled `system` and # remapped `kpoints`, so the count belongs to the cell that runs, and # before the dry-run short-circuit, so `vq` submit preflight sizes a # reduced job at n_IBZ x n_k rather than n_k^2 (sizing it at the full # mesh is the same defect as aborting it there). # # Deliberately NOT inside the estimate's try/except: when the caller # asked for the reduction, an unresolvable wedge is a hard error, not # a swallowed exception that quietly reverts to the full-mesh number. _symmetry_reduce_k_ibz: Optional[int] = None if symmetry_reduce_k: _symmetry_reduce_k_ibz = _ibz_kpoint_count(system, kpoints) if _symmetry_reduce_k_ibz is None: raise ValueError( "run_periodic_job: symmetry_reduce_k=True but the " "irreducible wedge of the requested k-mesh could not be " "resolved. It needs an attached symmetry model (pass " "symmetry='attach') and a Monkhorst-Pack mesh spec the " "reducer understands (a mesh tuple, a scalar, or a " "KPoints/BlochKMesh carrying `mesh`); a custom k-list " "quadrature has no spglib stars." ) if fmixing_percent is not None and fock_mixing is not None: raise ValueError( "run_periodic_job: pass either fmixing_percent= or fock_mixing=, " "not both" ) if fock_mixing is not None: fock_mixing_value = float(fock_mixing) if not (0.0 <= fock_mixing_value < 1.0): raise ValueError( "run_periodic_job: fock_mixing must be in [0, 1); " f"got {fock_mixing}" ) fmixing_percent = 100.0 * fock_mixing_value fock_mixing = 0.0 if fmixing_percent is not None: fock_mixing = float(fmixing_percent) / 100.0 if not (0.0 <= fock_mixing < 1.0): raise ValueError( "run_periodic_job: fmixing_percent must be in [0, 100); " f"got {fmixing_percent}" ) output_stem = Path(os.fspath(output)) out_path = stem_sibling(output_stem, ".out") molden_path = stem_sibling(output_stem, ".molden") xsf_path = stem_sibling(output_stem, ".xsf") out_path.parent.mkdir(parents=True, exist_ok=True) if qvf_wannier_centers: if not output_qvf: raise ValueError( "qvf_wannier_centers=True requires output_qvf=True so the " "x_ccm.wannier_centers overlay has a container to write into" ) if resolved_jk != PeriodicJKMethod.AICCM2026DEV_B and not _requested_gamma_only: raise NotImplementedError( "qvf_wannier_centers=True needs an explicit localization " "convention. Two are available: jk_method='aiccm2026dev-b' " "(finite torus), or any route sampled at Gamma only, where " "the occupied Bloch functions span the whole occupied space " "of the cell and localizing them is a well-defined Wannier " "construction for it. A multi-k run is neither: localizing " "only the Gamma orbitals would sample one k point and call " "the result a Wannier function. See " "handovers/HANDOVER_IBO.md section Milestone 3 for the " "multi-k scheme (Zicovich-Wilson, doi:10.1063/1.1415745), " "which is not implemented." ) if coop_cohp and not output_qvf: # The COOP/COHP analysis runs inside the DOS/QVF block below and its # only sink is the QVF archive (dos.coop / dos.cohp). With QVF output # off that block never executes, so the request used to be dropped # silently: converged run, no .qvf, not one COOP/COHP line in the # .out (IID 195). Fail closed before SCF, the same way # qvf_wannier_centers does just above for the same reason. raise ValueError( "coop_cohp=True requires output_qvf=True so the dos.coop and " "dos.cohp sections have a container to write into; the analysis " "has no .out or sidecar rendering of its own" ) _population_variant = ( "bipole" if resolved_jk == PeriodicJKMethod.BIPOLE else ( "aiccm2026dev-b" if resolved_jk == PeriodicJKMethod.AICCM2026DEV_B else "standard" ) ) real_plan = OutputPlan.from_run_job_kwargs( output=output_stem, method=method_upper, basis=basis.name, functional=functional, write_molden_file=write_molden_file, write_xyz=write_xyz_file, write_poscar=write_poscar_file, write_xsf_structure=write_xsf_structure_file, write_density_xsf=write_density, write_cif=write_cif_file, write_population=write_population_file, population_variant=_population_variant, citations=citations, crash_dump=False, output_qvf=output_qvf, job_kind="periodic_scf", ) if resolved_jk == PeriodicJKMethod.GDF and method_upper in ( "RKS", "UKS", "ROKS", ): reject_periodic_gdf_unsupported_functional( Functional( str(functional or "pbe"), 2 if method_upper == "UKS" else 1, ), where="run_periodic_job", ) # Queue/preflight validation must reject the same BIPOLE envelopes as # execution. These checks deliberately run before the dry-run manifest. if resolved_jk == PeriodicJKMethod.BIPOLE: from .pbc_bipole_common import ( reject_bipole_ecp_options, reject_bipole_fractional_legacy_gauge, reject_bipole_quartet_far_field, reject_bipole_unsupported_ks_functional, validate_bipole_kmesh, ) if method_upper in ("RKS", "UKS"): reject_bipole_unsupported_ks_functional( Functional( str(functional or "pbe"), 2 if method_upper == "UKS" else 1, ), driver="run_periodic_job", ) if exchange_exxdiv not in ("ewald", "none"): raise ValueError( "run_periodic_job: exchange_exxdiv must be 'ewald' or " f"'none'; got {exchange_exxdiv!r}." ) if int(max_iter) < 1: raise ValueError( "run_periodic_job: BIPOLE max_iter must be at least 1." ) if damping is not None and not (0.0 <= float(damping) < 1.0): raise ValueError( "run_periodic_job: BIPOLE damping must be in [0, 1); " f"got {damping!r}." ) if sr_image_precision is not None: _preflight_sr_precision = float(sr_image_precision) if not (0.0 < _preflight_sr_precision < 1.0): raise ValueError( "run_periodic_job: sr_image_precision must be in (0, 1) " f"or None; got {sr_image_precision!r}." ) if use_oda and use_diis: raise ValueError( "run_periodic_job: use_oda and use_diis are mutually exclusive." ) if use_oda and not (0.0 < float(oda_trust_lambda_max) <= 1.0): raise ValueError( "run_periodic_job: oda_trust_lambda_max must be in (0, 1]; " f"got {oda_trust_lambda_max!r}." ) if use_oda: raise NotImplementedError( "run_periodic_job: use_oda (BIPOLE ODA) is unavailable " "because a " "mixed line-search density has no single orbital " "representation (and the unrestricted slope omits one spin " "direction); use DIIS instead." ) if _bipole_exact_zone_bohr is not None: if use_exchange_ewald_split is False: raise ValueError( "run_periodic_job: bipole_exact_zone_bohr requires the " "corrected Ewald exchange split; " "use_exchange_ewald_split=False is incompatible." ) if sr_image_precision is None: raise ValueError( "run_periodic_job: bipole_exact_zone_bohr requires the " "padded short-range image path; sr_image_precision=None " "is incompatible." ) if symmetry_stabilize or symmetry_reduce_fock is True: raise NotImplementedError( "run_periodic_job: bipole_exact_zone_bohr is not wired " "for explicit Fock symmetry stabilization or reduction." ) _preflight_bipole_cutoff = float( _bipole_cutoff_bohr if _bipole_cutoff_bohr is not None else PeriodicRHFOptions().lattice_opts.cutoff_bohr ) if not _bipole_exact_zone_bohr < _preflight_bipole_cutoff: raise ValueError( "run_periodic_job: bipole_exact_zone_bohr must be " "strictly below the BIPOLE operator cutoff " f"({_bipole_exact_zone_bohr!r} vs " f"{_preflight_bipole_cutoff!r})." ) if method_upper in ("ROHF", "ROKS"): # These methods use the dedicated corrected-Ewald restricted-open # engine, not the four direct BIPOLE drivers. Validate every # BIPOLE-only control before dry-run so queue preflight cannot # certify a request that live dispatch will reject or ignore. _unsupported_open_shell_knobs = { "use_oda": bool(use_oda), "use_mom": bool(use_mom), "use_multipole_far_field": bool(use_multipole_far_field), "symmetry_stabilize": bool(symmetry_stabilize), "symmetry_reduce_fock": symmetry_reduce_fock is True, "ewald_omega": ewald_omega is not None, "ewald_precision": float(ewald_precision) != 1.0e-8, "use_exchange_ewald_split": use_exchange_ewald_split is False, "exchange_exxdiv": exchange_exxdiv != "ewald", "fock_mixing/fmixing_percent": fock_mixing != 0.0, } _set_knobs = [ key for key, requested in _unsupported_open_shell_knobs.items() if requested ] if _set_knobs: raise NotImplementedError( f"run_periodic_job: periodic {method_upper}/bipole runs " "on the corrected Ewald-exchange EWALD_3D engine, which " "does not implement these explicitly requested options: " + ", ".join(sorted(_set_knobs)) + ". Drop them or use a supported method/backend." ) reject_bipole_quartet_far_field( use_multipole_far_field, driver="run_periodic_job", ) reject_bipole_ecp_options( SimpleNamespace(), driver="run_periodic_job", basis=basis, system=system, ) _preflight_ecp = _resolve_ecp_data(system, basis) if ( len(_preflight_ecp[0]) > 0 or len(_preflight_ecp[1]) > 0 or int(_preflight_ecp[3] or 0) != 0 ): raise NotImplementedError( "run_periodic_job: ECP-bearing bases are not implemented on " "jk_method='bipole'." ) _preflight_smearing = smearing _preflight_smearing_engaged = ( smearing is not None or smearing_temperature is not _SMEARING_UNSET or smearing_metallic is not None or smearing_band_gap_hartree is not None ) if not _preflight_smearing_engaged: _preflight_smearing = getattr(kpoints, "smearing", None) _preflight_smearing_engaged = _preflight_smearing is not None _preflight_temperature_arg = ( 0.0 if smearing_temperature is _SMEARING_UNSET else smearing_temperature ) if _preflight_smearing is not None: if _preflight_temperature_arg not in (0.0, None): raise ValueError( "run_periodic_job: pass either smearing= or " "smearing_temperature=, not both" ) if not isinstance(_preflight_smearing, SmearingOptions): raise TypeError( "run_periodic_job: smearing must be a SmearingOptions " "instance" ) _preflight_smearing_temperature = float( _preflight_smearing.temperature ) _preflight_smearing_label = _preflight_smearing.flavor else: _preflight_smearing_resolution = resolve_smearing_temperature( _preflight_temperature_arg, unit=smearing_unit, method=smearing_method, metallic=smearing_metallic, band_gap_hartree=smearing_band_gap_hartree, n_electrons=system.n_electrons(), ) _preflight_smearing_temperature = float( _preflight_smearing_resolution.temperature ) _preflight_smearing_label = _preflight_smearing_resolution.method _preflight_smearing_label = str( _preflight_smearing_label ).strip().lower().replace("_", "-") _validate_smearing_dispatch( method=method_upper, jk_method=resolved_jk, smearing_temperature=_preflight_smearing_temperature, kpoints=kpoints, ) if ( _preflight_smearing_temperature > 0.0 and _preflight_smearing_label not in ("fermi-dirac", "mermin") ): raise NotImplementedError( "run_periodic_job: BIPOLE currently implements only " "Fermi-Dirac occupations (including the Mermin free-energy " "label); the requested flavour would otherwise be executed " "as Fermi-Dirac." ) _preflight_bz = bz_integration if _preflight_bz is _BZ_INTEGRATION_UNSET: _preflight_bz = getattr(kpoints, "bz_integration", None) if _preflight_bz is not None: _preflight_bz = str(_preflight_bz).strip().lower() if _preflight_bz not in ("smearing", "gilat"): raise ValueError( "run_periodic_job: bz_integration must be None, " f"'smearing', or 'gilat'; got {_preflight_bz!r}." ) if method_upper == "RHF" and _preflight_bz == "gilat": raise NotImplementedError( "run_periodic_job: BIPOLE RHF does not implement " "bz_integration='gilat'; use RKS/UHF/UKS or omit it." ) if method_upper in ("ROHF", "ROKS") and _preflight_bz == "gilat": raise NotImplementedError( "run_periodic_job: Gilat-Raubenheimer BZ integration is " f"not implemented for periodic {method_upper}." ) if ( _preflight_bz == "gilat" and _preflight_smearing_temperature > 0.0 ): raise ValueError( "run_periodic_job: bz_integration='gilat' is a T=0 " "integrator and cannot be combined with finite-temperature " "smearing." ) if _preflight_bz == "gilat": _gilat_ir = np.asarray( getattr(_requested_bloch_kmesh, "ir_mapping", []), dtype=int ).reshape(-1) _gilat_mesh = tuple( int(x) for x in getattr( _requested_bloch_kmesh, "mesh", (1, 1, 1) ) ) _gilat_stored = _bloch_kmesh_size(_requested_bloch_kmesh) if ( _gilat_ir.size == 0 and int(np.prod(_gilat_mesh)) != _gilat_stored ): raise NotImplementedError( "run_periodic_job: BIPOLE Gilat-Raubenheimer " "integration requires a complete Monkhorst-Pack mesh " "carrying its grid dimensions; arbitrary explicit or " "band-path k-point lists are not regular integration " "grids." ) # Resolve the final BIPOLE convergence strategy before dry-run for # every single-point job, not only optimizations. AUTO can introduce # finite-temperature occupations for metallic KS systems; the # fractional/legacy-gauge capability guard must see that same final # temperature that live dispatch installs on ``opts``. _preflight_convergence = resolve_convergence_strategy( system, method=method_upper, convergence=convergence, ks_driver_fock_mixing_floor=not bool(use_diis), explicit={ "fock_mixing": ( fock_mixing if fmixing_percent is not None else None ), "level_shift": ( float(level_shift) if level_shift is not None else None ), "damping": float(damping) if damping is not None else None, "smearing_temperature": ( _preflight_smearing_temperature if _preflight_smearing_engaged else None ), }, ) _preflight_convergence = _filter_bipole_restricted_open_convergence( _preflight_convergence, method_upper, ) _preflight_final_temperature = float( _preflight_convergence.value("smearing_temperature") ) _validate_smearing_dispatch( method=method_upper, jk_method=resolved_jk, smearing_temperature=_preflight_final_temperature, kpoints=kpoints, ) _preflight_fractional_occupations = bool( ( method_upper in ("RKS", "UKS") and ( _preflight_final_temperature > 0.0 or _preflight_bz == "gilat" ) ) or (method_upper == "UHF" and _preflight_bz == "gilat") ) if _preflight_fractional_occupations: # Fractional occupations need the corrected gauge. It is active # by default for a complete MP/IBZ mesh, but an ad-hoc multi-k # list makes AUTO fall back to the nonstationary legacy gauge. validate_bipole_kmesh( _requested_bloch_kmesh, driver="run_periodic_job fractional occupations", require_complete=True, ) reject_bipole_fractional_legacy_gauge( use_ewald_j_split=True, exchange_split_active=use_exchange_ewald_split is not False, fractional_occupations=True, driver="run_periodic_job", ) if optimize: _, _, _optimizer_true_multik, _ = validate_bipole_kmesh( _requested_bloch_kmesh, driver="run_periodic_job optimize", require_complete=True, ) if ( _optimizer_true_multik and use_exchange_ewald_split is False ): raise NotImplementedError( "run_periodic_job: multi-k BIPOLE optimization requires " "the corrected Ewald exchange split. The legacy gauge " "does not define a stationary geometry objective." ) if ( _preflight_final_temperature > 0.0 ): raise NotImplementedError( "run_periodic_job: finite-temperature BIPOLE " "optimization is unavailable until optimization and " "trajectory/QVF results distinguish the Mermin " "free-energy objective from internal energy. Run a " "fixed-geometry smeared SCF or optimize at T=0." ) if resolved_jk == PeriodicJKMethod.GDF and optimize: # Resolve the same final temperature that live GDF dispatch installs # after AUTO convergence classification. The analytic gradient is # dA/dR at finite T, but OptimizeResult and the optimized-geometry/QVF # schema currently expose its scalar only as generic energy. Until # the objective kind is represented explicitly, fail before dry-run # rather than publishing A as E. _gdf_preflight_smearing = smearing _gdf_preflight_smearing_engaged = ( smearing is not None or smearing_temperature is not _SMEARING_UNSET or smearing_metallic is not None or smearing_band_gap_hartree is not None ) if not _gdf_preflight_smearing_engaged: _gdf_preflight_smearing = getattr(kpoints, "smearing", None) _gdf_preflight_smearing_engaged = ( _gdf_preflight_smearing is not None ) _gdf_preflight_temperature_arg = ( 0.0 if smearing_temperature is _SMEARING_UNSET else smearing_temperature ) if _gdf_preflight_smearing is not None: if _gdf_preflight_temperature_arg not in (0.0, None): raise ValueError( "run_periodic_job: pass either smearing= or " "smearing_temperature=, not both" ) if not isinstance(_gdf_preflight_smearing, SmearingOptions): raise TypeError( "run_periodic_job: smearing must be a SmearingOptions " "instance" ) _gdf_preflight_temperature = float( _gdf_preflight_smearing.temperature ) else: _gdf_preflight_resolution = resolve_smearing_temperature( _gdf_preflight_temperature_arg, unit=smearing_unit, method=smearing_method, metallic=smearing_metallic, band_gap_hartree=smearing_band_gap_hartree, n_electrons=system.n_electrons(), ) _gdf_preflight_temperature = float( _gdf_preflight_resolution.temperature ) _gdf_preflight_convergence = resolve_convergence_strategy( system, method=method_upper, convergence=convergence, ks_driver_fock_mixing_floor=False, explicit={ "fock_mixing": ( fock_mixing if fmixing_percent is not None else None ), "level_shift": ( float(level_shift) if level_shift is not None else None ), "damping": float(damping) if damping is not None else None, "smearing_temperature": ( _gdf_preflight_temperature if _gdf_preflight_smearing_engaged else None ), }, ) _gdf_preflight_final_temperature = float( _gdf_preflight_convergence.value("smearing_temperature") ) if _gdf_preflight_final_temperature > 0.0: raise NotImplementedError( "run_periodic_job: finite-temperature GDF optimization is " "unavailable until optimization and trajectory/QVF results " "distinguish the Mermin free-energy objective from internal " "energy. Run a fixed-geometry smeared GDF SCF or optimize " "at T=0." ) _gdf_preflight_bz = bz_integration if _gdf_preflight_bz is _BZ_INTEGRATION_UNSET: _gdf_preflight_bz = getattr(kpoints, "bz_integration", None) if _gdf_preflight_bz is not None: _gdf_preflight_bz = str(_gdf_preflight_bz).strip().lower() if _gdf_preflight_bz not in (None, "smearing"): raise NotImplementedError( "run_periodic_job: GDF optimization supports Aufbau or " "Fermi-Dirac occupations only; the selected BZ integrator " "has no analytic occupation-response gradient." ) if gdf_method is not None and str(gdf_method).strip().lower() != "rsgdf": raise NotImplementedError( "run_periodic_job: GDF optimization currently supports " "gdf_method='rsgdf' only; MDF/compcell fit derivatives are " "not implemented." ) if dft_plus_u: raise NotImplementedError( "run_periodic_job: GDF optimization does not differentiate " "the DFT+U energy term. Run a +U single point or optimize " "without Hubbard sites." ) if method_upper in ("RKS", "UKS") and functional is not None: _gdf_opt_functional = Functional( str(functional), 2 if method_upper == "UKS" else 1 ) if bool(getattr(_gdf_opt_functional, "is_range_separated", False)): raise NotImplementedError( "run_periodic_job: GDF optimization does not support " "range-separated or screened hybrids because the " "analytic fitted-exchange gradient is full-range only." ) # Prove that live dispatch can capture a GDF driver whose analytic # gradient differentiates the same SCF objective. Without this # preflight, dry-run could certify jobs that either fell onto the # uncaptured legacy-Gamma bridge or failed only when the optimizer # reran the converged SCF with compute_gradient=True. _gdf_opt_kmesh = _runner_bloch_kmesh(system, kpoints) _gdf_opt_gamma = _gamma_kmesh_info(system, _gdf_opt_kmesh) is not None _gdf_opt_full_size = _bloch_kmesh_full_size(_gdf_opt_kmesh) _gdf_opt_true_multik = _gdf_opt_full_size > 1 _gdf_opt_dim = int(system.dim) _gdf_opt_density_mixer = _canonical_gdf_density_mixer(density_mixer) if _gdf_opt_dim == 1: raise NotImplementedError( "run_periodic_job: GDF optimization does not support dim=1 " "wire cells because the analytic wire GDF gradient is not " "implemented." ) _gdf_opt_fmix_resolution = _gdf_preflight_convergence.knobs[ "fock_mixing" ] _gdf_opt_shift_resolution = _gdf_preflight_convergence.knobs[ "level_shift" ] _gdf_opt_fmix = float(_gdf_opt_fmix_resolution.value) _gdf_opt_shift = float(_gdf_opt_shift_resolution.value) _gdf_opt_neutral = ( abs( float(sum(atom.Z for atom in system.unit_cell)) - int(system.n_electrons()) ) <= 0.5 ) _gdf_opt_default_rhf_gamma = ( method_upper == "RHF" and kpoints is None and gdf_method is None and _gdf_opt_dim == 3 and int(system.n_electrons()) % 2 == 0 and int(system.multiplicity) == 1 and _gdf_opt_neutral and not symmetry_stabilize and not symmetry_reduce_fock ) # Mirror the live GDF capability filter: AUTO-only aids that a chosen # exact-Gamma/open-shell driver cannot execute are zeroed there. An # explicit nonzero request remains part of the requested Hamiltonian # and must be rejected rather than silently discarded. if _gdf_opt_shift_resolution.source == "auto" and ( method_upper in ("ROHF", "UHF", "UKS") or (_gdf_opt_gamma and gdf_method is not None) or _gdf_opt_default_rhf_gamma ): _gdf_opt_shift = 0.0 if _gdf_opt_fmix_resolution.source == "auto" and ( method_upper == "ROHF" or (method_upper in ("UHF", "UKS") and kpoints is not None) or ( method_upper == "RHF" and _gdf_opt_gamma and gdf_method is not None ) or _gdf_opt_default_rhf_gamma ): _gdf_opt_fmix = 0.0 # The bounded slab adapter validates the finalized convergence # controls, after AUTO selection, and supports only plain Fock DIIS. # Mirror that envelope here so dry-run never certifies a relaxation # whose first live SCF would reject one of these controls (and so a # live rejection cannot leave a started manifest behind). if _gdf_opt_dim == 2: from ._vibeqc_core import BlochKMesh as _BlochKMesh from .kpoints import KPoints as _KPoints if isinstance(kpoints, (_KPoints, _BlochKMesh)): raise NotImplementedError( "run_periodic_job: slab GDF optimization currently " "requires a full Gamma-centered tuple mesh; custom, " "shifted, weighted, and symmetry-reduced mesh objects " "remain fail-closed." ) if rsgdf_tail_ke_cutoff is not None: raise NotImplementedError( "run_periodic_job: slab GDF optimization does not yet " "implement the bulk high-|G| tail correction." ) if _gdf_preflight_bz is not None: raise NotImplementedError( "run_periodic_job: slab GDF optimization currently " "supports integer zero-temperature occupations only; " "bz_integration must be omitted." ) if _gdf_opt_fmix != 0.0: raise NotImplementedError( "run_periodic_job: slab GDF optimization does not yet " "implement fock_mixing; pass fock_mixing=0.0." ) if _gdf_opt_shift != 0.0: raise NotImplementedError( "run_periodic_job: slab GDF optimization does not yet " "implement level_shift; pass level_shift=0.0." ) _gdf_opt_mixer_key = ( None if _gdf_opt_density_mixer is None else str(_gdf_opt_density_mixer).strip().lower() ) if _gdf_opt_mixer_key not in (None, "", "none", "diis"): raise NotImplementedError( "run_periodic_job: slab GDF optimization currently " "supports Fock DIIS only; Anderson, Broyden, and Kerker " "density mixing remain fail-closed." ) if _gdf_opt_gamma and _gdf_opt_dim == 3: _gdf_opt_legacy_reasons: list[str] = [] if not _gdf_opt_neutral: _gdf_opt_legacy_reasons.append("charged cell") if symmetry_stabilize or symmetry_reduce_fock: _gdf_opt_legacy_reasons.append("SCF symmetry reduction") if _gdf_opt_shift != 0.0: _gdf_opt_legacy_reasons.append("level shift") # Default-Gamma RHF and an explicit Gamma mesh without a density # mixer require the exact-Gamma fast path, which does not carry # Fock mixing. RKS with kpoints=None uses the native UKS-singlet # GDF engine and legitimately supports this aid. if _gdf_opt_fmix != 0.0 and ( (method_upper == "RHF" and kpoints is None) or ( method_upper in ("RHF", "RKS") and kpoints is not None and _gdf_opt_density_mixer is None ) ): _gdf_opt_legacy_reasons.append("Fock mixing") if _gdf_opt_legacy_reasons: raise NotImplementedError( "run_periodic_job: GDF optimization would use the " "legacy Gamma molecular-limit GDF driver, which has no " "analytic gradient (unsupported: " + ", ".join(_gdf_opt_legacy_reasons) + "). Remove those options or run a fixed-geometry " "single point." ) if _gdf_opt_true_multik: _gdf_opt_expanded = _expand_ibz_kmesh_to_full_bz( system, _gdf_opt_kmesh ) _gdf_opt_effective_kmesh = ( _gdf_opt_expanded if _gdf_opt_expanded is not None else _gdf_opt_kmesh ) _gdf_opt_weights = np.asarray( getattr(_gdf_opt_effective_kmesh, "weights", []), dtype=float, ).reshape(-1) _gdf_opt_nk = int(_gdf_opt_weights.size) if ( _gdf_opt_nk == 0 or not np.all(np.isfinite(_gdf_opt_weights)) or not np.allclose( _gdf_opt_weights, 1.0 / _gdf_opt_nk, rtol=0.0, atol=1.0e-12, ) ): raise NotImplementedError( "run_periodic_job: GDF optimization requires a uniform " "full-BZ k-point mesh. Custom nonuniform weights have no " "orbit-unfolded analytic exchange derivative." ) # Pure RKS in 3D normally uses the cheaper Ewald-3D J/K loop, whose # use_compcell=False state has no fitted Lpq cache to differentiate. # Any explicit non-Gamma mesh enters that loop, even when Nk == 1. # An explicit Gamma mesh with an active Anderson/Broyden density mixer # does too (rather than using the pure-GDF Gamma fast path). Default # DIIS aliases are canonicalized above and retain the fast path. Cover # every dispatch shape here. _gdf_opt_rks_ewald3d_loop = _gdf_opt_true_multik or ( kpoints is not None and ( not _gdf_opt_gamma or _gdf_opt_density_mixer is not None ) ) if ( method_upper == "RKS" and _gdf_opt_dim == 3 and _gdf_opt_rks_ewald3d_loop ): _gdf_opt_alpha = float( Functional(str(functional or "lda"), 1).hf_exchange_fraction ) if _gdf_opt_alpha == 0.0: if _gdf_opt_true_multik: _gdf_opt_sampling = "multi-k" elif _gdf_opt_gamma: _gdf_opt_sampling = "explicit-Gamma density-mixed" else: _gdf_opt_sampling = "explicit non-Gamma" raise NotImplementedError( "run_periodic_job: pure-functional " f"{_gdf_opt_sampling} RKS/GDF optimization is unavailable " "because live dispatch uses use_compcell=False, while " "the analytic gradient requires the cached-Lpq GDF J/K " "objective. Use a global hybrid, remove density_mixer " "from an explicit Gamma calculation, or run a " "fixed-geometry pure-DFT single point." ) # Validate the caller's density-mixer request before dry-run can publish # a manifest. The later call remains necessary because AUTO convergence # may select a density mixer after this boundary; this early call pins # dry/live parity for every explicit mixer and all of its parameters. _validate_density_mixer_dispatch( density_mixer=density_mixer, density_mixer_depth=density_mixer_depth, density_mixer_beta=density_mixer_beta, density_mixer_kerker=density_mixer_kerker, kerker_k0=kerker_k0, kerker_strength=kerker_strength, kerker_cutoff_ha=kerker_cutoff_ha, jk_method=resolved_jk, method=method_upper, kpoints=kpoints, ) # Dry-run short-circuit (Phase O5). Mirrors the molecular run_job # path: build the OutputPlan from current kwargs, write a one-shot # ``{output}.system`` with ``[outputs].status = "dry_run"``, print # the declared-artefacts summary, and return None without running # the SCF. Honours both the ``dry_run=True`` kwarg and the # ``VIBEQC_DRY_RUN=1`` env var that vq's submit pre-flight sets. if dry_run or is_dry_run_requested(): _estimate_bytes: Optional[int] = None if is_dry_run_estimate_requested(): try: _estimate_bytes = _periodic_gpw_gapw_dry_run_estimate_bytes( system, basis, resolved_jk=resolved_jk, method_upper=method_upper, functional=functional, cutoff_ha=cutoff_ha, kpoints=kpoints, ) except Exception: _estimate_bytes = None if _estimate_bytes is None: try: _gdf_memory = _periodic_gdf_estimate( system, basis, resolved_jk=resolved_jk, method_upper=method_upper, functional=functional, kpoints=kpoints, aux_basis=aux_basis, n_ibz_kpoints=_symmetry_reduce_k_ibz, ) if _gdf_memory is not None: _estimate_bytes = _gdf_memory.estimate.total_bytes except Exception: _estimate_bytes = None if optimize: try: _gradient_estimate_bytes = ( _periodic_xc_gradient_dry_run_estimate_bytes( system, basis, method_upper=method_upper, functional=functional, lattice_cutoff_bohr=_bipole_cutoff_bohr, ) ) if _gradient_estimate_bytes is not None: _estimate_bytes = max( int(_estimate_bytes or 0), int(_gradient_estimate_bytes), ) except Exception: pass from .periodic.exchange_convention import exchange_q0_label _dry_manifest = ManifestUpdater( real_plan, record_hostname=record_hostname, wall_seconds=0.0, estimate_bytes=_estimate_bytes, extra_run_fields={ "exchange_q0": exchange_q0_label(exchange_exxdiv), "jk_method_requested": _jk_requested_label, "jk_method_resolved": resolved_jk.value, "jk_method_executed": _executed_jk_method, "dft_plus_u": bool(dft_plus_u), "dft_plus_u_route": _dft_plus_u_route, }, ) _dry_manifest.mark_dry_run() print_dry_run_summary(real_plan) return None from .periodic.exchange_convention import exchange_q0_label _initial_run_fields = { "exchange_q0": exchange_q0_label(exchange_exxdiv), "jk_method_requested": _jk_requested_label, "jk_method_resolved": resolved_jk.value, "jk_method_executed": _executed_jk_method, "dft_plus_u": bool(dft_plus_u), "dft_plus_u_route": _dft_plus_u_route, } if resolved_jk == PeriodicJKMethod.GAPW: _initial_run_fields["gapw_one_centre_resolved"] = ( "analytic" if method_upper in ("RHF", "UHF") else "block" ) _initial_run_fields["gapw_molecular_limit_declared"] = bool( gapw_molecular_limit ) _output_writer = OutputWriter( real_plan, record_hostname=record_hostname, extra_run_fields=_initial_run_fields, ) _PERIODIC_OUTPUT_WRITER.set(_output_writer) plog = resolve_progress(progress, verbose=verbose) # Early status line so silent BIPOLE/GDF crashes leave at least one # diagnostic line in the .out file before any driver-specific setup. plog.info( f"Periodic {method_upper}/{resolved_jk.value} job starting " f"(basis: {basis.name}, natoms: {len(system.unit_cell)}, " f"dim: {int(system.dim)})" ) # --- Opt-in live QVF checkpointing (vibe-view hot-reload) ----------- # Build the checkpointer + its QVF-only plan up front. The job's real # OutputPlan already owns the final archive; this narrower plan is only a # type-gate because checkpoint sections come from per-snapshot context. # When enabled, wrap ``plog`` so each SCF cycle # that lands a ``plog.iteration(...)`` also refreshes the checkpoint # QVF on the configured cadence. This covers the routes that stream # per-iteration through the shared logger -- the Ewald, GDF, BIPOLE, and # GPW drivers all take ``progress=plog`` and call ``plog.iteration(...)`` # per SCF cycle. The GPW route (``periodic_gapw_j.run_periodic_rhf_gpw`` / # ``run_periodic_rks_gpw_multi_k`` + the open-shell UHF/UKS/multi-k # siblings in ``periodic_gapw_open_shell``) now threads the same # ``progress=`` handle into its Python SCF loop, so GPW jobs get # per-iteration cadence too. Molecular compiled-C++ SCFs now expose a # native diagnostics callback, but QVF checkpointing does not consume that # logging callback, so molecular jobs remain start + terminal frames only. from .output.checkpoint import ( QvfCheckpointer as _QvfCheckpointer, wrap_progress_for_checkpoints as _wrap_progress_for_checkpoints, ) _checkpointer = _QvfCheckpointer( checkpoint_qvf if output_qvf else None, checkpoint_every, plan=OutputPlan.from_run_job_kwargs( output=output_stem, method=method_upper, basis=basis.name, functional=functional, output_qvf=True, job_kind="periodic_scf", ), ) if _checkpointer.enabled and checkpoint_every > 0: def _periodic_scf_checkpoint(_n: int, _fields: dict) -> None: _checkpointer.maybe_snapshot( _n, energy_eh=_fields.get("energy"), system=system, method=method_upper, basis=basis.name, functional=functional, ) plog = _wrap_progress_for_checkpoints(plog, _periodic_scf_checkpoint) # --- Auto-read smearing / bz_integration from KPoints metadata ----- # When kpoints is a KPoints.recommend() result with .smearing / # .bz_integration set, and the user did not explicitly pass those # args, auto-apply them. Explicit user args always win (mirrors the # periodic_convergence_auto "explicit wins" contract). from .kpoints import KPoints as _KPoints _kpts_smearing = None _kpts_bz = None _kpts_uses_ml = False # [routes.numerics] keys naming the published k-point constructions this # job used, so the references block cites them (CLAUDE.md § 8). The SCF # mesh contributes its own key; an attached band structure contributes its # path convention. Plain Monkhorst-Pack and explicit lists contribute none. _numerics: List[str] = [] if isinstance(kpoints, _KPoints): _kpts_smearing = getattr(kpoints, "smearing", None) _kpts_bz = getattr(kpoints, "bz_integration", None) _kpts_uses_ml = getattr(kpoints, "uses_ml_predictor", False) _numerics.extend(getattr(kpoints, "citation_numerics", ()) or ()) _numerics.extend( getattr(getattr(band_structure, "kpath", None), "citation_numerics", ()) or () ) # Resolve bz_integration: explicit arg wins, else KPoints metadata. if bz_integration is _BZ_INTEGRATION_UNSET: bz_integration = _kpts_bz # None / "smearing" / "gilat" # Validate bz_integration early. if bz_integration is not None: bz_integration = str(bz_integration).strip().lower() if bz_integration not in ("smearing", "gilat"): raise ValueError( "run_periodic_job: bz_integration must be None, 'smearing', " f"or 'gilat'; got {bz_integration!r}." ) # GPW / GAPW never thread bz_integration into their occupation logic: # the GPW and GAPW drivers take no such argument, so the flag was # accepted and silently dropped -- the run used Fermi-Dirac while the # user asked for Gilat-Raubenheimer. Measured on H2/STO-3G, 12-bohr # box, (2,1,1), RKS/PBE: the energy is BITWISE identical with and # without the flag (-1.152074940754 either way), while the .out # recorded "smearing_method = fermi-dirac" and "bz_integration = # gilat" at once and the .references cited Gilat-Raubenheimer 1966 + # Gilat 1972 for numerics the run never performed. That is a # citation-integrity break (CLAUDE.md section 8), not just an # ergonomics wart. Refuse it here -- BEFORE the citation route is # registered below -- so the entry cannot fire for a backend that # does not honour the request. GDF, RIJCOSX and BIPOLE do honour it. if bz_integration == "gilat" and resolved_jk in ( PeriodicJKMethod.GPW, PeriodicJKMethod.GAPW, ): raise NotImplementedError( "run_periodic_job: bz_integration='gilat' is not implemented " f"for {resolved_jk}; the GPW/GAPW drivers do not thread it " "into their occupation logic, so honouring the request would " "require route-specific Gilat-Raubenheimer occupations. It is " "refused rather than silently ignored, because accepting it " "would run Fermi-Dirac while recording (and citing) " "Gilat-Raubenheimer. Use jk_method='gdf'/'rijcosx'/'bipole' " "for Gilat-Raubenheimer, or drop bz_integration on this route." ) if bz_integration == "gilat": _numerics.append("gilat_raubenheimer") if method_upper in ("ROHF", "ROKS") and bz_integration == "gilat": raise NotImplementedError( "run_periodic_job: Gilat-Raubenheimer BZ integration is not " f"implemented for periodic {method_upper}; it needs " "restricted-open-shell fractional per-k occupations." ) # Build the option object expected by the selected native driver. opts = ( PeriodicKSOptions() if method_upper in ("ROKS", "RKS", "UKS") else PeriodicRHFOptions() ) if method_upper in ("ROKS", "RKS", "UKS"): opts.functional = str(functional) if resolved_jk == PeriodicJKMethod.BIPOLE: if sr_range_screening: # M4b (pair-resolved truncation): QQR-style separation-aware # screening for the SR erfc J/K build; cited via the # bipole_sr_range route below. opts.lattice_opts.sr_range_screening = True if _bipole_cutoff_bohr is not None: opts.lattice_opts.cutoff_bohr = _bipole_cutoff_bohr if _bipole_nuclear_cutoff_bohr is not None: opts.lattice_opts.nuclear_cutoff_bohr = _bipole_nuclear_cutoff_bohr elif opts.lattice_opts.nuclear_cutoff_bohr > opts.lattice_opts.cutoff_bohr: # BIPOLE's corrected Ewald gauge relies on neutral-cell # cancellation between V_ne, E_nn, and the matching electronic # J/K cell set. The generic LatticeSumOptions default has a # longer nuclear cutoff (25 bohr) than electronic cutoff # (15 bohr), which overbinds molecular-limit hybrid RKS rows by # mHa. Keep the public BIPOLE default coherent unless the user # explicitly requested a separate nuclear cutoff. opts.lattice_opts.nuclear_cutoff_bohr = opts.lattice_opts.cutoff_bohr # --- Auto-attach ECPs from pob-TZVP-REV2 CRYSTAL ECP blocks ------- # When the basis carries inline ECP data (detected via Z+200 header # in CRYSTAL-format basis files), convert to libecpint inline-primitive # blocks and attach to the options object (Phase 14g). _ecp_blocks, _ecp_centers, _eff_z, _total_ncore = _resolve_ecp_data(system, basis) _ecp_active = ( len(_ecp_blocks) > 0 or len(_ecp_centers) > 0 or int(_total_ncore or 0) != 0 ) if _ecp_active and resolved_jk == PeriodicJKMethod.BIPOLE: raise NotImplementedError( "run_periodic_job: ECP-bearing bases are not implemented on " "jk_method='bipole'. The BIPOLE drivers do not yet consume the " "effective charges, removed-core electron count, ECP one-electron " "operator, and matching ionic energy consistently, so the route " "fails before SCF instead of running an all-electron Hamiltonian " "in an ECP orbital basis." ) if _ecp_blocks: opts.ecp_primitive_blocks = _ecp_blocks opts.ecp_home_centers = _ecp_centers opts.ecp_effective_charges = _eff_z opts.ecp_total_ncore = _total_ncore # --- BUG 99 guard: refuse ECP-paired molecular bases without ECP ---- # Molecular ECP-paired bases (dhf-*, *-PP, lanl*, etc.) pass through # the periodic runner without the molecular runner's auto-attach or # validation. Guard here so a user who copies an ORCA-style input # into a periodic calculation does not get a silently wrong answer. _basis_name = str(getattr(basis, "name", "") or "").strip() if _basis_name: from .ecp_metadata import is_ecp_paired_basis as _is_ecp_paired if _is_ecp_paired(_basis_name): _has_ecp = bool( getattr(opts, "ecp_primitive_blocks", None) or getattr(opts, "ecp_centers", None) ) if not _has_ecp: raise ValueError( f"basis={_basis_name!r} is designed for use with an " f"effective core potential (ECP), but no ECP centers " f"or primitive blocks were configured on the periodic " f"SCF options.\n" f"Either configure ecp_primitive_blocks / ecp_centers " f"on the options struct, or choose an all-electron " f"basis set (e.g. def2-TZVPP, cc-pVTZ).\n" f"See vibeqc.ecp_metadata.auto_ecp_centers() for " f"automatic ECP-centre construction." ) opts.use_diis = bool(use_diis) if dynamic_damping is not None: opts.dynamic_damping = bool(dynamic_damping) # --- Diagonalisation solver ----------------------------------------- if solver not in ("dense", "davidson", "lobpcg"): raise ValueError( f"run_periodic_job: solver='{solver}' is not recognised. " f"Supported: 'dense', 'davidson', 'lobpcg'." ) if solver != "dense": opts.use_davidson = True # --- Smearing: resolve any user-engaged smearing input first ------ smearing_engaged = ( smearing is not None or smearing_temperature is not _SMEARING_UNSET or smearing_metallic is not None or smearing_band_gap_hartree is not None ) # Auto-read smearing from KPoints metadata when not explicitly given. if not smearing_engaged and _kpts_smearing is not None: smearing = _kpts_smearing smearing_engaged = True _smearing_temperature_arg = ( 0.0 if smearing_temperature is _SMEARING_UNSET else smearing_temperature ) if smearing is not None: if _smearing_temperature_arg not in (0.0, None): raise ValueError( "run_periodic_job: pass either smearing= or " "smearing_temperature=, not both" ) if not isinstance(smearing, SmearingOptions): raise TypeError( "run_periodic_job: smearing must be a SmearingOptions instance" ) if smearing.enabled and smearing.flavor not in ( "fermi-dirac", "mermin", "methfessel-paxton", "marzari-vanderbilt", ): raise NotImplementedError( "run_periodic_job: smearing flavor " f"{smearing.flavor!r} is not implemented" ) smearing_temperature_hartree = float(smearing.temperature) smearing_method_label = smearing.flavor smearing_source = smearing.source smearing_reason = smearing.reason else: smearing_resolution = resolve_smearing_temperature( _smearing_temperature_arg, unit=smearing_unit, method=smearing_method, metallic=smearing_metallic, band_gap_hartree=smearing_band_gap_hartree, n_electrons=system.n_electrons(), ) smearing_temperature_hartree = float(smearing_resolution.temperature) smearing_method_label = smearing_resolution.method smearing_source = smearing_resolution.source smearing_reason = smearing_resolution.reason # --- Automatic convergence strategy (transparency contract) ------- # Explicit user knobs are never overridden; auto fills only unset # knobs, and the .out states the mode, the classification, and the # per-knob reasons. v1 applies auto on the BIPOLE and GDF routes -- # other routes run with mode "off" unless the user set knobs # (mode "manual" labels them explicitly there too). _auto_supported = resolved_jk in ( PeriodicJKMethod.BIPOLE, PeriodicJKMethod.GDF, ) _requested_convergence = convergence if ( convergence is not None and str(convergence).strip().lower() == "auto" and not _auto_supported ): _requested_convergence = "off" elif convergence is None and not _auto_supported: _requested_convergence = "off" convergence_strategy = resolve_convergence_strategy( system, method=method_upper, convergence=_requested_convergence, # Only the BIPOLE KS drivers carry an in-driver FMIXING-30% # default for DFT functionals, and since the 2026-07-13 Gap-B # validation only when DIIS is off (under DIIS the mixing is # redundant damping that measurably slows convergence -- see # pbc_bipole_rks.py). The floor mirrors the driver so the # printed strategy matches what actually runs; GDF honours the # resolved value verbatim, so no floor there. ks_driver_fock_mixing_floor=( resolved_jk == PeriodicJKMethod.BIPOLE and not bool(use_diis) ), explicit={ "fock_mixing": fock_mixing if fmixing_percent is not None else None, "level_shift": (float(level_shift) if level_shift is not None else None), "damping": float(damping) if damping is not None else None, "smearing_temperature": ( smearing_temperature_hartree if smearing_engaged else None ), }, ) _gdf_capability_knobs = dict(convergence_strategy.knobs) _gdf_capability_changed = False if resolved_jk == PeriodicJKMethod.GDF: # Mirror of the _gamma_default_pure_gdf_ok domain gate in the Γ GDF # dispatch below, minus the knob values this filter itself resolves. # When the default-Γ closed-shell RHF run qualifies for the # PySCF-µHa-validated run_pbc_gdf_rhf in every non-knob respect, an # AUTO-resolved fock-mixing / level-shift (e.g. the ionic-insulator # profile's FMIXING 30% on MgO-class cells) must not force the run # onto the legacy molecular-limit fallback, whose dense-core absolute # energies are PARITY_HELD (G-GDF-001 in HANDOVER_GATED_ITEMS.md). # Zero the auto knobs instead -- the parity route carries its own # convergence handling (DIIS + accelerators + the auto-sized # high-|G| tail). Explicit user knobs keep the legacy fallback. _gamma_pure_gdf_capable = ( method_upper == "RHF" and kpoints is None and gdf_method is None and int(system.dim) == 3 and int(system.n_electrons()) % 2 == 0 and int(system.multiplicity) == 1 and abs( float(sum(atom.Z for atom in system.unit_cell)) - int(system.n_electrons()) ) <= 0.5 and float(convergence_strategy.knobs["smearing_temperature"].value) <= 0.0 and not symmetry_stabilize and not symmetry_reduce_fock ) _level_shift_resolution = convergence_strategy.knobs["level_shift"] _gdf_level_shift_filter_reason = None if ( _level_shift_resolution.source == "auto" and _level_shift_resolution.value != 0.0 ): if method_upper in ("ROHF", "UHF", "UKS"): _gdf_level_shift_filter_reason = ( "capability-filtered to zero: open-shell GDF drivers do " "not implement the level-shift operator" ) elif _requested_gamma_only and gdf_method is not None: _gdf_level_shift_filter_reason = ( "capability-filtered to zero: the exact-Gamma GDF driver " "selected by an explicit gdf_method does not implement " "the level-shift operator" ) elif _gamma_pure_gdf_capable: _gdf_level_shift_filter_reason = ( "capability-filtered to zero: the default-Gamma " "closed-shell RHF run stays on the PySCF-parity " "run_pbc_gdf_rhf driver, which does not implement the " "level-shift operator (the legacy fallback's dense-core " "absolute energies are parity-held)" ) if _gdf_level_shift_filter_reason is not None: _gdf_capability_knobs["level_shift"] = KnobResolution( 0.0, "auto", _gdf_level_shift_filter_reason, ) _gdf_capability_changed = True _fock_mixing_resolution = convergence_strategy.knobs["fock_mixing"] _gdf_fock_mixing_filter_reason = None if ( _fock_mixing_resolution.source == "auto" and _fock_mixing_resolution.value != 0.0 ): if method_upper == "ROHF" or ( method_upper in ("UHF", "UKS") and kpoints is not None ): _gdf_fock_mixing_filter_reason = ( "capability-filtered to zero: the selected open-shell " "GDF driver does not implement Fock mixing" ) elif ( method_upper == "RHF" and _requested_gamma_only and gdf_method is not None ): _gdf_fock_mixing_filter_reason = ( "capability-filtered to zero: the closed-shell " "exact-Gamma GDF driver selected by an explicit " "gdf_method does not implement Fock mixing" ) elif _gamma_pure_gdf_capable: _gdf_fock_mixing_filter_reason = ( "capability-filtered to zero: the default-Gamma " "closed-shell RHF run stays on the PySCF-parity " "run_pbc_gdf_rhf driver, which does not implement Fock " "mixing (the legacy fallback's dense-core absolute " "energies are parity-held)" ) if _gdf_fock_mixing_filter_reason is not None: _gdf_capability_knobs["fock_mixing"] = KnobResolution( 0.0, "auto", _gdf_fock_mixing_filter_reason, ) _gdf_capability_changed = True if _gdf_capability_changed: # Auto selection must respect the capabilities of the chosen driver. # Leave unset/default requests at zero and state why in the strategy # block. Explicit unsupported requests remain fail-closed errors below. convergence_strategy = ConvergenceStrategy( mode=convergence_strategy.mode, classification=convergence_strategy.classification, knobs=_gdf_capability_knobs, ) if resolved_jk == PeriodicJKMethod.BIPOLE: convergence_strategy = _filter_bipole_restricted_open_convergence( convergence_strategy, method_upper, ) _conv_auto_note = None if ( convergence is not None and str(convergence).strip().lower() == "auto" and not _auto_supported ): _conv_auto_note = ( 'convergence="auto" is wired for jk_method="bipole" and ' 'jk_method="gdf" only in this version; using plain defaults ' "for this route" ) _density_mixer_auto_note = None _density_mixer_params_default = ( int(density_mixer_depth) == 8 and float(density_mixer_beta) == 0.5 and not bool(density_mixer_kerker) and float(kerker_k0) == 1.5 and float(kerker_strength) == 1.0 and float(kerker_cutoff_ha) == 120.0 ) _convergence_off_requested = ( convergence is not None and str(convergence).strip().lower() in ("off", "none") ) if ( density_mixer is None and _density_mixer_params_default and not _convergence_off_requested and resolved_jk == PeriodicJKMethod.GDF and method_upper == "RKS" and kpoints is not None and _functional_is_scan_family(functional) and int(system.dim) == 3 and int(system.multiplicity) == 1 and int(system.n_electrons()) % 2 == 0 ): _mgga_cls = classify_periodic_system(system) if _mgga_cls.profile in _COMPACT_MGGA_DENSITY_MIXER_PROFILES: density_mixer = "anderson" density_mixer_beta = 0.35 _density_mixer_auto_note = ( "compact periodic SCAN/r2SCAN RKS/GDF profile " f"'{_mgga_cls.profile}' uses density_mixer='anderson' " "with beta=0.35 by default; the GDF driver disables " "Fock-DIIS and FMIXING while density mixing is active. " "Pass density_mixer='diis' or convergence='off' to keep " "the Fock-DIIS route" ) opts.damping = convergence_strategy.value("damping") opts.fock_mixing = convergence_strategy.value("fock_mixing") if resolved_jk in ( PeriodicJKMethod.BIPOLE, PeriodicJKMethod.GDF, PeriodicJKMethod.AICCM2026DEV_B, ): opts.level_shift = convergence_strategy.value("level_shift") _smear_res = convergence_strategy.knobs["smearing_temperature"] if not smearing_engaged and _smear_res.source == "auto" and _smear_res.value > 0.0: # Auto strategy turned smearing on: surface it through the same # .out lines the explicit smearing path uses. smearing_method_label = "fermi-dirac" smearing_source = "auto-strategy" smearing_reason = _smear_res.reason opts.smearing_temperature = convergence_strategy.value("smearing_temperature") _insulator_smearing_note = insulator_smearing_warning( system, opts.smearing_temperature, band_gap_hartree=smearing_band_gap_hartree, metallic=smearing_metallic, ) if _insulator_smearing_note is not None: warnings.warn(_insulator_smearing_note, UserWarning, stacklevel=2) _validate_smearing_dispatch( method=method_upper, jk_method=resolved_jk, smearing_temperature=opts.smearing_temperature, kpoints=kpoints, ) if ( resolved_jk == PeriodicJKMethod.BIPOLE and float(opts.smearing_temperature) > 0.0 and str(smearing_method_label).strip().lower().replace("_", "-") not in ("fermi-dirac", "mermin") ): raise NotImplementedError( "run_periodic_job: BIPOLE currently implements only " "Fermi-Dirac occupations (including the Mermin free-energy " "label). Methfessel-Paxton and Marzari-Vanderbilt requests " "would otherwise execute Fermi-Dirac while reporting a different " "method, so they fail before SCF." ) _validate_density_mixer_dispatch( density_mixer=density_mixer, density_mixer_depth=density_mixer_depth, density_mixer_beta=density_mixer_beta, density_mixer_kerker=density_mixer_kerker, kerker_k0=kerker_k0, kerker_strength=kerker_strength, kerker_cutoff_ha=kerker_cutoff_ha, jk_method=resolved_jk, method=method_upper, kpoints=kpoints, ) opts.diis_start_iter = int(diis_start_iter) opts.diis_subspace_size = int(diis_subspace_size) opts.max_iter = int(max_iter) opts.conv_tol_energy = float(conv_tol_energy) # pybind11 enums expose ``__members__`` rather than supporting # ``Enum[name]`` subscripting directly. MOREAD / COREAD are accepted # ORCA-style spellings of the READ restart guess. _guess_aliases = {"MOREAD": "READ", "COREAD": "READ"} _guess_name = _guess_aliases.get(initial_guess.upper(), initial_guess.upper()) try: opts.initial_guess = InitialGuess.__members__[_guess_name] except KeyError as exc: valid = ", ".join(InitialGuess.__members__.keys()) raise ValueError( f"unknown initial_guess={initial_guess!r} (valid: {valid})" ) from exc # ATOMSPIN broken-symmetry seed (UHF/UKS only; the closed-shell misuse is # rejected above). Carried on the options struct so the open-shell # driver's guess step assembles the per-atom broken-symmetry density. The # GuessEngine validates the per-atom tag count against the SCF cell (so it # stays correct under cell reduction). if atomic_spins is not None: opts.atomic_spins = [int(s) for s in atomic_spins] # SPINLOCK (UHF/UKS only; resolved + validated above). Carried on the # options struct so the open-shell driver runs the two-phase schedule # (SPIN_SCHEDULE) or MOM-holds the seeded pattern (PATTERN_HOLD). if _spinlock_mode != SpinlockMode.OFF: opts.spinlock_mode = _spinlock_mode opts.spinlock_value = int(spinlock_value) opts.spinlock_iterations = int(spinlock_iterations) # READ restart. Gamma restarts resolve the prior g=0 cell density and put # it on the options struct. Closed-shell multi-k restarts keep the native # per-k density list separate so the selected multi-k driver can inject # D(k) directly instead of degrading to a Gamma density. _read_density_k_closed = None if opts.initial_guess == InitialGuess.READ: _read_path = opts.read_path _read_obj = read_from if isinstance(read_from, (str, os.PathLike)): _read_path = os.fspath(read_from) _read_obj = None opts.read_path = _read_path if _read_multik_request: from .guess_read import resolve_periodic_read_density_k_closed _read_density_k_closed = resolve_periodic_read_density_k_closed( read_from=_read_obj, read_path=_read_path, expected_n_k=_bloch_kmesh_full_size( _runner_bloch_kmesh(system, kpoints) ), n_basis=basis.nbasis, ) else: from .guess_read import ( resolve_periodic_read_densities_open, resolve_periodic_read_density_closed, ) if method_upper in ("UHF", "UKS"): _da, _db = resolve_periodic_read_densities_open( basis, read_path=_read_path, read_from=_read_obj ) opts.read_density_alpha = _da opts.read_density_beta = _db else: opts.read_density = resolve_periodic_read_density_closed( basis, read_path=_read_path, read_from=_read_obj ) label = f"{method_upper}" if functional: label = f"{label} / {functional}" _periodic_memory = None try: if resolved_jk in (PeriodicJKMethod.GPW, PeriodicJKMethod.GAPW): _gpw_estimate = _periodic_gpw_gapw_estimate( system, basis, resolved_jk=resolved_jk, method_upper=method_upper, functional=functional, cutoff_ha=cutoff_ha, kpoints=kpoints, ) if _gpw_estimate is not None: _periodic_memory = SimpleNamespace( estimate=_gpw_estimate, kind="generic", ) elif resolved_jk in (PeriodicJKMethod.GDF, PeriodicJKMethod.RIJCOSX): _gdf_estimate = _periodic_gdf_estimate( system, basis, resolved_jk=resolved_jk, method_upper=method_upper, functional=functional, kpoints=kpoints, aux_basis=aux_basis, rsgdf_ke_cutoff=float(rsgdf_ke_cutoff), n_ibz_kpoints=_symmetry_reduce_k_ibz, ) if _gdf_estimate is not None: _gdf_estimate.kind = "gdf" _periodic_memory = _gdf_estimate except Exception: _periodic_memory = None t_job_start = time.perf_counter() hessian_result = None # populated by hessian=True; consumed by QVF writer # Install the C++ → Python diagnostics bridge before any # computation, so VIBEQC_DIAG / vibeqc::diagnostic() calls in # the C++ core are forwarded into the .out. install_diagnostics_bridge() # Route C++ progress diagnostics into the .system manifest. install_progress_handler( lambda fields: _output_writer.update_progress(**fields) ) # Auto-enable structured log when running under vq. _structured_target: Path | None = None if os.environ.get("VQ_WORKDIR"): _structured_target = stem_sibling(output_stem, ".scf.jsonl") with ( _structured_log_ctx(_structured_target) as _slog, OutputChannel.to_file(out_path), ): # Structured log: banner + job_start records. _libs = library_versions() _fp = run_fingerprint( method=method_upper, basis=basis.name, functional=functional, molecule=system.unit_cell_molecule(), ) _slog.emit( "banner", vibeqc_version=VIBEQC_VERSION, libint=_libs.get("libint", "unknown"), libxc=_libs.get("libxc", "unknown"), spglib=_libs.get("spglib", "unknown"), run_fingerprint=_fp, ) _slog.emit( "job_start", method=method_upper, basis=basis.name, functional=functional if resolved_jk == PeriodicJKMethod.GPW else None, optimize=False, threads=int(get_num_threads()), n_atoms=int(len(system.unit_cell)), charge=int(system.charge), multiplicity=int(system.multiplicity), n_electrons=int(system.n_electrons()), output_stem=str(output_stem), periodic=True, jk_method=str(resolved_jk.name), ) # --- Banner --------------------------------------------------- write(banner() + "\n\n") libs = library_versions() write(f" Job: PERIODIC {label} basis={basis.name}\n") write(f" J/K method: {describe_jk_method(resolved_jk)}\n") if jk_method != "auto" and jk_method != PeriodicJKMethod.AUTO: write(f" (user-requested: {jk_method!r})\n") else: write(f" (resolved from AUTO)\n") write("\n") write(_system_summary(system)) if system_original_info is not None: write(system_original_info) elif system.symmetry is not None: sg = system.symmetry write( f" Symmetry: {sg.international_symbol} (No. {sg.number}), " f"point group {sg.point_group}, order {sg.order}\n\n" ) write(_basis_summary(basis)) write(section_header("SCF options", width=56)) write(f" use_diis = {opts.use_diis}\n") write(f" damping = {opts.damping}\n") if resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: write(f" dynamic_damping = {opts.dynamic_damping}\n") if fmixing_percent is not None: write(f" fmixing_percent = {float(fmixing_percent)}\n") if opts.smearing_temperature > 0.0 or smearing_source != "explicit": write(f" smearing_method = {smearing_method_label}\n") write(f" smearing_source = {smearing_source}\n") if smearing_reason: write(f" smearing_reason = {smearing_reason}\n") write(f" smearing_temperature = {opts.smearing_temperature}\n") if opts.smearing_temperature > 0.0: write( " smearing_temperature_K = " f"{hartree_to_kelvin_temperature(opts.smearing_temperature)}\n" ) if bz_integration is not None: write(f" bz_integration = {bz_integration}\n") write(f" diis_start_iter = {opts.diis_start_iter}\n") write(f" diis_subspace_size = {opts.diis_subspace_size}\n") write(f" max_iter = {opts.max_iter}\n") write(f" conv_tol_energy = {opts.conv_tol_energy}\n") write(f" initial_guess = {initial_guess.upper()}\n") if opts.fock_mixing != 0.0 and fmixing_percent is None: write(f" fock_mixing = {opts.fock_mixing}\n") if resolved_jk == PeriodicJKMethod.BIPOLE: write( " bipole_cutoff_bohr = " f"{float(opts.lattice_opts.cutoff_bohr)}\n" ) write( " bipole_nuclear_cutoff_bohr = " f"{float(opts.lattice_opts.nuclear_cutoff_bohr)}\n" ) if resolved_jk == PeriodicJKMethod.BIPOLE and ( convergence_strategy.value("level_shift") != 0.0 ): write( " level_shift = " f"{convergence_strategy.value('level_shift')}\n" ) if resolved_jk == PeriodicJKMethod.GDF or ( resolved_jk == PeriodicJKMethod.AICCM2026DEV_B and aiccm_backend.strip().lower().replace("-", "_") != "four_center" ): write(f" aux_basis = {aux_basis or '<auto>'}\n") write(f" gdf_method = {gdf_method or 'rsgdf'}\n") write(f" rsgdf_ke_cutoff = {float(rsgdf_ke_cutoff)}\n") if rsgdf_tail_ke_cutoff is not None: write( " rsgdf_tail_ke_cutoff = " f"{float(rsgdf_tail_ke_cutoff)}\n" ) write(f" mdf_ke_cutoff = {float(mdf_ke_cutoff)}\n") # State the space-group reduction the run is actually using: # the exchange bra count, the wedge it came from, and the # resulting cderi-pair saving. Without this the [memory] line # below reports a number the reader cannot account for. if symmetry_reduce_k and _symmetry_reduce_k_ibz is not None: _n_k_full = int( getattr( _periodic_memory, "n_kpoints", _requested_kmesh_size, ) ) _n_ibz = int(_symmetry_reduce_k_ibz) write( " symmetry_reduce_k = exchange bras " f"{_n_k_full} -> {_n_ibz} (irreducible wedge)\n" ) write( " Lpq cderi pairs " f"{_n_k_full * _n_k_full} -> {_n_ibz * _n_k_full} " f"({_n_k_full / max(_n_ibz, 1):.1f}x fewer)\n" ) write( " exact: K is " "symmetry-transported over each star\n" ) write( " OpenMP threads = " f"{get_num_threads()} (max for native parallel regions)\n" ) write("\n") # --- Convergence-strategy transparency block ------------------- # States whether the convergence aids were chosen automatically # (by default or by convergence="auto"), set manually, or left # plain -- with the classification evidence for auto choices. write(section_header("Convergence strategy", width=56)) for _line in convergence_strategy.log_lines(): write(f" {_line}\n") if _conv_auto_note: write(f" note: {_conv_auto_note}\n") if _density_mixer_auto_note: write(f" note: {_density_mixer_auto_note}\n") if _insulator_smearing_note: # A real warning: normalised to the canonical " WARNING: ..." # surface (survives --quiet, fires a structured event) rather # than the 4-space in-block echo it used to be. warn(_insulator_smearing_note, role="insulator_smearing") write("\n") plog.info( "convergence strategy: " f"{convergence_strategy.mode}" + ( f" ({convergence_strategy.classification.profile})" if convergence_strategy.classification is not None else "" ) ) if _density_mixer_auto_note: plog.info(_density_mixer_auto_note) if _insulator_smearing_note: plog.warn(_insulator_smearing_note) if _periodic_memory is not None: from .memory import ( check_memory, check_periodic_gdf_memory, format_memory_report, ) _estimate = _periodic_memory.estimate _gdf_override = bool(os.environ.get("VIBEQC_GDF_MEMORY_OVERRIDE")) _override_requested = memory_override or ( getattr(_periodic_memory, "kind", "") == "gdf" and _gdf_override ) write( " " + format_memory_report( _estimate, override_requested=_override_requested, ).replace("\n", "\n ") + "\n\n" ) flush() if getattr(_periodic_memory, "kind", "") == "gdf": check_periodic_gdf_memory( _estimate, n_kpoints=int(_periodic_memory.n_kpoints), route_label=str(_periodic_memory.route_label), allow_exceed=_override_requested, ) else: check_memory(_estimate, allow_exceed=memory_override) # --- Restart from previous GPW/GAPW calculation --------------- restart_density = None if restart_from is not None: from .periodic_gapw_restart import load_gpw_result restart_path = Path(os.fspath(restart_from)) if not restart_path.exists(): raise FileNotFoundError( f"restart_from={restart_from!r}: file not found" ) data = load_gpw_result(str(restart_path)) kind = data.get("kind", "") if kind not in ("gpw_scf", "gpw_multi_k_scf"): raise ValueError( f"restart_from={restart_from!r}: unsupported kind " f"{kind!r} (expected 'gpw_scf' or 'gpw_multi_k_scf')" ) restart_density = np.asarray(data["density"], dtype=float) plog.info( f"Restart from {restart_path} ({kind}, " f"E = {render_energy_labeled(float(data.get('energy', 0)), width=0, precision=6)})" ) write(f" restart_from = {restart_from}\n") write( f" restart_energy = {render_energy_labeled(float(data.get('energy', 0)), width=20, precision=10)}\n" ) # --- SCF (dispatch on resolved jk_method) -------------------- t0 = time.perf_counter() plog.banner(f"run_periodic_job PERIODIC {label} basis={basis.name}") plog.info(f"Output file: {out_path}") # Initial checkpoint frame: the input geometry, so a live viewer # has a structure to show the instant the SCF starts. Per-cycle # cadence frames follow via the wrapped ``plog`` (if enabled). if _checkpointer.enabled: _checkpointer.snapshot( system=system, method=method_upper, basis=basis.name, functional=functional, ) def _record_dft_plus_u_route(route: str, executed_jk: str) -> None: nonlocal _dft_plus_u_route, _executed_jk_method _dft_plus_u_route = route _executed_jk_method = executed_jk write(f" dft_plus_u_route = {route}\n") # --- GDF optimizer objective capture (G-PBC-002) -------------- # The GDF dispatch below records the exact driver + arguments the # SCF ran with, so optimize=True can relax on the identical GDF # objective (the same driver re-run with compute_gradient=True at # each candidate geometry) instead of silently switching to the # BIPOLE force surface. GDF branches without an analytic-gradient # capable driver (the legacy Γ molecular-limit fallback) leave # this empty and the optimize block fails closed. _gdf_opt_capture: dict = {} def _gdf_scf(_driver, *_args, **_kwargs): """Run a dispatched GDF driver and capture the exact call. The capture (driver + positional tail + keyword arguments) is what guarantees SCF-objective identity for the optimizer: the relaxation objective is byte-for-byte the same call, with only ``progress``/``compute_gradient`` overridden. """ _gdf_opt_capture["driver"] = _driver _gdf_opt_capture["args"] = _args _gdf_opt_capture["kwargs"] = _kwargs return _driver(system, basis, *_args, progress=plog, **_kwargs) # --- DFT+U interception (Increment 4d) ---------------------- # When dft_plus_u is set, route to the appropriate +U-capable # driver. Closed-shell true multi-k GDF/RIJCOSX and GPW/GAPW are # native below. AUTO single-k jobs are resolved to BIPOLE before # dry-run, and explicit unsupported combinations fail in preflight. # Reaching the historical fallback below is therefore an internal # planning error, never permission to change Coulomb Hamiltonians. if ( dft_plus_u and resolved_jk not in _dftu_native_routes and not _dftu_gdf_native_route and not _dftu_rijcosx_native_route ): raise RuntimeError( "internal DFT+U route-planning invariant violated: the " "requested backend reached dispatch without a native +U " "implementation or a preflight rejection" ) from . import HubbardSite as _HubbardSite # noqa: F401 from . import ( run_rhf_periodic_gamma as _run_rhf_periodic_gamma, ) from . import ( run_rks_periodic as _run_rks_periodic, ) if method_upper == "UKS": # Open-shell UKS +U via the BIPOLE driver (Increment # 4d-bipole UKS). Same per-spin pattern as UHF, plus # the UKS XC contribution. from .pbc_bipole_uks import run_pbc_bipole_uks kmesh = _runner_bloch_kmesh(system, kpoints) _record_dft_plus_u_route( "legacy_auto_bipole_uks_" + ("multi_k" if kpoints is not None else "gamma"), "bipole", ) result = run_pbc_bipole_uks( system, basis, kmesh, opts, functional=functional, linear_dep_threshold=1e-7, use_ewald_j_split=True, ewald_omega=ewald_omega, ewald_precision=ewald_precision, use_oda=use_oda, oda_trust_lambda_max=oda_trust_lambda_max, use_mom=use_mom, use_multipole_far_field=use_multipole_far_field, multipole_l_max=multipole_l_max, use_exchange_ewald_split=use_exchange_ewald_split, exchange_exxdiv=exchange_exxdiv, use_fock_symmetry=symmetry_stabilize, use_fock_symmetry_reduce=symmetry_reduce_fock, sr_image_precision=sr_image_precision, exact_zone_bohr=_bipole_exact_zone_bohr, progress=plog, dft_plus_u=dft_plus_u, bz_integration=bz_integration, ) elif method_upper == "UHF": # Open-shell UHF +U via the BIPOLE driver (Increment # 4d-bipole). Route directly to run_pbc_bipole_uhf # with the user-supplied kmesh. from .pbc_bipole_uhf import run_pbc_bipole_uhf kmesh = _runner_bloch_kmesh(system, kpoints) _record_dft_plus_u_route( "legacy_auto_bipole_uhf_" + ("multi_k" if kpoints is not None else "gamma"), "bipole", ) result = run_pbc_bipole_uhf( system, basis, kmesh, opts, linear_dep_threshold=1e-7, use_ewald_j_split=True, ewald_omega=ewald_omega, ewald_precision=ewald_precision, use_oda=use_oda, oda_trust_lambda_max=oda_trust_lambda_max, use_mom=use_mom, use_multipole_far_field=use_multipole_far_field, multipole_l_max=multipole_l_max, use_exchange_ewald_split=use_exchange_ewald_split, exchange_exxdiv=exchange_exxdiv, use_fock_symmetry=symmetry_stabilize, use_fock_symmetry_reduce=symmetry_reduce_fock, sr_image_precision=sr_image_precision, exact_zone_bohr=_bipole_exact_zone_bohr, progress=plog, dft_plus_u=dft_plus_u, bz_integration=bz_integration, ) elif method_upper == "RHF": if kpoints is not None and tuple(kpoints) != (1, 1, 1): raise NotImplementedError( f"DFT+U on multi-k periodic RHF (kpoints=" f"{kpoints!r}) is not yet wired. Increment 4c " "covers the closed-shell-DFT (RKS) multi-k " "path through cpp/src/periodic_scf.cpp; for " "RHF you can run Γ-only today via " "kpoints=None or kpoints=(1,1,1)." ) _record_dft_plus_u_route("legacy_auto_direct_rhf_gamma", "direct") # PeriodicRHFOptions field-by-field copy of the salient # convergence knobs; the +U-via-DIRECT path uses its # own Coulomb backend so jk_method-specific options # don't apply. rhf_opts = PeriodicRHFOptions() rhf_opts.max_iter = opts.max_iter rhf_opts.conv_tol_energy = opts.conv_tol_energy rhf_opts.conv_tol_grad = float(getattr(opts, "conv_tol_grad", 1e-6)) rhf_opts.damping = float(getattr(opts, "damping", 0.5)) rhf_opts.use_diis = opts.use_diis rhf_opts.diis_start_iter = opts.diis_start_iter rhf_opts.diis_subspace_size = opts.diis_subspace_size rhf_opts.lattice_opts = opts.lattice_opts rhf_opts.use_davidson = getattr(opts, "use_davidson", False) result = _run_rhf_periodic_gamma( system, basis, rhf_opts, dft_plus_u=dft_plus_u, ) else: # RKS via the multi-k DIRECT_TRUNCATED driver. Now # supports arbitrary kmesh (Increment 4c) -- uses # the k-averaged AO occupation matrix and adds # S(k) V_AO S(k) per k. from .kpoints import KPoints _record_dft_plus_u_route( "legacy_auto_direct_rks_" + ("gamma" if kpoints is None else "multi_k"), "direct", ) ks_opts = PeriodicKSOptions() ks_opts.functional = functional or "lda" ks_opts.max_iter = opts.max_iter ks_opts.conv_tol_energy = opts.conv_tol_energy ks_opts.conv_tol_grad = float(getattr(opts, "conv_tol_grad", 1e-6)) ks_opts.damping = float(getattr(opts, "damping", 0.5)) ks_opts.use_diis = opts.use_diis ks_opts.diis_start_iter = opts.diis_start_iter ks_opts.diis_subspace_size = opts.diis_subspace_size ks_opts.lattice_opts = opts.lattice_opts ks_opts.use_davidson = getattr(opts, "use_davidson", False) if kpoints is None: kmesh = KPoints.monkhorst_pack(system, (1, 1, 1)) else: kp = ( list(kpoints) if isinstance(kpoints, (list, tuple)) else [kpoints, kpoints, kpoints] ) kmesh = KPoints.monkhorst_pack(system, tuple(kp)) result = _run_rks_periodic( system, basis, kmesh, ks_opts, dft_plus_u=dft_plus_u, ) elif resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: if bz_integration not in (None, "smearing"): raise NotImplementedError( "aiccm2026dev-b uses the exact finite cyclic-group sum; " "alternative Brillouin-zone integration is not applicable" ) if ( aiccm_lattice_extension is not None or aiccm_wigner_seitz_shells is not None ): if kpoints is not None: raise ValueError( "aiccm2026dev-b accepts either the real-space " "aiccm_lattice_extension/aiccm_wigner_seitz_shells " "control or the legacy kpoints mesh alias, not both" ) _extension = cyclic_lattice_extension( system, aiccm_lattice_extension, wigner_seitz_shells=aiccm_wigner_seitz_shells, ) aiccm_mesh = _extension.repetitions elif kpoints is None: _extension = cyclic_lattice_extension(system) aiccm_mesh = _extension.repetitions elif isinstance(kpoints, (int, list, tuple)): _extension = cyclic_lattice_extension(system, mesh=kpoints) aiccm_mesh = _extension.repetitions elif getattr(kpoints, "mesh", None) is not None: if tuple(getattr(kpoints, "shift", (0, 0, 0))) != (0, 0, 0): raise ValueError( "aiccm2026dev-b requires a Gamma-centred k mesh " "(shift=(0,0,0)) because it represents a cyclic cluster" ) _extension = cyclic_lattice_extension(system, mesh=tuple(kpoints.mesh)) aiccm_mesh = _extension.repetitions else: raise TypeError( "aiccm2026dev-b kpoints must be a cyclic mesh size, a mesh " "tuple/list, or a Gamma-centred KPoints object" ) write(f" lattice_extension = {aiccm_mesh}\n") write( " WS half-extent = " f"{_extension.wigner_seitz_half_extent} lattice vectors\n" ) write(f" equivalent k net = {aiccm_mesh} (Gamma-centred)\n") write(f" aiccm_backend = {aiccm_backend}\n") write(f" aiccm_symmetry = {aiccm_symmetry}\n") if method_upper == "RHF": result = run_aiccm2026dev_b_rhf( system, basis, aiccm_mesh, opts, backend=aiccm_backend, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, fock_mixing=opts.fock_mixing, symmetry_mode=aiccm_symmetry, symmetry_precision=symmetry_precision, symmetry_require_full_group=(aiccm_symmetry_require_full_group), progress=plog, ) elif method_upper == "RKS": result = run_aiccm2026dev_b_rks( system, basis, functional or "pbe", aiccm_mesh, opts, backend=aiccm_backend, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, fock_mixing=opts.fock_mixing, symmetry_mode=aiccm_symmetry, symmetry_precision=symmetry_precision, symmetry_require_full_group=(aiccm_symmetry_require_full_group), progress=plog, ) elif method_upper == "UHF": result = run_aiccm2026dev_b_uhf( system, basis, aiccm_mesh, opts, backend=aiccm_backend, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, fock_mixing=opts.fock_mixing, symmetry_mode=aiccm_symmetry, symmetry_precision=symmetry_precision, symmetry_require_full_group=(aiccm_symmetry_require_full_group), progress=plog, ) else: # UKS result = run_aiccm2026dev_b_uks( system, basis, functional or "pbe", aiccm_mesh, opts, backend=aiccm_backend, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, fock_mixing=opts.fock_mixing, symmetry_mode=aiccm_symmetry, symmetry_precision=symmetry_precision, symmetry_require_full_group=(aiccm_symmetry_require_full_group), progress=plog, ) elif resolved_jk == PeriodicJKMethod.AICCM2026DEV_A_REAL_GAMMA: # EXPERIMENTAL: neutral fitted-torus real-Γ control via the # per-unit-cell adapter (D89: a representation control, not the # union-and-weight Γ-CCM construction). One real supercell-Γ # SCF on the BvK torus defined by the Γ-centred mesh. from .periodic.exchange_convention import ( BVK_EWALD, exchange_q0_label, ) from .periodic_ccm_real_gamma import run_real_gamma_scf if bz_integration not in (None, "smearing"): raise NotImplementedError( "jk_method='real-gamma' uses the exact finite BvK-torus " "sum; alternative Brillouin-zone integration is not " "applicable" ) # Mesh parsing mirrors aiccm2026dev-b: the k-mesh argument IS # the BvK nrep (Γ-centred required). if kpoints is None: _extension = cyclic_lattice_extension(system) _rg_mesh = _extension.repetitions elif isinstance(kpoints, (int, list, tuple)): _extension = cyclic_lattice_extension(system, mesh=kpoints) _rg_mesh = _extension.repetitions elif getattr(kpoints, "mesh", None) is not None: if tuple(getattr(kpoints, "shift", (0, 0, 0))) != (0, 0, 0): raise ValueError( "jk_method='real-gamma' requires a Gamma-centred " "k mesh (shift=(0,0,0)): the mesh defines the BvK " "torus (nrep), not a Bloch sampling" ) _extension = cyclic_lattice_extension( system, mesh=tuple(kpoints.mesh)) _rg_mesh = _extension.repetitions else: raise TypeError( "jk_method='real-gamma' kpoints must be a BvK mesh " "size, a mesh tuple/list, or a Gamma-centred KPoints " "object" ) # The direct drivers implement exxdiv='ewald' and None # (strict-zero-mode); map the runner's exchange_exxdiv label. _rg_exxdiv = ( "ewald" if exchange_q0_label(exchange_exxdiv) == BVK_EWALD else None ) write(f" BvK torus (nrep) = {_rg_mesh}\n") write(" route = real-gamma (neutral " "fitted-torus control; EXPERIMENTAL)\n") write(f" exchange_exxdiv = {_rg_exxdiv}\n") result = run_real_gamma_scf( system, str(basis.name), method_upper, _rg_mesh, functional=(functional if method_upper in ("RKS", "UKS") else None), exxdiv=_rg_exxdiv, aux_basis=aux_basis, ke_cutoff=(float(rsgdf_ke_cutoff) if rsgdf_ke_cutoff is not None else 200.0), max_iter=int(opts.max_iter), conv_tol=float(opts.conv_tol_energy), ) elif resolved_jk == PeriodicJKMethod.GDF: # Closed-shell multi-k GDF and the legacy closed-shell Gamma # driver apply the shared Saunders-Hillier operator. The pure # PBC-GDF Gamma driver and all open-shell GDF drivers do not yet # implement it, so reject those combinations instead of silently # accepting and dropping an explicit or auto-resolved shift. if float(opts.level_shift) != 0.0 and method_upper == "ROHF": raise NotImplementedError( "run_periodic_job: level_shift is not implemented for " "ROHF/GDF. The Roothaan effective-Fock driver supports " "DIIS and density damping; pass level_shift=0.0." ) if float(opts.level_shift) != 0.0 and method_upper in ("UHF", "UKS"): raise NotImplementedError( "run_periodic_job: level_shift is not implemented for " f"open-shell {method_upper}/GDF drivers. Use " "jk_method='bipole' " "for a shifted open-shell periodic SCF, or pass " "level_shift=0.0." ) if ( float(opts.level_shift) != 0.0 and _requested_gamma_only and gdf_method is not None ): raise NotImplementedError( "run_periodic_job: Gamma-only GDF with an explicit gdf_method " f"({gdf_method!r}) does not implement level_shift. Omit " "gdf_method to use the level-shift-capable closed-shell " "Gamma fallback, provide a non-Gamma k-point mesh, or pass " "level_shift=0.0." ) if float(opts.fock_mixing) != 0.0 and method_upper == "ROHF": raise NotImplementedError( "run_periodic_job: fock_mixing is not implemented for " "ROHF/GDF. The Roothaan effective-Fock driver supports " "DIIS and density damping; pass fock_mixing=0.0." ) if ( float(opts.fock_mixing) != 0.0 and method_upper in ("UHF", "UKS") and kpoints is not None ): raise NotImplementedError( "run_periodic_job: fock_mixing is not implemented for " f"open-shell {method_upper}/GDF k-mesh drivers. Omit " "kpoints only if Gamma is intended, or pass " "fock_mixing=0.0." ) if method_upper == "ROHF": # The validated KROHF/GDF driver fixes the BvK exchange # convention and implements Pulay DIIS plus density damping. # BIPOLE-only controls and SCF transformations without a # KROHF/GDF implementation must fail here, not disappear in # argument forwarding. _unsupported_rohf_gdf_knobs = { "use_oda": bool(use_oda), "oda_trust_lambda_max": float(oda_trust_lambda_max) != 1.0, "use_mom": bool(use_mom), "use_multipole_far_field": bool(use_multipole_far_field), "multipole_l_max": int(multipole_l_max) != 2, "ewald_omega": ewald_omega is not None, "ewald_precision": float(ewald_precision) != 1e-8, "use_exchange_ewald_split": ( use_exchange_ewald_split is not None ), "exchange_exxdiv": exchange_exxdiv != "ewald", "sr_image_precision": ( sr_image_precision is None or float(sr_image_precision) != 1e-6 ), "sr_range_screening": bool(sr_range_screening), "symmetry_stabilize": bool(symmetry_stabilize), "symmetry_reduce_fock": bool(symmetry_reduce_fock), } _set_rohf_gdf_knobs = [ key for key, active in _unsupported_rohf_gdf_knobs.items() if active ] if _set_rohf_gdf_knobs: raise NotImplementedError( "run_periodic_job: periodic ROHF/GDF does not " "implement these explicitly requested options: " + ", ".join(sorted(_set_rohf_gdf_knobs)) + ". Drop them or use a supported method/backend." ) if ( float(opts.fock_mixing) != 0.0 and method_upper == "RHF" and _requested_gamma_only and gdf_method is not None ): raise NotImplementedError( "run_periodic_job: closed-shell Gamma-only RHF/GDF with " f"an explicit gdf_method ({gdf_method!r}) does not " "implement fock_mixing. Omit gdf_method to use the " "Fock-mixing-capable Gamma fallback, provide a non-Gamma " "k-point mesh, or pass fock_mixing=0.0." ) # Default-Γ closed-shell RHF (no explicit gdf_method) now routes # through the PySCF-µHa-validated run_pbc_gdf_rhf (exxdiv='ewald'), # the same driver as the explicit-gdf_method and open-shell Γ # UHF/UKS paths -- but ONLY when the cell is in its supported # domain. The legacy run_rhf_periodic_gamma_gdf (molecular limit / # exxdiv=None) stays the fallback for everything it cannot do: # RKS, dim<3, charged cells, finite-T smearing, and the symmetry / # Fock-mixing convergence aids it threads. The gate mirrors # run_pbc_gdf_rhf's own preconditions (pbc_gdf.py ~l.563-601) and # the knobs that driver honours (DIIS + damping, NOT fock_mixing / # level_shift / smearing) so we never route a cell it would reject # or silently drop a convergence aid. It also matches the # kpoints=(1,1,1) routing in run_krhf so default-Γ and an explicit # Γ k-mesh never disagree. Pre-2026-06-15 the default fell to the # legacy driver, which computes the molecular limit and disagreed # with PySCF / the explicit path by the finite-size Madelung shift # (~5.8 mHa on H2/sto-3g/12-bohr). _gamma_q_nuc = float(sum(atom.Z for atom in system.unit_cell)) _gamma_n_elec = int(system.n_electrons()) _gamma_default_pure_gdf_ok = ( method_upper == "RHF" and int(system.dim) == 3 and _gamma_n_elec % 2 == 0 and int(system.multiplicity) == 1 and abs(_gamma_q_nuc - _gamma_n_elec) <= 0.5 and float(opts.smearing_temperature) <= 0.0 and float(opts.fock_mixing) == 0.0 and float(getattr(opts, "level_shift", 0.0)) == 0.0 and not symmetry_stabilize and not symmetry_reduce_fock ) # Closed-shell Γ RKS can use the native pure-GDF KS engine via the # spin-unrestricted implementation in its singlet limit. This is # the only Γ KS path here whose Hartree J is the Lpq/GDF build; the # legacy run_rhf_periodic_gamma_gdf fallback uses the molecular- # limit Ewald-J bridge and is not a PySCF-GDF parity route for # condensed cells. Keep the gate narrow so we never drop knobs that # run_pbc_gdf_uks does not honour. The Γ UKS/GDF driver applies # Fock mixing itself, so ionic auto profiles can remain on the # pure-GDF path instead of falling back to the legacy bridge. _gamma_default_rks_gdf_ok = ( method_upper == "RKS" and int(system.dim) == 3 and _gamma_n_elec % 2 == 0 and int(system.multiplicity) == 1 and abs(_gamma_q_nuc - _gamma_n_elec) <= 0.5 and float(opts.smearing_temperature) <= 0.0 and float(getattr(opts, "level_shift", 0.0)) == 0.0 and not symmetry_stabilize and not symmetry_reduce_fock ) # The explicit slab-GDF route is deliberately bounded to # closed-shell RHF/RKS on a full Gamma-centered tuple mesh. AUTO # continues to select the independent direct SLAB_EWALD_2D route. # Gamma and multi-k both enter the same signed truncated-metric # driver so their Coulomb/exchange gauge cannot diverge. if int(system.dim) == 2: from ._vibeqc_core import CoulombMethod as _CoulombMethod opts.lattice_opts.coulomb_method = ( _CoulombMethod.SLAB_EWALD_2D ) if kpoints is None: slab_gdf_kpoints = (1, 1, 1) elif isinstance(kpoints, int): slab_gdf_kpoints = (int(kpoints), int(kpoints), 1) else: slab_gdf_kpoints = kpoints plog.info(f"slab GDF kmesh = {slab_gdf_kpoints}") write_kmesh_line(slab_gdf_kpoints, system=system) # Dispatch through the GDF optimizer-objective capture # (_gdf_scf) so optimize=True relaxes on the identical # slab driver re-run with compute_gradient=True -- # never on a different force surface (G-PBC-002 § 6 # rung 5; the bulk multi-k wiring's pattern). if method_upper == "RKS": result = _gdf_scf( run_krks_periodic_gdf, slab_gdf_kpoints, opts, functional=functional, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, fock_mixing=opts.fock_mixing, density_mixer=density_mixer, dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, ) else: result = _gdf_scf( run_krhf_periodic_gdf, slab_gdf_kpoints, opts, functional=None, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, fock_mixing=opts.fock_mixing, density_mixer=density_mixer, dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, ) # Multi-k bulk GDF dispatch when a k-mesh is requested. elif kpoints is not None: plog.info(f"kmesh = {kpoints}") write_kmesh_line(kpoints, system=system) if bz_integration is not None: write(f" bz_integration = {bz_integration}\n") if method_upper == "UKS": if bz_integration == "gilat": raise NotImplementedError( "run_periodic_job: bz_integration='gilat' is " "wired for closed-shell multi-k GDF (RHF/RKS) " "only; open-shell GDF needs per-spin " "Gilat-Raubenheimer occupations." ) result = _gdf_scf( run_kuks_periodic_gdf, kpoints, opts, functional=functional, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ibz_native=symmetry_reduce_k, ) elif method_upper == "UHF": if bz_integration == "gilat": raise NotImplementedError( "run_periodic_job: bz_integration='gilat' is " "wired for closed-shell multi-k GDF (RHF/RKS) " "only; open-shell GDF needs per-spin " "Gilat-Raubenheimer occupations." ) result = _gdf_scf( run_kuhf_periodic_gdf, kpoints, opts, functional=None, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ibz_native=symmetry_reduce_k, ) elif method_upper == "ROHF": result = _gdf_scf( run_krohf_periodic_gdf, kpoints, opts, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ) elif method_upper == "RKS": if dft_plus_u: _record_dft_plus_u_route("gdf_rks_multi_k", "gdf") result = _gdf_scf( run_krks_periodic_gdf, kpoints, opts, functional=functional, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ibz_native=symmetry_reduce_k, bz_integration=bz_integration, fock_mixing=opts.fock_mixing, density_mixer=density_mixer, density_mixer_depth=density_mixer_depth, density_mixer_beta=density_mixer_beta, density_mixer_kerker=density_mixer_kerker, kerker_k0=kerker_k0, kerker_strength=kerker_strength, kerker_cutoff_ha=kerker_cutoff_ha, dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, ) else: if dft_plus_u: _record_dft_plus_u_route("gdf_rhf_multi_k", "gdf") result = _gdf_scf( run_krhf_periodic_gdf, kpoints, opts, functional=None, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ibz_native=symmetry_reduce_k, bz_integration=bz_integration, fock_mixing=opts.fock_mixing, density_mixer=density_mixer, density_mixer_depth=density_mixer_depth, density_mixer_beta=density_mixer_beta, density_mixer_kerker=density_mixer_kerker, kerker_k0=kerker_k0, kerker_strength=kerker_strength, kerker_cutoff_ha=kerker_cutoff_ha, dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, ) elif method_upper == "ROHF": # Gamma is the one-point mesh of the same spin-restricted # open-shell GDF engine; keeping one path preserves the # Roothaan orbital gauge and BvK exchange convention. result = _gdf_scf( run_krohf_periodic_gdf, (1, 1, 1), opts, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ) elif method_upper == "UKS": # Γ open-shell UKS -- the pure-GDF driver on the rsgdf # path (µHa-validated; consistent with the multi-k route). result = _gdf_scf( run_pbc_gdf_uks, opts, functional=functional, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ) elif method_upper == "UHF": result = _gdf_scf( run_pbc_gdf_uhf, opts, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ) elif _gamma_default_rks_gdf_ok: result = _gdf_scf( run_pbc_gdf_uks, opts, functional=functional, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, ) elif gdf_method is not None or _gamma_default_pure_gdf_ok: # Closed-shell Γ RHF -> the PySCF-µHa-validated run_pbc_gdf_rhf # (exxdiv='ewald'). Fires for an explicit gdf_method (e.g. # 'mdf') AND for the plain default (gdf_method=None) once the # domain gate above passes. Γ RKS with an explicit gdf_method # is wired through the singlet UKS/GDF path above when that # path can honour all requested knobs; otherwise keep the # historical fail-closed behaviour for explicit gdf_method. if method_upper == "RKS": raise NotImplementedError( "run_periodic_job: Γ RKS with an explicit gdf_method " f"({gdf_method!r}, e.g. 'mdf') cannot be combined with " "the requested convergence/symmetry options. Drop " "unsupported knobs such as fmixing_percent/fock_mixing " "or use method='UKS'." ) result = _gdf_scf( run_pbc_gdf_rhf, opts, aux_basis=aux_basis, gdf_method=(gdf_method or "rsgdf"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, exxdiv="ewald", ) else: # Legacy Γ GDF driver (molecular limit / exxdiv=None): the # fallback for the cases run_pbc_gdf_rhf does not support -- RKS, # dim<3, charged cells, finite-T smearing, and symmetry / # Fock-mixing convergence aids. (Open-shell Γ already routed to # run_pbc_gdf_u{hf,ks} above.) if rsgdf_tail_ke_cutoff is not None: raise NotImplementedError( "run_periodic_job: rsgdf_tail_ke_cutoff requires the " "pure PBC-GDF route. The requested Γ job falls back to " "the legacy molecular-limit GDF driver, which has no " "high-|G| tail correction." ) result = run_rhf_periodic_gamma_gdf( system, basis, opts, functional=(functional if method_upper == "RKS" else None), aux_basis=aux_basis, fock_mixing=opts.fock_mixing, symmetry_stabilize=symmetry_stabilize, symmetry_reduce_fock=symmetry_reduce_fock, progress=plog, ) _mark_legacy_gamma_gdf_parity_hold(result, system, plog) elif resolved_jk == PeriodicJKMethod.SLAB_EWALD_2D: # dim=2 vacuum-free slab (rigorous Parry / de Leeuw-Perram gauge). # Route through lattice_opts.coulomb_method=SLAB_EWALD_2D: the # RHF/RKS dispatchers split Gamma vs multi-k on the kmesh # internally; UKS uses its slab drivers directly. The slab-normal # axis is non-periodic, so the k-mesh along it is always a single # Gamma point. See handovers/HANDOVER_SLAB_EWALD_2D.md. from ._vibeqc_core import CoulombMethod as _CoulombMethod from .periodic_rhf_dispatch import run_rhf_periodic_scf as _run_rhf_slab from .periodic_ks_dispatch import run_rks_periodic_scf as _run_rks_slab from .kpoints import KPoints, as_bloch_kmesh as _as_bloch if int(system.dim) != 2: raise NotImplementedError( "jk_method='slab_ewald_2d' requires a dim=2 slab; got " f"dim={int(system.dim)}." ) opts.lattice_opts.coulomb_method = _CoulombMethod.SLAB_EWALD_2D if kpoints is None: _kp = [1, 1, 1] elif isinstance(kpoints, int): _kp = [int(kpoints), int(kpoints), 1] elif isinstance(kpoints, (list, tuple)): _seq = list(kpoints) _kp = (_seq + [1, 1, 1])[:3] _kp[2] = 1 # slab normal is non-periodic else: _kp = None # already a KPoints / BlochKMesh slab_kmesh = ( kpoints if _kp is None else KPoints.monkhorst_pack(system, tuple(_kp)) ) if method_upper == "RHF": result = _run_rhf_slab(system, basis, slab_kmesh, opts, progress=plog) elif method_upper == "RKS": result = _run_rks_slab(system, basis, slab_kmesh, opts, progress=plog) elif method_upper == "UKS": from .periodic_uks_ewald import ( run_uks_periodic_gamma_ewald2d as _uks_gamma_slab, ) from .periodic_uks_multi_k_ewald import ( run_uks_periodic_multi_k_ewald3d as _uks_multik_slab, ) _bm = _as_bloch(slab_kmesh) if len(_bm.kpoints) == 1 and np.allclose(_bm.kpoints[0], 0.0): result = _uks_gamma_slab(system, basis, opts, progress=plog) else: result = _uks_multik_slab(system, basis, _bm, opts, progress=plog) else: # UHF-on-slab is a follow-on; pick_jk_method already blocks it, # so this is defense-in-depth (CLAUDE.md Sec. 7 fail-closed). raise NotImplementedError( "SLAB_EWALD_2D open-shell HF (UHF) on slabs is a follow-on; " "use RHF, RKS, or UKS." ) elif resolved_jk == PeriodicJKMethod.FFT_POISSON: # Unreachable: validate_jk_method (above) raises on FFT_POISSON, # retired as a user route (v0.13.0) because Γ-only EWALD_3D is # wrong on dense ionic crystals. Kept as a defense-in-depth # fail-closed should a future caller bypass validation. The # internal Γ-only drivers (run_r{h,k}f_periodic_gamma_ewald3d) # remain for dilute periodic + mechanics, fail-closed on dense # cells (CLAUDE.md Sec.7). raise ValueError( "jk_method='fft_poisson' (Γ-only EWALD_3D) is retired " "(v0.13.0). Use jk_method='gdf' (default), 'bipole', or " "'gpw'." ) elif resolved_jk == PeriodicJKMethod.RIJCOSX: if not _requested_true_multik: if method_upper not in ("RHF", "RKS", "UHF", "UKS"): raise NotImplementedError( "run_periodic_job: Gamma RIJCOSX is implemented for " "RHF and vacuum-padded RKS/UHF/UKS only. Other " "methods use the true multi-k GDF/COSX backend; pass " "a mesh with at least two k-points (got " f"kpoints={kpoints!r})." ) if float(getattr(opts, "smearing_temperature", 0.0) or 0.0) > 0.0: raise NotImplementedError( "run_periodic_job: Gamma RIJCOSX RHF uses integer " "occupations. Pass a true multi-k mesh to use the " "GDF/COSX smearing path." ) if density_mixer not in (None, "", "none", "diis"): raise NotImplementedError( "run_periodic_job: density_mixer is wired on the " "true multi-k RIJCOSX route only. Pass a mesh with " "at least two k-points, or use the Gamma RIJCOSX RHF " "driver with DIIS." ) if opts.initial_guess == InitialGuess.READ: raise NotImplementedError( "run_periodic_job: READ restart is wired on the " "true multi-k RIJCOSX route only. The Gamma RIJCOSX " "RHF driver currently starts from its default guess." ) if method_upper == "RKS": # Vacuum-padded envelope only; the driver fails closed # on tight cells (their XC needs the periodic-density # grid). from .periodic_rijcosx import run_periodic_rijcosx_rks result = run_periodic_rijcosx_rks( system, basis, opts, functional=functional, aux_basis=aux_basis, progress=plog, ) elif method_upper == "UHF": from .periodic_rijcosx import run_periodic_rijcosx_uhf result = run_periodic_rijcosx_uhf( system, basis, opts, aux_basis=aux_basis, progress=plog, ) elif method_upper == "UKS": from .periodic_rijcosx import run_periodic_rijcosx_uks result = run_periodic_rijcosx_uks( system, basis, opts, functional=functional, aux_basis=aux_basis, progress=plog, ) else: from .periodic_rijcosx import run_periodic_rijcosx_rhf result = run_periodic_rijcosx_rhf( system, basis, opts, aux_basis=aux_basis, gdf_method=(gdf_method or "compcell"), rsgdf_ke_cutoff=rsgdf_ke_cutoff, progress=plog, ) else: plog.info(f"kmesh = {kpoints}") write_kmesh_line(kpoints, system=system) write(" k_exchange = cosx\n") if bz_integration is not None: write(f" bz_integration = {bz_integration}\n") rijcosx_gdf_method = gdf_method or "rsgdf" write(f" aux_basis = {aux_basis or '<auto>'}\n") write(f" gdf_method = {rijcosx_gdf_method}\n") write(f" rsgdf_ke_cutoff = {float(rsgdf_ke_cutoff)}\n") if rsgdf_tail_ke_cutoff is not None: write( " rsgdf_tail_ke_cutoff = " f"{float(rsgdf_tail_ke_cutoff)}\n" ) write(f" mdf_ke_cutoff = {float(mdf_ke_cutoff)}\n") if method_upper == "UKS": if bz_integration == "gilat": raise NotImplementedError( "run_periodic_job: bz_integration='gilat' is " "wired for closed-shell multi-k GDF/RIJCOSX " "(RHF/RKS) only; open-shell RIJCOSX needs " "per-spin Gilat-Raubenheimer occupations." ) if rsgdf_tail_ke_cutoff is not None: raise NotImplementedError( "run_periodic_job: multi-k UKS/RIJCOSX does not " "yet accept rsgdf_tail_ke_cutoff. The open-shell " "multi-k GDF tail route must be wired first." ) result = run_kuks_periodic_gdf( system, basis, kpoints, opts, functional=functional, aux_basis=aux_basis, gdf_method=rijcosx_gdf_method, rsgdf_ke_cutoff=rsgdf_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, k_exchange="cosx", progress=plog, ) elif method_upper == "UHF": if bz_integration == "gilat": raise NotImplementedError( "run_periodic_job: bz_integration='gilat' is " "wired for closed-shell multi-k GDF/RIJCOSX " "(RHF/RKS) only; open-shell RIJCOSX needs " "per-spin Gilat-Raubenheimer occupations." ) if rsgdf_tail_ke_cutoff is not None: raise NotImplementedError( "run_periodic_job: multi-k UHF/RIJCOSX does not " "yet accept rsgdf_tail_ke_cutoff. The open-shell " "multi-k GDF tail route must be wired first." ) result = run_kuhf_periodic_gdf( system, basis, kpoints, opts, functional=None, aux_basis=aux_basis, gdf_method=rijcosx_gdf_method, rsgdf_ke_cutoff=rsgdf_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, k_exchange="cosx", progress=plog, ) elif method_upper == "RKS": if dft_plus_u: _record_dft_plus_u_route("rijcosx_rks_multi_k", "rijcosx") result = run_krks_periodic_gdf( system, basis, kpoints, opts, functional=functional, aux_basis=aux_basis, use_compcell=True, k_exchange="cosx", gdf_method=rijcosx_gdf_method, rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, bz_integration=bz_integration, fock_mixing=opts.fock_mixing, density_mixer=density_mixer, density_mixer_depth=density_mixer_depth, density_mixer_beta=density_mixer_beta, density_mixer_kerker=density_mixer_kerker, kerker_k0=kerker_k0, kerker_strength=kerker_strength, kerker_cutoff_ha=kerker_cutoff_ha, dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, progress=plog, ) else: if dft_plus_u: _record_dft_plus_u_route("rijcosx_rhf_multi_k", "rijcosx") result = run_krhf_periodic_gdf( system, basis, kpoints, opts, functional=None, aux_basis=aux_basis, use_compcell=True, k_exchange="cosx", gdf_method=rijcosx_gdf_method, rsgdf_ke_cutoff=rsgdf_ke_cutoff, rsgdf_tail_ke_cutoff=rsgdf_tail_ke_cutoff, mdf_ke_cutoff=mdf_ke_cutoff, bz_integration=bz_integration, fock_mixing=opts.fock_mixing, density_mixer=density_mixer, density_mixer_depth=density_mixer_depth, density_mixer_beta=density_mixer_beta, density_mixer_kerker=density_mixer_kerker, kerker_k0=kerker_k0, kerker_strength=kerker_strength, kerker_cutoff_ha=kerker_cutoff_ha, dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, progress=plog, ) elif resolved_jk == PeriodicJKMethod.DIRECT: # Dispatch DIRECT through the periodic_jk_direct wrapper. # AUTO never picks DIRECT (it diverges on tight ionic # crystals); the user has explicitly opted in here. # NOTE: this is a placeholder dispatch -- there's no DIRECT # SCF driver yet that builds J + K via jk_via_direct each # iter. It's a wrapper one would call directly outside the # periodic_runner SCF loop. Track the SCF-driver port in # docs/design_native_gdf.md (DIRECT loop variant). raise NotImplementedError( "DIRECT SCF driver is not wired into run_periodic_job " "yet. Use vibeqc.periodic_jk_direct.jk_via_direct(...) " "for one-shot J/K builds. AUTO picks GDF for SCF; opt " "in to DIRECT only for vacuum-padded debug studies." ) elif resolved_jk == PeriodicJKMethod.GPW: # M2-full / M3a / M3b / M3d / M3e GPW SCF entry. # Γ-only: RHF, ROHF, ROKS, RKS, UHF, UKS via the closed-shell and # open-shell GPW drivers. # Multi-k: pure-DFT RKS, ROKS, and UKS via per-k drivers. if method_upper not in ( "RHF", "ROHF", "ROKS", "RKS", "UHF", "UKS" ): raise NotImplementedError( f"PeriodicJKMethod.GPW currently supports RHF, ROHF, ROKS, " f"RKS, UHF, and UKS; got method={method!r}." ) from .periodic_gapw_j import run_periodic_rhf_gpw from .periodic_gapw_runner_adapter import ( gpw_result_to_runner_shape, gpw_uhf_result_to_runner_shape, gpw_uks_result_to_runner_shape, ) is_dft = method_upper in ("ROKS", "RKS", "UKS") xc_for_gpw = functional if is_dft else None if is_dft and not xc_for_gpw: raise ValueError( f"PeriodicJKMethod.GPW + method={method_upper!r} " "requires a functional= argument (e.g. 'lda', " "'pbe', 'b3lyp'). Got functional=None." ) def _gpw_explicit_gamma_mesh(value) -> bool: if value is None: return False if isinstance(value, (int, np.integer)): return int(value) == 1 if isinstance(value, (list, tuple)): return tuple(int(x) for x in value) == (1, 1, 1) return False gpw_use_gamma_branch = ( method_upper != "RKS" and _gpw_explicit_gamma_mesh(kpoints) ) if kpoints is not None and not gpw_use_gamma_branch: # Multi-k GPW: pure-DFT RKS, ROKS, and UKS (LDA/GGA/meta-GGA; # hybrids raise from the drivers). Build a BlochKMesh and # dispatch. if method_upper not in ("RKS", "ROKS", "UKS"): raise NotImplementedError( f"PeriodicJKMethod.GPW multi-k through " f"run_periodic_job supports RKS, ROKS, and UKS (pure " f"DFT); got method={method!r}. Multi-k UHF / " f"hybrids need per-k exact exchange and are not " f"wired." ) if method_upper in ("ROKS", "UKS") and dft_plus_u: raise NotImplementedError( "run_periodic_job: dft_plus_u is not wired on the " f"multi-k {method_upper} GPW driver yet. Use a " "supported Gamma GPW route or multi-k BIPOLE for " "open-shell +U." ) from ._vibeqc_core import monkhorst_pack as _mp from .periodic_gapw_j import run_periodic_rks_gpw_multi_k kp = ( list(kpoints) if isinstance(kpoints, (list, tuple)) else [kpoints, kpoints, kpoints] ) # Use symmetry-reduced k-mesh when the system has # symmetry attached (attach_symmetry was called # upstream by the reduce_to_primitive / symmetry # kwarg resolution). if system.symmetry is not None: from .kpoints import KPoints kmesh_obj = KPoints.monkhorst_pack(system, tuple(kp), symmetry=True) kmesh = kmesh_obj.to_bloch_kmesh() plog.info( f" GPW multi-k: mesh = {kp} " f"(symmetry-reduced: {len(kmesh.kpoints)} k)" ) write_kmesh_line( kp, suffix=f" (symmetry-reduced: {len(kmesh.kpoints)} k)", system=system, kpoints_cart=kmesh.kpoints, ) else: kmesh = _mp(system, list(kp)) plog.info(f" GPW multi-k: mesh = {kp}") write_kmesh_line(kp, system=system) if method_upper in ("ROKS", "UKS"): # Pure-DFT multi-k ROKS/UKS (LDA/GGA/meta-GGA; hybrids # raise in the drivers). Smearing for restricted-open # shell and non-GDF UKS is gated upstream; +U gated above. from .periodic_gapw_open_shell import ( run_periodic_roks_gpw_multi_k, run_periodic_uks_gpw_multi_k, ) from .periodic_gapw_runner_adapter import ( gpw_roks_multik_result_to_runner_shape, gpw_uks_multik_result_to_runner_shape, ) multik_open_driver = ( run_periodic_roks_gpw_multi_k if method_upper == "ROKS" else run_periodic_uks_gpw_multi_k ) gpw_result = multik_open_driver( system, basis, kmesh, functional=functional, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, quiet=True, progress=plog, ) if (not opts.use_diis) or opts.damping != 0.0: write( " note: use_diis/damping are not honored " f"by the GPW multi-k {method_upper} driver (internal " "per-k Pulay DIIS)\n" ) multik_open_adapter = ( gpw_roks_multik_result_to_runner_shape if method_upper == "ROKS" else gpw_uks_multik_result_to_runner_shape ) result = multik_open_adapter( gpw_result, system, basis ) else: if dft_plus_u: _record_dft_plus_u_route("gpw_rks_multi_k", "gpw") gpw_result = run_periodic_rks_gpw_multi_k( system, basis, kmesh, functional=functional, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, smearing_temperature=opts.smearing_temperature, smearing_method=smearing_method_label, fock_mixing=( opts.fock_mixing if fmixing_percent is not None else None ), dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, quiet=True, progress=plog, ) if getattr(gpw_result, "fock_mixing", 0.0) != 0.0: write( " fock_mixing = " f"{float(gpw_result.fock_mixing):.3g} " "(GPW multi-k)\n" ) if (not opts.use_diis) or opts.damping != 0.0: write( " note: use_diis/damping are not honored by " "the GPW multi-k driver (internal scheme)\n" ) result = _GapwMultiKRunnerProxy(gpw_result) elif method_upper in ("RHF", "RKS"): if dft_plus_u: _record_dft_plus_u_route( f"gpw_{method_upper.lower()}_gamma", "gpw", ) gpw_result = run_periodic_rhf_gpw( system, basis, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, functional=xc_for_gpw, use_diis=opts.use_diis, damping=opts.damping, diis_subspace_size=opts.diis_subspace_size, diis_start_iter=opts.diis_start_iter, smearing_temperature=opts.smearing_temperature, smearing_method=smearing_method_label, dft_plus_u_sites=dft_plus_u, quiet=True, progress=plog, **( {} if restart_density is None else {"initial_density": restart_density} ), ) result = gpw_result_to_runner_shape( gpw_result, system, basis, ) elif method_upper in ("ROHF", "ROKS"): from .periodic_gapw_open_shell import ( run_periodic_rohf_gpw, run_periodic_roks_gpw, ) from .periodic_gapw_runner_adapter import ( gpw_rohf_result_to_runner_shape, ) if opts.fock_mixing != 0.0: raise NotImplementedError( "run_periodic_job: fock_mixing/fmixing_percent is not " f"implemented for periodic {method_upper}/GPW." ) restricted_open_driver = ( run_periodic_roks_gpw if method_upper == "ROKS" else run_periodic_rohf_gpw ) gpw_result = restricted_open_driver( system, basis, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_grad=1e-7, use_diis=opts.use_diis, damping=opts.damping, diis_subspace_size=opts.diis_subspace_size, diis_start_iter=opts.diis_start_iter, level_shift=float(level_shift or 0.0), initial_guess=opts.initial_guess, quiet=True, progress=plog, **( {"functional": str(functional)} if method_upper == "ROKS" else {} ), ) result = gpw_rohf_result_to_runner_shape(gpw_result) elif method_upper == "UHF": from .periodic_gapw_open_shell import ( run_periodic_uhf_gpw, ) if dft_plus_u: _record_dft_plus_u_route("gpw_uhf_gamma", "gpw") gpw_result = run_periodic_uhf_gpw( system, basis, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, use_diis=opts.use_diis, damping=opts.damping, diis_subspace_size=opts.diis_subspace_size, diis_start_iter=opts.diis_start_iter, dft_plus_u_sites=dft_plus_u, quiet=True, progress=plog, **( {} if restart_density is None else {"initial_density": restart_density} ), ) result = gpw_uhf_result_to_runner_shape( gpw_result, system, basis, ) else: # method_upper == "UKS" from .periodic_gapw_open_shell import ( run_periodic_uks_gpw, ) if dft_plus_u: _record_dft_plus_u_route("gpw_uks_gamma", "gpw") gpw_result = run_periodic_uks_gpw( system, basis, functional=xc_for_gpw, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, use_diis=opts.use_diis, damping=opts.damping, diis_subspace_size=opts.diis_subspace_size, diis_start_iter=opts.diis_start_iter, dft_plus_u_sites=dft_plus_u, quiet=True, progress=plog, **( {} if restart_density is None else {"initial_density": restart_density} ), ) result = gpw_uks_result_to_runner_shape( gpw_result, system, basis, ) elif resolved_jk == PeriodicJKMethod.GAPW: # M3c GAPW all-electron SCF entry. Same dispatch structure # as the GPW block, but uses the per-atom augmentation # correction for all-electron accuracy. Multi-k RKS is # available via :func:`run_periodic_rks_gapw_multi_k`. # # The GAPW all-electron augmentation route's two open correctness # bugs are FIXED (2026-06-26): the Hartree Fock is now the exact # derivative of the energy (analytic J = dE_H/dD), and the bonded- # molecule overlapping-augmentation double-count is root-caused # (own-atom compensator + partition-of-unity). The run-level # GAPWExperimentalWarning that flagged those bugs is therefore # retired for jk_method='gapw'. The remaining caveats are accuracy, # not correctness -- the per-atom augmentation has a grid-convergent # absolute residual (hundreds of mHa for 2nd-row atoms at the # default N=24 grid). Hydrogen's no-core soft-basis residual is # fixed by retaining its full valence contraction. The remaining # caveat is documented in docs/user_guide/gapw.md (the .out # "(experimental)" label stays). if method_upper not in ("RHF", "RKS", "UHF", "UKS"): raise NotImplementedError( f"PeriodicJKMethod.GAPW currently supports RHF, " f"RKS, UHF, and UKS; got method={method!r}." ) def _gapw_explicit_gamma_mesh(value) -> bool: if value is None: return False if isinstance(value, (int, np.integer)): return int(value) == 1 if isinstance(value, (list, tuple)): return tuple(int(x) for x in value) == (1, 1, 1) return False gapw_use_gamma_branch = ( method_upper != "RKS" and _gapw_explicit_gamma_mesh(kpoints) ) if kpoints is not None and not gapw_use_gamma_branch: # Multi-k GAPW: pure DFT only (RKS). if method_upper != "RKS": raise NotImplementedError( f"PeriodicJKMethod.GAPW multi-k only supports " f"RKS (pure DFT) at v0.10.x; hybrid / HF " f"multi-k is not wired. Got method={method!r}." ) from ._vibeqc_core import monkhorst_pack as _mp from .periodic_gapw_augment import ( run_periodic_rks_gapw_multi_k, ) kp = ( list(kpoints) if isinstance(kpoints, (list, tuple)) else [kpoints, kpoints, kpoints] ) # Use symmetry-reduced k-mesh when the system has # symmetry attached. if system.symmetry is not None: from .kpoints import KPoints kmesh_obj = KPoints.monkhorst_pack(system, tuple(kp), symmetry=True) kmesh = kmesh_obj.to_bloch_kmesh() plog.info( f" GAPW multi-k: mesh = {kp} " f"(symmetry-reduced: {len(kmesh.kpoints)} k)" ) write_kmesh_line( kp, suffix=f" (symmetry-reduced: {len(kmesh.kpoints)} k)", system=system, kpoints_cart=kmesh.kpoints, ) else: kmesh = _mp(system, list(kp)) plog.info(f" GAPW multi-k: mesh = {kp}") write_kmesh_line(kp, system=system) if dft_plus_u: _record_dft_plus_u_route("gapw_rks_multi_k", "gapw") gapw_result = run_periodic_rks_gapw_multi_k( system, basis, kmesh, functional=functional, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, dft_plus_u_sites=dft_plus_u, initial_density_k=_read_density_k_closed, quiet=True, ) # Adapt the multi-k result to the runner duck-type. # GpwMultiKScfResult carries per-k data under # ``mo_coeffs_k`` / ``mo_energies_k`` / ``occupations_k``; # the runner output code looks for ``mo_coeffs``, # ``mo_energies``, and ``occupations`` as per-k lists. result = _GapwMultiKRunnerProxy(gapw_result) else: from .periodic_gapw_augment import ( run_periodic_rhf_gapw, run_periodic_rks_gapw, run_periodic_uhf_gapw, run_periodic_uks_gapw, ) from .periodic_gapw_runner_adapter import ( gpw_result_to_runner_shape, gpw_uhf_result_to_runner_shape, gpw_uks_result_to_runner_shape, ) is_dft = method_upper in ("RKS", "UKS") xc_for_gapw = functional if is_dft else None if is_dft and not xc_for_gapw: raise ValueError( f"PeriodicJKMethod.GAPW + method={method_upper!r} " "requires a functional= argument (e.g. 'lda', " "'pbe', 'b3lyp'). Got functional=None." ) if method_upper in ("RHF", "RKS"): if dft_plus_u: _record_dft_plus_u_route( f"gapw_{method_upper.lower()}_gamma", "gapw", ) gapw_result = run_periodic_rhf_gapw( system, basis, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, functional=xc_for_gapw, use_diis=opts.use_diis, damping=opts.damping, diis_subspace_size=opts.diis_subspace_size, diis_start_iter=opts.diis_start_iter, smearing_temperature=opts.smearing_temperature, smearing_method=smearing_method_label, dft_plus_u_sites=dft_plus_u, molecular_limit=gapw_molecular_limit, memory_override=memory_override, quiet=True, **( {} if restart_density is None else {"initial_density": restart_density} ), ) result = gpw_result_to_runner_shape( gapw_result, system, basis, ) elif method_upper == "UHF": if dft_plus_u: _record_dft_plus_u_route("gapw_uhf_gamma", "gapw") gapw_result = run_periodic_uhf_gapw( system, basis, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, use_diis=opts.use_diis, damping=opts.damping, diis_subspace_size=opts.diis_subspace_size, diis_start_iter=opts.diis_start_iter, dft_plus_u_sites=dft_plus_u, molecular_limit=gapw_molecular_limit, memory_override=memory_override, quiet=True, **( {} if restart_density is None else {"initial_density": restart_density} ), ) result = gpw_uhf_result_to_runner_shape( gapw_result, system, basis, ) else: # method_upper == "UKS" if dft_plus_u: _record_dft_plus_u_route("gapw_uks_gamma", "gapw") gapw_result = run_periodic_uks_gapw( system, basis, functional=xc_for_gapw, cutoff_ha=cutoff_ha, max_iter=opts.max_iter, conv_tol_energy=opts.conv_tol_energy, conv_tol_density=1e-7, use_diis=opts.use_diis, damping=opts.damping, diis_subspace_size=opts.diis_subspace_size, diis_start_iter=opts.diis_start_iter, dft_plus_u_sites=dft_plus_u, memory_override=memory_override, quiet=True, **( {} if restart_density is None else {"initial_density": restart_density} ), ) result = gpw_uks_result_to_runner_shape( gapw_result, system, basis, ) elif resolved_jk == PeriodicJKMethod.BIPOLE: # CRYSTAL-gauge Ewald J-split -- RHF/RKS/UHF/UKS, plus the # restricted-open-shell corrected-Ewald-exchange engine. # Use user-provided k-mesh if given, else default to Γ-only kmesh = _runner_bloch_kmesh(system, kpoints) if kpoints is not None: plog.info(f" BIPOLE kmesh: {kpoints}") if bz_integration == "gilat" and method_upper not in ( "RKS", "UHF", "UKS", ): raise NotImplementedError( "run_periodic_job: bz_integration='gilat' is wired for " "BIPOLE RKS/UHF/UKS; BIPOLE RHF/ROHF/ROKS still need " "route-specific Gilat-Raubenheimer occupation support." ) if bz_integration is not None and kpoints is not None: write(f" bz_integration = {bz_integration}\n") if dft_plus_u: _record_dft_plus_u_route( f"bipole_{method_upper.lower()}_" + ("multi_k" if _requested_true_multik else "gamma"), "bipole", ) if method_upper in ("ROHF", "ROKS"): # Restricted-open-shell BIPOLE route: the corrected # Ewald-exchange EWALD_3D engine (erfc K_SR + reciprocal # q = k-k' K_LR + BvK probe-charge G=0 correction) with # Roothaan's effective Fock per k. ROKS adds the # spin-polarised V_xc to each per-spin Fock and scales the # exchange by the functional's global fraction (a pure # functional builds no exchange at all). Γ runs on the # (1,1,1) mesh of the same driver, so there is one code # path. The engine fixes its exchange convention and Ewald # α internally; the BIPOLE knob surface below is therefore # rejected rather than silently ignored. if method_upper == "ROKS": from .periodic_roks_multi_k_ewald import ( run_roks_periodic_multi_k_ewald3d, ) result = run_roks_periodic_multi_k_ewald3d( system, basis, kmesh, opts, sr_image_precision=sr_image_precision, progress=plog, ) else: from .periodic_rohf_multi_k_ewald import ( run_rohf_periodic_multi_k_ewald3d, ) result = run_rohf_periodic_multi_k_ewald3d( system, basis, kmesh, opts, sr_image_precision=sr_image_precision, progress=plog, ) elif method_upper == "RHF": from .pbc_bipole import run_pbc_bipole_rhf result = run_pbc_bipole_rhf( system, basis, kmesh, opts, linear_dep_threshold=1e-7, use_ewald_j_split=True, ewald_omega=ewald_omega, ewald_precision=ewald_precision, use_oda=use_oda, oda_trust_lambda_max=oda_trust_lambda_max, use_mom=use_mom, use_multipole_far_field=use_multipole_far_field, multipole_l_max=multipole_l_max, use_exchange_ewald_split=use_exchange_ewald_split, exchange_exxdiv=exchange_exxdiv, use_fock_symmetry=symmetry_stabilize, use_fock_symmetry_reduce=symmetry_reduce_fock, sr_image_precision=sr_image_precision, exact_zone_bohr=_bipole_exact_zone_bohr, progress=plog, dft_plus_u=dft_plus_u, bz_integration=bz_integration, ) elif method_upper == "UHF": from .pbc_bipole_uhf import run_pbc_bipole_uhf result = run_pbc_bipole_uhf( system, basis, kmesh, opts, linear_dep_threshold=1e-7, use_ewald_j_split=True, ewald_omega=ewald_omega, ewald_precision=ewald_precision, use_oda=use_oda, oda_trust_lambda_max=oda_trust_lambda_max, use_mom=use_mom, use_multipole_far_field=use_multipole_far_field, multipole_l_max=multipole_l_max, use_exchange_ewald_split=use_exchange_ewald_split, exchange_exxdiv=exchange_exxdiv, use_fock_symmetry=symmetry_stabilize, use_fock_symmetry_reduce=symmetry_reduce_fock, sr_image_precision=sr_image_precision, exact_zone_bohr=_bipole_exact_zone_bohr, progress=plog, dft_plus_u=dft_plus_u, bz_integration=bz_integration, ) elif method_upper == "RKS": from .pbc_bipole_rks import run_pbc_bipole_rks result = run_pbc_bipole_rks( system, basis, kmesh, opts, functional=functional, linear_dep_threshold=1e-7, use_ewald_j_split=True, ewald_omega=ewald_omega, ewald_precision=ewald_precision, use_oda=use_oda, oda_trust_lambda_max=oda_trust_lambda_max, use_mom=use_mom, use_multipole_far_field=use_multipole_far_field, multipole_l_max=multipole_l_max, use_exchange_ewald_split=use_exchange_ewald_split, exchange_exxdiv=exchange_exxdiv, use_fock_symmetry=symmetry_stabilize, use_fock_symmetry_reduce=symmetry_reduce_fock, sr_image_precision=sr_image_precision, exact_zone_bohr=_bipole_exact_zone_bohr, progress=plog, dft_plus_u=dft_plus_u, # Forward the resolved GR selector explicitly; silently # dropping it here was the historical BUG-PER-002 mode. bz_integration=bz_integration, ) elif method_upper == "UKS": from .pbc_bipole_uks import run_pbc_bipole_uks result = run_pbc_bipole_uks( system, basis, kmesh, opts, functional=functional, linear_dep_threshold=1e-7, use_ewald_j_split=True, ewald_omega=ewald_omega, ewald_precision=ewald_precision, use_oda=use_oda, oda_trust_lambda_max=oda_trust_lambda_max, use_mom=use_mom, use_multipole_far_field=use_multipole_far_field, multipole_l_max=multipole_l_max, use_exchange_ewald_split=use_exchange_ewald_split, exchange_exxdiv=exchange_exxdiv, use_fock_symmetry=symmetry_stabilize, use_fock_symmetry_reduce=symmetry_reduce_fock, sr_image_precision=sr_image_precision, exact_zone_bohr=_bipole_exact_zone_bohr, progress=plog, dft_plus_u=dft_plus_u, bz_integration=bz_integration, ) else: # Defensive: method_upper is validated to RHF/RKS/UHF/UKS at # the top of run_periodic_job and never reassigned, so this is # unreachable today. The guard keeps the BIPOLE branch self- # consistent with the GPW/GAPW branches (which guard locally) # so widening the allowed-method set upstream can never silently # fall through to an UnboundLocalError on `result` below. raise NotImplementedError( "run_periodic_job: PeriodicJKMethod.BIPOLE dispatch: " f"method={method_upper!r} is not supported " "(expected RHF, ROHF, ROKS, RKS, UHF, or UKS)." ) else: raise NotImplementedError( f"Periodic JK method {resolved_jk.value!r} dispatch " f"is not yet implemented in v0.7.1-spike." ) t_scf = time.perf_counter() - t0 # --- Write SCF trace + energies ------------------------------ # Stamp live progress into the .system manifest so vq can # read iteration + energy from a single file. _trace = getattr(result, "scf_trace", None) if _trace: _last = _trace[-1] if isinstance(_trace, list) else _trace _fields = {} if hasattr(_last, "iter"): _fields["iteration"] = int(_last.iter) _fields["energy_eh"] = float(_last.energy) _fields["gradient_norm"] = float(_last.grad_norm) _fields["diis_subspace"] = int(_last.diis_subspace) elif isinstance(_last, dict): _fields["iteration"] = int(_last.get("iter", 0)) _fields["energy_eh"] = float(_last.get("energy", 0)) _fields["gradient_norm"] = float(_last.get("grad_norm", 0)) _fields["diis_subspace"] = int(_last.get("diis_subspace", 0)) if _fields: _fields["phase"] = "scf" _output_writer.update_progress(**_fields) # Shared with the molecular path: iteration table, the # "converged in N iterations" line, and the energy-component # breakdown all come from format_scf_trace so periodic and # molecular .out files read identically. Periodic-only smearing # quantities follow. ``label`` (RHF/RKS/...) is already in the # "Job: PERIODIC <label>" header above, so it is not repeated here. write_scf_trace( result, include_banner=False, include_properties=False, energy_label="Total energy", trailing="\n\n", ) # IID 344: a parity-held result states the hold next to the energy # it qualifies ("" for ordinary runs, so the .out is unchanged). write(_parity_hold_summary(result)) write(_smearing_summary(result)) write(_linear_dependence_summary(result)) write(_band_summary(result)) write(_mo_summary(result)) if resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: _aiccm_diag = result.aiccm2026dev_b write(section_header("AICCM2026DEV-B invariants", width=56)) write(f" cyclic mesh = {_aiccm_diag.mesh}\n") write(f" cyclic cells = {_aiccm_diag.n_cyclic_cells}\n") write(f" electronic method = {_aiccm_diag.electronic_method}\n") write(f" integral backend = {_aiccm_diag.backend}\n") write(f" CCM approach = {_aiccm_diag.ccm_approach}\n") write(f" CCM construction = {_aiccm_diag.ccm_construction}\n") write( " evaluation repr. = " f"{_aiccm_diag.evaluation_representation}\n" ) write( " coulomb kernel = " f"{getattr(_aiccm_diag, 'coulomb_kernel', 'not-recorded')}\n" ) write( " exchange q=0 = " f"{getattr(_aiccm_diag, 'exchange_q0', 'not-recorded')}\n" ) write( " q=0 applicability = " f"{getattr(_aiccm_diag, 'exchange_q0_applicability', 'not-recorded')}\n" ) _aiccm_exchange = getattr( _aiccm_diag, "exact_exchange_assembly", None, ) if _aiccm_exchange is not None: write( " EXX assembly schema = " f"{_aiccm_exchange.schema}\n" ) write( " screened EXX live = " f"{_aiccm_exchange.screened_exchange_applicability}\n" ) write( " screened EXX build = " f"{_aiccm_exchange.screened_exchange_assembly}\n" ) write( " boundary model = " f"{getattr(_aiccm_diag, 'boundary_model', 'not-recorded')}\n" ) _aiccm_convention = getattr(_aiccm_diag, "finite_torus_convention", None) if _aiccm_convention is not None: write( " BvK Madelung cell = " f"{_aiccm_convention.bvk_madelung_supercell_repetitions}\n" ) write( " WS partition error = " f"{_aiccm_diag.wigner_seitz_partition_error:.3e}\n" ) write( " idempotency error = " f"{_aiccm_diag.density_idempotency_error:.3e}\n" ) write( f" electron-count err = {_aiccm_diag.electron_count_error:.3e}\n" ) write( " inverse-Bloch Im = " f"{_aiccm_diag.inverse_bloch_imaginary_residual:.3e}\n\n" ) _symmetry_diag = getattr(result, "aiccm2026dev_b_symmetry", None) if _symmetry_diag is not None: _symmetry_plan = _symmetry_diag.plan write(section_header("AICCM2026DEV-B space-group diagnostic", width=56)) write( " space group = " f"{_symmetry_plan.international_symbol} " f"(No. {_symmetry_plan.space_group_number})\n" ) write( " compatible ops = " f"{_symmetry_plan.n_operations_compatible}/" f"{_symmetry_plan.n_operations_full}\n" ) write( " full/irreducible k = " f"{_symmetry_plan.n_kpoints_full}/" f"{_symmetry_plan.n_kpoints_irreducible}\n" ) write(" acceleration = diagnostic only (not applied)\n") write( " shell pairs = " f"{_symmetry_diag.n_unique_shell_pairs}/" f"{_symmetry_diag.n_shell_pairs} unique\n" ) if _symmetry_diag.n_shell_quartets is not None: write( " shell quartets = " f"{_symmetry_diag.n_unique_shell_quartets}/" f"{_symmetry_diag.n_shell_quartets} unique\n" ) else: write(" shell quartets = count skipped (>24 shells)\n") if _symmetry_diag.gamma_fock_residual is not None: write( " Gamma Fock residual = " f"{_symmetry_diag.gamma_fock_residual:.3e}\n" ) if _symmetry_diag.gamma_density_residual is not None: write( " Gamma dens residual = " f"{_symmetry_diag.gamma_density_residual:.3e}\n" ) write("\n") # --- Convergence-strategy post-SCF check ---------------------- # The pre-SCF classification is a guess; the converged spectrum # is the truth. On auto-mode runs, compare and say so when they # disagree (generalizes the GDF conducting-state warning). if convergence_strategy.mode.startswith("auto") and bool( getattr(result, "converged", False) ): from .periodic_convergence_auto import ( converged_gap_hartree, post_scf_profile_check, ) _n_elec_chk = int(round(system.n_electrons())) _mult_chk = int(getattr(system, "multiplicity", 1) or 1) _n_a_chk = (_n_elec_chk + (_mult_chk - 1)) // 2 _n_b_chk = _n_elec_chk - _n_a_chk _gap_chk = converged_gap_hartree( result, n_alpha=( _n_a_chk if method_upper in ("ROHF", "ROKS", "UHF", "UKS") else _n_elec_chk // 2 ), n_beta=( _n_b_chk if method_upper in ("ROHF", "ROKS", "UHF", "UKS") else None ), ) _profile_check = post_scf_profile_check(convergence_strategy, _gap_chk) if _profile_check is not None: _lvl, _msg = _profile_check write(section_header("Convergence strategy check", width=56)) write(f" {_lvl}: {_msg}\n\n") if _lvl == "warning": warnings.warn( f"run_periodic_job: {_msg}", UserWarning, stacklevel=2, ) else: plog.info(f"convergence strategy check: {_msg}") # --- Fail-closed SCF convergence gate -------------------------- # Mirrors the molecular runner's post-SCF guard (run_job): a # periodic SCF that hit its iteration cap must fail loudly, not # return an unconverged energy stamped "complete". The 2026-08-12 # CaO PBE GDF 200-iter exit-0 incident is the canonical failure # mode: run_periodic_job returned the capped result, the CLI # exited 0, and the .system manifest flipped to "complete", so # every downstream consumer of exit codes / manifest status # promoted an unconverged energy into the validation database. # Everything after this gate (dispersion, sidecars, QVF, # optimization) assumes a converged reference, so it is skipped # on the failure path exactly like the molecular route. if method_upper in ("RHF", "UHF", "RKS", "UKS", "ROHF", "ROKS") and not bool( getattr(result, "converged", False) ): _n_iter = int(getattr(result, "n_iter", 0)) _energy = float(getattr(result, "energy", 0.0)) _msg = ( f"{method_upper} periodic SCF did not converge after " f"{_n_iter} iterations; refusing to treat the energy or " "post-SCF properties as a successful calculation." ) write(f"\n FATAL: {_msg}\n") flush() try: _output_writer.crash(wall_seconds=t_scf) except Exception as _crash_exc: warn_output_failure( _crash_exc, _output_writer.manifest_path, role="manifest_crash_status", category=OutputFailureKind.manifest_recording, ) if _checkpointer.enabled: _finalize_periodic_checkpoint( _checkpointer, result, system, method_upper, basis.name, functional, ) raise RuntimeError(_msg) # --- Post-SCF dispersion (D3-BJ) ----------------------------- # Mirrors the molecular runner's _DispersionAugmented wrapping # (see python/vibeqc/runner.py): the SCF result is left # untouched (energy = pure SCF) and a wrapper exposes the # dispersion piece via .e_dispersion / .energy_total. The # periodic lattice sum is handled by # vibeqc.dispersion_periodic.compute_d3bj_periodic. if dispersion is not None and dispersion is not False: from .dispersion_periodic import compute_d3bj_periodic from .runner import _DispersionAugmented, _resolve_dispersion d3_params = _resolve_dispersion( dispersion, functional if method_upper in ("ROKS", "RKS", "UKS") else None, ) if d3_params is not None: disp = compute_d3bj_periodic( system, d3_params, cutoff_bohr=dispersion_cutoff_bohr, backend=dispersion_backend, ) e_scf = float(getattr(result, "energy", 0.0)) e_total = e_scf + float(disp.energy) write( "\n" + section_header("Dispersion correction (D3-BJ, periodic)") + f" {'backend':>10s} {disp.backend!s:>14s}\n" f" {'supercell':>10s} {disp.supercell!s:>14s}\n" f" {'s6':>10s} {d3_params.s6:14.6f}\n" f" {'s8':>10s} {d3_params.s8:14.6f}\n" f" {'a1':>10s} {d3_params.a1:14.6f}\n" f" {'a2':>10s} {d3_params.a2:14.6f}\n" f" {'E_disp':>10s} {render_energy_labeled(disp.energy, width=14, precision=8)}" f" ({disp.energy * 627.5094740631:+.4f} kcal/mol)\n" f" {'E_SCF':>10s} {render_energy_labeled(e_scf, width=14, precision=8)}\n" f" {'E_total':>10s} {render_energy_labeled(e_total, width=14, precision=8)}\n" ) plog.info( f" E_disp = {disp.energy:+.6e} Ha " f"({disp.backend}, supercell={disp.supercell})" ) result = _DispersionAugmented(result, float(disp.energy), d3_params) # --- Molden --------------------------------------------------- _qvf_wf = None _qvf_bloch_wf = None if write_molden_file and _has_valid_sidecar_mo_coeffs(result): try: from .runner import _DispersionAugmented # noqa: F401 mol = system.unit_cell_molecule() # Molden carries one real orbital set, so export the Γ block. # Periodic drivers hand back per-k lists even for a Γ-only # run (restricted under ``mo_coeffs``, unrestricted under # ``mo_coeffs_alpha`` / ``_beta``); both shapes go through # the same Γ-locating proxy. molden_result = result if isinstance( getattr(result, "mo_coeffs", None), (list, tuple) ) or isinstance( getattr(result, "mo_coeffs_alpha", None), (list, tuple) ): molden_result = _gamma_orbital_proxy(result) _output_writer.dispatch_role( "orbitals", only_format="molden", result=molden_result, basis=basis, molecule=mol, title=( f"{output_stem.name} -- Gamma-block orbitals, " "home cell only (no lattice sum)" ), raise_on_error=True, ) # Molden has no periodic representation: a viewer evaluates # these coefficients over home-cell basis functions with no # image sum, so the render is the home-cell truncation of # the Gamma crystalline orbital and clips wherever an # orbital straddles a cell face. Say so rather than let the # file read as a crystalline-orbital export. QVF can carry # the coefficients together with the periodic metadata and # torus-periodic grids, so it does not share this limitation. write( f" Gamma-point orbitals written to {molden_path.name}\n" " Molden is a molecular format: no lattice, no " "k-points. Coefficients are\n" " evaluated over home-cell basis functions only, so " "orbitals straddling a\n" " cell face appear clipped. For crystalline orbitals " "use output_qvf=True\n" " and open the archive with vibe-view.\n" ) except Exception as exc: warn( f"molden write failed: {type(exc).__name__}: {exc}", role="molden", ) warn_writer_failure( exc, molden_path, role="molden", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) # Downgrade the Molden plan row so finish() does not # raise IncompleteOutputError for a legitimate # physics-gated refusal (non-real Bloch orbitals). _output_writer.downgrade_planned("orbitals") if _should_prepare_periodic_qvf_wavefunction(output_qvf, result): # Build the ``wavefunction.gto`` section from the Gamma-point # MO coefficients. vibe-view resamples orbitals on its own grid, # so the raw coefficients are valuable even when the periodic # runner's own torus-periodic grid render (above) is the primary # delivery path. Gamma-point coefficients are real and portable; # multi-k runs that include Gamma export the k=0 block. try: from vibeqc.output.formats.qvf import qvf_wf_data _wf_mol = system.unit_cell_molecule() if isinstance(getattr(result, "mo_coeffs", None), (list, tuple)): _wf_result = _gamma_proxy_for_multi_k(result) elif isinstance(getattr(result, "mo_coeffs_alpha", None), (list, tuple)): _wf_result = _gamma_orbital_proxy(result) else: _wf_result = result _qvf_wf = qvf_wf_data(_wf_result, basis, _wf_mol) except Exception as _qvf_wf_exc: warn_output_failure( _qvf_wf_exc, stem_sibling(output_stem, ".qvf"), role="qvf_wavefunction_prep", category=OutputFailureKind.compatibility_fallback, ) if ( method_upper in ("RHF", "ROHF", "ROKS", "RKS") and resolved_jk != PeriodicJKMethod.AICCM2026DEV_B ): try: from vibeqc.output.formats.qvf import qvf_bloch_wf_data if isinstance(getattr(result, "mo_coeffs", None), (list, tuple)): _kpts_cart = _result_kpoints_cart(result) if _kpts_cart is None: raise ValueError( "multi-k QVF READ payload requires k-point metadata" ) _kpts_frac = _wrap_reciprocal_fractional( _fractional_kpoints_for_output(system, _kpts_cart) ) else: _kpts_frac = np.zeros((1, 3), dtype=float) _qvf_bloch_wf = qvf_bloch_wf_data( result, basis, system.unit_cell_molecule(), k_points=_kpts_frac, ) except Exception as _qvf_bloch_exc: warn_output_failure( _qvf_bloch_exc, stem_sibling(output_stem, ".qvf"), role="qvf_bloch_wavefunction_prep", category=OutputFailureKind.compatibility_fallback, ) # --- Population dump (Phase O6, periodic) --------------------- _qvf_pop = None _qvf_bond_orders = None _qvf_dipole = None # Periodic UHF/UKS results carry ``mo_coeffs_alpha`` / ``mo_coeffs_beta`` # but not ``mo_coeffs``, so _has_valid_sidecar_mo_coeffs (which checks # both) gates the QVF population section for open-shell as well. if write_population_file and _has_valid_sidecar_mo_coeffs(result): try: from ._vibeqc_core import Molecule as _Mol # noqa: F401 mol_p = system.unit_cell_molecule() pop_result = result if resolved_jk == PeriodicJKMethod.BIPOLE: from vibeqc.output.formats.population import ( compute_bipole_population_summary, unsupported_population_summary, ) try: _bipole_pop_kmesh = ( kmesh if "kmesh" in locals() else _runner_bloch_kmesh(system, kpoints) ) _qvf_pop = compute_bipole_population_summary( result, basis, mol_p, system, lattice_options=opts.lattice_opts, kmesh=_bipole_pop_kmesh, ) except Exception as _bipole_pop_exc: warn_output_failure( _bipole_pop_exc, stem_sibling(output_stem, ".population.txt"), role="bipole_population_summary", category=OutputFailureKind.compatibility_fallback, ) _qvf_pop = unsupported_population_summary( "periodic BIPOLE population properties are " "unavailable because lattice-summed Mulliken " "evaluation failed" ) elif resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: from vibeqc.output.formats.population import ( compute_aiccm2026dev_b_population_summary, unsupported_population_summary, ) try: _qvf_pop = compute_aiccm2026dev_b_population_summary( result, basis, mol_p, system, ) except Exception as _aiccm_pop_exc: warn_output_failure( _aiccm_pop_exc, stem_sibling(output_stem, ".population.txt"), role="aiccm2026dev_b_population_summary", category=OutputFailureKind.compatibility_fallback, ) _qvf_pop = unsupported_population_summary( "periodic AICCM2026DEV-B population properties " "are unavailable because finite-torus population " "evaluation failed" ) else: # GDF / GPW / GAPW / RIJCOSX / legacy direct: # extract a Gamma-point proxy so compute_population_summary # sees a single real coefficient block. Open-shell # (UHF/UKS) results store ``mo_coeffs_alpha`` / ``_beta`` # lists; use _gamma_orbital_proxy which dispatches to # the correct per-spin Gamma slice. if isinstance(getattr(result, "mo_coeffs", None), (list, tuple)): pop_result = _gamma_proxy_for_multi_k(result) elif isinstance(getattr(result, "mo_coeffs_alpha", None), (list, tuple)): pop_result = _gamma_orbital_proxy(result) _output_writer.dispatch_role( "population", result=pop_result, basis=basis, molecule=mol_p, population_summary=_qvf_pop, raise_on_error=True, ) write( f" Population dump written to " f"{output_stem.name}.population.txt + " f"{output_stem.name}.population.json\n" ) # Also compute summary for QVF atom_properties section. if output_qvf: try: if _qvf_pop is None: from vibeqc.output.formats.population import ( compute_population_summary, ) _qvf_pop = compute_population_summary( pop_result, basis, mol_p, ) # Extract bond-order table for bond_orders QVF section. if _qvf_pop.mayer_bonds: import math _pos = [(a.xyz[0], a.xyz[1], a.xyz[2]) for a in mol_p.atoms] _qvf_bond_orders = { "method": "mayer", "pairs": [ { "i": int(i), "j": int(j), "order": float(order), "symbol_i": si, "symbol_j": sj, "distance_ang": float( math.dist(_pos[i], _pos[j]) * 0.529177210903 ), } for (i, j, si, sj, order) in _qvf_pop.mayer_bonds ], } # Extract dipole moment for manifest root metadata. if _qvf_pop.dipole is not None: _qvf_dipole = { "total_debye": float(_qvf_pop.dipole["total_debye"]), "vector_debye": [ float(_qvf_pop.dipole["x_ebohr"]) * 2.541746473, float(_qvf_pop.dipole["y_ebohr"]) * 2.541746473, float(_qvf_pop.dipole["z_ebohr"]) * 2.541746473, ], "origin": [ float(value) for value in _qvf_pop.dipole["origin_bohr"] ], } except Exception as _qvf_pop_exc: warn_output_failure( _qvf_pop_exc, stem_sibling(output_stem, ".qvf"), role="qvf_population_prep", category=OutputFailureKind.compatibility_fallback, ) except Exception as exc: warn( f"population dump failed: {type(exc).__name__}: {exc}", role="population", ) warn_writer_failure( exc, stem_sibling(output_stem, ".population.txt"), role="population_summary", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) # --- Geometry siblings (Phase O5) ----------------------------- # Extended-XYZ (lattice in comment line), POSCAR, and the # structure-only XSF -- every viewer / chem-toolkit / fellow # QC code can consume at least one of these. Default-on with # opt-out via the matching kwarg; failures are best-effort # warnings so a finished SCF never gets dragged down by a # geometry writer. if write_xyz_file: xyz_path = stem_sibling(output_stem, ".xyz") try: energy_ha = float(getattr(result, "energy", float("nan"))) if energy_ha != energy_ha: # NaN sentinel energy_ha = None _output_writer.dispatch_role( "geometry", only_format="extended-xyz", system=system, energy_ha=energy_ha, raise_on_error=True, ) write( f" Final geometry written to {xyz_path.name} " f"(extended XYZ with lattice)\n" ) except Exception as exc: warn( f"extended-xyz write failed: {type(exc).__name__}: {exc}", role="extended_xyz", ) warn_writer_failure( exc, xyz_path, role="extended_xyz", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) if write_poscar_file: poscar_path = stem_sibling(output_stem, ".POSCAR") try: _output_writer.dispatch_role( "geometry", only_format="poscar", system=system, comment=f"vibe-qc periodic {label} basis={basis.name}", raise_on_error=True, ) write(f" Structure written to {poscar_path.name} (VASP-5 POSCAR)\n") except Exception as exc: warn( f"POSCAR write failed: {type(exc).__name__}: {exc}", role="poscar", ) warn_writer_failure( exc, poscar_path, role="poscar", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) if write_cif_file: cif_path = stem_sibling(output_stem, ".cif") try: _output_writer.dispatch_role( "geometry", only_format="cif", system=system, comment=f"vibe-qc periodic {label} basis={basis.name}", raise_on_error=True, ) write(f" Structure written to {cif_path.name} (CIF, P 1)\n") except Exception as exc: warn(f"CIF write failed: {type(exc).__name__}: {exc}", role="cif") warn_writer_failure( exc, cif_path, role="cif", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) # --- Citations (Phase O5b) ------------------------------------ # Assemble the citation list for this periodic job (software + # libint + libxc-if-DFT + basis-set + functional + DIIS + # spglib + ECP-if-used) and emit {stem}.bibtex / .references # siblings plus a "## References" block in the .out so the # text log is self-contained. Failures are non-fatal -- the # SCF result is the load-bearing artefact, citations are # observability. cite_block_text: str | None = None _bibtex_content: str = "" # Full assembled provenance rows destined for the .system # manifest's [citations] section (CLAUDE.md Sec.8.3/Sec.8.5). # The upfront OutputWriter receives these once assembly completes. cite_manifest_rows: list[dict[str, Any]] = [] if citations: try: _db = load_default_database() # Symmetry-unfolded IBZ exchange (Pisani/Dovesi star # transport): the driver flags it on the result when the # full-range or erfc-screened exchange arm ran with a # symmetry-reduced mesh. if getattr( result, "used_kpoint_symmetry_unfolding", False ) and "kpoint_symmetry_unfolding" not in _numerics: _numerics.append("kpoint_symmetry_unfolding") # FFT-Poisson backend uses FFTW3 (fftw_plan_* / # fftw_execute in cpp/src/fft_poisson.cpp). The native # EWALD_3D ``fft_poisson`` method now defaults to the # analytical AO-pair-FT Hartree J path; it only exercises # FFTW3 when the legacy grid backend is explicitly # restored for diagnostics. _j_ewald_backend = os.environ.get( "VIBEQC_J_EWALD3D_BACKEND", "analytic_ft" ).lower() _uses_fftw = resolved_jk in ( PeriodicJKMethod.GPW, PeriodicJKMethod.GAPW, ) or ( resolved_jk == PeriodicJKMethod.FFT_POISSON and _j_ewald_backend == "grid" ) _uses_ewald_ao_ft = ( resolved_jk == PeriodicJKMethod.FFT_POISSON and _j_ewald_backend != "grid" ) _uses_gpw = resolved_jk == PeriodicJKMethod.GPW _uses_gapw = resolved_jk == PeriodicJKMethod.GAPW # GDF citation gating. The Sun-Berkelbach 2017 + MD78 # + HJO00 stack fires whenever the GDF path runs. The # rsgdf sub-route in the database is reserved for the # GDF chat to extend with Ye-Berkelbach 2021 -- until # then it carries the same row as ``gdf`` and the runner # always uses the ``gdf`` flag (the periodic-runner # surface doesn't currently expose which gdf_method ran). _aiccm_backend_name = aiccm_backend.strip().lower().replace("-", "_") _uses_gdf = resolved_jk == PeriodicJKMethod.GDF or ( resolved_jk == PeriodicJKMethod.AICCM2026DEV_B and _aiccm_backend_name != "four_center" ) _uses_gdf_2d = ( resolved_jk == PeriodicJKMethod.GDF and int(system.dim) == 2 ) _uses_bipole = resolved_jk == PeriodicJKMethod.BIPOLE or ( resolved_jk == PeriodicJKMethod.AICCM2026DEV_B and _aiccm_backend_name == "four_center" ) # RIJCOSX acceleration citations (Neese 2009, Izsak-Neese # 2011, Helmich-Paris 2021) fire for the dedicated RIJCOSX # route (Gamma builders + the multi-k GDF/COSX backend) # and for the AICCM2026DEV_B rijcosx backend. _jk_acceleration = ( ("rijcosx",) if resolved_jk == PeriodicJKMethod.RIJCOSX or ( resolved_jk == PeriodicJKMethod.AICCM2026DEV_B and _aiccm_backend_name == "rijcosx" ) else () ) # SCF initial guess. Periodic runs default to SAD # (superposition of atomic densities, van Lenthe 2006); # map the validated guess name to its citation route. AUTO / # HCORE carry no defining-paper route. _periodic_guess_routes = { "sad": "sad", "sap": "sap", "hueckel": "huckel", "huckel": "huckel", } _scf_guess = _periodic_guess_routes.get(initial_guess.strip().lower()) # Smearing flavour. Fermi-Dirac, Mermin, Methfessel-Paxton, # and Marzari-Vanderbilt are all implemented (unsupported # flavours raise NotImplementedError above, before any SCF # runs). The citation route keys are the canonical spellings; # Fermi-Dirac is skipped by assemble() and its Mermin # citation comes from uses_smearing. _smearing_method = None if opts.smearing_temperature > 0.0: _smearing_method = ( str(smearing_method_label).strip().lower().replace("-", "_") ) # COOP/COHP bonding analysis (Hughbanks-Hoffmann COOP, # Dronskowski-Bloechl COHP, LOBSTER AO projection). The # analysis itself runs in the DOS/QVF block further down # (it needs the lattice matrices assembled there), so gate # the citation on the exact conditions that gate that # block: coop_cohp requested, QVF output on, SCF # converged. Both routes fire together -- the runner # always passes H_terms, so COHP is computed alongside # COOP whenever the analysis runs. _props: list[str] = [] if coop_cohp and output_qvf and result.converged: _props = ["coop", "cohp"] _refs = _db.assemble( method=( "aiccm2026dev-b" if resolved_jk == PeriodicJKMethod.AICCM2026DEV_B else method_upper ), basis=basis.name, functional=functional, periodic=True, # => spglib fires uses_ecp=bool(_ecp_blocks), uses_fftw_poisson=_uses_fftw, uses_smearing=opts.smearing_temperature > 0.0, # Saunders-Hillier level shift (constant, warm-up, or an # explicit level_shift_schedule) is a cited convergence # technique. uses_level_shift=( float(getattr(opts, "level_shift", 0.0) or 0.0) != 0.0 or any( float(s) != 0.0 for s in (getattr(opts, "level_shift_schedule", None) or []) ) ), smearing_method=_smearing_method, scf_guess=_scf_guess, # Cite the accelerator this run actually used. Without # this the default fires and every Anderson/Broyden/ # Kerker run cited Pulay DIIS instead # (PERIODIC-CITES-DIIS-IT-DID-NOT-USE). scf_accelerator=citation_scf_accelerator( density_mixer, density_mixer_kerker ), dft_plus_u=bool(dft_plus_u), uses_slab_ewald_2d=( resolved_jk == PeriodicJKMethod.SLAB_EWALD_2D or _uses_gdf_2d ), uses_gpw=_uses_gpw, uses_gapw=_uses_gapw, uses_gdf=_uses_gdf and not _uses_gdf_2d, uses_gdf_2d=_uses_gdf_2d, uses_bipole=_uses_bipole, # M4b QQR-style separation-aware SR screening. M5 # enables it with the default padded image domain. uses_bipole_sr_range=( _uses_bipole and bool( getattr( getattr(opts, "lattice_opts", None), "sr_range_screening", False, ) ) ), uses_ewald_ao_ft=_uses_ewald_ao_ft, uses_ml_kpredictor=_kpts_uses_ml, acceleration=_jk_acceleration, properties=_props, numerics=_numerics, ) cite_manifest_rows = citation_manifest_rows(_refs) _output_writer.dispatch_role( "citations", citations=_refs, raise_on_error=True, ) cite_block_text = format_references_block(_refs) _bibtex_content = format_bibtex(_refs) bibtex_path = stem_sibling(output_stem, ".bibtex") write( f" Citations written to {bibtex_path.name} + " f"{stem_sibling(output_stem, '.references').name}\n" ) except Exception as exc: warn( f"citation emission failed: {type(exc).__name__}: {exc}", role="citations", ) warn_writer_failure( exc, stem_sibling(output_stem, ".bibtex"), role="citations", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) if write_xsf_structure_file: xsf_struct_path = stem_sibling(output_stem, ".xsf") try: # When write_density=True we'd collide with the # density XSF below; route the structure-only XSF to # a different suffix in that case. if write_density: xsf_struct_path = stem_sibling(output_stem, ".structure.xsf", ) _output_writer.dispatch_role( "geometry", only_format="xsf", system=system, raise_on_error=True, ) write( f" Structure written to {xsf_struct_path.name} " f"(XSF crystal block)\n" ) except Exception as exc: warn( f"XSF structure write failed: {type(exc).__name__}: {exc}", role="xsf_structure", ) warn_writer_failure( exc, xsf_struct_path, role="xsf_structure", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) # --- Density XSF + grid for QVF ------------------------------- # Compute the primitive-cell density grid once; reuse it for # both the XSF writer and the QVF archive (when requested). # The grid origin is (0,0,0) and the span is the lattice- # vector transpose (rows = spanning vectors), matching the XSF # convention. All units are bohr -- write_qvf stores them # as-is in the grid descriptor (see _grid_descriptor). _qvf_volume_data = None _qvf_system = system _qvf_mo_data = None _qvf_extensions = None _qvf_vendor_json_sections = None if ( output_qvf and resolved_jk == PeriodicJKMethod.AICCM2026DEV_A_REAL_GAMMA ): _qvf_vendor_json_sections = _real_gamma_qvf_vendor_sections(result) if _qvf_vendor_json_sections: _qvf_extensions = _qvf_extensions_with( _qvf_extensions, "x_vibeqc", ) if output_qvf and resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: _qvf_vendor_json_sections = _aiccm_b_qvf_vendor_sections(result) if _qvf_vendor_json_sections: _qvf_extensions = _qvf_extensions_with( _qvf_extensions, "x_vibeqc", ) if output_qvf and qvf_wannier_centers: try: if resolved_jk != PeriodicJKMethod.AICCM2026DEV_B: # Gamma-only route (the gate above rejects multi-k): # the occupied Bloch functions span the cell's whole # occupied space, so localizing them is a well-defined # Wannier construction for that cell. from .periodic_localise import localise_periodic_gamma center_sections = _gamma_qvf_wannier_center_sections( localise_periodic_gamma(result, basis, system) ) else: if hasattr(result, "mo_coeffs_alpha"): from .periodic_aiccm2026dev_b_localization import ( localize_aiccm2026dev_b_unrestricted_occupied, ) localization = ( localize_aiccm2026dev_b_unrestricted_occupied( result, system, basis, ) ) else: from .periodic_aiccm2026dev_b_localization import ( localize_aiccm2026dev_b_occupied, ) localization = localize_aiccm2026dev_b_occupied( result, system, basis, ) center_sections = _aiccm_b_qvf_wannier_center_sections( localization, ) if center_sections: _qvf_vendor_json_sections = list( _qvf_vendor_json_sections or [] ) _qvf_vendor_json_sections.extend(center_sections) _qvf_extensions = _qvf_extensions_with( _qvf_extensions, "x_ccm", ) except Exception as _qvf_wannier_exc: warn( "χ-CCM-B QVF Wannier-centre overlay failed: " f"{type(_qvf_wannier_exc).__name__}: {_qvf_wannier_exc}", role="qvf_wannier_overlay", ) warn_output_failure( _qvf_wannier_exc, stem_sibling(output_stem, ".qvf"), role="qvf_wannier_centers", category=OutputFailureKind.optional_artifact, ) _needs_density_grid = write_density or output_qvf if _needs_density_grid: try: L_bohr = np.asarray(system.lattice, dtype=float) density_write_system = system shape = tuple( max( 1, int( np.ceil(np.linalg.norm(L_bohr[:, i]) / density_spacing_bohr) ), ) for i in range(3) ) if resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: diag = getattr(result, "aiccm2026dev_b", None) mesh = tuple( int(value) for value in getattr(diag, "mesh", (1, 1, 1)) ) super_system = _aiccm_b_qvf_supercell_system(system, mesh) super_basis = BasisSet( super_system.unit_cell_molecule(), basis.name, ) L_bohr = np.asarray(super_system.lattice, dtype=float) shape = tuple( max( 1, int( np.ceil( np.linalg.norm(L_bohr[:, i]) / density_spacing_bohr ) ), ) for i in range(3) ) density_matrix = _aiccm_b_full_density_matrix_for_qvf( result, system, mesh, ) if density_matrix.shape != ( int(super_basis.nbasis), int(super_basis.nbasis), ): raise ValueError( "aiccm2026dev-b QVF supercell density shape " f"{density_matrix.shape!r} does not match " f"supercell basis size {int(super_basis.nbasis)}" ) rho, per_voxel = _evaluate_density_matrix_on_lattice_grid( density_matrix, super_basis, L_bohr, shape, system=super_system, ) _qvf_system = super_system density_write_system = super_system if output_qvf: try: _qvf_mo_data = _aiccm_b_qvf_gamma_orbital_grids( result, super_basis, super_system, L_bohr, shape, mesh, ) except Exception as _qvf_mo_exc: warn( "periodic QVF orbital grid preparation " f"failed: {type(_qvf_mo_exc).__name__}: " f"{_qvf_mo_exc}", role="qvf_orbital_grid_prep", ) warn_output_failure( _qvf_mo_exc, stem_sibling(output_stem, ".qvf"), role="qvf_orbital_grid_prep", category=OutputFailureKind.compatibility_fallback, ) else: rho, per_voxel = _exact_periodic_density_grid_artifact( basis, system, result, lattice_bohr=L_bohr, grid_shape=shape, spacing_bohr=density_spacing_bohr, gamma_only=_requested_gamma_only, expected_electrons=float( int(system.n_electrons()) - int(_total_ncore or 0) ), bvk_mesh=tuple( int(value) for value in getattr( _requested_bloch_kmesh, "mesh", (), ) ), ) # --- XSF output --- if write_density: _output_writer.dispatch_role( "density", only_format="xsf", system=density_write_system, data=rho, name=f"{output_stem.name}_density", raise_on_error=True, ) write(f" Density written to {xsf_path.name}\n") # --- Package for QVF ---------------------------------- # QVF writes grid in bohr. Origin is (0,0,0) for a # primitive-cell grid. voxel_vectors are per-voxel # step vectors: lattice column / shape_i. if output_qvf: _qvf_volume_data = { "Electron density": ( rho, np.zeros(3, dtype=float), per_voxel, ), } except Exception as exc: warn( f"density grid evaluation failed: {type(exc).__name__}: {exc}", role="density_grid", ) if write_density: warn_writer_failure( exc, xsf_path, role="density_grid", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) if output_qvf: warn_output_failure( exc, stem_sibling(output_stem, ".qvf"), role="qvf_density_grid", category=OutputFailureKind.compatibility_fallback, ) # --- Timing --------------------------------------------------- t_total = time.perf_counter() - t_job_start n_iter = int(getattr(result, "n_iter", 0)) iter_avg = (t_scf / n_iter) if n_iter > 0 else float("nan") write( "\n" + _format_timing_summary( [ ("SCF total", t_scf, None), ("SCF avg per iter", iter_avg, f"({n_iter} iters)"), ("Job total", t_total, None), ], label_width=28, body_indent=2, ) + "\n" ) flush() # --- TD-DFT excited states (Gamma-point, TDA) ------------------- if tddft and _has_valid_mo_coeffs(result): try: from vibeqc.tddft import ( run_tddft_tda_periodic as _run_td_periodic, ) # Extract n_occ from the result. _n_occ = None _occ = getattr(result, "occupations", None) if _occ is not None: if isinstance(_occ, (list, tuple)): _occ = _occ[0] _occ = np.asarray(_occ, dtype=float) _n_occ = int(np.sum(_occ > 1.0 - 1e-8)) if _n_occ is None: _n_el = getattr(result, "n_electrons", 0) _n_occ = _n_el // 2 if _n_el else 1 _td = _run_td_periodic( result, basis, n_occ=_n_occ, n_states=tddft_n_states, functional=functional, ) _td_func = f" ({functional})" if functional else "" write( f"\n ## TD-DFT excited states" f" (TDA{_td_func}, Gamma-point)\n" f" {'─' * 50}\n" ) write( f" {'State':>6s} {'E (eV)':>10s}" f" {'λ (nm)':>10s} {'f_osc':>10s}" f" {'Dominant transition'}\n" ) for _st in _td.states: _dom_str = ", ".join( f"{occ}{virt} ({abs(amp):.3f})" for occ, virt, amp in _st.dominant_amplitudes[:3] ) write( f" {_st.index:>6d} " f"{_st.excitation_energy_ev:>10.4f} " f"{_st.wavelength_nm:>10.1f} " f"{_st.oscillator_strength:>10.4f} " f"{_dom_str}\n" ) write("\n") flush() except Exception as _td_per_exc: write( f"\n ## TD-DFT excited states\n" f" {'─' * 50}\n" f" FAILED: {type(_td_per_exc).__name__}:" f" {_td_per_exc}\n" ) flush() # --- References block (Phase O5b) ----------------------------- # Embed the assembled citation list in the .out so the text # log is self-contained -- a user reading the .out doesn't have # to chase the .bibtex / .references siblings to know what to # cite. Same content that lives in the .references file, hard- # wrapped to match the SCF-trace layout. Emitted through the # citation printer's channel writer (a first-class logger op). if cite_block_text: write_references_block(block=cite_block_text) flush() # --- Harmonic vibrational analysis (finite-difference Hessian) --- if hessian: _scf_converged = bool(getattr(result, "converged", False)) if not _scf_converged: write( "\n" + section_header("## Vibrational Frequencies") + " SKIPPED -- SCF did not converge.\n" ) flush() else: try: from ._vibeqc_core import ( RHFOptions, RKSOptions, UHFOptions, UKSOptions, ) from .hessian import ( HessianFDOptions, compute_hessian_fd, ir_intensities, ) if method_upper == "RHF": _hess_scf_opts = RHFOptions() elif method_upper == "UHF": _hess_scf_opts = UHFOptions() elif method_upper == "RKS": _hess_scf_opts = RKSOptions() _hess_scf_opts.functional = str(functional) elif method_upper == "UKS": _hess_scf_opts = UKSOptions() _hess_scf_opts.functional = str(functional) else: raise ValueError( f"Hessian not available for method={method_upper}" ) _hess_opts = HessianFDOptions( include_dipole_derivatives=True, frozen_indices=hessian_frozen_indices, ) _uc_mol = system.unit_cell_molecule() hessian_result = compute_hessian_fd( _uc_mol, basis.name, method=method_upper, scf_options=_hess_scf_opts, hessian_options=_hess_opts, ) write( "\n" + section_header("## Vibrational Frequencies") + f" Finite-difference Hessian (unit cell)" f" (step = {_hess_opts.step_bohr:.3f} bohr," f" {hessian_result.n_displacements} displacements)\n" f" Imaginary modes: {hessian_result.imaginary_count}\n" f" Linear molecule: {hessian_result.is_linear}\n\n" ) _n_skip = 5 if hessian_result.is_linear else 6 _freqs = hessian_result.frequencies_cm1 _ir = None try: _ir = ir_intensities(hessian_result) except Exception as _ir_exc: write( f" (warning: IR intensities not available: " f"{type(_ir_exc).__name__}: {_ir_exc})\n" ) # Headered column table via Table (content-sized rule), # mirroring the molecular frequency table. _freq_cols = [ Column("Mode", "<"), Column("Freq/cm\u207b\u00b9", ">"), ] if _ir is not None: _freq_cols.append(Column("IR/(km/mol)", ">")) _freq_tbl = Table(_freq_cols) for k in range(_n_skip, len(_freqs)): _label = f"{k - _n_skip + 1}" _freq = _freqs[k] if _freq < 0: _freq_str = f"{render_frequency(abs(_freq))}i" else: _freq_str = render_frequency(_freq) if _ir is not None: _freq_tbl.add_row(_label, _freq_str, f"{_ir[k]:.2f}") else: _freq_tbl.add_row(_label, _freq_str) write(_freq_tbl.render() + "\n\n") flush() except Exception as _hess_exc: write( "\n" + section_header("## Vibrational Frequencies") + f" FAILED: {type(_hess_exc).__name__}: {_hess_exc}\n" ) flush() hessian_result = None _output_writer.record(out_path, wall_time_s=t_total) # Fill late-bound execution and finite-torus fields while preserving the # upfront ``status = running`` manifest and all dispatch outcomes. # IID 344: the executed low-level backend identity and the structured # parity-hold state are reproducibility facts of this run -- carried as # result-struct fields through the existing [run] extension point # (``update_run_fields``) so a held absolute energy is visible in the # .system manifest, not only in the .err sidecar. _executed_backend = str( getattr(result, "runtime_backend", None) or getattr(result, "backend", "") or "" ) _manifest_run_fields = { "exchange_q0": exchange_q0_label(exchange_exxdiv), "jk_method_requested": _jk_requested_label, "jk_method_resolved": resolved_jk.value, "jk_method_executed": _executed_jk_method, "backend": _executed_backend, "parity_held": bool( getattr( result, "parity_held", "+PARITY_HELD" in _executed_backend, ) ), "dft_plus_u": bool(dft_plus_u), "dft_plus_u_route": _dft_plus_u_route, } if resolved_jk == PeriodicJKMethod.GAPW: _manifest_run_fields["gapw_one_centre_resolved"] = getattr( result, "one_centre", "analytic" if method_upper in ("RHF", "UHF") else "block", ) _manifest_run_fields["gapw_molecular_limit_declared"] = bool( getattr( result, "molecular_limit_declared", gapw_molecular_limit, ) ) if resolved_jk == PeriodicJKMethod.AICCM2026DEV_B: _b_convention = getattr(result, "finite_torus_convention", None) _b_exchange_q0 = getattr(_b_convention, "exchange_q0", None) _manifest_run_fields["exchange_q0"] = ( "not-recorded" if _b_exchange_q0 is None else exchange_q0_label(_b_exchange_q0) ) _manifest_run_fields["ccm_approach"] = getattr( _b_convention, "ccm_approach", "not-recorded", ) _manifest_run_fields["ccm_construction"] = getattr( _b_convention, "ccm_construction", "not-recorded", ) _manifest_run_fields["evaluation_representation"] = getattr( _b_convention, "evaluation_representation", "not-recorded", ) _manifest_run_fields["exchange_q0_applicability"] = getattr( _b_convention, "exchange_q0_applicability", "not-recorded", ) _b_exact_exchange = getattr( result, "exact_exchange_assembly", None, ) _manifest_run_fields["exact_exchange_assembly_schema"] = getattr( _b_exact_exchange, "schema", "not-recorded", ) _manifest_run_fields["exact_exchange_resolver"] = getattr( _b_exact_exchange, "resolver", "not-recorded", ) _manifest_run_fields["exact_exchange_c_full"] = getattr( _b_exact_exchange, "c_full", "not-recorded", ) _manifest_run_fields["exact_exchange_c_sr"] = getattr( _b_exact_exchange, "c_sr", "not-recorded", ) _manifest_run_fields["exact_exchange_omega_screen_bohr_inv"] = getattr( _b_exact_exchange, "omega_screen_bohr_inv", "not-recorded", ) _manifest_run_fields["screened_exchange_applicability"] = getattr( _b_exact_exchange, "screened_exchange_applicability", "not-recorded", ) _manifest_run_fields["screened_exchange_assembly"] = getattr( _b_exact_exchange, "screened_exchange_assembly", "not-recorded", ) if resolved_jk == PeriodicJKMethod.AICCM2026DEV_A_REAL_GAMMA: # EXPERIMENTAL real-Γ control: record the RESULT-derived convention # (never the requested label -- for a screened hybrid the seam is # structurally inactive regardless of exchange_exxdiv) plus the D86 # executed values and the lattice-vector convention. _rg_convention = getattr(result, "real_gamma", None) _rg = lambda field: getattr(_rg_convention, field, "not-recorded") _manifest_run_fields["exchange_q0"] = ( _rg("exchange_q0") or "not-applicable" ) _manifest_run_fields["exchange_q0_applicability"] = _rg( "exchange_q0_applicability") _manifest_run_fields["ccm_construction"] = ( "none (neutral fitted-torus representation control)") _manifest_run_fields["evaluation_representation"] = ( "real supercell-Gamma eigenproblem") _manifest_run_fields["lattice_vector_convention"] = _rg( "lattice_vector_convention") _manifest_run_fields["executed_fock_mixing"] = _rg( "executed_fock_mixing") _manifest_run_fields["executed_damping"] = _rg("executed_damping") _manifest_run_fields["executed_level_shift"] = _rg( "executed_level_shift") _manifest_run_fields["bvk_nrep"] = list( _rg("nrep")) if _rg_convention is not None else "not-recorded" _manifest_run_fields["bvk_n_cells"] = _rg("n_cells") _manifest_run_fields["cderi_build"] = _rg("cderi_build") _manifest_run_fields["experimental_route"] = True _rg_screened = getattr(_rg_convention, "screened_exchange", None) if _rg_screened is not None: c_full, c_sr, omega_screen = _rg_screened _manifest_run_fields["exact_exchange_c_full"] = c_full _manifest_run_fields["exact_exchange_c_sr"] = c_sr _manifest_run_fields["exact_exchange_omega_screen_bohr_inv"] = ( omega_screen) _manifest_run_fields["screened_exchange_assembly"] = ( "sr-direct (erfc-attenuated fitted kernel + sigma0 zero " "mode; no exchange-q0 seam)") _output_writer.update_run_fields(_manifest_run_fields) if cite_manifest_rows: # Feed the full assembled provenance into the .system manifest's # [citations] section, mirroring the molecular runner. Without # this the periodic .system ended with `count = 0` even though # the references were assembled and printed to .out / .bibtex. _output_writer.set_citations(cite_manifest_rows) # --- DOS (total + projected) for QVF embedding ------------------- # Compute Fock lattice terms from the converged SCF density, then # Gaussian-broaden eigenvalues on a dense Monkhorst-Pack mesh. # Both total and projected DOS are serialized into the QVF archive # so vibe-view can render interactive side-by-side bands+DOS panels. _qvf_dos_data: Optional[dict[str, Any]] = None _qvf_pdos_data: Optional[dict[str, Any]] = None _qvf_coop_data: Optional[dict[str, Any]] = None _qvf_cohp_data: Optional[dict[str, Any]] = None if ( output_qvf and result.converged and _qvf_periodic_property_payload_supported(resolved_jk) ): try: system = _system_with_valid_unit_cell_multiplicity(system) from vibeqc._vibeqc_core import ( LatticeSumOptions, build_jk_2e_real_space, build_fock_2e_real_space, compute_kinetic_lattice, compute_nuclear_lattice, compute_nuclear_lattice_ewald, compute_overlap_lattice, monkhorst_pack, ) from vibeqc.bands import ( _HARTREE_TO_EV, _density_of_states_from_terms, _projected_dos_from_terms, ao_groups_per_atom_l, ) lat_opts = LatticeSumOptions() from vibeqc._vibeqc_core import CoulombMethod lat_opts.coulomb_method = CoulombMethod.EWALD_3D lat_opts.cutoff_bohr = 18.0 # Overlap lattice for the legacy DOS lattice template. S_real = compute_overlap_lattice(basis, system, lat_opts) _is_unrestricted = hasattr(result, "density_alpha") and hasattr( result, "density_beta" ) if _is_unrestricted: D_real_alpha = _density_lattice_set_for_output( basis, system, _density_proxy_with_k_metadata(result, result.density_alpha), lat_opts, ) D_real_beta = _density_lattice_set_for_output( basis, system, _density_proxy_with_k_metadata(result, result.density_beta), lat_opts, ) D_real = _sum_lattice_density_sets_for_output( basis, system, lat_opts, D_real_alpha, D_real_beta, label="periodic spin density", ) else: D_real = _density_lattice_set_for_output( basis, system, result, lat_opts, ) # Real-space Fock terms: T(g), V(g), and 2e J-K(g). # For 3D bulk, use Ewald-summed nuclear attraction to avoid # the conditionally convergent point-charge sum. T_real = compute_kinetic_lattice(basis, system, lat_opts) if system.dim == 3: from vibeqc._vibeqc_core import ( EwaldOptions, GridOptions, build_grid, ) # Reuse the SCF Ewald a if available (BIPOLE result # stores it); otherwise compute a reasonable default. ewald_alpha = getattr(result, "ewald_alpha_bohr_inv", None) if ewald_alpha is None: from vibeqc.bipole_ext_el_pole import ( crystal_default_ewald_alpha, ) V_cell_au = float( abs(np.linalg.det(np.asarray(system.lattice, dtype=float))) ) ewald_alpha = crystal_default_ewald_alpha(V_cell_au) # Molecular grid for Ewald V_ne integration. mol = system.unit_cell_molecule() grid_opts = GridOptions() grid_opts.n_radial = 75 grid_opts.angular = "lebedev" grid_opts.lebedev_order = 29 grid_opts.partition = "becke" grid = build_grid(mol, grid_opts) ewald_opts = EwaldOptions() ewald_opts.alpha = float(ewald_alpha) ewald_opts.real_cutoff_bohr = lat_opts.cutoff_bohr ewald_opts.tolerance = 1e-8 V_real = compute_nuclear_lattice_ewald( basis, system, grid, lat_opts, ewald_opts, ) else: V_real = compute_nuclear_lattice(basis, system, lat_opts) F2e_real = build_fock_2e_real_space( basis, system, lat_opts, D_real, 1.0, 0.0, ) fock_terms = [T_real, V_real, F2e_real] # For unrestricted, build a separate beta Fock channel. if _is_unrestricted: F_J_real = build_fock_2e_real_space( basis, system, lat_opts, D_real, 0.0, 0.0, ) if method_upper == "UHF": output_hf_exchange_fraction = 1.0 elif method_upper in ("ROKS", "UKS") and functional: output_hf_exchange_fraction = float( Functional(functional, 2).hf_exchange_fraction ) else: output_hf_exchange_fraction = 0.0 if output_hf_exchange_fraction != 0.0: from vibeqc.pbc_bipole_common import _copy_lattice_with_blocks jk_alpha = build_jk_2e_real_space( basis, system, lat_opts, D_real_alpha, 0.0, ) jk_beta = build_jk_2e_real_space( basis, system, lat_opts, D_real_beta, 0.0, ) j_blocks = [ np.asarray(block, dtype=float).copy() for block in F_J_real.blocks ] alpha_blocks = [ j - output_hf_exchange_fraction * np.asarray(k, dtype=float) for j, k in zip(j_blocks, jk_alpha.K.blocks) ] beta_blocks = [ j - output_hf_exchange_fraction * np.asarray(k, dtype=float) for j, k in zip(j_blocks, jk_beta.K.blocks) ] F2e_real_alpha = _copy_lattice_with_blocks( basis, system, lat_opts, F_J_real.cells, alpha_blocks, fill_missing=True, ) F2e_real_beta = _copy_lattice_with_blocks( basis, system, lat_opts, F_J_real.cells, beta_blocks, fill_missing=True, ) else: F2e_real_alpha = F_J_real F2e_real_beta = F_J_real fock_terms_alpha = [T_real, V_real, F2e_real_alpha] fock_terms_beta = [T_real, V_real, F2e_real_beta] _n_spin = 2 else: fock_terms_alpha = fock_terms fock_terms_beta = None _n_spin = 1 # DOS k-mesh -- denser than the SCF mesh for smooth curves. _dos_mesh_ints = list(dos_kmesh) if dos_kmesh is not None else [8, 8, 8] dos_kmesh_obj = monkhorst_pack(system, _dos_mesh_ints) n_elec = system.n_electrons() sigma_ev = 0.05 # eV, Gaussian broadening sigma_ha = sigma_ev / _HARTREE_TO_EV if _is_unrestricted: mult = int(system.multiplicity) n_alpha = (n_elec + mult - 1) // 2 n_beta = (n_elec - mult + 1) // 2 else: n_alpha = n_elec // 2 n_beta = 0 # --- Total DOS ------------------------------------------------- dos_result = _density_of_states_from_terms( fock_terms_alpha, S_real, dos_kmesh_obj, sigma=sigma_ha, n_grid=500, n_electrons_per_cell=( 2 * n_alpha if _is_unrestricted else n_elec ), ) dos_result_beta = None if _is_unrestricted and fock_terms_beta is not None: dos_result_beta = _density_of_states_from_terms( fock_terms_beta, S_real, dos_kmesh_obj, sigma=sigma_ha, energy_grid=dos_result.energies, n_grid=500, n_electrons_per_cell=2 * n_beta, ) # Energies in eV, shifted to Fermi = 0 eV. e_fermi_values = [ e for e in ( dos_result.e_fermi, getattr(dos_result_beta, "e_fermi", None), ) if e is not None ] e_fermi_ha = max(e_fermi_values) if e_fermi_values else 0.0 energies_ev = (dos_result.energies - e_fermi_ha) * _HARTREE_TO_EV if dos_result_beta is not None: dos_arr = np.stack( [ np.asarray(dos_result.dos, dtype=np.float64), np.asarray(dos_result_beta.dos, dtype=np.float64), ], axis=0, ) else: dos_arr = np.asarray(dos_result.dos, dtype=np.float64) # Convert DOS units: states / Hartree / cell -> states / eV / cell dos_arr = dos_arr / _HARTREE_TO_EV _qvf_dos_data = { "energies": energies_ev, "dos": dos_arr, "smearing": sigma_ev, "smearing_type": "gaussian", "fermi_energy_ev": float(e_fermi_ha * _HARTREE_TO_EV), "n_electrons": float(n_elec), "n_spin": _n_spin, } # --- Projected DOS -------------------------------------------- groups = ao_groups_per_atom_l(system, basis) if groups: pdos_result = _projected_dos_from_terms( fock_terms_alpha, S_real, dos_kmesh_obj, groups, sigma_ha, dos_result.energies, n_grid=500, pad=5.0, n_electrons_per_cell=( 2 * n_alpha if _is_unrestricted else n_elec ), ) pdos_result_beta = None if _is_unrestricted and fock_terms_beta is not None: pdos_result_beta = _projected_dos_from_terms( fock_terms_beta, S_real, dos_kmesh_obj, groups, sigma_ha, dos_result.energies, n_grid=500, pad=5.0, n_electrons_per_cell=2 * n_beta, ) # Build channel metadata: (atom_index, symbol, l, label) channels: list[dict[str, Any]] = [] projections_list: list[np.ndarray] = [] projections_beta_list: list[np.ndarray] = [] for label in pdos_result.group_labels: contrib = np.asarray( pdos_result.contributions[label], dtype=np.float64, ) # Convert from states/Hartree -> states/eV contrib = contrib / _HARTREE_TO_EV projections_list.append(contrib) if pdos_result_beta is not None: contrib_beta = np.asarray( pdos_result_beta.contributions[label], dtype=np.float64, ) projections_beta_list.append( contrib_beta / _HARTREE_TO_EV ) # Parse label like "Mg1-s" -> atom_index, symbol, l # Use the same element-symbol table as bands.py. from vibeqc.basis_crystal import _ELEMENT_SYMBOLS as _SYMS parts = label.rsplit("-", 1) atom_label = parts[0] if len(parts) == 2 else label l_letter = parts[1] if len(parts) == 2 else "?" # Extract atom index from "H1", "Mg2", etc. atom_idx = 0 atom_symbol = label for a_idx, atom in enumerate(system.unit_cell): z = int(atom.Z) sym = _SYMS[z] if 0 < z < len(_SYMS) else f"Z{z}" expected = sym + str(a_idx + 1) if expected == atom_label: atom_idx = a_idx atom_symbol = sym break l_map = {"s": 0, "p": 1, "d": 2, "f": 3, "g": 4, "h": 5} l_val = l_map.get(l_letter.lower(), -1) channels.append( { "atom_index": atom_idx, "symbol": atom_symbol, "l": l_val, "label": label, } ) projections_alpha = np.stack(projections_list, axis=0) if projections_beta_list: projections = np.stack( [ projections_alpha, np.stack(projections_beta_list, axis=0), ], axis=0, ) else: projections = projections_alpha _qvf_pdos_data = { "energies": energies_ev, "projections": projections, "energies_units": "eV", "n_spin": _n_spin, "fermi_energy_ev": float(e_fermi_ha * _HARTREE_TO_EV), "channels": channels, } # --- COOP/COHP bonding analysis -------------------------- if coop_cohp: try: from vibeqc.coop_cohp import compute_coop_cohp as _compute_coop_cohp _cc_result = _compute_coop_cohp( fock_terms_alpha, S_real, system, basis, dos_kmesh_obj, H_terms=[T_real, V_real], # Hcore = T + V F_terms_beta=fock_terms_beta, sigma=sigma_ha, n_grid=500, n_electrons_per_cell=n_elec, ) # Energies in eV, shifted to Fermi = 0. _cc_fermi_ha = _cc_result.fermi_energy _cc_energies_ev = ( _cc_result.energies - _cc_fermi_ha ) * _HARTREE_TO_EV _qvf_coop_data = { "energies": _cc_energies_ev, "projections": np.asarray(_cc_result.coop, dtype=np.float64), "integrated": np.asarray( _cc_result.integrated_coop, dtype=np.float64 ), "energies_units": "eV", "n_spin": _n_spin, "fermi_energy_ev": float(_cc_fermi_ha * _HARTREE_TO_EV), "sigma_ev": float(sigma_ha * _HARTREE_TO_EV), "pairs": _cc_result.pairs, } if _cc_result.cohp is not None: _qvf_cohp_data = { "energies": _cc_energies_ev, "projections": np.asarray( _cc_result.cohp, dtype=np.float64 ), "integrated": np.asarray( _cc_result.integrated_cohp, dtype=np.float64 ), "energies_units": "eV", "n_spin": _n_spin, "fermi_energy_ev": float(_cc_fermi_ha * _HARTREE_TO_EV), "sigma_ev": float(sigma_ha * _HARTREE_TO_EV), "pairs": _cc_result.pairs, } except Exception as _cc_exc: warn_output_failure( _cc_exc, stem_sibling(output_stem, ".qvf"), role="coop_cohp_computation", category=OutputFailureKind.optional_artifact, ) # --- Periodic Mayer bond orders (k-space) ----------- try: from vibeqc.basis_crystal import _ELEMENT_SYMBOLS as _SYMS from vibeqc.coop_cohp import periodic_mayer_bond_orders as _pmbo _bo_matrix = _pmbo( fock_terms_alpha, S_real, system, basis, dos_kmesh_obj, n_electrons_per_cell=n_elec, F_terms_beta=fock_terms_beta, ) _bo_pairs: list[dict[str, Any]] = [] _atoms = list(system.unit_cell) _coords = np.asarray([np.asarray(a.xyz) for a in _atoms]) for i in range(_bo_matrix.shape[0]): for j in range(i + 1, _bo_matrix.shape[1]): order = float(_bo_matrix[i, j]) if order < 0.05: continue zi = int(_atoms[i].Z) zj = int(_atoms[j].Z) sym_i = _SYMS[zi] if 0 < zi < len(_SYMS) else f"Z{zi}" sym_j = _SYMS[zj] if 0 < zj < len(_SYMS) else f"Z{zj}" dist = float(np.linalg.norm(_coords[i] - _coords[j])) _bo_pairs.append( { "i": i, "j": j, "symbol_i": sym_i, "symbol_j": sym_j, "order": order, "distance_ang": float(dist * 0.529177210903), } ) if _bo_pairs: _qvf_bond_orders = { "method": "mayer", "pairs": _bo_pairs, } except Exception as _bo_exc: warn_output_failure( _bo_exc, stem_sibling(output_stem, ".qvf"), role="bond_orders_computation", category=OutputFailureKind.optional_artifact, ) except Exception as _dos_exc: warn_output_failure( _dos_exc, stem_sibling(output_stem, ".qvf"), role="dos_computation", category=OutputFailureKind.optional_artifact, ) # Keep the converged electronic result used to assemble the archive. # Geometry optimization replaces ``result`` with its own return object, # but the QVF wavefunction/property payloads above describe this SCF # result and must remain internally consistent with it. _qvf_archive_result = result # ---- Geometry optimization --------------------------------------- opt_result = None if optimize and result.converged: if _executed_jk_method not in ( PeriodicJKMethod.BIPOLE.value, PeriodicJKMethod.GDF.value, PeriodicJKMethod.AICCM2026DEV_A_REAL_GAMMA.value, ): raise NotImplementedError( "run_periodic_job: the executed periodic route " f"{_executed_jk_method!r} has no same-Hamiltonian optimizer. " "The job will not fall through to BIPOLE forces." ) # The optimize+U guard from prior commits is lifted: all four # BIPOLE drivers now accept dft_plus_u= and the per-k Pulay # overlap-derivative term is plumbed through # bipole_optimize._compute_gradient. from .bipole_optimize import relax_atoms, relax_full plog.info("") plog.banner("Geometry optimization") basis_name = basis.name # Reconstruct kmesh from the options used during SCF. km = _runner_bloch_kmesh(system, kpoints) _bipole_opt_driver_kwargs = { "use_ewald_j_split": True, "ewald_omega": ewald_omega, "ewald_precision": ewald_precision, "use_oda": use_oda, "oda_trust_lambda_max": oda_trust_lambda_max, "use_mom": use_mom, "use_multipole_far_field": use_multipole_far_field, "multipole_l_max": multipole_l_max, "use_exchange_ewald_split": use_exchange_ewald_split, "exchange_exxdiv": exchange_exxdiv, "use_fock_symmetry": symmetry_stabilize, "use_fock_symmetry_reduce": symmetry_reduce_fock, "sr_image_precision": sr_image_precision, "exact_zone_bohr": _bipole_exact_zone_bohr, "bz_integration": bz_integration, "dft_plus_u": dft_plus_u, } _bipole_opt_nuclear_cutoff = float( getattr( opts.lattice_opts, "nuclear_cutoff_bohr", getattr(opts.lattice_opts, "cutoff_bohr", 8.0), ) ) # One ambient append-channel spans the whole relaxation: both the # per-step trace and final geometry block emit through output.write(). # No file handle is threaded into the optimizer. # GDF-routed SCF (the GDF dispatch ran and captured its exact # driver call): relax on the same GDF analytic-gradient objective # the SCF produced, never on the BIPOLE force surface -- a # relaxation driven by forces from a different Hamiltonian # converges to a stationary point of the wrong surface. The # dft_plus_u legacy interception can execute a non-GDF driver # even when resolved_jk is GDF, so gate on the executed route. _gdf_routed = ( resolved_jk == PeriodicJKMethod.GDF and _executed_jk_method == PeriodicJKMethod.GDF.value ) with OutputChannel.to_file(out_path, mode="a"): write(section_header("Geometry optimization", width=56)) flush() if resolved_jk == PeriodicJKMethod.AICCM2026DEV_A_REAL_GAMMA: # EXPERIMENTAL: relax on the real-Γ direct-torus surface. # run_ccm_direct_optimize steps on the multi-k control # objective and verifies the direct-vs-control energy # parity at the initial AND final geometries, so the # relaxed stationary point is attributable to the direct # energy at a measured residual rather than by assumption # (that verification is this route's "same-Hamiltonian # optimizer" claim, checked per run instead of asserted). from .bipole_optimize import OptimizeResult from .periodic.ccm import CCMSystem from .periodic.ccm.direct import run_ccm_direct_optimize write( " force objective = real-Γ direct-torus, " "parity-verified against the multi-k control\n" ) flush() _rg_opt = run_ccm_direct_optimize( CCMSystem(system, _rg_mesh, str(basis.name)), functional=(functional if method_upper in ("RKS", "UKS") else None), exxdiv=_rg_exxdiv, conv_tol_grad=float(optimize_conv_tol_grad), max_steps=int(optimize_max_iter), aux_basis=aux_basis, ) write( f" parity (initial) = " f"{_rg_opt.parity_initial.parity_residual_ha_per_cell:.2e}" f" Ha/cell\n" f" parity (final) = " f"{_rg_opt.parity_final.parity_residual_ha_per_cell:.2e}" f" Ha/cell\n" ) flush() opt_result = OptimizeResult( _rg_opt.system_opt, float(_rg_opt.energy_per_cell), np.asarray(_rg_opt.gradient, dtype=float).ravel(), int(_rg_opt.n_steps), bool(_rg_opt.converged), ) elif _gdf_routed: if not _gdf_opt_capture: # The Γ dispatch fell back to the legacy molecular- # limit GDF driver, which has no analytic gradient. # Fail closed instead of silently relaxing on BIPOLE # forces (the objective-swap bug this wiring fixes). raise NotImplementedError( "run_periodic_job: optimize=True on the GDF route " "requires a GDF driver with an analytic gradient; " "this run fell back to the legacy Γ molecular-" "limit GDF driver (run_rhf_periodic_gamma_gdf), " "which has none. Drop the conflicting options so " "the pure PBC-GDF driver runs (see the Γ GDF " "dispatch preconditions), or use " "jk_method='bipole' for a BIPOLE-objective " "relaxation." ) write( " force objective = GDF analytic gradient " f"({_gdf_opt_capture['kwargs'].get('gdf_method', 'rsgdf')})\n" ) flush() def _gdf_opt_rerun(sys2, basis2): # The captured dispatch call, byte-identical except # progress (silenced) and compute_gradient (on). An # envelope the GDF gradient rejects surfaces the # driver's NotImplementedError -- no silent fallback. _kwargs = dict(_gdf_opt_capture["kwargs"]) _kwargs["compute_gradient"] = True return _gdf_opt_capture["driver"]( sys2, basis2, *_gdf_opt_capture["args"], progress=False, **_kwargs, ) opt_result = _relax_periodic_gdf_atoms( system, basis_name, _gdf_opt_rerun, max_iter=optimize_max_iter, conv_tol_grad=optimize_conv_tol_grad, ) elif optimize_cell and system.dim == 3: from .bipole_optimize import relax_cell_gradient # First relax atoms, then cell (gradient-based), then atoms again opt_result = relax_atoms( system, basis_name, km, method.upper(), functional=functional, max_iter=optimize_max_iter, conv_tol_grad=optimize_conv_tol_grad, scf_options=opts, cutoff_bohr=float(getattr(opts.lattice_opts, "cutoff_bohr", 8.0)), nuclear_cutoff_bohr=_bipole_opt_nuclear_cutoff, **_bipole_opt_driver_kwargs, ) _before_cell_system = opt_result.system opt_result = relax_cell_gradient( _before_cell_system, basis_name, km, method.upper(), functional=functional, max_iter=10, scf_options=opts, cutoff_bohr=float(getattr(opts.lattice_opts, "cutoff_bohr", 8.0)), nuclear_cutoff_bohr=_bipole_opt_nuclear_cutoff, **_bipole_opt_driver_kwargs, ) from .bipole_optimize import _kmesh_for_system km = _kmesh_for_system( km, _before_cell_system, opt_result.system, ) opt_result = relax_atoms( opt_result.system, basis_name, km, method.upper(), functional=functional, max_iter=optimize_max_iter, conv_tol_grad=optimize_conv_tol_grad, scf_options=opts, cutoff_bohr=float(getattr(opts.lattice_opts, "cutoff_bohr", 8.0)), nuclear_cutoff_bohr=_bipole_opt_nuclear_cutoff, **_bipole_opt_driver_kwargs, ) else: opt_result = relax_atoms( system, basis_name, km, method.upper(), functional=functional, max_iter=optimize_max_iter, conv_tol_grad=optimize_conv_tol_grad, scf_options=opts, cutoff_bohr=float(getattr(opts.lattice_opts, "cutoff_bohr", 8.0)), nuclear_cutoff_bohr=_bipole_opt_nuclear_cutoff, **_bipole_opt_driver_kwargs, ) if opt_result is not None: opt_sys = opt_result.system write("\n" + _optimized_geometry_summary(opt_result)) write(_system_summary(opt_sys)) flush() # Write optimized geometry files as SEPARATE artefacts # ({stem}.opt.xyz / {stem}.opt.POSCAR), not overwriting the # SCF geometry. The writers do stem_sibling(stem, EXT), # which appends EXT unless the name already ends in exactly # EXT -- so the stem is passed already ending in # ".opt.<EXT>" and the writer's own derivation stays a # no-op, yielding the intended compound name. (Before issue # #254 the writers used Path.with_suffix, which *replaced*; # stem_sibling keeps that no-op deliberately, see its # module docstring.) Each is recorded in # the manifest (runtime-conditional, so not in real_plan; # mark_written appends it to [[outputs.files]]). if write_xyz_file: try: _output_writer.dispatch_role( "geometry", runtime_path=stem_sibling(output_stem, ".opt.xyz"), runtime_format="extended-xyz", runtime_description="Optimized periodic geometry.", system=opt_sys, energy_ha=opt_result.energy, raise_on_error=True, ) except Exception as _opt_xyz_exc: warn_writer_failure( _opt_xyz_exc, stem_sibling(output_stem, ".opt.xyz"), role="optimized_extended_xyz", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) if write_poscar_file: try: _output_writer.dispatch_role( "geometry", runtime_path=stem_sibling(output_stem, ".opt.POSCAR"), runtime_format="poscar", runtime_description="Optimized periodic POSCAR geometry.", system=opt_sys, comment=f"vibe-qc optimized {label}", raise_on_error=True, ) except Exception as _opt_poscar_exc: warn_writer_failure( _opt_poscar_exc, stem_sibling(output_stem, ".opt.POSCAR"), role="optimized_poscar", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) result = opt_result # Return optimization result # --- QVF visualisation archive (v1) ---------------------------------- # This is deliberately the final artefact write. In particular, periodic # optimization appends its geometry epilogue above, so run.record.log can # now contain the last byte and never needs a truncated marker. if output_qvf: try: from vibeqc.output.formats.qvf import ( assemble_run_record as _assemble_run_record, terminal_run_status as _terminal_run_status, scf_history_from_result as _scf_history_from_result, ) t_total = time.perf_counter() - t_job_start _qvf_scf_history = _scf_history_from_result(_qvf_archive_result) _qvf_run_record = _assemble_run_record( real_plan, wall_seconds=t_total, log_truncated=False, ) # --- Symmetry data for QVF structure.symmetry ---------- # Extract space group from the system if available. _qvf_symmetry = None try: _sg = getattr(_qvf_system, "symmetry", None) if _sg is not None: _qvf_symmetry = { "space_group_number": int(getattr(_sg, "number", 0)), "space_group_symbol": str( getattr(_sg, "international_symbol", "") ), "point_group": str(getattr(_sg, "point_group", "")), } except Exception: pass _output_writer.dispatch_role( "qvf", atomic=True, record_hostname=record_hostname, system=_qvf_system, result=_qvf_archive_result, method=method_upper, basis=basis.name, functional=functional, jk_method=_jk_requested_label, jk_method_resolved=resolved_jk.value, jk_method_executed=_executed_jk_method, dft_plus_u=bool(dft_plus_u), dft_plus_u_route=_dft_plus_u_route, wall_seconds=t_total, volume_data=_qvf_volume_data, mo_data=_qvf_mo_data, wf_data=_qvf_wf, bloch_wf_data=_qvf_bloch_wf, hessian_result=hessian_result, band_structure=band_structure, population_summary=_qvf_pop, bond_orders_data=_qvf_bond_orders, dipole_moment_data=_qvf_dipole, dos_data=_qvf_dos_data, pdos_data=_qvf_pdos_data, coop_data=_qvf_coop_data, cohp_data=_qvf_cohp_data, scf_history_data=_qvf_scf_history, extensions=_qvf_extensions, vendor_json_sections=_qvf_vendor_json_sections, bibtex_content=_bibtex_content, job_spec={ "job_type": "periodic", "method": method_upper, "basis": basis.name, "functional": functional, "jk_method": resolved_jk.value, "charge": int(system.charge), "multiplicity": int(system.multiplicity), }, symmetry_data=_qvf_symmetry, run_record=_qvf_run_record, # Stamp from the same result the archive describes, not the # post-optimization ``result``: run_status shares the # provenance block with scf_converged, and geometry # optimization rebinds ``result`` to its own object whose # ``converged`` means "geometry converged" (see the # _qvf_archive_result snapshot above). run_status=_terminal_run_status(_qvf_archive_result), raise_on_error=True, ) except Exception as _qvf_exc: warn_writer_failure( _qvf_exc, stem_sibling(output_stem, ".qvf"), role="qvf_archive", category=OutputFailureKind.optional_artifact, writer=_output_writer, ) # Terminal checkpoint frame: use the actual terminal result status so a # live viewer can stop watching without mistaking a non-converged SCF or # optimization for a converged one. Uses the final (optimized, if any) # system + result. The OutputWriter lifecycle wrapper also marks the # manifest crashed if terminal work raises. if _checkpointer.enabled: _finalize_periodic_checkpoint( _checkpointer, result, system, method_upper, basis.name, functional, ) # Optimization appends to the log after the SCF-side record above. # Re-recording updates the existing outcome row and final hash. The public # lifecycle wrapper finishes the manifest after runtime ``.opt.*`` files. _output_writer.record(out_path) return result