{"spec_id":"line-tanabe-sugano","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nline-tanabe-sugano: Tanabe-Sugano Diagram for Crystal Field Theory\nLibrary: altair 6.3.0 | Python 3.13.15\nQuality: 88/100 | Created: 2026-10-01\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nSPIN_ALLOWED_COLOR = \"#009E73\"  # Imprint palette position 1 — spin-allowed terms\nSPIN_FORBIDDEN_COLOR = \"#C475FD\"  # Imprint palette position 2 — spin-forbidden terms\n\n# Data — Tanabe-Sugano matrices for the d² octahedral ion V³⁺ at C/B = 4.42,\n# in units of B with the Racah A dropped. Each symmetry block is diagonalized at\n# every field strength; the strong-field basis states carry 10Dq/B = Δ_o/B per\n# electron promoted from t₂g to e_g, so the diagonals grow by Δ_o/B or 2·Δ_o/B.\nracah_c_over_b = 4.42\ndelta_over_b = np.linspace(0.0, 40.0, 301)\nflat = np.zeros_like(delta_over_b)\n\ntriplet_t1 = np.linalg.eigvalsh(\n    np.moveaxis(np.array([[-5.0 + flat, 6.0 + flat], [6.0 + flat, 4.0 + delta_over_b]]), -1, 0)\n)\nsinglet_e = np.linalg.eigvalsh(\n    np.moveaxis(\n        np.array(\n            [\n                [1.0 + 2 * racah_c_over_b + flat, -2 * np.sqrt(3.0) + flat],\n                [-2 * np.sqrt(3.0) + flat, 2 * racah_c_over_b + 2 * delta_over_b],\n            ]\n        ),\n        -1,\n        0,\n    )\n)\nsinglet_t2 = np.linalg.eigvalsh(\n    np.moveaxis(\n        np.array(\n            [\n                [1.0 + 2 * racah_c_over_b + flat, 2 * np.sqrt(3.0) + flat],\n                [2 * np.sqrt(3.0) + flat, 2 * racah_c_over_b + delta_over_b],\n            ]\n        ),\n        -1,\n        0,\n    )\n)\nsinglet_a1 = np.linalg.eigvalsh(\n    np.moveaxis(\n        np.array(\n            [\n                [10.0 + 5 * racah_c_over_b + flat, np.sqrt(6.0) * (2.0 + racah_c_over_b) + flat],\n                [np.sqrt(6.0) * (2.0 + racah_c_over_b) + flat, 8.0 + 4 * racah_c_over_b + 2 * delta_over_b],\n            ]\n        ),\n        -1,\n        0,\n    )\n)\n\n# Term energies measured from the ³T₁g(F) ground term, inside an E/B ≤ 80 window\nground_term = triplet_t1[:, 0]\nterm_energies = {\n    \"³T₁g(F)\": triplet_t1[:, 0],\n    \"¹T₂g(D)\": singlet_t2[:, 0],\n    \"¹Eg\": singlet_e[:, 0],\n    \"¹A₁g\": singlet_a1[:, 0],\n    \"³T₂g\": -8.0 + delta_over_b,\n    \"³T₁g(P)\": triplet_t1[:, 1],\n    \"¹T₂g(G)\": singlet_t2[:, 1],\n    \"¹T₁g\": 4.0 + 2 * racah_c_over_b + delta_over_b,\n    \"³A₂g\": -8.0 + 2 * delta_over_b,\n}\nspin_allowed_terms = {\"³T₁g(F)\", \"³T₂g\", \"³T₁g(P)\", \"³A₂g\"}\n\ncurves = pd.DataFrame(\n    {\n        \"delta_over_b\": np.tile(delta_over_b, len(term_energies)),\n        \"term\": np.repeat(list(term_energies), delta_over_b.size),\n        \"energy_over_b\": np.concatenate([e - ground_term for e in term_energies.values()]),\n    }\n)\ncurves[\"spin_class\"] = np.where(\n    curves[\"term\"].isin(spin_allowed_terms), \"Spin-allowed (ΔS = 0)\", \"Spin-forbidden (ΔS ≠ 0)\"\n)\n\n# Right-edge term labels; the offsets only separate labels where curves converge\nlabel_offsets = {\"³T₁g(F)\": 1.6, \"¹T₂g(D)\": -1.7, \"¹Eg\": 1.7, \"¹A₁g\": -0.8, \"³T₂g\": 0.6, \"³T₁g(P)\": -0.4, \"¹T₁g\": 0.4}\nlabels = curves[curves[\"delta_over_b\"] == delta_over_b[-1]].copy()\nlabels[\"label_y\"] = labels[\"energy_over_b\"] + labels[\"term\"].map(label_offsets).fillna(0.0)\n\n# Plot — near-square view, both axes starting at 0, right gutter for the labels\nfield_strength = alt.X(\n    \"delta_over_b:Q\",\n    scale=alt.Scale(domain=[0, 47], nice=False),\n    axis=alt.Axis(values=[0, 10, 20, 30, 40], title=\"Δₒ/B — reduced ligand-field strength\"),\n)\nspin_color = alt.Color(\n    \"spin_class:N\",\n    scale=alt.Scale(\n        domain=[\"Spin-allowed (ΔS = 0)\", \"Spin-forbidden (ΔS ≠ 0)\"], range=[SPIN_ALLOWED_COLOR, SPIN_FORBIDDEN_COLOR]\n    ),\n    legend=alt.Legend(title=None, orient=\"top-left\", offset=12),\n)\n\nterm_lines = (\n    alt.Chart(curves)\n    .mark_line()\n    .encode(\n        x=field_strength,\n        y=alt.Y(\n            \"energy_over_b:Q\",\n            scale=alt.Scale(domain=[0, 80], nice=False),\n            axis=alt.Axis(values=[0, 20, 40, 60, 80], title=\"E/B — reduced term energy\"),\n        ),\n        detail=\"term:N\",\n        color=spin_color,\n        strokeDash=alt.StrokeDash(\n            \"spin_class:N\",\n            scale=alt.Scale(domain=[\"Spin-allowed (ΔS = 0)\", \"Spin-forbidden (ΔS ≠ 0)\"], range=[[1, 0], [10, 6]]),\n            legend=alt.Legend(title=None, orient=\"top-left\", offset=12),\n        ),\n        strokeWidth=alt.StrokeWidth(\n            \"spin_class:N\",\n            scale=alt.Scale(domain=[\"Spin-allowed (ΔS = 0)\", \"Spin-forbidden (ΔS ≠ 0)\"], range=[4.2, 2.0]),\n            legend=None,\n        ),\n        tooltip=[\n            alt.Tooltip(\"term:N\", title=\"Term\"),\n            alt.Tooltip(\"delta_over_b:Q\", title=\"Δₒ/B\", format=\".1f\"),\n            alt.Tooltip(\"energy_over_b:Q\", title=\"E/B\", format=\".2f\"),\n        ],\n    )\n)\n\nterm_labels = (\n    alt.Chart(labels)\n    .mark_text(align=\"left\", baseline=\"middle\", fontSize=13, fontWeight=600, dx=7)\n    .encode(\n        x=field_strength,\n        y=alt.Y(\"label_y:Q\", scale=alt.Scale(domain=[0, 80], nice=False)),\n        text=\"term:N\",\n        color=alt.Color(\"spin_class:N\", legend=None),\n    )\n)\n\nchart = (\n    alt.layer(term_lines, term_labels)\n    .properties(\n        width=500,\n        height=460,\n        background=PAGE_BG,\n        padding={\"left\": 4, \"right\": 4, \"top\": 4, \"bottom\": 4},\n        title=alt.Title(\n            \"line-tanabe-sugano · python · altair · anyplot.ai\",\n            subtitle=[\"d² octahedral ion (V³⁺) at C/B = 4.42\", \"E/B measured from the ³T₁g(F) ground term\"],\n            fontSize=16,\n            subtitleFontSize=12,\n            anchor=\"start\",\n            offset=16,\n        ),\n    )\n    .configure(font=\"Lato\")\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.15,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n        labelPadding=7,\n        titlePadding=12,\n    )\n    .configure_title(color=INK, subtitleColor=INK_SOFT, subtitleLineHeight=16)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        labelColor=INK_SOFT,\n        labelFontSize=10,\n        symbolStrokeWidth=3,\n        symbolSize=260,\n        padding=11,\n        cornerRadius=3,\n    )\n)\n\n# Save\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTW, TH = 2400, 2400\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}