codexanderson-chain-eigenstate.py
anderson-chain-eigenstate.py
"""Finite open Anderson chain; Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
Run from _media; --sites (40..100), --disorder (full box width / t), and
--seed expose the model controls. Energies and hopping use t=1 by default.
"""
import argparse
import json
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def spectrum(epsilon, hopping=1.0):
n = len(epsilon)
h = np.diag(epsilon) - hopping * (np.eye(n, k=1) + np.eye(n, k=-1))
energy, vectors = np.linalg.eigh(h)
# Test the returned vectors, rather than silently renormalizing them.
np.testing.assert_allclose(vectors.T @ vectors, np.eye(n), atol=2e-14, rtol=0)
np.testing.assert_allclose(h @ vectors, vectors * energy, atol=2e-14, rtol=0)
np.testing.assert_allclose(np.sum(abs(vectors) ** 2, axis=0), 1, atol=2e-14, rtol=0)
return energy, vectors
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--sites', type=int, default=80)
parser.add_argument('--disorder', type=float, default=6.0)
parser.add_argument('--seed', type=int, default=20261015)
parser.add_argument('--hopping', type=float, default=1.0)
args = parser.parse_args()
if not 40 <= args.sites <= 100 or args.disorder < 0 or args.hopping <= 0:
parser.error('require 40 <= sites <= 100, disorder >= 0, hopping > 0')
n, t = args.sites, args.hopping
epsilon = t * args.disorder * np.random.default_rng(args.seed).uniform(-0.5, 0.5, n)
energy, vectors = spectrum(epsilon, t)
j = np.argmin(abs(energy))
probability = abs(vectors[:, j]) ** 2
participation = 1 / np.sum(probability ** 2)
clean_energy, clean_vectors = spectrum(np.zeros(n), t)
# Pick the lower central standing wave unambiguously in the clean chain.
m = n // 2
clean_probability = abs(clean_vectors[:, m - 1]) ** 2
sites = np.arange(1, n + 1)
exact = np.sqrt(2 / (n + 1)) * np.sin(m * np.pi * sites / (n + 1))
np.testing.assert_allclose(clean_probability, exact ** 2, atol=2e-14, rtol=0)
clean_p = 1 / np.sum(clean_probability ** 2)
np.testing.assert_allclose(clean_p, 2 * (n + 1) / 3, atol=2e-12, rtol=0)
assert 1 - 1e-12 <= participation <= n + 1e-12
plt.rcParams.update({'font.size': 11, 'figure.facecolor': 'white', 'axes.facecolor': 'white'})
fig, axes = plt.subplots(3, 1, figsize=(8.2, 7.4), sharex=True,
gridspec_kw={'height_ratios': [1, 1.3, 1.3]})
fig.suptitle(f'One random chain, one normalized eigenstate: N = {n}, W/t = {args.disorder:g}', fontsize=14)
axes[0].plot(sites, epsilon / t, color='#b56c24', lw=1.1)
axes[0].axhline(energy[j] / t, color='#235f93', ls='--', label=f'Selected E/t = {energy[j]/t:.3f}')
axes[0].set_ylabel(r'On-site energy $\epsilon_n/t$')
axes[0].legend(loc='upper right', fontsize=10)
axes[1].plot(sites, probability, color='#235f93', lw=1.4, label=f'Random chain: P = {participation:.2f}')
axes[1].plot(sites, clean_probability, color='#777777', lw=0.9, alpha=0.9, label=f'Clean standing wave: P = {clean_p:.0f}')
axes[1].set_ylabel(r'Probability $|\psi_n|^2$')
axes[1].legend(loc='upper right', fontsize=10)
axes[2].semilogy(sites, probability, color='#235f93', lw=1.2)
axes[2].semilogy(sites, clean_probability, color='#777777', lw=0.9, alpha=0.8)
axes[2].set_ylabel(r'$|\psi_n|^2$ (log scale)')
axes[2].set_ylim(max(probability.min() / 3, 1e-32), 1)
axes[2].set_xlabel('Site n; open ends (no bond from N to 1)')
for ax in axes:
ax.grid(alpha=0.2)
ax.set_xlim(1, n)
fig.subplots_adjust(left=0.11, right=0.98, top=0.91, bottom=0.11, hspace=0.16)
fig.text(0.5, 0.012, f'Chosen model, seed {args.seed}; no absorption, fitted tail, or measured data.', ha='center', fontsize=10)
fig.savefig(Path(__file__).stem + '.png', dpi=120, facecolor='white', transparent=False)
print(json.dumps({'N': n, 'W_over_t': args.disorder, 'seed': args.seed,
'selected_energy_over_t': float(energy[j]/t), 'P': float(participation),
'peak_site': int(np.argmax(probability) + 1), 'clean_P': float(clean_p),
'normalization_residual': float(abs(probability.sum() - 1)),
'checks': ['all eigenvector normalizations', 'orthogonality', 'eigenvalue residuals',
'analytic clean standing wave', 'clean P = 2(N+1)/3', 'P bounds']}))
if __name__ == '__main__':
main()