import json
import os
import numpy as np
import plotly.express as px
import streamlit as st
from squadds import SQuADDS_DB
from squadds.interpolations.physics import ScalingInterpolator
from squadds.ui.utils_query import extract_cavity_only_params, find_closest_cached
# SQuADDS logo and links
LOGO_PATH = "https://raw.githubusercontent.com/LFL-Lab/SQuADDS/master/docs/_static/images/squadds_logo_transparent.png"
HF_LINK = "https://huggingface.co/datasets/SQuADDS/SQuADDS_DB"
GITHUB_LINK = "https://github.com/LFL-Lab/SQuADDS"
DOCSITE_LINK = "https://lfl-lab.github.io/SQuADDS/"
PAPER_LINK = "https://quantum-journal.org/papers/q-2024-09-09-1465/"
DEEPWIKI_LINK = "https://deepwiki.com/LFL-Lab/SQuADDS/1-overview"
PORTAL_LINK = "https://squadds-portal.vercel.app"
[docs]
def convert_numpy_to_list(obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, dict):
return {k: convert_numpy_to_list(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_numpy_to_list(v) for v in obj]
else:
return obj
[docs]
def initialize_session_state():
"""Initialize session state variables"""
if "db" not in st.session_state:
st.session_state.db = SQuADDS_DB()
if "supported_qubits" not in st.session_state:
qubits = st.session_state.db.get_component_names("qubit")
st.session_state.supported_qubits = qubits
if "supported_cavities" not in st.session_state:
cavities = st.session_state.db.get_component_names("cavity_claw")
st.session_state.supported_cavities = cavities
[docs]
def main():
st.set_page_config(page_title="SQuADDS WebUI", page_icon="🔍", layout="wide")
initialize_session_state()
# SQuADDS logo and title
st.markdown(
f"""
<div style='display: flex; align-items: center; gap: 2rem;'>
<img src='{LOGO_PATH}' alt='SQuADDS Logo' style='height: 80px;'>
<h1 style='margin-bottom: 0;'>SQuADDS WebUI</h1>
</div>
""",
unsafe_allow_html=True,
)
# Links row
st.markdown(
f"""
<div style='margin-bottom: 1.5rem;'>
<a href='{HF_LINK}' target='_blank' style='margin-right: 1.5rem;'>🤗 HuggingFace</a>
<a href='{GITHUB_LINK}' target='_blank' style='margin-right: 1.5rem;'>🐙 GitHub</a>
<a href='{DOCSITE_LINK}' target='_blank' style='margin-right: 1.5rem;'>📖 Docsite</a>
<a href='{PAPER_LINK}' target='_blank' style='margin-right: 1.5rem;'>📄 Paper</a>
<a href='{DEEPWIKI_LINK}' target='_blank' style='margin-right: 1.5rem;'>🧠 DeepWiki</a>
<a href='{PORTAL_LINK}' target='_blank'>🌐 Portal</a>
</div>
""",
unsafe_allow_html=True,
)
st.markdown("""
Find the closest superconducting device designs based on your target parameters.
""")
# Sidebar for system selection
with st.sidebar:
st.header("System Configuration")
# Track previous selections
prev_system_type = st.session_state.get("prev_system_type", None)
prev_qubit_type = st.session_state.get("prev_qubit_type", None)
prev_cavity_type = st.session_state.get("prev_cavity_type", None)
prev_coupling_type = st.session_state.get("prev_coupling_type", None)
prev_resonator_type = st.session_state.get("prev_resonator_type", None)
system_type = st.selectbox("Select System Type", ["Qubit-Cavity", "Qubit Only", "Cavity Only"], index=0)
# Qubit selection
if system_type in ["Qubit-Cavity", "Qubit Only"]:
qubit_type = st.selectbox(
"Select Qubit Type",
st.session_state.supported_qubits,
index=st.session_state.supported_qubits.index("TransmonCross")
if "TransmonCross" in st.session_state.supported_qubits
else 0,
)
if qubit_type == "CLT":
st.warning("⚠️ 'CLT' is not a valid qubit type for design search. Please select another qubit type.")
else:
qubit_type = None
# Cavity selection
if system_type in ["Qubit-Cavity", "Cavity Only"]:
cavity_type = st.selectbox(
"Select Cavity Type",
st.session_state.supported_cavities,
index=st.session_state.supported_cavities.index("RouteMeander")
if "RouteMeander" in st.session_state.supported_cavities
else 0,
)
if cavity_type == "CLT":
st.warning("⚠️ 'CLT' is not a valid cavity type for design search. Please select another cavity type.")
else:
cavity_type = None
# Coupling type for Qubit-Cavity
show_num_cpu = False
num_cpu = "auto"
if system_type == "Qubit-Cavity":
coupling_type = st.selectbox("Select Coupling Type", ["Capacitive"], index=0)
resonator_type = st.selectbox("Select Resonator Type", ["quarter", "half"], index=0)
if resonator_type == "half":
show_num_cpu = True
elif system_type == "Cavity Only":
coupling_type = None
resonator_type = st.selectbox("Select Resonator Type", ["quarter", "half"], index=0)
else:
coupling_type = None
resonator_type = None
# Dynamic num_cpu for Qubit-Cavity + half
if show_num_cpu:
cpu_options = ["auto"] + [str(i) for i in range(1, os.cpu_count() + 1)]
num_cpu = st.selectbox("Number of CPUs", cpu_options, index=0)
# Reset results if any major config changes
if (
prev_system_type != system_type
or prev_qubit_type != qubit_type
or prev_cavity_type != cavity_type
or prev_coupling_type != coupling_type
or prev_resonator_type != resonator_type
):
for k in ["results", "params", "analyzer", "data_qubit", "data_cpw", "data_coupler", "LJs"]:
if k in st.session_state:
del st.session_state[k]
# Update previous selections
st.session_state.prev_system_type = system_type
st.session_state.prev_qubit_type = qubit_type
st.session_state.prev_cavity_type = cavity_type
st.session_state.prev_coupling_type = coupling_type
st.session_state.prev_resonator_type = resonator_type
st.divider()
st.markdown("### Search Settings")
num_results = st.slider("Number of results", 1, 10, 1) # Default to 1
# Main content area
col1, col2 = st.columns([2, 3])
with col1:
st.subheader("Target Hamiltonian Parameters")
# Parameter input fields based on system type
params = {}
if system_type in ["Qubit-Cavity", "Qubit Only"]:
params["qubit_frequency_GHz"] = st.number_input("Qubit Frequency (GHz)", 3.0, 8.0, 4.0)
params["anharmonicity_MHz"] = st.number_input("Anharmonicity (MHz)", -500.0, -50.0, -200.0)
if system_type in ["Qubit-Cavity", "Cavity Only"]:
params["cavity_frequency_GHz"] = st.number_input("Cavity Frequency (GHz)", 5.0, 12.0, 9.2)
params["kappa_kHz"] = st.number_input("Kappa (kHz)", 10.0, 1000.0, 80.0)
if system_type == "Qubit-Cavity":
params["g_MHz"] = st.number_input("Coupling Strength g (MHz)", 10.0, 200.0, 70.0)
params["resonator_type"] = resonator_type
elif system_type == "Cavity Only":
params["resonator_type"] = resonator_type
# No resonator_type for Qubit Only
if st.button("Find Designs", type="primary"):
# Show message for half-wave Qubit-Cavity search
if system_type == "Qubit-Cavity" and resonator_type == "half":
st.info(
"Half-wave Qubit-Cavity searches may take a while due to the size of the dataset and parallel processing. If you'd like to help speed this up, please consider contributing code! 🙏🏽 [Contribute on GitHub](https://github.com/LFL-Lab/SQuADDS)"
)
# Block Cavity Only + half-wave and show info message
if system_type == "Cavity Only" and resonator_type == "half":
st.info("Half-wave Cavity Only search is not yet supported. We're working on this feature!")
return
# Track last-used system config for smart skip logic
last_config = st.session_state.get("last_system_config", None)
current_config = {
"system_type": system_type,
"qubit_type": qubit_type,
"cavity_type": cavity_type,
"resonator_type": resonator_type,
"num_cpu": num_cpu,
"num_results": num_results,
}
skip_df_gen = False
if last_config is not None:
# If only params changed, skip df gen
config_keys = ["system_type", "qubit_type", "cavity_type", "resonator_type", "num_cpu", "num_results"]
if all(current_config[k] == last_config[k] for k in config_keys):
skip_df_gen = True
with st.spinner("Searching for closest designs..."):
try:
results, analyzer = find_closest_cached(
system_type, qubit_type, cavity_type, resonator_type, params, num_results, num_cpu, skip_df_gen
)
st.session_state.results = results
st.session_state.params = params
st.session_state.analyzer = analyzer
st.session_state.last_system_config = current_config
if system_type == "Qubit-Cavity":
st.session_state.data_qubit = convert_numpy_to_list(analyzer.get_qubit_options(results))
st.session_state.data_cpw = convert_numpy_to_list(analyzer.get_cpw_options(results))
st.session_state.data_coupler = convert_numpy_to_list(analyzer.get_coupler_options(results))
st.session_state.LJs = convert_numpy_to_list(analyzer.get_Ljs(results))
# --- Interpolated Results for Qubit-Cavity ---
try:
interpolator = ScalingInterpolator(analyzer, params)
interpolated_df = interpolator.get_design()
st.session_state.interpolated_df = interpolated_df
except Exception as e:
st.session_state.interpolated_df = None
st.session_state.interpolated_error = str(e)
elif system_type == "Qubit Only":
st.session_state.data_qubit = convert_numpy_to_list(analyzer.get_qubit_options(results))
st.session_state.data_cpw = None
st.session_state.data_coupler = None
st.session_state.LJs = None
st.session_state.interpolated_df = None
elif system_type == "Cavity Only":
# Use utility function for extraction
cpw_vals, coupler_vals = extract_cavity_only_params(results)
st.session_state.data_qubit = None
st.session_state.data_cpw = cpw_vals
st.session_state.data_coupler = coupler_vals
st.session_state.LJs = None
st.session_state.interpolated_df = None
except Exception as e:
st.error(f"Error: {str(e)}")
st.error("Please check your system configuration and parameters.")
with col2:
if "results" in st.session_state:
st.subheader("Design Space Results")
# Create tabs for different visualizations
tab_labels = ["Design Parameters", "ℋ Parameter Space Plots", "`design` Options"]
if system_type == "Qubit-Cavity":
tab_labels.append("Interpolated Results")
tabs = st.tabs(tab_labels)
tab1, tab2, tab3 = tabs[:3]
if system_type == "Qubit-Cavity":
tab4 = tabs[3]
with tab1:
# Design Parameters Tab (was previously Extracted Data)
analyzer = st.session_state.analyzer
results = st.session_state.results
if system_type == "Qubit Only":
keys_cross = ["cross_gap", "cross_length", "cross_width"]
keys_claw = ["claw_gap", "claw_length", "claw_width", "ground_spacing"]
cross_vals = {k: [] for k in keys_cross}
claw_vals = {k: [] for k in keys_claw}
for _idx, row in results.iterrows():
opts = convert_numpy_to_list(row["design_options"])
# Cross parameters
for k in keys_cross:
v = opts.get(k)
if v is not None:
cross_vals[k].append(v)
# Claw parameters (nested in connection_pads.readout)
claw = opts.get("connection_pads", {}).get("readout", {})
for k in keys_claw:
v = claw.get(k)
if v is not None:
claw_vals[k].append(v)
LJs = convert_numpy_to_list(analyzer.get_Ljs(results))
st.markdown("**TransmonCross (Qubit) Values**")
st.code(json.dumps(cross_vals, indent=2))
st.markdown("**Claw Values**")
st.code(json.dumps(claw_vals, indent=2))
st.markdown("**Josephson Inductances (LJs, nH)**")
st.code(json.dumps(LJs, indent=2))
elif system_type == "Cavity Only":
# Use extracted values from session state
cpw_vals = st.session_state.data_cpw
coupler_vals = st.session_state.data_coupler
st.markdown("**RouteMeander (CPW) Values**")
st.code(json.dumps(cpw_vals, indent=2))
st.markdown("**CoupledLineTee (Coupler) Values**")
st.code(json.dumps(coupler_vals, indent=2))
else:
# Qubit-Cavity: show with QComponent names and 'Values'
if st.session_state.data_qubit is not None:
st.markdown("**TransmonCross (Qubit) Values**")
st.code(json.dumps(st.session_state.data_qubit, indent=2))
if st.session_state.data_cpw is not None:
st.markdown("**RouteMeander (CPW) Values**")
st.code(json.dumps(st.session_state.data_cpw, indent=2))
if st.session_state.data_coupler is not None:
# Try to get coupler type from results
coupler_type = (
results.iloc[0]["coupler_type"] if "coupler_type" in results.iloc[0] else "CoupledLineTee"
)
coupler_qcomp = (
"CapNInterdigital"
if coupler_type in ["CapNInterdigital", "CapNInterdigitalTee"]
else "CoupledLineTee"
)
st.markdown(f"**{coupler_qcomp} (Coupler) Values**")
st.code(json.dumps(st.session_state.data_coupler, indent=2))
if st.session_state.LJs is not None:
st.markdown("**Josephson Inductances (LJs, nH)**")
st.code(json.dumps(st.session_state.LJs, indent=2))
with tab2:
# Parameter Space Plots (was previously tab1, now tab2)
try:
results = st.session_state.results
target = st.session_state.params
if system_type in ["Qubit-Cavity", "Qubit Only"]:
fig1 = px.scatter(
results,
x="qubit_frequency_GHz",
y="anharmonicity_MHz",
title="Qubit Frequency vs Anharmonicity",
labels={
"qubit_frequency_GHz": "Qubit Frequency (GHz)",
"anharmonicity_MHz": "Anharmonicity (MHz)",
},
)
fig1.add_scatter(
x=[target["qubit_frequency_GHz"]],
y=[target["anharmonicity_MHz"]],
mode="markers",
marker=dict(size=15, symbol="x", color="red"),
name="Target",
)
st.plotly_chart(fig1, use_container_width=True)
if system_type in ["Qubit-Cavity", "Cavity Only"]:
fig2 = px.scatter(
results,
x="cavity_frequency_GHz",
y="kappa_kHz",
title="Cavity Frequency vs Kappa",
labels={"cavity_frequency_GHz": "Cavity Frequency (GHz)", "kappa_kHz": "Kappa (kHz)"},
)
fig2.add_scatter(
x=[target["cavity_frequency_GHz"]],
y=[target["kappa_kHz"]],
mode="markers",
marker=dict(size=15, symbol="x", color="red"),
name="Target",
)
st.plotly_chart(fig2, use_container_width=True)
except Exception as e:
st.error(f"Error plotting results: {str(e)}")
with tab3:
# Design Options (was previously tab2, now tab3)
for i, design in st.session_state.results.iterrows():
with st.expander(f"Design {i + 1}"):
c1, c2 = st.columns(2)
if system_type == "Qubit Only":
with c1:
st.markdown("#### Qubit Parameters")
st.write(f"Frequency: {design['qubit_frequency_GHz']:.2f} GHz")
st.write(f"Anharmonicity: {design['anharmonicity_MHz']:.2f} MHz")
if "EJ" in design and "EC" in design:
st.write(f"EJ/EC: {design['EJ'] / design['EC']:.2f}")
qubit_options = convert_numpy_to_list(design.get("design_options", {}))
st.code(json.dumps(qubit_options, indent=2))
if st.button(f"Copy Qubit Design {i + 1}", key=f"copy_qubit_{i}"):
st.write("Qubit design copied to clipboard! ✅")
st.session_state[f"clipboard_qubit_{i}"] = json.dumps(qubit_options)
elif system_type == "Cavity Only":
with c2:
st.markdown("#### Cavity Parameters")
st.write(f"Frequency: {design['cavity_frequency_GHz']:.2f} GHz")
st.write(f"Kappa: {design['kappa_kHz']:.2f} kHz")
if "g_MHz" in design:
st.write(f"Coupling g: {design['g_MHz']:.2f} MHz")
cavity_options = convert_numpy_to_list(design.get("design_options", {}))
st.code(json.dumps(cavity_options, indent=2))
if st.button(f"Copy Cavity Design {i + 1}", key=f"copy_cavity_{i}"):
st.write("Cavity design copied to clipboard! ✅")
st.session_state[f"clipboard_cavity_{i}"] = json.dumps(cavity_options)
elif system_type == "Qubit-Cavity":
with c1:
st.markdown("#### Qubit Parameters")
st.write(f"Frequency: {design['qubit_frequency_GHz']:.2f} GHz")
st.write(f"Anharmonicity: {design['anharmonicity_MHz']:.2f} MHz")
if "EJ" in design and "EC" in design:
st.write(f"EJ/EC: {design['EJ'] / design['EC']:.2f}")
qubit_options = convert_numpy_to_list(design["design_options"].get("qubit_options", {}))
st.code(json.dumps(qubit_options, indent=2))
if st.button(f"Copy Qubit Design {i + 1}", key=f"copy_qubit_{i}"):
st.write("Qubit design copied to clipboard! ✅")
st.session_state[f"clipboard_qubit_{i}"] = json.dumps(qubit_options)
with c2:
st.markdown("#### Cavity Parameters")
st.write(f"Frequency: {design['cavity_frequency_GHz']:.2f} GHz")
st.write(f"Kappa: {design['kappa_kHz']:.2f} kHz")
if "g_MHz" in design:
st.write(f"Coupling g: {design['g_MHz']:.2f} MHz")
cavity_options = convert_numpy_to_list(
design["design_options"].get("cavity_claw_options", {})
)
st.code(json.dumps(cavity_options, indent=2))
if st.button(f"Copy Cavity Design {i + 1}", key=f"copy_cavity_{i}"):
st.write("Cavity design copied to clipboard! ✅")
st.session_state[f"clipboard_cavity_{i}"] = json.dumps(cavity_options)
# Show full design options with copy button
if st.checkbox(f"Show Full Design Options {i + 1}", key=f"design_{i}"):
st.code(json.dumps(convert_numpy_to_list(design["design_options"]), indent=2))
if st.button(f"Copy Full Design {i + 1}", key=f"copy_full_{i}"):
st.write("Full design copied to clipboard! ✅")
st.session_state[f"clipboard_full_{i}"] = json.dumps(
convert_numpy_to_list(design["design_options"])
)
if system_type == "Qubit-Cavity":
with tab4:
# Interpolated Results Tab
interpolated_df = st.session_state.get("interpolated_df", None)
if interpolated_df is None:
st.warning(st.session_state.get("interpolated_error", "No interpolated results available."))
else:
for i, row in interpolated_df.iterrows():
# Extract values for Design Parameters (mirroring main tab, but from interpolated data)
# Qubit
keys_cross = ["cross_gap", "cross_length", "cross_width"]
keys_claw = ["claw_gap", "claw_length", "claw_width", "ground_spacing"]
cross_vals = {k: [] for k in keys_cross}
claw_vals = {k: [] for k in keys_claw}
qubit_opts = convert_numpy_to_list(row["design_options"].get("qubit_options", {}))
for k in keys_cross:
v = qubit_opts.get(k)
if v is not None:
cross_vals[k].append(v)
claw = qubit_opts.get("connection_pads", {}).get("readout", {})
for k in keys_claw:
v = claw.get(k)
if v is not None:
claw_vals[k].append(v)
# CPW
cpw_keys = ["total_length", "trace_gap", "trace_width"]
cpw_vals = {k: [] for k in cpw_keys}
cpw_opts = convert_numpy_to_list(
row["design_options"]
.get("cavity_claw_options", {})
.get("cpw_opts", {})
.get("left_options", {})
)
for k in cpw_keys:
v = cpw_opts.get(k)
if v is not None:
cpw_vals[k].append(v)
# Coupler
coupler_keys = [
"coupling_length",
"coupling_space",
"down_length",
"orientation",
"prime_gap",
"prime_width",
"second_gap",
"second_width",
"cap_distance",
"cap_gap",
"cap_gap_ground",
"cap_width",
"finger_count",
"finger_length",
]
coupler_vals = {k: [] for k in coupler_keys}
coupler_opts = convert_numpy_to_list(
row["design_options"].get("cavity_claw_options", {}).get("coupler_options", {})
)
for k in coupler_keys:
v = coupler_opts.get(k)
if v is not None:
coupler_vals[k].append(v)
with st.expander(f"Interpolated Design {i + 1} - Design Parameters"):
st.markdown("**TransmonCross (Qubit) Values**")
st.code(json.dumps(cross_vals, indent=2))
st.markdown("**Claw Values**")
st.code(json.dumps(claw_vals, indent=2))
st.markdown("**RouteMeander (CPW) Values**")
st.code(json.dumps(cpw_vals, indent=2))
coupler_type = (
row["design_options"]
.get("cavity_claw_options", {})
.get("coupler_type", "CoupledLineTee")
)
coupler_qcomp = (
"CapNInterdigital"
if coupler_type in ["CapNInterdigital", "CapNInterdigitalTee"]
else "CoupledLineTee"
)
st.markdown(f"**{coupler_qcomp} (Coupler) Values**")
st.code(json.dumps(coupler_vals, indent=2))
if "LJ" in row:
st.markdown("**Josephson Inductance (LJ, nH)**")
st.code(json.dumps(row["LJ"], indent=2))
with st.expander(f"Interpolated Design {i + 1} - `design` Options"):
st.markdown("#### Qubit Parameters")
st.code(json.dumps(qubit_opts, indent=2))
st.markdown("#### Cavity Parameters")
cavity_opts = convert_numpy_to_list(
row["design_options"].get("cavity_claw_options", {})
)
st.code(json.dumps(cavity_opts, indent=2))
st.markdown("#### Coupler Parameters")
st.code(json.dumps(coupler_opts, indent=2))
st.markdown("#### CPW Parameters")
st.code(json.dumps(cpw_opts, indent=2))
# Simple feedback link in the bottom right corner
st.markdown(
"""
<div style="position: fixed; bottom: 16px; right: 24px; z-index: 9999;">
<a href="mailto:shanto@usc.edu?subject=SQuADDS%20WebUI%20Feedback%20or%20Feature%20Request&body=Please%20describe%20your%20bug%2C%20feature%20request%2C%20or%20feedback%20below%3A%0A%0A"
style="color: #3578e5; font-size: 0.95em; text-decoration: underline; background: rgba(255,255,255,0.85); padding: 2px 10px; border-radius: 1em; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
💬 Feedback
</a>
</div>
""",
unsafe_allow_html=True,
)
if __name__ == "__main__":
main()