diff --git a/README.md b/README.md index fc7f344..99bd726 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,101 @@ # Quantum Folk Lab -Quantum computing is difficult to learn because many demonstrations are abstract, opaque, and -disconnected from meaningful subject matter. Quantum Folk Lab turns folk-music-derived -optimisation problems into transparent, exact-first learning experiences in which users can -inspect the classical truth, compare quantum heuristics, and understand what real hardware did -and did not demonstrate. +> Learn quantum computing by checking the exact answer first — folk-music problems, transparent +> experiments, real IBM hardware and no hype. -The public product deliberately separates three evidence layers: a deterministic synthetic fixture -for guided teaching; a governed, licence- and provenance-gated real-data experiment programme; -and bounded IBM Quantum hardware results. Not every guided example uses raw historical notation, -and the application does not provide audio playback. Exact classical evaluation remains -authoritative throughout. +Quantum Folk Lab is an Education product for learners and educators. Folk music supplies the +structured choice problems: the console reveals known classical truth, compares a bounded quantum +method, and then shows what governed IBM hardware did and did not reproduce. -## Research Question +## Three-step learner journey -Given a small set of synthetic symbolic melodies and interpretable pairwise similarities, can a two-family QUBO formulation recover known tune families, and how do local QAOA-style samples compare with exact classical optima? +1. **Reveal the exact answer.** +2. **Compare the quantum method.** +3. **Inspect real IBM hardware evidence.** -```mermaid -flowchart LR - A["Local Qiskit circuit infrastructure"] --> B["Reference optimisation benchmarks"] - B --> C["Synthetic melody families"] - C --> D["Interval, contour, rhythm similarity"] - D --> E["Sparse weighted graph"] - E --> F["Two-family QUBO"] - F --> G["Exact classical solver"] - F --> H["Verified local QAOA"] - G --> I["Evaluation"] - H --> I -``` +## Four proof points -## Quick Start +- The Guided Experiment checks all **256 assignments exactly** before interpretation. +- The compact experiment uses **public folk-tune-family data** in a 16-state problem. +- The IBM landscape reached rho **`0.96`** and was independently reproduced with cross-run rho + approximately **`0.978`**. +- **GPT-5.6 may explain governed evidence but cannot alter it.** -PowerShell: + -```powershell -py -m venv .venv -.\.venv\Scripts\Activate.ps1 -python -m pip install -e ".[dev]" -qfl doctor -qfl compare --seed 42 -``` - -Bash: +## Launch the Learning Console ```bash -python -m venv .venv -source .venv/bin/activate -python -m pip install -e ".[dev]" -qfl doctor -qfl compare --seed 42 +pip install -e ".[learning]" +streamlit run apps/learning_console/app.py ``` -## OpenAI Build Week 2026 — judge quick start +Open **Experiments** and begin with **Start here · Guided experiment**. Reveal all 256 answers, +then compare registered local simulation, the real folk-data compact experiment, the first IBM +validation, and the replicated hardware landscape. **Foundations** and **Glossary** provide the +beginner path. No IBM credential or OpenAI API key is required; optional Qiskit remains +button-gated and optional GPT-5.6 fails closed to deterministic explanation. -Quantum Folk Lab is an Education product: learners reveal every answer to a small, interpretable -problem before comparing a bounded quantum heuristic or reading an explanation. +## Governed IBM evidence -- **Before Build Week:** the public Foundations console and registered research experiments - already existed at commit `281ba40`. -- **Built during Build Week:** the validated exact-first service and Guided Experiment were added - after governing-plan commit `3950a1f`. +The read-only Learning Console panel distinguishes three layers: -```powershell -python -m pip install -e ".[learning]" -streamlit run apps/learning_console/app.py -``` - -In the app, open **Experiments** (the default), follow **Guided Experiment**, and click -**Reveal all 256 assignments**. Then inspect **Foundations** and **Glossary**. The current app -shows exact synthetic-fixture evidence, registered local ideal-simulator evidence, and the compact -experiment story; it does not yet contain a dedicated EXP-010D/011 result panel. +- **EXP-010C — first hardware validation:** the exact optimum `1010` remained the most likely state. +- **EXP-010D — controlled 25-cell landscape:** ideal/hardware rho `0.96`, classified + **LANDSCAPE SUPPORTED**. +- **EXP-011 — independent 81-cell replication:** full rho `0.9047`, embedded-25 rho `0.9315`, and + cross-run rho `0.9777`, classified **STRONGLY REPLICATED**. -The core app requires no IBM credential. Its deterministic explanation works without an OpenAI -API key. Optional GPT-5.6 explanation uses `.[ai]`, receives only governed evidence, and fails -closed to the deterministic explanation when unavailable or invalid. Optional local Qiskit uses -`.[quantum]` and is explicitly button-gated. +Both landscape reports retain the predeclared control warning. Read the authoritative +[EXP-010D report](experiments/EXP-010D-hardware-parameter-landscape-run/RESULT-REPORT.md) and +[EXP-011 report](experiments/EXP-011-dense-hardware-landscape-run/RESULT-REPORT.md). -Public hardware reports: +## Built with Codex and GPT-5.6 -- [EXP-010D result](experiments/EXP-010D-hardware-parameter-landscape-run/RESULT-REPORT.md) -- [EXP-011 result](experiments/EXP-011-dense-hardware-landscape-run/RESULT-REPORT.md) +Codex accelerated bounded implementation, tests, CI diagnosis, experiment packaging, visual +verification, and pull-request review under human-defined scientific gates. Gwri retained product, +scientific, hardware-authorization, interpretation, and merge authority. GPT-5.6 is an optional, +filtered explanation layer: it does not calculate results, and invalid or unavailable output fails +closed. **The AI can explain the experiment. It cannot rewrite the evidence.** -See the [Build Week judging guide](docs/build-week/JUDGING-GUIDE.md) for the shortest review path. +## Honest limits -### Hardware-era result summary +Exact classical evaluation remains authoritative, and the project makes no quantum-advantage +claim. These deliberately small experiments do not demonstrate speedup, scalability, +generalisation, musical quality, musical truth, or commercial superiority. The application has no +audio and makes no live IBM call. -**EXP-010D.** One `ibm_fez` job returned all 32 PUBs at 4,096 shots per PUB. The -ideal-versus-hardware landscape Spearman rho was `0.96`, producing the frozen classification -**LANDSCAPE SUPPORTED**. The centre ranked first, its most-likely state was `1010`, and the -predeclared control-quality warning was retained. +## Deeper research documentation -**EXP-011.** One separate `ibm_fez` job returned all 88 PUBs at 4,096 shots per PUB. Across the -full 81-cell landscape, rho was `0.9046747967479675`; the embedded original 25-cell rho was -`0.9315384615384615`; and EXP-010D/EXP-011 cross-run rho was `0.9776923076923076`, with repeated- -cell mean absolute R difference `0.01871101322274423`. The frozen classification was **STRONGLY -REPLICATED**. The centre ranked `4/81`, within the frozen top-five check, its most-likely state was -`1010`, and the control-quality warning was retained. +Start with the [Build Week judging guide](docs/build-week/JUDGING-GUIDE.md), +[Codex contribution log](docs/build-week/CODEX-CONTRIBUTION-LOG.md), +[Codex and GPT-5.6 evidence](docs/build-week/CODEX-AND-GPT56-EVIDENCE.md), and +[before and after](docs/build-week/BEFORE-AND-AFTER.md). Detailed experiments and developer +commands continue below. -These results concern preservation and independent replication of a small parameter-landscape -structure. Exact classical evaluation remains authoritative. They do not demonstrate quantum -advantage, speedup, generalisation, musical truth, or commercial superiority. +### Research question -### Built with Codex and GPT-5.6 +Given a small set of synthetic symbolic melodies and interpretable pairwise similarities, can a +two-family QUBO formulation recover known tune families, and how do local QAOA-style samples +compare with exact classical optima? -Codex accelerated repository inspection, bounded implementation, tests, CI diagnosis, visual -review, experiment packaging, fail-closed hardware preparation, result regeneration, and PR -verification. Gwri retained and explicitly exercised product, scientific, hardware-authorization, -interpretation, and merge authority. GPT-5.6 remained an optional explanation layer; it did not -calculate experiments, choose hardware work, or determine claims. +```mermaid +flowchart LR + A["Folk-music problem"] --> B["Exact classical truth"] + B --> C["Bounded quantum comparison"] + C --> D["Governed hardware evidence"] + D --> E["Validated explanation"] +``` -GPT-5.6 optionally explains validated results at different learner levels; deterministic code -calculates the result. Its input is filtered, and output is schema-, grounding-, number-, and -claim-checked. It cannot alter registered values, and invalid or unavailable output fails closed to -the deterministic explanation. **The AI can explain the experiment. It cannot rewrite the -evidence.** +### Developer quick start -Evidence: [Codex contribution log](docs/build-week/CODEX-CONTRIBUTION-LOG.md), -[Codex and GPT-5.6 evidence](docs/build-week/CODEX-AND-GPT56-EVIDENCE.md), -[before and after](docs/build-week/BEFORE-AND-AFTER.md), and -[judging guide](docs/build-week/JUDGING-GUIDE.md). +```bash +python -m venv .venv +source .venv/bin/activate +python -m pip install -e ".[dev]" +qfl doctor +qfl compare --seed 42 +``` ## EXP-001: Local Qiskit Circuit Infrastructure diff --git a/apps/learning_console/app.py b/apps/learning_console/app.py index e09897c..f78fddb 100644 --- a/apps/learning_console/app.py +++ b/apps/learning_console/app.py @@ -37,30 +37,40 @@ initial_sidebar_state="collapsed", ) -st.title("Quantum Folk Lab — Learning Console") -st.caption( - "Public experimental edition. Portable Markdown lessons under `learn/`. " - "Simulator-first. No claim of quantum advantage." +st.title("Quantum Folk Lab") +st.subheader("Can a quantum method recover hidden structure in folk music?") +st.write( + "Make a prediction, reveal every possible answer, and see how a quantum method — and a real " + "IBM quantum computer — measured up against the exact truth." ) +st.caption("The exact answer is always computed first. No quantum-advantage claim is made.") registry = load_registry() experiments_tab, foundations_tab, glossary_tab = st.tabs(["Experiments", "Foundations", "Glossary"]) with experiments_tab: - exp010a_tab, exp005a_tab = st.tabs( + st.write( + "Ask a musical question, reveal the exact answer, then compare simulation and real " + "quantum hardware with that known truth." + ) + exp005a_tab, exp010a_tab = st.tabs( [ - "EXP-010A · Four-family optimisation", - "EXP-005A · Synthetic partitioning", + "Start here · Guided experiment", + "Real folk data & IBM results", ] ) - with exp010a_tab: - render_compact_experiment() with exp005a_tab: render_guided_experiment(load_guided_experiment()) + with exp010a_tab: + render_compact_experiment() with foundations_tab: st.header("Foundations") + st.write( + "New to quantum computing? Learn the few core ideas used by the experiments, one concept " + "at a time." + ) entries = registry.foundations_entries() foundation_labels = ["Bits & qubits", "Gates", "Hadamard", "Entanglement", "Optimisation"] if len(entries) != len(foundation_labels): @@ -73,6 +83,7 @@ with glossary_tab: st.header("Glossary") + st.write("Look up the technical terms used in the experiments in plain language.") query = st.text_input("Search glossary", placeholder="e.g. qubit, QAOA, shot") terms = load_glossary() for term in terms: diff --git a/apps/learning_console/renderers/compact_experiment.py b/apps/learning_console/renderers/compact_experiment.py index 375a2de..4daac21 100644 --- a/apps/learning_console/renderers/compact_experiment.py +++ b/apps/learning_console/renderers/compact_experiment.py @@ -38,10 +38,22 @@ def render_compact_experiment() -> None: st.header("EXP-010A") st.subheader("Choosing one setting from four folk-tune families") + st.markdown( + "**Can we apply the same exact-first approach to choices drawn from real public " + "folk-tune families?**" + ) + st.write( + "The lab checks all sixteen choices exactly, compares an ideal quantum circuit, and then " + "examines what happened on real IBM hardware." + ) st.markdown( "**Real public tune data · 4 binary choices · 16 combinations** \n" "**Exact classical result · Ideal simulation · IBM hardware · Frozen uniform control**" ) + st.write( + "Four real folk-tune families, two settings each: sixteen combinations, all checked " + "exactly." + ) st.markdown("## Why this experiment exists") st.write( @@ -100,6 +112,8 @@ def render_compact_experiment() -> None: ) st.markdown("## Exact classical result") + st.markdown("**Which combination is best when every possibility is checked?**") + st.write("This answer is not a prediction — the computer tried every possibility.") optimum = exact["optimum_bitstrings"][0] left, middle, right = st.columns(3) left.metric("Exact optimum", optimum) @@ -110,6 +124,7 @@ def render_compact_experiment() -> None: ) st.markdown("## Ideal quantum simulation") + st.markdown("**Does the ideal quantum circuit concentrate on the better choices?**") metrics = qaoa["ideal_metrics"] first, second, third = st.columns(3) first.metric( @@ -122,7 +137,12 @@ def render_compact_experiment() -> None: "hardware run below. No Qiskit computation runs while this tab renders." ) - st.markdown("## IBM hardware") + st.markdown("## First IBM hardware validation") + st.markdown("**Did the correct answer remain visible on real hardware?**") + st.write( + "One IBM hardware run tested whether the compact real-data result remained visible under " + "device noise." + ) hardware_qaoa = hardware["qaoa"] first, second, third = st.columns(3) first.metric("Hardware QAOA R", f"{hardware_qaoa['r']:.6f}") diff --git a/apps/learning_console/renderers/guided_experiment.py b/apps/learning_console/renderers/guided_experiment.py index bae3553..7e16035 100644 --- a/apps/learning_console/renderers/guided_experiment.py +++ b/apps/learning_console/renderers/guided_experiment.py @@ -80,6 +80,10 @@ def _render_landscape(view: GuidedExperimentView) -> None: "index layout and has no musical meaning. The two outlined cells are global optima: " "top-right is the Canonical representative; bottom-left is the Equivalent complement." ) + st.success( + "You just checked every possible answer. The outlined cells are the best groupings — " + "not a prediction, but the exact result." + ) left, middle, right = st.columns(3) left.metric("Assignments checked", str(len(landscape.entries))) middle.metric("Distinct energy levels", str(landscape.distinct_energy_levels)) @@ -99,11 +103,15 @@ def _render_landscape(view: GuidedExperimentView) -> None: def _render_registered_comparison(view: GuidedExperimentView) -> None: evidence = view.registered_qaoa - st.subheader("Part B · Compare registered QAOA evidence") + st.subheader("How did the quantum method do?") st.markdown( "**Registered evidence · Local ideal simulation · Not quantum hardware · " "No quantum advantage claimed**" ) + st.write( + "The bounded quantum method found one of the best answers far more often than random " + "guessing." + ) exact_column, qaoa_column, random_column = st.columns(3) exact_column.metric("Exact enumeration", "256 / 256 checked") exact_column.caption("Authoritative answer space — not a sampling probability.") @@ -191,7 +199,6 @@ def _render_registered_comparison(view: GuidedExperimentView) -> None: def _render_evidence_hierarchy() -> None: - st.subheader("Part C · Explain the evidence hierarchy") st.markdown( "**Exact enumeration** \n" "↓ governs \n" @@ -199,7 +206,8 @@ def _render_evidence_hierarchy() -> None: "↓ interpreted through \n" "**Validated GPT-5.6 explanation**" ) - st.info("The AI can explain the experiment. It cannot rewrite the evidence.") + st.info("GPT-5.6 may explain the validated result. It may not change the evidence.") + st.caption("The AI can explain the experiment. It cannot rewrite the evidence.") st.write( "GPT-5.6 receives validated evidence; it does not calculate the objective, choose the " "optimum, or change registered values. Schema, grounding, numerical and claim checks " @@ -214,17 +222,18 @@ def _render_evidence_hierarchy() -> None: def render_guided_experiment(view: GuidedExperimentView) -> None: result = view.result - st.header("Guided Experiment") - st.caption("A fixed synthetic music question, solved transparently from evidence to result.") - - st.subheader("1–2 · Meet the fixture and question") - st.write(result.fixture_description) - st.info( - "Can interval, contour, and rhythm similarities separate these eight synthetic tune " - "variants into two unlabeled families? Labels are hidden until evaluation." + st.header("Guided experiment") + st.markdown( + "**Eight tune variants. Two hidden families. 256 possible groupings.**\n\n" + "Look at the musical evidence if you like, then make your prediction: which tunes " + "belong together?" ) - with st.expander("3 · Inspect the musical evidence"): + with st.expander("Look at the musical evidence"): + st.write( + "This is the evidence your prediction can use. The data is synthetic teaching " + "material, not authentic cultural material." + ) pairs = result.evidence_summary["pairs"] st.dataframe(pairs, width="stretch", hide_index=True) st.caption( @@ -232,28 +241,17 @@ def render_guided_experiment(view: GuidedExperimentView) -> None: "threshold. This is not authentic cultural data." ) - with st.expander("4 · Understand the model"): - st.write( - "Each tune receives a 0 or 1. The verified QUBO rewards a coherent, balanced split; " - "0 and 1 are exchangeable family labels." - ) - st.json({"parameters": result.parameters, "QUBO summary": result.qubo_summary}) - - st.subheader("5 · The 256 Reveal") - st.write( - "Eight synthetic tune variants. Two hidden families. There are 256 possible " - "assignments. Before the system reveals the answer, which split would you expect to win?" - ) - if st.button("Reveal all 256 assignments", type="primary"): + if st.button("Reveal all 256 answers", type="primary"): st.session_state["build_week_256_revealed"] = True if not st.session_state.get("build_week_256_revealed", False): - st.caption("Reveal the complete answer space to continue the exact-first journey.") + st.caption("The answer stays hidden until you reveal it.") return - st.subheader("Part A · Complete exact answer space") + st.subheader("Every possible answer") _render_landscape(view) - st.subheader("6 · Exact result computed now") + st.subheader("The exact result") + st.write("This answer is not a prediction — the computer tried every possibility.") exact = result.exact_result left, middle, right = st.columns(3) left.metric("Minimum energy", f"{float(exact['minimum_energy']):.6f}") @@ -264,10 +262,22 @@ def render_guided_experiment(view: GuidedExperimentView) -> None: "bitwise complement denotes the same unlabeled partition." ) + st.info( + "Because the exact answer is known, every quantum result below can be checked rather " + "than taken on trust." + ) _render_registered_comparison(view) - _render_evidence_hierarchy() - st.subheader("7–8 · Optional local-Qiskit comparison") + st.subheader("How was this model built?") + with st.expander("Technical model and QUBO"): + st.write(result.fixture_description) + st.write( + "Each tune receives a 0 or 1. The verified QUBO rewards a coherent, balanced split; " + "0 and 1 are exchangeable family labels." + ) + st.json({"parameters": result.parameters, "QUBO summary": result.qubo_summary}) + + st.subheader("Optional local-Qiskit comparison") st.caption( "This live bounded quick run uses a smaller submission-safe contract. It is separate " "from the registered 4,096-shot evidence shown above." @@ -290,7 +300,8 @@ def render_guided_experiment(view: GuidedExperimentView) -> None: ) else: st.code(view.quantum.install_command) - st.subheader("9–10 · Explain this result") + st.subheader("Can AI explain the result safely?") + _render_evidence_hierarchy() label = st.selectbox("Explanation level", list(LEVEL_LABELS)) level = LEVEL_LABELS[label] explanation = view.explanation(level) @@ -306,7 +317,7 @@ def render_guided_experiment(view: GuidedExperimentView) -> None: st.caption(f"Validated grounded explanation from {generated.model}.") st.write(explanation) - st.subheader("11 · Export a reproducibility record") + st.subheader("Export a reproducibility record") first, second = st.columns(2) first.download_button( "Download validated JSON", @@ -321,6 +332,6 @@ def render_guided_experiment(view: GuidedExperimentView) -> None: "text/markdown", ) - st.subheader("12 · Limitations") + st.subheader("Limitations") for boundary in result.claims_boundary: st.write(f"- {boundary}") diff --git a/apps/learning_console/renderers/hardware_replication.py b/apps/learning_console/renderers/hardware_replication.py index a92d4d4..3071731 100644 --- a/apps/learning_console/renderers/hardware_replication.py +++ b/apps/learning_console/renderers/hardware_replication.py @@ -25,21 +25,21 @@ def render_hardware_replication(repo_root: Path) -> None: exp010d = evidence.exp010d exp011 = evidence.exp011 - st.markdown("## IBM Hardware Replication") + st.markdown("## Replicated IBM hardware landscape") st.write( - "After validating the problem exactly and testing it locally, Quantum Folk Lab ran the " - "same controlled four-qubit landscape experiment on IBM hardware. A second, denser run " - "then tested whether the observed structure was reproducible rather than a one-off " - "calibration result." + "Two governed hardware runs tested whether the map of circuit settings, called the " + "parameter landscape, could be reproduced." ) st.markdown("### EXP-010D — Controlled hardware landscape") + st.markdown("**Did real hardware preserve which circuit settings should perform better?**") st.caption( - f"{exp010d.backend} · {exp010d.unique_cells} unique cells · " - f"{exp010d.pub_count} PUBs · {exp010d.shots_per_pub:,} shots per PUB" + f"{exp010d.backend} · {exp010d.unique_cells} unique settings · " + f"{exp010d.pub_count} circuit evaluations (PUBs) · " + f"{exp010d.shots_per_pub:,} measurements (shots) each" ) first, second, third, fourth = st.columns(4) - first.metric("Ideal/hardware rho", f"{exp010d.spearman_rho:.4f}") + first.metric("Ordering agreement (rho)", f"{exp010d.spearman_rho:.4f}") second.metric("Classification", exp010d.classification) third.metric("Centre rank", str(exp010d.centre_rank)) fourth.metric("Centre most-likely state", exp010d.centre_most_likely_state) @@ -49,15 +49,60 @@ def render_hardware_replication(repo_root: Path) -> None: ) st.warning(CONTROL_WARNING) - st.markdown("### EXP-011 — Dense independent replication") + st.markdown("#### Did real hardware preserve the predicted landscape?") + ideal_hardware_rows = [ + { + "cell": point.cell_id, + "Ideal value": point.ideal_r, + "Hardware value": point.hardware_r, + } + for point in evidence.ideal_hardware_points + ] + st.vega_lite_chart( + ideal_hardware_rows, + { + "height": 260, + "mark": {"type": "point", "filled": True, "size": 80}, + "encoding": { + "x": { + "field": "Ideal value", + "type": "quantitative", + "scale": {"zero": False}, + "axis": {"title": "Ideal relative improvement (R)"}, + }, + "y": { + "field": "Hardware value", + "type": "quantitative", + "scale": {"zero": False}, + "axis": {"title": "Measured hardware improvement (R)"}, + }, + "tooltip": [ + {"field": "cell", "type": "nominal", "title": "Cell"}, + {"field": "Ideal value", "type": "quantitative", "format": ".4f"}, + {"field": "Hardware value", "type": "quantitative", "format": ".4f"}, + ], + }, + }, + width="stretch", + ) + st.caption( + f"Across 25 governed cells, rho was {exp010d.spearman_rho:.4f}. Points following the " + "same upward pattern mean theory and hardware broadly agreed about which settings were " + "better. This is agreement in ordering, not evidence of speedup, scale or quantum " + "advantage." + ) + + st.markdown("### EXP-011 — Independent dense replication") + st.markdown("**Did a second, denser run reproduce the same pattern?**") st.caption( - f"{exp011.backend} · {exp011.unique_cells} unique cells · {exp011.pub_count} PUBs · " - f"{exp011.shots_per_pub:,} shots per PUB" + f"{exp011.backend} · {exp011.unique_cells} unique settings · " + f"{exp011.pub_count} circuit evaluations (PUBs) · " + f"{exp011.shots_per_pub:,} measurements (shots) each" ) first, second, third = st.columns(3) - first.metric("Full-grid ideal/hardware rho", f"{exp011.spearman_rho:.4f}") + first.metric("Full-grid ordering agreement (rho)", f"{exp011.spearman_rho:.4f}") second.metric("Embedded 25-cell rho", f"{exp011.embedded_25_rho:.4f}") - third.metric("Cross-run rho", f"{exp011.cross_run_rho:.4f}") + third.metric("Cross-run agreement (rho)", f"{exp011.cross_run_rho:.4f}") first, second, third = st.columns(3) first.metric( "Repeated-cell mean absolute difference", f"{exp011.cross_run_mean_absolute_difference:.4f}" @@ -71,6 +116,48 @@ def render_hardware_replication(repo_root: Path) -> None: ) st.warning(CONTROL_WARNING) + st.markdown("#### Did the hardware pattern reproduce?") + cross_run_rows = [ + { + "cell": point.cell_id, + "EXP-010D result": point.exp010d_r, + "EXP-011 result": point.exp011_r, + } + for point in evidence.cross_run_points + ] + st.vega_lite_chart( + cross_run_rows, + { + "height": 260, + "mark": {"type": "point", "filled": True, "size": 80}, + "encoding": { + "x": { + "field": "EXP-010D result", + "type": "quantitative", + "scale": {"zero": False}, + "axis": {"title": "EXP-010D measured improvement (R)"}, + }, + "y": { + "field": "EXP-011 result", + "type": "quantitative", + "scale": {"zero": False}, + "axis": {"title": "EXP-011 measured improvement (R)"}, + }, + "tooltip": [ + {"field": "cell", "type": "nominal", "title": "Shared cell"}, + {"field": "EXP-010D result", "type": "quantitative", "format": ".4f"}, + {"field": "EXP-011 result", "type": "quantitative", "format": ".4f"}, + ], + }, + }, + width="stretch", + ) + st.caption( + f"Across 25 shared governed cells, cross-run rho was {exp011.cross_run_rho:.4f}. The " + "second hardware run closely reproduced the first. Replication strengthens confidence " + "in this small experiment; it does not establish general performance." + ) + st.markdown("### Comparison") st.dataframe( [ diff --git a/apps/learning_console/renderers/lesson_renderer.py b/apps/learning_console/renderers/lesson_renderer.py index 7c3fe09..504258a 100644 --- a/apps/learning_console/renderers/lesson_renderer.py +++ b/apps/learning_console/renderers/lesson_renderer.py @@ -26,6 +26,8 @@ def render_lesson(lesson: LessonDocument, registry: LessonRegistry) -> None: st.subheader(lesson.metadata.title) st.caption(f"Route `{lesson.metadata.route}` · content v{lesson.metadata.version}") + if lesson.metadata.learning_objectives: + st.write(lesson.metadata.learning_objectives[0]) st.markdown(semantic_marker_html(lesson, renderer="streamlit"), unsafe_allow_html=True) if lesson.metadata.learning_objectives: @@ -46,8 +48,9 @@ def render_lesson(lesson: LessonDocument, registry: LessonRegistry) -> None: elif isinstance(block, InteractionDirective): render_interaction(block.interaction_id, block.params) elif isinstance(block, DisclosureDirective): - with st.expander(block.label): - st.caption("Optional detail — keep plain language first.") + # Disclosure directives currently carry metadata but no associated body. + # Rendering nothing is safer than presenting an empty interactive control. + pass elif isinstance(block, GlossaryDirective): st.info("Open the Glossary tab for portable definitions.") diff --git a/docs/build-week/DEMO-SCRIPT.md b/docs/build-week/DEMO-SCRIPT.md index 6123b3a..50b58f4 100644 --- a/docs/build-week/DEMO-SCRIPT.md +++ b/docs/build-week/DEMO-SCRIPT.md @@ -12,7 +12,7 @@ examples where the truth can be checked before a quantum or AI result is interpr ## 0:15–0:45 — working learner journey -**Screen:** **Guided Experiment**; click **Reveal all 256 assignments**. +**Screen:** **Start here · Guided experiment**; click **Reveal all 256 answers**. **Narration:** “This synthetic teaching fixture has 256 possible assignments. The app evaluates every one, reveals both complement-equivalent optima, and makes the exact classical truth visible @@ -28,9 +28,9 @@ optimum-class baseline. This is governed simulator evidence—not hardware or ad ## 1:05–1:35 — real data and IBM hardware -**Screen:** briefly show the current application evidence view, then one prepared public-safe frame -from the EXP-010D and EXP-011 result reports. Do not imply a dedicated hardware panel exists in the -app. +**Screen:** open **Real folk data & IBM results**. Briefly show **First IBM hardware +validation**, then the governed **Replicated IBM hardware landscape** panel and its two scatter +plots. **Narration:** “A separate governed programme progressed from licence-checked folk-derived data to a compact four-qubit problem. On IBM hardware, EXP-010D produced ideal-versus-hardware rho 0.96. @@ -65,5 +65,5 @@ release.” - Use public repository files and prepared public-safe frames only. - Do not expose credentials, account information, local paths, the private Session ID, or raw intent/receipt files. -- Do not claim audio playback, authentic-notation analysis in the guided fixture, an in-app - EXP-010D/011 panel, or actual QPU usage from a preflight estimate. +- Do not claim audio playback, authentic-notation analysis in the guided fixture, live IBM access + from the read-only EXP-010D/EXP-011 panel, or actual QPU usage from a preflight estimate. diff --git a/docs/build-week/JUDGING-GUIDE.md b/docs/build-week/JUDGING-GUIDE.md index 9769a03..c6884ed 100644 --- a/docs/build-week/JUDGING-GUIDE.md +++ b/docs/build-week/JUDGING-GUIDE.md @@ -20,8 +20,8 @@ python -m pip install -e ".[learning]" streamlit run apps/learning_console/app.py ``` -Open the displayed URL. In **Experiments**, follow **Guided Experiment** and click **Reveal all 256 -assignments**. Then inspect **Foundations** and **Glossary**. The exact path works without Qiskit, +Open the displayed URL. In **Experiments**, follow **Start here · Guided experiment** and click +**Reveal all 256 answers**. Then inspect **Foundations** and **Glossary**. The exact path works without Qiskit, IBM credentials, an OpenAI API key, or network access. ## Three strongest proof points @@ -40,14 +40,15 @@ Public reports: - [`experiments/EXP-010D-hardware-parameter-landscape-run/RESULT-REPORT.md`](../../experiments/EXP-010D-hardware-parameter-landscape-run/RESULT-REPORT.md) - [`experiments/EXP-011-dense-hardware-landscape-run/RESULT-REPORT.md`](../../experiments/EXP-011-dense-hardware-landscape-run/RESULT-REPORT.md) -The current app does not yet have a dedicated EXP-010D/011 panel; use these public reports or a -prepared public-safe frame for hardware review. +The merged Learning Console includes a governed, read-only EXP-010D/EXP-011 hardware replication +panel. Its two scatter plots and headline statistics load directly from the public evidence above; +no IBM access is performed while the app renders. ## Judging criteria | Criterion | Evidence | | --- | --- | -| Education | progressive Guided Experiment, exact 256 Reveal, portable Foundations and Glossary | +| Education | progressive Guided experiment, exact 256 Reveal, portable Foundations and Glossary | | Technological Implementation | deterministic contracts, QUBO/Ising verification, bounded Qiskit, fail-closed IBM intent/receipt and tests | | Design | tabbed learner path, capability gating, accessible exact landscape and reproducibility exports | | Potential Impact | teaches scientific restraint while connecting quantum optimisation with culturally meaningful subject matter | diff --git a/docs/build-week/SUBMISSION-CHECKLIST.md b/docs/build-week/SUBMISSION-CHECKLIST.md index 560d08e..b555b2d 100644 --- a/docs/build-week/SUBMISSION-CHECKLIST.md +++ b/docs/build-week/SUBMISSION-CHECKLIST.md @@ -24,7 +24,7 @@ - [ ] Capture public-safe Guided Experiment, 256 Reveal, exact result, Foundations, Glossary, and evidence-report screenshots. -- [ ] Do not claim or capture a dedicated EXP-010D/011 app panel unless one is later merged. +- [x] Present the merged governed, read-only EXP-010D/EXP-011 app panel without implying live IBM access. - [ ] Check screenshots for credentials, usernames, personal paths, emails, account data, and private windows. - [ ] Create and upload the submission thumbnail. diff --git a/src/quantum_folk_lab/hardware_replication_evidence.py b/src/quantum_folk_lab/hardware_replication_evidence.py index 490a101..c8a593a 100644 --- a/src/quantum_folk_lab/hardware_replication_evidence.py +++ b/src/quantum_folk_lab/hardware_replication_evidence.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import re from dataclasses import dataclass from pathlib import Path from typing import Any @@ -26,12 +27,32 @@ class HardwareLandscapeEvidence: cross_run_mean_absolute_difference: float | None = None +@dataclass(frozen=True) +class IdealHardwarePoint: + """One governed EXP-010D cell for the ideal/hardware chart.""" + + cell_id: str + ideal_r: float + hardware_r: float + + +@dataclass(frozen=True) +class CrossRunPoint: + """One governed cell shared by EXP-010D and EXP-011.""" + + cell_id: str + exp010d_r: float + exp011_r: float + + @dataclass(frozen=True) class HardwareReplicationEvidence: """The two governed layers shown together in the Learning Console.""" exp010d: HardwareLandscapeEvidence exp011: HardwareLandscapeEvidence + ideal_hardware_points: tuple[IdealHardwarePoint, ...] + cross_run_points: tuple[CrossRunPoint, ...] def _object(path: Path) -> dict[str, Any]: @@ -79,6 +100,26 @@ def _assert_equal(actual: object, expected: object, field: str) -> None: raise ValueError(f"governed evidence mismatch: {field}") +def _cell_map(analysis: dict[str, Any], experiment: str) -> dict[str, dict[str, Any]]: + cells: dict[str, dict[str, Any]] = {} + for index, value in enumerate(_list(analysis.get("cells"), f"{experiment} cells")): + cell = _mapping(value, f"{experiment} cell {index}") + point_id = _string(cell.get("point_id"), f"{experiment} point_id") + if point_id in cells: + raise ValueError(f"governed evidence contains duplicate cell: {point_id}") + cells[point_id] = cell + return cells + + +def _exp011_shared_id(exp010d_id: str) -> str: + match = re.fullmatch(r"g([+-][0-2])_b([+-][0-2])", exp010d_id) + if match is None: + raise ValueError(f"governed EXP-010D cell id is malformed: {exp010d_id}") + gamma_index = 2 * (int(match.group(1)) + 2) + beta_index = 2 * (int(match.group(2)) + 2) + return f"g{gamma_index}_b{beta_index}" + + def _load_landscape( root: Path, *, @@ -131,6 +172,8 @@ def load_hardware_replication_evidence(repo_root: Path) -> HardwareReplicationEv d_secondary = _mapping(d_analysis["secondary"], "EXP-010D secondary") e_primary = _mapping(e_analysis["primary"], "EXP-011 primary") e_secondary = _mapping(e_analysis["secondary"], "EXP-011 secondary") + d_cells = _cell_map(d_analysis, "EXP-010D") + e_cells = _cell_map(e_analysis, "EXP-011") exp010d = HardwareLandscapeEvidence( experiment_id=_string(d_analysis["experiment_id"], "EXP-010D id"), @@ -166,4 +209,30 @@ def load_hardware_replication_evidence(repo_root: Path) -> HardwareReplicationEv "EXP-011 cross-run difference", ), ) - return HardwareReplicationEvidence(exp010d=exp010d, exp011=exp011) + ideal_hardware_points = tuple( + IdealHardwarePoint( + cell_id=point_id, + ideal_r=_number(cell.get("ideal_r"), f"EXP-010D {point_id} ideal_r"), + hardware_r=_number(cell.get("r"), f"EXP-010D {point_id} r"), + ) + for point_id, cell in sorted(d_cells.items()) + ) + cross_run_points = tuple( + CrossRunPoint( + cell_id=point_id, + exp010d_r=_number(cell.get("r"), f"EXP-010D {point_id} r"), + exp011_r=_number( + e_cells.get(_exp011_shared_id(point_id), {}).get("r"), + f"EXP-011 match for {point_id}", + ), + ) + for point_id, cell in sorted(d_cells.items()) + ) + _assert_equal(len(ideal_hardware_points), 25, "EXP-010D chart point count") + _assert_equal(len(cross_run_points), 25, "cross-run chart point count") + return HardwareReplicationEvidence( + exp010d=exp010d, + exp011=exp011, + ideal_hardware_points=ideal_hardware_points, + cross_run_points=cross_run_points, + ) diff --git a/tests/learning/test_build_week_console.py b/tests/learning/test_build_week_console.py index 840415e..76cda75 100644 --- a/tests/learning/test_build_week_console.py +++ b/tests/learning/test_build_week_console.py @@ -7,12 +7,15 @@ import pytest from streamlit.testing.v1 import AppTest +from quantum_folk_lab.learning.registry import load_registry + def test_navigation_hierarchy_and_no_secret_display() -> None: app = Path("apps/learning_console/app.py").read_text(encoding="utf-8") assert '["Experiments", "Foundations", "Glossary"]' in app - assert "EXP-010A · Four-family optimisation" in app - assert "EXP-005A · Synthetic partitioning" in app + assert "Start here · Guided experiment" in app + assert "Real folk data & IBM results" in app + assert "Experiment archive" not in app assert "st.sidebar.radio" not in app assert "st.sidebar.selectbox" not in app assert 'st.selectbox("Lesson"' not in app @@ -20,7 +23,7 @@ def test_navigation_hierarchy_and_no_secret_display() -> None: encoding="utf-8" ) assert "OPENAI_" + "API" + "_KEY" not in renderer - assert "Guided Experiment" in renderer + assert "Guided experiment" in renderer def test_service_loads_without_streamlit_qiskit_or_openai_imports() -> None: @@ -52,12 +55,17 @@ def test_guided_experiment_256_reveal_journey_requires_no_openai_key( "Glossary", ] assert labels[1:3] == [ - "EXP-010A · Four-family optimisation", - "EXP-005A · Synthetic partitioning", + "Start here · Guided experiment", + "Real folk data & IBM results", ] - assert app.tabs[1].label.startswith("EXP-010A") - assert any(element.value == "5 · The 256 Reveal" for element in app.subheader) - reveal = next(button for button in app.button if button.label == "Reveal all 256 assignments") + assert app.tabs[1].label.startswith("Start here") + assert any( + element.value == "The answer stays hidden until you reveal it." for element in app.caption + ) + assert not any(element.value == "Every possible answer" for element in app.subheader) + assert not any(metric.label == "Minimum energy" for metric in app.metric) + assert not any(expander.label == "Technical model and QUBO" for expander in app.expander) + reveal = next(button for button in app.button if button.label == "Reveal all 256 answers") reveal.click().run(timeout=30) @@ -76,7 +84,11 @@ def test_guided_experiment_256_reveal_journey_requires_no_openai_key( for element in collection ) for expected in ( - "Part A · Complete exact answer space", + "Every possible answer", + "The exact result", + "How did the quantum method do?", + "How was this model built?", + "Can AI explain the result safely?", "256", "00001111", "11110000", @@ -88,9 +100,16 @@ def test_guided_experiment_256_reveal_journey_requires_no_openai_key( "Local ideal simulation", "Not quantum hardware", "No quantum advantage claimed", + "Eight tune variants. Two hidden families. 256 possible groupings.", + "You just checked every possible answer", + "This answer is not a prediction", + "found one of the best answers far more often than random guessing", + "every quantum result below can be checked rather than taken on trust", + "GPT-5.6 may explain the validated result", ): assert expected in rendered assert len(app.dataframe) >= 1 + assert any(expander.label == "Technical model and QUBO" for expander in app.expander) def test_foundation_tabs_follow_registry_and_render_without_execution() -> None: @@ -108,6 +127,28 @@ def test_foundation_tabs_follow_registry_and_render_without_execution() -> None: assert any(text.value == "Glossary" for text in app.header) assert any(widget.label == "Search glossary" for widget in app.text_input) assert "build_week_quantum" not in app.session_state.filtered_state + rendered = "\n".join(str(item.value) for item in (*app.markdown, *app.caption)) + for expected in ( + "New to quantum computing? Learn the few core ideas used by the experiments", + "Look up the technical terms used in the experiments in plain language", + ): + assert expected in rendered + objectives = { + load_registry().load_document(entry.id).metadata.learning_objectives[0] + for entry in load_registry().foundations_entries() + } + assert all(objective in rendered for objective in objectives) + disclosure_labels = { + "Show the notation", + "Why measurement matters", + "Why phase matters", + "Why correlation is not communication", + "Why exact-first matters", + } + assert not disclosure_labels.intersection(expander.label for expander in app.expander) + assert not any( + caption.value == "Optional detail — keep plain language first." for caption in app.caption + ) def test_optional_qiskit_stays_button_gated() -> None: @@ -139,7 +180,7 @@ def test_compact_experiment_presents_four_frozen_evidence_layers() -> None: for expected in ( "Exact classical result", "Ideal quantum simulation", - "IBM hardware", + "First IBM hardware validation", "Frozen uniform control", "ibm_fez", "4,096 QAOA shots", @@ -147,6 +188,15 @@ def test_compact_experiment_presents_four_frozen_evidence_layers() -> None: "not quantum advantage", "a speedup", "Exact enumeration remains the scientific authority", + "Four real folk-tune families, two settings each", + "Can we apply the same exact-first approach to choices drawn from real public " + "folk-tune families?", + "Which combination is best when every possibility is checked?", + "Does the ideal quantum circuit concentrate on the better choices?", + "Did the correct answer remain visible on real hardware?", + "Replicated IBM hardware landscape", + "Did real hardware preserve which circuit settings should perform better?", + "Did a second, denser run reproduce the same pattern?", ): assert expected in rendered metrics = {metric.label: metric.value for metric in app.metric} @@ -158,6 +208,50 @@ def test_compact_experiment_presents_four_frozen_evidence_layers() -> None: assert metrics["QAOA minus control"] == "0.536242" +def test_human_question_opening_and_top_level_explainers_render() -> None: + app = AppTest.from_file("apps/learning_console/app.py") + app.run(timeout=30) + assert not app.exception + rendered = "\n".join( + str(item.value) + for item in (*app.title, *app.header, *app.subheader, *app.markdown, *app.caption) + ) + for expected in ( + "Quantum Folk Lab", + "Can a quantum method recover hidden structure in folk music?", + "Make a prediction, reveal every possible answer", + "The exact answer is always computed first", + "No quantum-advantage claim is made", + "Ask a musical question, reveal the exact answer", + ): + assert expected in rendered + source = Path("apps/learning_console/app.py").read_text(encoding="utf-8") + for removed in ( + "1 · Reveal the exact answer", + "2 · Compare the quantum method", + "3 · See what happened on real hardware", + "Start with _Start here", + ): + assert removed not in source + + +def test_stale_hardware_panel_wording_is_absent() -> None: + paths = ( + Path("README.md"), + Path("docs/build-week/JUDGING-GUIDE.md"), + Path("docs/build-week/DEMO-SCRIPT.md"), + Path("docs/build-week/SUBMISSION-CHECKLIST.md"), + ) + text = "\n".join(path.read_text(encoding="utf-8").lower() for path in paths) + for stale in ( + "does not yet contain a dedicated exp-010d/011 result panel", + "does not yet have a dedicated exp-010d/011 panel", + "do not imply a dedicated hardware panel exists", + "do not claim or capture a dedicated exp-010d/011 app panel", + ): + assert stale not in text + + def test_renderer_keeps_registered_and_quick_run_claims_separate() -> None: renderer = Path("apps/learning_console/renderers/guided_experiment.py").read_text( encoding="utf-8" diff --git a/tests/learning/test_hardware_replication_evidence.py b/tests/learning/test_hardware_replication_evidence.py index 65e4b14..2ca749c 100644 --- a/tests/learning/test_hardware_replication_evidence.py +++ b/tests/learning/test_hardware_replication_evidence.py @@ -35,6 +35,40 @@ def test_governed_hardware_evidence_loads_exact_headline_values() -> None: assert exp011.cross_run_mean_absolute_difference == 0.01871101322274423 assert exp011.classification == "STRONGLY REPLICATED" assert (exp011.centre_rank, exp011.centre_most_likely_state) == (4, "1010") + assert len(evidence.ideal_hardware_points) == 25 + assert len(evidence.cross_run_points) == 25 + assert {point.cell_id for point in evidence.ideal_hardware_points} == { + point.cell_id for point in evidence.cross_run_points + } + centre = next(point for point in evidence.ideal_hardware_points if point.cell_id == "g+0_b+0") + assert centre.ideal_r == 0.5999118429921135 + assert centre.hardware_r == 0.5184178203750598 + repeated_centre = next( + point for point in evidence.cross_run_points if point.cell_id == "g+0_b+0" + ) + assert repeated_centre.exp010d_r == 0.5184178203750598 + assert repeated_centre.exp011_r == 0.4962957979192144 + + d_payload = json.loads( + Path( + "experiments/EXP-010D-hardware-parameter-landscape-run/hardware-analysis.json" + ).read_text(encoding="utf-8") + ) + e_payload = json.loads( + Path("experiments/EXP-011-dense-hardware-landscape-run/hardware-analysis.json").read_text( + encoding="utf-8" + ) + ) + d_cells = {cell["point_id"]: cell for cell in d_payload["cells"]} + e_cells = {cell["point_id"]: cell for cell in e_payload["cells"]} + assert { + (point.cell_id, point.ideal_r, point.hardware_r) for point in evidence.ideal_hardware_points + } == {(cell_id, cell["ideal_r"], cell["r"]) for cell_id, cell in d_cells.items()} + for point in evidence.cross_run_points: + gamma, beta = point.cell_id.removeprefix("g").split("_b") + exp011_id = f"g{2 * (int(gamma) + 2)}_b{2 * (int(beta) + 2)}" + assert point.exp010d_r == d_cells[point.cell_id]["r"] + assert point.exp011_r == e_cells[exp011_id]["r"] def test_missing_or_malformed_governed_evidence_fails_closed(tmp_path: Path) -> None: @@ -53,6 +87,34 @@ def test_missing_or_malformed_governed_evidence_fails_closed(tmp_path: Path) -> load_hardware_replication_evidence(tmp_path) +def test_malformed_chart_cell_fails_closed_and_sources_are_governed(tmp_path: Path) -> None: + experiments = tmp_path / "experiments" + for directory in ( + "EXP-010D-hardware-parameter-landscape-run", + "EXP-011-dense-hardware-landscape-run", + ): + source = Path("experiments") / directory + target = experiments / directory + target.mkdir(parents=True) + for name in ("hardware-analysis.json", "execution-manifest.json"): + shutil.copyfile(source / name, target / name) + + analysis_path = ( + experiments / "EXP-010D-hardware-parameter-landscape-run" / "hardware-analysis.json" + ) + payload = json.loads(analysis_path.read_text(encoding="utf-8")) + del payload["cells"][0]["ideal_r"] + analysis_path.write_text(json.dumps(payload), encoding="utf-8") + with pytest.raises(ValueError, match="ideal_r"): + load_hardware_replication_evidence(tmp_path) + + loader = Path("src/quantum_folk_lab/hardware_replication_evidence.py").read_text( + encoding="utf-8" + ) + assert "EXP-010D-hardware-parameter-landscape-run" in loader + assert "EXP-011-dense-hardware-landscape-run" in loader + + def test_learning_console_renders_replication_without_credentials( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -75,23 +137,32 @@ def test_learning_console_renders_replication_without_credentials( for element in collection ) for expected in ( - "IBM Hardware Replication", + "Replicated IBM hardware landscape", "EXP-010D — Controlled hardware landscape", - "EXP-011 — Dense independent replication", + "EXP-011 — Independent dense replication", "LANDSCAPE SUPPORTED", "STRONGLY REPLICATED", "Control warning retained", "Exact classical enumeration remains authoritative", "do not demonstrate quantum advantage or speedup", "No IBM access is needed", + "Did real hardware preserve the predicted landscape?", + "Did the hardware pattern reproduce?", + "map of circuit settings, called the parameter landscape", + "circuit evaluations (PUBs)", + "measurements (shots) each", ): assert expected in rendered metrics = {metric.label: metric.value for metric in app.metric} - assert metrics["Ideal/hardware rho"] == "0.9600" - assert metrics["Full-grid ideal/hardware rho"] == "0.9047" + assert metrics["Ordering agreement (rho)"] == "0.9600" + assert metrics["Full-grid ordering agreement (rho)"] == "0.9047" assert metrics["Embedded 25-cell rho"] == "0.9315" - assert metrics["Cross-run rho"] == "0.9777" + assert metrics["Cross-run agreement (rho)"] == "0.9777" assert metrics["Repeated-cell mean absolute difference"] == "0.0187" + renderer = Path("apps/learning_console/renderers/hardware_replication.py").read_text( + encoding="utf-8" + ) + assert renderer.count("st.vega_lite_chart(") == 2 def test_renderer_contains_no_unsupported_claim_or_service_access() -> None: