Skip to content
201 changes: 134 additions & 67 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

755 changes: 425 additions & 330 deletions quantui/app.py

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions quantui/ir_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import numpy as np
import plotly.graph_objects as go

# x-axis range follows the standard IR convention: high → low wavenumber
_XRANGE = [4000, 400]
# x-axis range is low → high wavenumber (user-facing convention in QuantUI)
_XRANGE = [400, 4000]
_XGRID = np.arange(400, 4001, 1.0) # 1 cm⁻¹ resolution for broadened mode


Expand Down
4 changes: 2 additions & 2 deletions quantui/pubchem.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def check_pubchem_availability() -> bool:
url = f"{PUBCHEM_BASE_URL}/compound/cid/962/property/MolecularFormula/JSON"
response = requests.get(url, timeout=5)
return bool(response.status_code == 200)
except:
except Exception:
return False


Expand Down Expand Up @@ -383,7 +383,7 @@ def smiles_to_xyz(smiles: str, optimize_3d: bool = True) -> Tuple[str, Dict[str,
# Optimize with UFF force field
try:
AllChem.UFFOptimizeMolecule(mol)
except:
except Exception:
logger.warning("UFF optimization failed, using unoptimized coordinates")

# Extract coordinates
Expand Down
119 changes: 57 additions & 62 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,9 +866,10 @@ def test_ir_accordion_exists(self):
assert hasattr(app, "_ir_accordion")
assert isinstance(app._ir_accordion, widgets.Accordion)

def test_ir_accordion_hidden_initially(self):
def test_ir_accordion_visible_and_collapsed_initially(self):
app = QuantUIApp()
assert app._ir_accordion.layout.display == "none"
assert app._ir_accordion.layout.display == ""
assert app._ir_accordion.selected_index is None

def test_ir_mode_toggle_exists(self):
app = QuantUIApp()
Expand Down Expand Up @@ -914,13 +915,13 @@ def test_show_ir_spectrum_returns_true_with_data(self):
ok = app._show_ir_spectrum(self._make_freq_result())
assert ok is True

def test_accordion_revealed_via_activate(self):
# _show_ir_spectrum populates widget; _activate_ana_panel reveals it.
def test_accordion_expanded_via_activate(self):
# _show_ir_spectrum populates widget; _activate_ana_panel expands it.
app = QuantUIApp()
app._show_ir_spectrum(self._make_freq_result())
assert app._ir_accordion.layout.display == "none" # still hidden
assert app._ir_accordion.selected_index is None # still collapsed
app._activate_ana_panel("IR Spectrum")
assert app._ir_accordion.layout.display == ""
assert app._ir_accordion.selected_index == 0

def test_fwhm_slider_shown_when_broadened(self):
app = QuantUIApp()
Expand Down Expand Up @@ -948,9 +949,10 @@ def test_orb_accordion_exists(self):
app = QuantUIApp()
assert hasattr(app, "_orb_accordion")

def test_orb_accordion_hidden_initially(self):
def test_orb_accordion_visible_collapsed_initially(self):
app = QuantUIApp()
assert app._orb_accordion.layout.display == "none"
assert app._orb_accordion.layout.display == ""
assert app._orb_accordion.selected_index is None

def test_orb_diagram_html_exists(self):
app = QuantUIApp()
Expand All @@ -968,11 +970,11 @@ def test_orb_iso_controls_hidden_initially(self):
app = QuantUIApp()
assert app._orb_iso_controls.layout.display == "none"

def test_orb_accordion_hidden_after_run_clicked(self):
def test_orb_accordion_collapsed_after_run_clicked(self):
app = QuantUIApp()
app._orb_accordion.layout.display = ""
app._orb_accordion.selected_index = 0
app._on_run_clicked(None)
assert app._orb_accordion.layout.display == "none"
assert app._orb_accordion.selected_index is None


class TestShowOrbitalDiagram:
Expand All @@ -997,29 +999,35 @@ def test_show_orbital_diagram_returns_true_with_mo_data(self):
ok = app._show_orbital_diagram(self._make_result_with_mo())
assert ok is True

def test_accordion_revealed_via_activate(self):
# _show_orbital_diagram populates widget; _activate_ana_panel reveals it.
def test_accordion_expanded_via_activate(self):
# _show_orbital_diagram populates widget; _activate_ana_panel expands it.
app = QuantUIApp()
app._show_orbital_diagram(self._make_result_with_mo())
assert app._orb_accordion.layout.display == "none" # still hidden
assert app._orb_accordion.selected_index is None # still collapsed
app._activate_ana_panel("Energies")
assert app._orb_accordion.layout.display == ""
assert app._orb_accordion.selected_index == 0

def test_accordion_stays_hidden_when_no_mo_data(self):
def test_accordion_stays_collapsed_when_no_mo_data(self):
from unittest.mock import MagicMock

app = QuantUIApp()
r = MagicMock()
r.mo_energy_hartree = None
r.mo_occ = None
app._show_orbital_diagram(r)
assert app._orb_accordion.layout.display == "none"
assert app._orb_accordion.selected_index is None

def test_diagram_html_populated(self):
app = QuantUIApp()
app._show_orbital_diagram(self._make_result_with_mo())
# plotly renders an interactive <div>; matplotlib fallback renders <img>
val = app._orb_diagram_html.value
# Plotly renders an interactive <div>; matplotlib fallback renders <img>.
# The diagram is now rendered via Output display_data (not HTML.value).
payloads = [
out.get("data", {}).get("text/html", "")
for out in app._orb_diagram_html.outputs
if out.get("output_type") == "display_data"
]
val = "\n".join(payloads)
assert "<div" in val or "<img" in val

def test_isosurface_controls_hidden_when_no_mo_coeff(self):
Expand Down Expand Up @@ -1109,11 +1117,7 @@ def test_ir_accordion_in_analysis_tab(self):


class TestAnaSwitcher:
"""Panel switcher strip: buttons, state, activation, and deactivation."""

def test_eight_buttons_exist(self):
app = QuantUIApp()
assert len(app._ana_btns) == 8
"""Analysis panel state: activation, deactivation, and placeholder swapping."""

def test_panel_names(self):
app = QuantUIApp()
Expand All @@ -1128,53 +1132,42 @@ def test_panel_names(self):
"NMR",
]

def test_buttons_initially_dimmed(self):
app = QuantUIApp()
for btn in app._ana_btns:
assert btn.layout.opacity == "0.35"

def test_no_panels_available_initially(self):
app = QuantUIApp()
assert len(app._ana_available) == 0

def test_all_accordions_hidden_initially(self):
def test_all_accordions_visible_and_collapsed_initially(self):
app = QuantUIApp()
for acc in app._ana_accordions:
assert acc.layout.display == "none"

def test_switcher_box_in_analysis_tab(self):
app = QuantUIApp()
assert app._ana_switcher_box in app.analysis_tab_panel.children
assert acc.layout.display == ""
assert acc.selected_index is None

def test_activate_panel_marks_available(self):
app = QuantUIApp()
app._activate_ana_panel("Energies")
assert "Energies" in app._ana_available

def test_activate_panel_sets_opacity(self):
app = QuantUIApp()
app._activate_ana_panel("Energies")
orb_btn = app._ana_btns[0]
assert orb_btn.layout.opacity == "1.0"

def test_activate_panel_auto_selects(self):
app = QuantUIApp()
app._activate_ana_panel("Energies")
assert app._orb_accordion.layout.display == ""
assert app._orb_accordion.selected_index == 0

def test_activate_panel_no_auto_select(self):
app = QuantUIApp()
app._activate_ana_panel("Energies", auto_select=False)
assert app._orb_accordion.layout.display == "none"
# Panel is available but not expanded; still visible in DOM.
assert "Energies" in app._ana_available
assert app._orb_accordion.selected_index is None
assert app._orb_accordion.layout.display == ""

def test_activate_hides_other_accordions(self):
def test_activate_collapses_other_accordions(self):
app = QuantUIApp()
app._activate_ana_panel("Energies")
# All other accordions should be hidden
# Other accordions remain visible but collapsed (not hidden).
for name, acc in zip(app._ana_panel_names, app._ana_accordions):
if name != "Energies":
assert acc.layout.display == "none"
assert acc.layout.display == ""
assert acc.selected_index is None

def test_deactivate_all_clears_available(self):
app = QuantUIApp()
Expand All @@ -1183,32 +1176,34 @@ def test_deactivate_all_clears_available(self):
app._deactivate_all_ana_panels()
assert len(app._ana_available) == 0

def test_deactivate_all_hides_accordions(self):
def test_deactivate_all_collapses_accordions(self):
app = QuantUIApp()
app._activate_ana_panel("Energies")
app._deactivate_all_ana_panels()
# All panels remain visible in the DOM but are collapsed.
for acc in app._ana_accordions:
assert acc.layout.display == "none"
assert acc.layout.display == ""
assert acc.selected_index is None

def test_deactivate_all_dims_buttons(self):
def test_unavail_message_shown_initially(self):
app = QuantUIApp()
app._activate_ana_panel("Energies")
app._deactivate_all_ana_panels()
for btn in app._ana_btns:
assert btn.layout.opacity == "0.35"
# Every panel starts with the unavailable placeholder visible.
for name in app._ana_panel_names:
assert app._ana_unavail_msgs[name].layout.display == ""
assert app._ana_content_boxes[name].layout.display == "none"

def test_click_unavailable_shows_warning(self):
def test_activate_swaps_placeholder_for_content(self):
app = QuantUIApp()
app._on_ana_panel_click("Energies")
assert app._ana_unavail_html.layout.display == ""
assert "Energies" in app._ana_unavail_html.value
app._activate_ana_panel("Energies", auto_select=False)
assert app._ana_unavail_msgs["Energies"].layout.display == "none"
assert app._ana_content_boxes["Energies"].layout.display == ""

def test_click_available_selects_panel(self):
def test_deactivate_restores_placeholder(self):
app = QuantUIApp()
app._activate_ana_panel("IR Spectrum", auto_select=False)
app._on_ana_panel_click("IR Spectrum")
assert app._ir_accordion.layout.display == ""
assert app._ana_active == "IR Spectrum"
app._activate_ana_panel("Energies", auto_select=False)
app._deactivate_all_ana_panels()
assert app._ana_unavail_msgs["Energies"].layout.display == ""
assert app._ana_content_boxes["Energies"].layout.display == "none"


# ---------------------------------------------------------------------------
Expand Down
29 changes: 29 additions & 0 deletions tests/test_code_quality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Static analysis guards for patterns that fail silently at runtime."""

import re
from pathlib import Path

SRC = Path(__file__).parent.parent / "quantui"


def _grep(pattern: str) -> list[str]:
hits = []
for path in SRC.rglob("*.py"):
for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if re.search(pattern, line):
hits.append(f"{path.relative_to(SRC.parent)}:{i}: {line.strip()}")
return hits


def test_no_cdn_plotlyjs():
hits = _grep(r'include_plotlyjs\s*=\s*["\']cdn["\']')
assert not hits, "CDN plotlyjs detected (fails silently offline):\n" + "\n".join(
hits
)


def test_no_bare_except_pass():
hits = _grep(r"^\s*except\s*(\(\s*\))?\s*:\s*(pass\s*)?$")
assert not hits, "Bare except/pass detected (swallows all errors):\n" + "\n".join(
hits
)
8 changes: 4 additions & 4 deletions tests/test_ir_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ def test_trace_is_scatter(self):
fig = plot_ir_spectrum(_SIMPLE_FREQS, _SIMPLE_INTS)
assert isinstance(fig.data[0], go.Scatter)

def test_xaxis_inverted(self):
def test_xaxis_low_to_high(self):
fig = plot_ir_spectrum(_SIMPLE_FREQS, _SIMPLE_INTS)
x_range = list(fig.layout.xaxis.range)
assert x_range[0] > x_range[1], "x-axis must run highlow (4000400)"
assert x_range[0] < x_range[1], "x-axis must run lowhigh (4004000)"

def test_xaxis_title_contains_wavenumber(self):
fig = plot_ir_spectrum(_SIMPLE_FREQS, _SIMPLE_INTS)
Expand Down Expand Up @@ -106,10 +106,10 @@ def test_fwhm_affects_peak_width(self):
y_wide.sum() > y_narrow.sum()
), "Wider FWHM should produce larger integrated area"

def test_xaxis_inverted_broadened(self):
def test_xaxis_low_to_high_broadened(self):
fig = plot_ir_spectrum(_SIMPLE_FREQS, _SIMPLE_INTS, mode="broadened")
x_range = list(fig.layout.xaxis.range)
assert x_range[0] > x_range[1]
assert x_range[0] < x_range[1]


# ---------------------------------------------------------------------------
Expand Down
20 changes: 12 additions & 8 deletions tests/test_pes_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,18 @@ def test_scan_steps_default(self):
app = QuantUIApp()
assert app._scan_steps.value == 10

def test_pes_scan_accordion_hidden_initially(self):
def test_pes_scan_accordion_visible_collapsed_initially(self):
from quantui.app import QuantUIApp

app = QuantUIApp()
assert app._pes_scan_accordion.layout.display == "none"
assert app._pes_scan_accordion.layout.display == ""
assert app._pes_scan_accordion.selected_index is None

def test_pes_plot_html_empty_initially(self):
from quantui.app import QuantUIApp

app = QuantUIApp()
assert app._pes_plot_html.value == ""
assert len(app._pes_plot_html.outputs) == 0

def test_on_calc_type_changed_to_pes_scan_populates_extras(self):
from quantui.app import QuantUIApp
Expand All @@ -210,15 +211,18 @@ def test_on_calc_type_changed_to_pes_scan_populates_extras(self):
app.calc_type_dd.value = "PES Scan"
assert len(app.calc_extra_opts.children) > 0

def test_pes_scan_accordion_cleared_on_run_clicked(self):
def test_pes_scan_accordion_collapsed_on_run_clicked(self):
from IPython.display import HTML

from quantui.app import QuantUIApp

app = QuantUIApp()
app._pes_scan_accordion.layout.display = ""
app._pes_plot_html.value = "<div>old</div>"
app._pes_scan_accordion.selected_index = 0
app._pes_plot_html.append_display_data(HTML("<div>old</div>"))
assert len(app._pes_plot_html.outputs) == 1
app._on_run_clicked(None)
assert app._pes_scan_accordion.layout.display == "none"
assert app._pes_plot_html.value == ""
assert app._pes_scan_accordion.selected_index is None
assert len(app._pes_plot_html.outputs) == 0


# ── Format method ─────────────────────────────────────────────────────────────
Expand Down
Loading