#!/usr/bin/env python3
"""
Two, Eight, Eight, Eighteen -- companion script v1
solvetheuniverse.com / Pressure-Based Theory, 2026-09-23

WHAT THIS IS. The numbers behind the article, re-runnable by anyone. It
builds the period lengths of the periodic table from three ingredients and
says which ingredient each factor comes from:

  1. ANGULAR degeneracy. A standing wave around a centre of spherical
     symmetry has 2l+1 independent angular patterns for each l (the
     spherical harmonics). Geometry supplies this; no atomic physics needed.
  2. SPIN. Each spatial pattern holds two electrons (Pauli exclusion, spin
     1/2). Subshell capacity = 2(2l+1) = 2, 6, 10, 14 for s, p, d, f.
  3. FILLING ORDER. Which (n, l) subshell fills next is set by the radial
     potential of a many-electron atom. The empirical summary is Madelung's
     rule: order by n+l, ties by n. It is an empirical summary of which
     subshells are occupied in the neutral atoms, not an energy ordering;
     about twenty known exceptions (Cr, Cu, Pd, Th, ...) are noted in the article.

A period ends when an s subshell of the next n opens, i.e. the row breaks
just after each p subshell fills (and after 1s for period 1). Counting
elements between those breaks gives the period lengths. The check against
reality is the atomic numbers of the noble gases, which end each period:
2, 10, 18, 36, 54, 86, 118 -- these are INPUTS from the real table, and the
script's job is to reproduce them from ingredients 1-3.

Also printed: the hydrogen-like shell capacities 2n^2 (2, 8, 18, 32, 50),
which are NOT the period lengths, and why (Madelung interleaves shells).

Run:
  python3 two-eight-eight-eighteen-v1.py
  python3 two-eight-eight-eighteen-v1.py --selftest
"""
import argparse

L_NAME = {0: "s", 1: "p", 2: "d", 3: "f"}
NOBLE_GAS_Z = [2, 10, 18, 36, 54, 86, 118]      # He, Ne, Ar, Kr, Xe, Rn, Og (real table)
REAL_PERIOD_LENGTHS = [2, 8, 8, 18, 18, 32, 32]  # from the noble-gas Z above

def angular_degeneracy(l):
    """Number of independent angular patterns for orbital quantum number l:
    the dimension of the spherical-harmonic space Y_lm, m = -l..l."""
    return 2 * l + 1

def subshell_capacity(l, spin_states=2):
    """Electrons a subshell holds: angular patterns times spin states."""
    return spin_states * angular_degeneracy(l)

def madelung_order(n_max=8):
    """All (n, l) subshells with n <= n_max, in Madelung order:
    ascending n+l, ties broken by ascending n."""
    subshells = [(n, l) for n in range(1, n_max + 1) for l in range(0, n)]
    return sorted(subshells, key=lambda nl: (nl[0] + nl[1], nl[0]))

def period_lengths(order=None, n_periods=7):
    """Walk the filling order; a new period starts at each s subshell
    (n, 0) for n >= 2. Returns the lengths of the first n_periods periods."""
    order = order or madelung_order()
    lengths, current = [], 0
    for (n, l) in order:
        if l == 0 and n >= 2 and current > 0:
            lengths.append(current)
            current = 0
        current += subshell_capacity(l)
        if len(lengths) == n_periods:
            break
    return lengths

def noble_gas_z_from(lengths):
    z, out = 0, []
    for L in lengths:
        z += L
        out.append(z)
    return out

def hydrogenic_shell_capacity(n):
    """Sum over l < n of 2(2l+1) = 2 n^2. What you get if all subshells of a
    given n filled together, which they do not."""
    return sum(subshell_capacity(l) for l in range(n))

