Sparse vs Dense Scaling¶

Model. This notebook compares dense and sparse matrix construction for selected tight-binding and Hubbard builders. Sparse matrices store only nonzero entries and are useful for low-energy calculations.

Typical uses. Memory-scaling checks, deciding when to switch to sparse workflows, low-energy spectra with eigsh, and sanity checks that sparse and dense builders agree.

Parameters. n_sites, n_rows, n_cols, and max_occupancy drive dimension growth. nnz counts sparse nonzero entries. lowest_eigenvalues(H, k) computes low-energy values with sparse solvers for sparse matrices.

Useful plots. Sparse density and storage comparisons, sparse/dense eigenvalue agreement, and low-energy spectra.

In [1]:
import matplotlib.pyplot as plt
import numpy as np

from quantum_lattice_models.models import (
    bose_hubbard_chain,
    bose_hubbard_chain_sparse,
    fermi_hubbard_chain,
    fermi_hubbard_chain_sparse,
    square_lattice_tight_binding,
    square_lattice_tight_binding_sparse,
)
from quantum_lattice_models.spectra import eigenvalues, lowest_eigenvalues
In [2]:
comparisons = []
for n in [2, 3, 4]:
    dense = bose_hubbard_chain(n_sites=n, max_occupancy=2)
    sparse = bose_hubbard_chain_sparse(n_sites=n, max_occupancy=2)
    sparse_bytes = sparse.data.nbytes + sparse.indices.nbytes + sparse.indptr.nbytes
    comparisons.append(
        (
            f"Bose n={n}",
            dense.shape[0],
            sparse.nnz,
            sparse.nnz / dense.size,
            dense.nbytes / sparse_bytes,
        )
    )
for n in [2, 3]:
    dense = fermi_hubbard_chain(n_sites=n)
    sparse = fermi_hubbard_chain_sparse(n_sites=n)
    sparse_bytes = sparse.data.nbytes + sparse.indices.nbytes + sparse.indptr.nbytes
    comparisons.append(
        (
            f"Fermi n={n}",
            dense.shape[0],
            sparse.nnz,
            sparse.nnz / dense.size,
            dense.nbytes / sparse_bytes,
        )
    )
for rows, cols in [(4, 4), (6, 6)]:
    dense = square_lattice_tight_binding(rows, cols)
    sparse = square_lattice_tight_binding_sparse(rows, cols)
    comparisons.append(
        (
            f"Square {rows}x{cols}",
            dense.shape[0],
            sparse.nnz,
            sparse.nnz / dense.size,
            dense.nbytes / (sparse.data.nbytes + sparse.indices.nbytes + sparse.indptr.nbytes),
        )
    )

print("model          | dimension | nonzeros | sparse density | dense/sparse bytes")
print("---            | ---       | ---      | ---            | ---")
for label, dimension, nnz, density, storage_ratio in comparisons:
    print(
        f"{label:<14s} | {dimension:>9d} | {nnz:>8d} | "
        f"{density:.4f}         | {storage_ratio:>6.2f}x"
    )
model          | dimension | nonzeros | sparse density | dense/sparse bytes
---            | ---       | ---      | ---            | ---
Bose n=2       |         9 |       13 | 0.1605         |   4.32x
Bose n=3       |        27 |       67 | 0.0919         |   8.03x
Bose n=4       |        81 |      281 | 0.0428         |  17.65x
Fermi n=2      |        16 |       23 | 0.0898         |   7.76x
Fermi n=3      |        64 |      165 | 0.0403         |  18.41x
Square 4x4     |        16 |       48 | 0.1875         |   3.98x
Square 6x6     |        36 |      120 | 0.0926         |   8.14x
In [3]:
dense = square_lattice_tight_binding(3, 3)
sparse = square_lattice_tight_binding_sparse(3, 3)
dense_lowest = eigenvalues(dense)[:4]
sparse_lowest = lowest_eigenvalues(sparse, k=4)
print("Square 3x3 lowest eigenvalues")
print("  dense: ", np.array2string(dense_lowest, precision=6, separator=", "))
print("  sparse:", np.array2string(sparse_lowest, precision=6, separator=", "))
Square 3x3 lowest eigenvalues
  dense:  [-2.828427e+00, -1.414214e+00, -1.414214e+00, -1.737460e-16]
  sparse: [-2.828427e+00, -1.414214e+00, -1.414214e+00, -8.133100e-17]
In [4]:
labels = [row[0] for row in comparisons]
densities = [100 * row[3] for row in comparisons]
storage_ratios = [row[4] for row in comparisons]

fig, axes = plt.subplots(1, 2, figsize=(10, 3.5))
x = np.arange(len(labels))
axes[0].bar(x, densities, color="#0072B2")
axes[0].set_ylabel("Nonzero entries (%)")
axes[0].set_title("Sparse matrix density")
axes[1].bar(x, storage_ratios, color="#009E73")
axes[1].axhline(1.0, color="0.35", linestyle="--", linewidth=0.9)
axes[1].set_ylabel("Dense / sparse storage")
axes[1].set_title("Storage reduction")
for ax in axes:
    ax.set_xticks(x)
    ax.set_xticklabels(labels, rotation=35, ha="right")
    ax.grid(axis="y", color="0.88", linewidth=0.8)
fig.tight_layout()
No description has been provided for this image