

Made with Chatgpt 5, Python 3, Google Colab notebook, and DajaVu Sans.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Orbitals up to n = 8 (204 states), square tiles + fixed 24px DejaVu Sans labels with AA, with TOP_MARGIN and ROW_SPACING controls, using all 24-bit RGB colors exactly once. """ import math, time, numpy as np from PIL import Image, ImageDraw, ImageFont # ---------------------------- # Layout controls # ---------------------------- WIDTH, HEIGHT = 4096, 4096 GRID_COLS, GRID_ROWS = 17, 12 TOP_MARGIN = 40 # px – adds empty space at the very top ROW_SPACING = 20 # px – uniform gap between rows (affects whitespace areas) # ---------------------------- # Physics / render controls # ---------------------------- N_MAX = 8 Z_SAMPLES = 17 GAMMA = 0.70 BLOCK_SIZE = 262144 SEED = 1337 # Typography (fixed) FONT_SIZE = 24 FONT_PATHS = [ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/local/share/fonts/DejaVuSans.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Book.ttf", "DejaVuSans.ttf", ] LABEL_VERT_PAD = 3 # top padding inside the label band LABEL_SIDE_PAD = 8 # horizontal padding (still centered) LABEL_BOX_HEIGHT = 36 # fixed label band height (fits 24px + pads) LABEL_GAP = 6 # gap between tile bottom and label band np.random.seed(SEED) # ---------------------------- # Math (no SciPy) # ---------------------------- def double_factorial(n: int) -> int: if n <= 0: return 1 res = 1 for k in range(n, 0, -2): res *= k return res def assoc_legendre(l: int, m: int, x: np.ndarray) -> np.ndarray: m = abs(m) if l < m: return np.zeros_like(x, np.float32) x = x.astype(np.float32) Pmm = ((-1)**m) * double_factorial(2*m - 1) * (1.0 - x*x)**(0.5*m) if l == m: return Pmm Pm1m = x * (2*m + 1) * Pmm if l == m+1: return Pm1m p2, p1 = Pmm, Pm1m for L in range(m+2, l+1): p = ((2*L-1)*x*p1 - (L+m-1)*p2) / (L-m) p2, p1 = p1, p return p1 def real_sph_harm_sq(l: int, m: int, cos_theta: np.ndarray, phi: np.ndarray) -> np.ndarray: mabs = abs(m) num = math.factorial(l - mabs); den = math.factorial(l + mabs) Nlm = math.sqrt((2*l + 1)/(4*math.pi) * (num/den)) Plm = assoc_legendre(l, mabs, cos_theta) if m == 0: Y = Nlm * Plm else: fac = math.sqrt(2.0) * Nlm * Plm Y = fac * (np.cos(mabs*phi) if m>0 else np.sin(mabs*phi)) return (Y*Y).astype(np.float32) def radial_envelope(n: int, l: int, r: np.ndarray) -> np.ndarray: rr = np.clip(r, 0.0, 1.0).astype(np.float32) nr = max(0, n - l - 1) cent = np.power(rr + 1e-6, 2*l) k = 3.0 / max(1, n) expo = np.exp(-2.0 * k * rr) nodes = np.cos(np.pi * nr * rr)**2 if nr>0 else 1.0 return (cent * expo * nodes).astype(np.float32) def tile_orbital_intensity(n: int, l: int, m: int, side: int, z_samples: int) -> np.ndarray: xs = np.linspace(-1.0, 1.0, side, dtype=np.float32) ys = np.linspace(-1.0, 1.0, side, dtype=np.float32) X, Y = np.meshgrid(xs, ys) intensity = np.zeros((side, side), dtype=np.float32) zs = np.linspace(-1.0, 1.0, z_samples, dtype=np.float32) XY2 = X*X + Y*Y phi = (np.arctan2(Y, X) + 2*np.pi) % (2*np.pi) for z in zs: r2 = XY2 + z*z inside = r2 <= 1.0 r = np.sqrt(np.maximum(r2, 1e-9)) cos_theta = np.zeros_like(r, np.float32); cos_theta[inside] = z / r[inside] A = np.zeros_like(r, np.float32); A[inside] = real_sph_harm_sq(l, m, cos_theta[inside], phi[inside]) R = np.zeros_like(r, np.float32); R[inside] = radial_envelope(n, l, r[inside]) intensity += A * R if intensity.max() > 0: intensity /= intensity.max() return intensity.astype(np.float32) # ---------------------------- # Font + AA helpers # ---------------------------- def load_dejavu(size: int) -> ImageFont.FreeTypeFont: last_err = None for p in FONT_PATHS: try: f = ImageFont.truetype(p, size) if hasattr(f, "getmetrics"): return f except Exception as e: last_err = e continue raise RuntimeError( "DejaVu Sans not found. Install DejaVuSans.ttf and update FONT_PATHS. " f"Last error: {last_err}" ) def render_text_alpha(text: str, max_w: int, max_h: int, side_pad: int, top_pad: int) -> np.ndarray: """Fixed 24px rendering; top-aligned inside the label band. Returns uint8 alpha array.""" font = load_dejavu(FONT_SIZE) # measure scratch = Image.new("L", (max_w, max_h), 0) drw = ImageDraw.Draw(scratch) bbox = drw.textbbox((0, 0), text, font=font, anchor="lt") w = bbox[2] - bbox[0] # horizontal center; vertical top align x = max(0, (max_w - w)//2) y = max(0, top_pad) img = Image.new("L", (max_w, max_h), 0) drw = ImageDraw.Draw(img) drw.text((x, y), text, fill=255, font=font) return np.array(img, dtype=np.uint8) # ---------------------------- # Layout helpers # ---------------------------- def split_evenly(total: int, parts: int): base = total // parts; rem = total % parts return [base + 1 if i < rem else base for i in range(parts)] def enumerate_orbitals(n_max: int): for n in range(1, n_max+1): for l in range(0, n): for m in range(-l, l+1): yield (n, l, m) # ---------------------------- # Color space + matching # ---------------------------- def rgb_cube_arrays(): r = np.repeat(np.arange(256, dtype=np.uint16), 256*256) g = np.tile(np.repeat(np.arange(256, dtype=np.uint16), 256), 256) b = np.tile(np.arange(256, dtype=np.uint16), 256*256) Y = (0.2126*r + 0.7152*g + 0.0722*b).astype(np.float32) rf = (r/255.0).astype(np.float32); gf = (g/255.0).astype(np.float32); bf = (b/255.0).astype(np.float32) maxc = np.maximum(np.maximum(rf, gf), bf); minc = np.minimum(np.minimum(rf, gf), bf) v = maxc; delta = maxc - minc s = np.zeros_like(v); nz = v > 0; s[nz] = delta[nz] / v[nz] h = np.zeros_like(v); mask = delta > 1e-20 mr = mask & (maxc == rf); mg = mask & (maxc == gf); mb = mask & (maxc == bf) h[mr] = ((gf[mr] - bf[mr]) / delta[mr]) % 6.0 h[mg] = ((bf[mg] - rf[mg]) / delta[mg]) + 2.0 h[mb] = ((rf[mb] - gf[mb]) / delta[mb]) + 4.0 h = (h / 6.0) % 1.0 return r.astype(np.uint8), g.astype(np.uint8), b.astype(np.uint8), h, s, v, Y def circ_dist(a: np.ndarray, b: float) -> np.ndarray: d = np.abs(a - b); return np.minimum(d, 1.0 - d) def pref_metric_for_group(h: np.ndarray, s: np.ndarray, L: int) -> np.ndarray: if L == 0: return (s + 0.02 * circ_dist(h, 0.5)).astype(np.float32) centers = {1:[0.00,0.08,0.17], 2:[0.33,0.45], 3:[0.66,0.83]}.get(L, [0.25,0.58,0.92]) d = circ_dist(h, centers[0]) for c in centers[1:]: d = np.minimum(d, circ_dist(h, c)) return (d - 0.05*s).astype(np.float32) # ---------------------------- # Build intensities + masks + label alpha (with TOP_MARGIN & ROW_SPACING) # ---------------------------- def build_canvas_intensity_masks_labels(): # Column widths stay evenly split col_widths = split_evenly(WIDTH, GRID_COLS) x_off = np.cumsum([0] + col_widths[:-1]) # Row heights with explicit top margin and fixed spacing BOTTOM_MARGIN = ROW_SPACING # guarantee at least this much usable_height = HEIGHT - TOP_MARGIN - BOTTOM_MARGIN - ROW_SPACING * (GRID_ROWS - 1) row_heights = split_evenly(usable_height, GRID_ROWS) # y offsets: start at TOP_MARGIN, then add row height + spacing per row y_off = [TOP_MARGIN] for rh in row_heights[:-1]: y_off.append(y_off[-1] + rh + ROW_SPACING) y_off = np.array(y_off) # Square tile side must fit width and (row height - label band - gap) side_by_row = [max(1, rh - LABEL_BOX_HEIGHT - LABEL_GAP) for rh in row_heights] side = min(min(side_by_row), min(col_widths)) I = np.zeros((HEIGHT, WIDTH), np.float32) Lgroup = np.full((HEIGHT, WIDTH), 255, np.uint8) # ℓ for tiles tile_mask = np.zeros((HEIGHT, WIDTH), bool) label_alpha = np.zeros((HEIGHT, WIDTH), np.uint8) # AA for labels orbitals = list(enumerate_orbitals(N_MAX)) assert len(orbitals) == GRID_COLS * GRID_ROWS idx = 0 for r in range(GRID_ROWS): for c in range(GRID_COLS): n, l, m = orbitals[idx] rh = row_heights[r]; cw = col_widths[c] y0, y1 = y_off[r], y_off[r] + rh x0, x1 = x_off[c], x_off[c] + cw # Tile packed at the TOP of the row area ys = y0 ye = ys + side xs = x0 + (cw - side)//2 xe = xs + side # Orbital intensity tile = tile_orbital_intensity(n, l, m, side, Z_SAMPLES) I[ys:ye, xs:xe] = tile Lgroup[ys:ye, xs:xe] = l tile_mask[ys:ye, xs:xe] = True # Label band directly under the tile lbl_y0 = ye + LABEL_GAP lbl_y1 = lbl_y0 + LABEL_BOX_HEIGHT text = f"n={n} ℓ={l} m={m if m>=0 else '−'+str(abs(m))}" alpha = render_text_alpha( text=text, max_w=cw, max_h=LABEL_BOX_HEIGHT, side_pad=LABEL_SIDE_PAD, top_pad=LABEL_VERT_PAD ) la = label_alpha[lbl_y0:lbl_y1, x0:x1] label_alpha[lbl_y0:lbl_y1, x0:x1] = np.maximum(la, alpha) idx += 1 # Normalize/gamma over tile pixels only tile_vals = I[tile_mask]; vmax = tile_vals.max() if tile_vals.size else 0.0 if vmax > 0: I[tile_mask] = (tile_vals / vmax) ** GAMMA return I, Lgroup, tile_mask, label_alpha, side # ---------------------------- # Color assignment (labels → orbitals → whitespace) # ---------------------------- def rgb_match_and_render(I: np.ndarray, Lgroup: np.ndarray, tile_mask: np.ndarray, label_alpha: np.ndarray): H, W = I.shape; N = H*W I_flat = I.reshape(-1); L_flat = Lgroup.reshape(-1) tmask = tile_mask.reshape(-1); la = label_alpha.reshape(-1) lbl_idx = np.nonzero(la > 0)[0] orb_idx = np.nonzero(tmask)[0] wht_idx = np.nonzero(~(tmask | (la > 0)))[0] N_lbl, N_orb, N_wht = len(lbl_idx), len(orb_idx), len(wht_idx) print(f"Orbital pixels: {N_orb:,} | Label pixels: {N_lbl:,} | Whitespace: {N_wht:,}") rC, gC, bC, hC, sC, vC, YC = rgb_cube_arrays() assert rC.size == N col_order = np.argsort(YC, kind="stable") # Labels: darkest → highest alpha (AA) col_lbl = col_order[:N_lbl] rest = col_order[N_lbl:] sort_lbl = np.argsort(-la[lbl_idx], kind="stable") lbl_sorted = lbl_idx[sort_lbl] outR = np.empty(N, np.uint8); outG = np.empty(N, np.uint8); outB = np.empty(N, np.uint8) outR[lbl_sorted] = rC[col_lbl]; outG[lbl_sorted] = gC[col_lbl]; outB[lbl_sorted] = bC[col_lbl] # Orbitals: global intensity → luminance with ℓ-hue nudges col_orb = rest[:N_orb]; col_wht = rest[N_orb:] orb_sorted = orb_idx[np.argsort(I_flat[orb_idx], kind="stable")] blocks = (N_orb + BLOCK_SIZE - 1) // BLOCK_SIZE for bi in range(blocks): s = bi*BLOCK_SIZE; e = min((bi+1)*BLOCK_SIZE, N_orb); Lb = e - s pix_block = orb_sorted[s:e]; cols_block = col_orb[s:e] groups = L_flat[pix_block]; h_block = hC[cols_block]; s_block = sC[cols_block] used = np.zeros(Lb, bool); chosen = {} uniq, counts = np.unique(groups, return_counts=True) for L in uniq[np.argsort(-counts)]: need = counts[uniq==L][0]; avail = np.where(~used)[0] P = pref_metric_for_group(h_block[avail], s_block[avail], int(L)) chosen_rel = avail[np.argpartition(P, min(need-1, len(avail)-1))[:need]] used[chosen_rel] = True; chosen[int(L)] = chosen_rel assert used.all() for L in uniq: rel = chosen[int(L)]; cols_g = cols_block[rel]; pix_g = pix_block[groups==L] if pix_g.size>1: pix_g = pix_g[np.argsort(I_flat[pix_g], kind="stable")] if cols_g.size>1: cols_g = cols_g[np.argsort(YC[cols_g], kind="stable")] outR[pix_g] = rC[cols_g]; outG[pix_g] = gC[cols_g]; outB[pix_g] = bC[cols_g] if (bi+1)%8==0 or bi==blocks-1: print(f" Blocks {bi+1}/{blocks} ({100*(bi+1)/blocks:.1f}%)") # Whitespace: leftover colors, shuffled (noise) cols_wht_shuf = col_wht[np.random.permutation(len(col_wht))] outR[wht_idx] = rC[cols_wht_shuf]; outG[wht_idx] = gC[cols_wht_shuf]; outB[wht_idx] = bC[cols_wht_shuf] img = np.stack([outR, outG, outB], axis=1).reshape(HEIGHT, WIDTH, 3) return img # ---------------------------- # Main # ---------------------------- def main(): print("Building intensities, masks, and label AA with margins/spacings...") I, Lgroup, tile_mask, label_alpha, side = build_canvas_intensity_masks_labels() print(f"Tile side = {side}px | Label alpha pixels = {int((label_alpha>0).sum()):,}") print("Assigning colors (labels → orbitals → whitespace)...") img = rgb_match_and_render(I, Lgroup, tile_mask, label_alpha) expected_sum = 65536 * (255 * 256 // 2) # 2,137,014,272 chsum = img.reshape(-1, 3).sum(axis=0, dtype=np.uint64) print(f"Channel sums R,G,B = {tuple(int(x) for x in chsum)}; expected {expected_sum}") ts = time.strftime("%Y%m%d_%H%M%S") outfile = f"orbitals_n8_204_allRGB_labels24_margins_{ts}.png" print(f"Saving {outfile} ...") Image.fromarray(img, "RGB").save(outfile, compress_level=0) print("Done.") if __name__ == "__main__": main()
Date | |
---|---|
Colors | 16,777,216 |
Pixels | 16,777,216 |
Dimensions | 4,096 × 4,096 |
Bytes | 50,351,853 |