""" HuggingFace Space — Quantum Iris Classifier Interactive demo: classify Iris flowers using a Variational Quantum Circuit Built by Vijaya Kumari | github.com/vijayarjun7 """ import gradio as gr import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import io, warnings warnings.filterwarnings("ignore") from PIL import Image from sklearn.datasets import load_iris from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import train_test_split from qiskit.circuit.library import ZZFeatureMap, RealAmplitudes from qiskit_machine_learning.algorithms import VQC from qiskit_algorithms.optimizers import COBYLA from qiskit.primitives import Sampler # -- Train VQC at startup print("Training quantum classifier... (runs once at startup)") iris = load_iris() X = iris.data[:100, 2:4] y = iris.target[:100] scaler = MinMaxScaler(feature_range=(0, np.pi)) X_sc = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split( X_sc, y, test_size=0.2, random_state=42, stratify=y ) feature_map = ZZFeatureMap(feature_dimension=2, reps=2) ansatz = RealAmplitudes(num_qubits=2, reps=3) vqc = VQC( sampler=Sampler(), feature_map=feature_map, ansatz=ansatz, optimizer=COBYLA(maxiter=150, rhobeg=0.5), ) vqc.fit(X_train, y_train) test_acc = vqc.score(X_test, y_test) print(f"VQC trained! Test accuracy: {test_acc:.1%}") # -- Pre-compute decision boundary once at startup print("Pre-computing decision boundary...") h = 0.08 x0_min, x0_max = X_sc[:, 0].min() - 0.1, X_sc[:, 0].max() + 0.1 x1_min, x1_max = X_sc[:, 1].min() - 0.1, X_sc[:, 1].max() + 0.1 xx, yy = np.meshgrid(np.arange(x0_min, x0_max, h), np.arange(x1_min, x1_max, h)) Z = vqc.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape) print("Decision boundary ready!") petal_len_range = (float(X[:, 0].min()), float(X[:, 0].max())) petal_wid_range = (float(X[:, 1].min()), float(X[:, 1].max())) def fig_to_pil(fig): buf = io.BytesIO() fig.savefig(buf, format="png", dpi=120, bbox_inches="tight") buf.seek(0) img = Image.open(buf).copy() plt.close(fig) buf.close() return img def classify(petal_length, petal_width): x_raw = np.array([[petal_length, petal_width]]) x_sc = scaler.transform(x_raw) pred = vqc.predict(x_sc)[0] label = "Setosa" if pred == 0 else "Versicolor" confidence = "High confidence" if (x_sc[0, 0] < 1.0 or x_sc[0, 0] > 2.5) else "Moderate confidence" fig1, ax1 = plt.subplots(figsize=(9, 2.2)) ax1.axis("off") ax1.text(0.5, 0.65, f"Input: petal_length={petal_length:.1f}cm, petal_width={petal_width:.1f}cm", ha="center", va="center", fontsize=13, fontweight="bold", color="#222") ax1.text(0.5, 0.28, f"Quantum angles: [{x_sc[0,0]:.3f} rad, {x_sc[0,1]:.3f} rad] -> Prediction: {label}", ha="center", va="center", fontsize=11, bbox=dict(boxstyle="round,pad=0.5", facecolor="#ede0ff", alpha=0.95)) ax1.set_title( "ZZFeatureMap (data encoding) + RealAmplitudes (8 trainable weights) + COBYLA", fontsize=9, color="#555", pad=5) img1 = fig_to_pil(fig1) fig2, ax2 = plt.subplots(figsize=(6, 5)) ax2.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdBu) ax2.contour(xx, yy, Z, colors="gray", linewidths=0.8, alpha=0.5) for cls, (col, lbl) in enumerate(zip( ["#E74C3C", "#2980B9"], ["Setosa (train)", "Versicolor (train)"] )): mask = y_train == cls ax2.scatter(X_train[mask, 0], X_train[mask, 1], c=col, s=40, label=lbl, alpha=0.7, edgecolors="white", lw=0.4) star_col = "#E74C3C" if pred == 0 else "#2980B9" ax2.scatter(x_sc[0, 0], x_sc[0, 1], c=star_col, s=350, marker="*", edgecolors="gold", linewidths=1.8, zorder=10, label=f"Your input -> {label}") ax2.set_xlabel("Petal Length (radians)") ax2.set_ylabel("Petal Width (radians)") ax2.set_title(f"VQC Decision Boundary | Test Accuracy: {test_acc:.1%}", fontsize=11, fontweight="bold") ax2.legend(fontsize=9, loc="upper left") ax2.grid(True, alpha=0.2) img2 = fig_to_pil(fig2) result_text = f""" **Prediction: {label}** - {confidence} **How it decided:** 1. Inputs scaled to quantum angles: [{x_sc[0,0]:.3f}, {x_sc[0,1]:.3f}] radians 2. ZZFeatureMap encoded into 2-qubit state via H + ZZ-entanglement 3. RealAmplitudes applied 8 trained rotation angles 4. Qubit measured: {'|0> = Setosa' if pred == 0 else '|1> = Versicolor'} **Model:** 2-qubit VQC | 8 parameters | {test_acc:.1%} test accuracy """ return result_text, img1, img2 with gr.Blocks( title="Quantum Iris Classifier", theme=gr.themes.Soft(primary_hue="purple"), css=".gradio-container { max-width: 900px; margin: auto; }" ) as demo: gr.Markdown(""" # Quantum Iris Classifier ### Variational Quantum Circuit (VQC) - Qiskit Machine Learning A 2-qubit quantum circuit classifies Iris flowers by encoding petal measurements as quantum rotation angles, then using trained quantum weights to predict the species. > **Setosa** = short narrow petals | **Versicolor** = long wide petals """) with gr.Row(): with gr.Column(scale=1): petal_len = gr.Slider( minimum=round(petal_len_range[0], 1), maximum=round(petal_len_range[1], 1), value=1.5, step=0.1, label="Petal Length (cm)", info="Setosa: 1.0-1.9cm | Versicolor: 3.0-5.1cm" ) petal_wid = gr.Slider( minimum=round(petal_wid_range[0], 1), maximum=round(petal_wid_range[1], 1), value=0.3, step=0.1, label="Petal Width (cm)", info="Setosa: 0.1-0.6cm | Versicolor: 1.0-1.8cm" ) classify_btn = gr.Button("Run Quantum Classifier", variant="primary") with gr.Column(scale=2): result_md = gr.Markdown() with gr.Row(): circuit_img = gr.Image(label="Quantum Circuit Info", type="pil") boundary_img = gr.Image(label="Decision Boundary (your input = star)", type="pil") classify_btn.click( fn=classify, inputs=[petal_len, petal_wid], outputs=[result_md, circuit_img, boundary_img] ) gr.Markdown(""" --- **How this works:** - **ZZFeatureMap** encodes petal measurements as rotation angles in a 2-qubit quantum state - **ZZ-entanglement** captures petal length x width interaction automatically - **RealAmplitudes** = 8 Ry rotation angles trained with COBYLA (gradient-free) - Measurement: |0> Setosa | |1> Versicolor Built by [Vijaya Kumari](https://github.com/vijayarjun7) | Quantum ML Learning Journey """) if __name__ == "__main__": demo.launch()