# ---- report -------------------------------------------------------------------
def run():
    print("=" * 74)
    print("Two, Eight, Eight, Eighteen -- companion script v1 (2026-09-23)")
    print("=" * 74)
    print("\n[1] angular degeneracy 2l+1 (geometry) and subshell capacity 2(2l+1) (with spin)")
    for l in range(4):
        print(f"    l={l} ({L_NAME[l]}): patterns {angular_degeneracy(l)}, capacity {subshell_capacity(l)}")
    print("\n[2] Madelung filling order (n+l ascending, then n), first 19 subshells:")
    order = madelung_order()
    print("    " + " ".join(f"{n}{L_NAME[l]}" for (n, l) in order[:19]))
    lengths = period_lengths(order)
    print("\n[3] period lengths from the order above (new period at each ns, n>=2):")
    print("    computed:", lengths)
    print("    real    :", REAL_PERIOD_LENGTHS, " (from noble-gas Z:", NOBLE_GAS_Z, ")")
    print("    noble-gas Z reproduced:", noble_gas_z_from(lengths))
    print("\n[4] hydrogen-like shell capacities 2n^2, for contrast (NOT the period lengths):")
    print("    ", [hydrogenic_shell_capacity(n) for n in range(1, 6)])
    print("    the 8-8-18-18-32-32 doubling comes from Madelung's interleaving: 4s before 3d, 5s before 4d,")
    print("    6s before 4f and 5d, 7s before 5f and 6d. A filling order by n alone would give 2, 8, 18, 32.")
    print("\n[5] what a filling order by n alone (no Madelung) would give:")
    by_n = sorted([(n, l) for n in range(1, 8) for l in range(n)], key=lambda nl: (nl[0], nl[1]))
    print("    ", period_lengths(by_n), " -> wrong after period 2 (the third closed shell would fall at Z=28)")
    print("\nNotes: [1] and [4] are arithmetic. [2] is an empirical rule with known exceptions in")
    print("individual ground states (about twenty: Cr, Cu, Nb, Mo, Ru, Rh, Pd, Ag, Pt, Au, La, Ce, Gd,")
    print("Ac, Th, Pa, U, Np, Cm, Lr); the period LENGTHS are unaffected, because each exception")
    print("moves one or two electrons within a period, never across a boundary. [3]'s real column is input.")

# ---- self-test ----------------------------------------------------------------
def selftest():
    n = 0
    # 1. spherical-harmonic degeneracies 1,3,5,7 and capacities 2,6,10,14
    assert [angular_degeneracy(l) for l in range(4)] == [1, 3, 5, 7]; n += 1
    assert [subshell_capacity(l) for l in range(4)] == [2, 6, 10, 14]; n += 1
    # 2. Madelung order begins 1s 2s 2p 3s 3p 4s 3d 4p 5s 4d 5p 6s 4f 5d 6p 7s 5f 6d 7p
    want = [(1,0),(2,0),(2,1),(3,0),(3,1),(4,0),(3,2),(4,1),(5,0),(4,2),(5,1),(6,0),(4,3),(5,2),(6,1),(7,0),(5,3),(6,2),(7,1)]
    assert madelung_order()[:19] == want, madelung_order()[:19]; n += 1
    # 3. the period lengths and the noble-gas atomic numbers (the real-table check)
    L = period_lengths()
    assert L == REAL_PERIOD_LENGTHS, L; n += 1
    assert noble_gas_z_from(L) == NOBLE_GAS_Z, noble_gas_z_from(L); n += 1
    # 4. hydrogenic 2n^2
    assert [hydrogenic_shell_capacity(k) for k in range(1, 6)] == [2, 8, 18, 32, 50]; n += 1
    # 5. failure path: filling by n alone does NOT give the real lengths (the rule is doing work)
    by_n = sorted([(k, l) for k in range(1, 8) for l in range(k)], key=lambda nl: (nl[0], nl[1]))
    assert period_lengths(by_n) != REAL_PERIOD_LENGTHS; n += 1
    # 6. failure path: a wrong tie-break (n+l, then l) also fails
    wrong = sorted([(k, l) for k in range(1, 9) for l in range(k)], key=lambda nl: (nl[0] + nl[1], nl[1]))
    assert period_lengths(wrong) != REAL_PERIOD_LENGTHS, period_lengths(wrong); n += 1
    print(f"selftest: {n}/8 passed (degeneracies; capacities; Madelung order; period lengths 2-8-8-18-18-32-32;"
          f" noble-gas Z; 2n^2; two failure paths: filling by n alone, wrong tie-break)")

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--selftest", action="store_true")
    a = ap.parse_args()
    selftest() if a.selftest else run()
