Comparing periodic stockholder methods

This notebook applies Hirshfeld, Hirshfeld-I, MBIS, LISA, and AVH-B to the same periodic density and uniform grid. The example illustrates method-specific basis requirements and common numerical checks; it is not intended as a physical model of crystalline hydrogen.

[1]:
import numpy as np

from horton_part import ProAtomDB
from horton_part.periodic import partition_periodic
from periodic_setup import make_demo_spline_basis, make_demo_system
[2]:
coordinates, numbers, grid, density, lisa_basis = make_demo_system()
spline_basis = make_demo_spline_basis()
print(f"integrated electrons: {grid.integrate(density):.8f}")
integrated electrons: 1.99999972

Basis requirements

MBIS constructs its own Slater basis. LISA uses normalized exponential functions with fixed exponents and optimized nonnegative populations. Hirshfeld, Hirshfeld-I, and AVH use electron-normalized radial-spline densities of isolated charged atoms. AVH-B selects the physically bound states from that library. The neutral spline schema is shared with finite-system calculations; no basis conversion is required.

[3]:
finite_proatoms = ProAtomDB.from_spline_file(spline_basis)
print("finite-system elements:", finite_proatoms.get_numbers())
print("available H charge states:", finite_proatoms.get_charges(1))
finite-system elements: [1]
available H charge states: [1, 0, -1]
[4]:
calculations = {
    "Hirshfeld": ("hirshfeld", {"basis": spline_basis}),
    "Hirshfeld-I": ("hirshfeld-i", {"basis": spline_basis}),
    "MBIS": ("mbis", {}),
    "LISA": ("lisa", {"basis": lisa_basis}),
    "AVH-B": ("avh", {"basis": spline_basis, "avh_variant": "B"}),
}
results = {}
for label, (method, options) in calculations.items():
    results[label] = partition_periodic(
        method,
        coordinates,
        numbers,
        grid,
        density,
        density_cutoff=1.0e-14,
        threshold=1.0e-7,
        maxiter=1000,
        **options,
    )
[5]:
print(f"{'method':12s} {'q(H1)':>10s} {'q(H2)':>10s} {'iterations':>11s} {'solver':>10s}")
for label, result in results.items():
    print(
        f"{label:12s} {result.charges[0]:+10.6f} {result.charges[1]:+10.6f} "
        f"{result.iterations:11d} {result.solver:>10s}"
    )
method            q(H1)      q(H2)  iterations     solver
Hirshfeld     -0.202526  +0.202532           0      fixed
Hirshfeld-I   -0.254761  +0.254762           8         sc
MBIS          -0.193535  +0.193536          30  optimizer
LISA          -0.200000  +0.200000          27  optimizer
AVH-B         -0.196296  +0.196297          18  optimizer

Check every partition

A converged calculation should conserve the total charge, reconstruct the supplied density from the AIM densities, and satisfy the pointwise partition of unity wherever the promolecular density is nonzero.

[6]:
for label, result in results.items():
    active = result.promolecule > 1.0e-15
    unity_error = np.max(
        np.abs(result.aim_weights[:, active].sum(axis=0) - 1.0)
    )
    assert result.converged
    assert abs(result.charges.sum()) < 1.0e-5
    assert unity_error < 1.0e-12
    assert result.reconstruction_error < 1.0e-12
print("All five partitions passed the conservation checks.")
All five partitions passed the conservation checks.

Optional self-consistent solver

LISA, AVH, and MBIS also accept solver="sc". The optimizer remains the default because relative performance and convergence depend on the material and basis. The two LISA routes agree for this well-conditioned example.

[7]:
lisa_sc = partition_periodic(
    "lisa",
    coordinates,
    numbers,
    grid,
    density,
    basis=lisa_basis,
    solver="sc",
    threshold=1.0e-7,
    maxiter=1000,
)
print("optimizer charges:", results["LISA"].charges)
print("SC charges:       ", lisa_sc.charges)
print("maximum difference:", np.max(np.abs(results["LISA"].charges - lisa_sc.charges)))
optimizer charges: [-0.19999981  0.20000009]
SC charges:        [-0.19999983  0.20000011]
maximum difference: 1.6057171592365194e-08

The charge magnitude varies slightly because each method defines a different pro-atom model. This dependence is expected: AIM charges are analysis quantities rather than unique observables. For a scientific comparison, keep the density, quadrature, atomic-density libraries, convergence thresholds, and charge reference fixed across methods.