{"spec_id":"line-tanabe-sugano","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nline-tanabe-sugano: Tanabe-Sugano Diagram for Crystal Field Theory\nLibrary: bokeh 3.10.0 | Python 3.13.15\nQuality: 90/100 | Created: 2026-10-01\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Label, Span, Title\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — canonical order, first series is always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — d7 (Co2+) in an octahedral field, Racah C/B = 4.633\nracah_b = 1.0\nracah_c = 4.633 * racah_b\nfield_strength = np.linspace(0.0, 30.0, 321)  # delta_o / B\ndq = field_strength / 10.0  # delta_o = 10 Dq\nsqrt2, sqrt3 = np.sqrt(2.0), np.sqrt(3.0)\n\n# Tanabe-Sugano matrices for d7: one symmetry/multiplicity block each, given as\n# (diagonal, upper-triangle couplings) in units of B at the chosen C/B.\nts_blocks = {\n    \"quartet_t1\": ([2 * dq - 3 * racah_b, -8 * dq - 12 * racah_b], {(0, 1): 6 * racah_b}),\n    \"doublet_e\": (\n        [\n            12 * dq - 6 * racah_b + 3 * racah_c,\n            2 * dq + 8 * racah_b + 6 * racah_c,\n            2 * dq - racah_b + 3 * racah_c,\n            -18 * dq - 8 * racah_b + 4 * racah_c,\n        ],\n        {\n            (0, 1): -6 * sqrt2 * racah_b,\n            (0, 2): -3 * sqrt2 * racah_b,\n            (1, 2): 10 * racah_b,\n            (1, 3): sqrt3 * (2 * racah_b + racah_c),\n            (2, 3): 2 * sqrt3 * racah_b,\n        },\n    ),\n    \"doublet_t1\": (\n        [\n            12 * dq - 6 * racah_b + 3 * racah_c,\n            2 * dq + 3 * racah_c,\n            2 * dq - 6 * racah_b + 3 * racah_c,\n            -8 * dq - 6 * racah_b + 3 * racah_c,\n            -8 * dq - 2 * racah_b + 3 * racah_c,\n        ],\n        {\n            (0, 1): -3 * racah_b,\n            (0, 2): 3 * racah_b,\n            (0, 4): -2 * sqrt3 * racah_b,\n            (1, 2): -3 * racah_b,\n            (1, 3): 3 * racah_b,\n            (1, 4): 3 * sqrt3 * racah_b,\n            (2, 3): -3 * racah_b,\n            (2, 4): -sqrt3 * racah_b,\n            (3, 4): 2 * sqrt3 * racah_b,\n        },\n    ),\n    \"doublet_t2\": (\n        [\n            12 * dq + 5 * racah_c,\n            2 * dq - 6 * racah_b + 3 * racah_c,\n            2 * dq + 4 * racah_b + 3 * racah_c,\n            -8 * dq + 6 * racah_b + 5 * racah_c,\n            -8 * dq - 2 * racah_b + 3 * racah_c,\n        ],\n        {\n            (0, 1): -3 * sqrt3 * racah_b,\n            (0, 2): -5 * sqrt3 * racah_b,\n            (0, 3): 4 * racah_b + 2 * racah_c,\n            (0, 4): 2 * racah_b,\n            (1, 2): 3 * racah_b,\n            (1, 3): -3 * sqrt3 * racah_b,\n            (1, 4): -3 * sqrt3 * racah_b,\n            (2, 3): -sqrt3 * racah_b,\n            (2, 4): sqrt3 * racah_b,\n            (3, 4): 10 * racah_b,\n        },\n    ),\n}\n\n# Diagonalize every block over the whole field range at once; eigvalsh returns\n# ascending levels, so terms of one symmetry avoid each other instead of crossing.\nlevels = {}\nfor name, (diagonal, couplings) in ts_blocks.items():\n    matrix = np.zeros((field_strength.size, len(diagonal), len(diagonal)))\n    for index, element in enumerate(diagonal):\n        matrix[:, index, index] = element\n    for (row, column), element in couplings.items():\n        matrix[:, row, column] = matrix[:, column, row] = element\n    levels[name] = np.linalg.eigvalsh(matrix)\n\n# The ground term switches from high-spin 4T1g to low-spin 2Eg at the crossover\nground = np.minimum(levels[\"quartet_t1\"][:, 0], levels[\"doublet_e\"][:, 0])\ncrossover = np.interp(0.0, levels[\"quartet_t1\"][:, 0] - levels[\"doublet_e\"][:, 0], field_strength)\n\n# Term curves: plain-text key, typeset label, raw energy, label nudge in E/B\nterms = (\n    (\"⁴T₁g(F)\", r\"$$^{4}\\mathrm{T}_{1g}(\\mathrm{F})$$\", levels[\"quartet_t1\"][:, 0], 0.0),\n    (\"⁴T₂g\", r\"$$^{4}\\mathrm{T}_{2g}$$\", 2 * dq - 15 * racah_b, 0.0),\n    (\"⁴A₂g\", r\"$$^{4}\\mathrm{A}_{2g}$$\", 12 * dq - 15 * racah_b, 0.0),\n    (\"⁴T₁g(P)\", r\"$$^{4}\\mathrm{T}_{1g}(\\mathrm{P})$$\", levels[\"quartet_t1\"][:, 1], 0.0),\n    (\"²Eg\", r\"$$^{2}\\mathrm{E}_{g}$$\", levels[\"doublet_e\"][:, 0], 0.0),\n    (\"²T₁g\", r\"$$^{2}\\mathrm{T}_{1g}$$\", levels[\"doublet_t1\"][:, 0], -2.0),\n    (\"²T₂g\", r\"$$^{2}\\mathrm{T}_{2g}$$\", levels[\"doublet_t2\"][:, 0], 2.0),\n    (\"²A₁g\", r\"$$^{2}\\mathrm{A}_{1g}$$\", 2 * dq - 11 * racah_b + 3 * racah_c, 0.0),\n)\n\n# Plot — square canvas, the near-square plot area the diagram is read on\np = figure(\n    width=2400,\n    height=2400,\n    title=\"line-tanabe-sugano · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Ligand-field strength Δₒ / B\",\n    y_axis_label=\"Term energy E / B\",\n    x_range=(0.0, 34.4),\n    y_range=(-1.6, 72.0),\n    toolbar_location=None,\n    min_border_bottom=170,\n    min_border_left=190,\n    min_border_top=220,\n    min_border_right=60,\n)\n\nfor color, (term, typeset, energy, nudge) in zip(IMPRINT_PALETTE, terms, strict=True):\n    spin_allowed = term.startswith(\"⁴\")  # same multiplicity as the 4T1g ground term\n    source = ColumnDataSource(data={\"field\": field_strength, \"energy\": energy - ground})\n    p.line(\n        x=\"field\",\n        y=\"energy\",\n        source=source,\n        line_color=color,\n        line_width=12 if spin_allowed else 7,\n        line_dash=\"solid\" if spin_allowed else [22, 16],\n        line_cap=\"round\",\n    )\n    p.add_layout(\n        Label(\n            x=field_strength[-1] + 0.7,\n            y=energy[-1] - ground[-1] + nudge,\n            text=typeset,\n            text_color=color,\n            text_font_size=\"34pt\",\n            text_baseline=\"middle\",\n        )\n    )\n\n# Crossover: every curve kinks here because the reference ground term changes\np.add_layout(\n    Span(\n        location=crossover, dimension=\"height\", line_color=INK_SOFT, line_dash=[26, 18], line_width=7, level=\"underlay\"\n    )\n)\np.add_layout(\n    Label(\n        x=crossover - 0.6,\n        y=69.0,\n        text=f\"high-spin → low-spin crossover at Δₒ/B = {crossover:.1f}\",\n        text_color=INK_MUTED,\n        text_font_size=\"30pt\",\n        text_align=\"right\",\n        text_baseline=\"middle\",\n    )\n)\n\n# Style\np.add_layout(\n    Title(\n        text=\"d⁷ octahedral (Co²⁺), C/B = 4.63 · solid: spin-allowed quartets · dashed: spin-forbidden doublets\",\n        text_color=INK_SOFT,\n        text_font_size=\"30pt\",\n        text_font_style=\"normal\",\n    ),\n    \"above\",\n)\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\np.title.text_color = INK\np.title.text_font_size = \"50pt\"\np.title.standoff = 26\np.axis.axis_label_text_color = INK\np.axis.axis_label_text_font_size = \"42pt\"\np.axis.axis_label_text_font_style = \"normal\"\np.axis.axis_label_standoff = 30\np.axis.major_label_text_color = INK_SOFT\np.axis.major_label_text_font_size = \"34pt\"\np.axis.major_label_standoff = 20\np.axis.axis_line_color = INK_SOFT\np.axis.axis_line_width = 3\np.axis.major_tick_line_color = INK_SOFT\np.axis.minor_tick_line_color = None\np.grid.grid_line_color = INK\np.grid.grid_line_alpha = 0.15\np.grid.grid_line_width = 3\n\n# Save — interactive HTML plus a headless-Chrome screenshot for the gallery PNG\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\nW, H = 2400, 2400\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(6)  # let bokeh render the canvas and MathJax typeset the term labels\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}