{"spec_id":"line-tanabe-sugano","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nline-tanabe-sugano: Tanabe-Sugano Diagram for Crystal Field Theory\nLibrary: plotnine 0.15.8 | Python 3.13.15\nQuality: 90/100 | Created: 2026-10-01\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_linetype_manual,\n    scale_size_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens (Imprint palette + 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 = \"#009E73\"  # Imprint palette position 1 — always the first series\nSPIN_FORBIDDEN = \"#C475FD\"  # Imprint palette position 2\n\n# Data — d2 (V3+) term energies from the Tanabe-Sugano matrices, in units of the\n# Racah parameter B at C/B = 4.42, with Dq = (delta_o / B) / 10\nc_over_b = 4.42\ndelta_over_b = np.linspace(0, 40, 321)\ndq = delta_over_b / 10.0\nground_term = r\"$^{3}T_{1g}(F)$\"\n\n# A symmetry that occurs once in the d2 configuration is a single state, so its\n# energy is linear in the ligand-field strength\nenergies = {r\"$^{3}T_{2g}$\": -8 + 2 * dq, r\"$^{3}A_{2g}$\": -8 + 12 * dq, r\"$^{1}T_{1g}(G)$\": 4 + 2 * c_over_b + 2 * dq}\n\n# A symmetry that occurs twice gives a 2x2 matrix (diagonal, off-diagonal mixing,\n# diagonal); diagonalizing it keeps the two roots an avoided crossing instead of\n# letting the curves touch and swap identity\nblocks = {\n    (r\"$^{3}T_{1g}(F)$\", r\"$^{3}T_{1g}(P)$\"): (-5 - 8 * dq, -6.0, 4 + 2 * dq),\n    (r\"$^{1}E_{g}(D)$\", r\"$^{1}E_{g}(G)$\"): (1 + 2 * c_over_b - 8 * dq, 2 * np.sqrt(3), 2 * c_over_b + 12 * dq),\n    (r\"$^{1}T_{2g}(D)$\", r\"$^{1}T_{2g}(G)$\"): (1 + 2 * c_over_b - 8 * dq, 2 * np.sqrt(3), 2 * c_over_b + 2 * dq),\n    (r\"$^{1}A_{1g}(G)$\", r\"$^{1}A_{1g}(S)$\"): (\n        10 + 5 * c_over_b - 8 * dq,\n        np.sqrt(6) * (2 + c_over_b),\n        8 + 4 * c_over_b + 12 * dq,\n    ),\n}\nfor (lower_term, upper_term), (first, mixing, second) in blocks.items():\n    block = np.zeros((len(dq), 2, 2))\n    block[:, 0, 0] = first\n    block[:, 1, 1] = second\n    block[:, 0, 1] = block[:, 1, 0] = mixing\n    energies[lower_term], energies[upper_term] = np.linalg.eigvalsh(block).T\n\n# Every energy is measured from the ground term, which is flat at E/B = 0 by\n# construction; the upper partners of the avoided crossings keep climbing past\n# the top of the window, so they are clipped where they leave the frame\nterms = pd.DataFrame(energies, index=delta_over_b)\nterms = terms.sub(terms[ground_term], axis=0)\n\ndf = terms.stack().reset_index()\ndf.columns = [\"delta_over_b\", \"term\", \"energy_over_b\"]\ndf = df[df[\"energy_over_b\"] <= 80]\ndf[\"spin\"] = pd.Categorical(\n    np.where(df[\"term\"].str.startswith(\"$^{3}\"), \"Spin-allowed (triplet)\", \"Spin-forbidden (singlet)\"),\n    categories=[\"Spin-allowed (triplet)\", \"Spin-forbidden (singlet)\"],\n    ordered=True,\n)\n\n# Term labels sit in a gutter right of the curves, each at its own end value. The\n# two 1D-derived terms stay near-coincident all the way across and the two upper\n# partners leave the window, so those four are labelled along the curve instead,\n# each resting on the line it names — above it for the upper term of a pair,\n# below it for the lower one\nalong_curve = {r\"$^{1}E_{g}(D)$\": 35.0, r\"$^{1}T_{2g}(D)$\": 22.0, r\"$^{1}E_{g}(G)$\": 30.0, r\"$^{1}A_{1g}(S)$\": 17.0}\nanchors = pd.DataFrame(\n    [\n        {\n            \"delta_over_b\": x,\n            \"term\": term,\n            \"energy_over_b\": terms[term].to_numpy()[np.searchsorted(delta_over_b, x)],\n            \"spin\": \"Spin-forbidden (singlet)\",  # all four along-curve terms are singlets\n        }\n        for term, x in along_curve.items()\n    ]\n)\nlower_of_pair = anchors[\"term\"] == r\"$^{1}T_{2g}(D)$\"\n\ncurve_ends = df[(df[\"delta_over_b\"] == delta_over_b[-1]) & ~df[\"term\"].isin(along_curve)].copy()\ncurve_ends[\"delta_over_b\"] = 41.2\n\n# Plot\nplot = (\n    ggplot(df, aes(\"delta_over_b\", \"energy_over_b\", group=\"term\", color=\"spin\"))\n    + geom_line(aes(linetype=\"spin\", size=\"spin\"))\n    + geom_text(aes(label=\"term\"), data=curve_ends, size=10, ha=\"left\", show_legend=False)\n    + geom_text(aes(label=\"term\"), data=anchors[~lower_of_pair], size=10, ha=\"right\", va=\"bottom\", show_legend=False)\n    + geom_text(aes(label=\"term\"), data=anchors[lower_of_pair], size=10, ha=\"right\", va=\"top\", show_legend=False)\n    + scale_color_manual(values=[SPIN_ALLOWED, SPIN_FORBIDDEN])\n    + scale_linetype_manual(values=[\"solid\", \"dashed\"])\n    + scale_size_manual(values=[1.7, 0.75])\n    + scale_x_continuous(limits=(0, 47.5), breaks=range(0, 41, 5), expand=(0.008, 0))\n    + scale_y_continuous(limits=(0, 80), breaks=range(0, 81, 10), expand=(0.022, 0))\n    + labs(\n        x=\"Ligand-field strength  $\\\\Delta_{o}/B$\",\n        y=\"Term energy  $E/B$\",\n        title=\"line-tanabe-sugano · python · plotnine · anyplot.ai\",\n        subtitle=\"V$^{3+}$ d$^{2}$ in an octahedral field, C/B = 4.42\",\n        color=\"\",\n        linetype=\"\",\n        size=\"\",\n    )\n    + theme_minimal()\n    + theme(\n        figure_size=(6, 6),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_border=element_blank(),\n        panel_grid_major=element_line(color=INK, size=0.3, alpha=0.15),\n        panel_grid_minor=element_blank(),\n        axis_line=element_line(color=INK_SOFT, size=0.5),\n        axis_ticks=element_blank(),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_title=element_text(size=12, color=INK),\n        plot_subtitle=element_text(size=9, color=INK_SOFT),\n        legend_position=\"bottom\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=ELEVATED_BG),\n        legend_key=element_rect(fill=ELEVATED_BG, color=ELEVATED_BG),\n        legend_key_width=26,\n        legend_margin=8,\n        legend_title=element_blank(),\n        legend_text=element_text(size=8, color=INK_SOFT),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\", verbose=False)\n"}