Resonance Curation
Compare palette curation candidates by quantum fidelity so artists pick the closest match in one tap.
SWAP testยท similarity
Section ยท Quantum
full primer โThe hook.
Two palette curation candidates go into a quantum similarity check; the app shows painters a single 0โ100% match score so they can pick the closest one in a tap.
Why this primitiveSWAP test is the right primitive here because palette curation reduces to a similarity problem; the kernel returns a result you can drop straight into the UI.
Kernel
a 2-register SWAP-test kernel returning fidelity in [0,1] between two amplitude-encoded vectors
Drives the UI as
a similarity score (0-100%) and a 'closest match' surface
Appendix A
The mega-prompt.
This prompt is engineered to ship in a single Lovable build. Real Quantinuum Guppy/Selene circuit runs in the Linux sandbox at build time and the results are baked in as JSON. read the build strategy โ
~14.5 KB294 lines1 msg ยท ~5 credits
Build "Resonance Curation"โa one-page quantum palette-fidelity app for artists. Ship in ONE Lovable message (~5 credits).
CONSTRAINTS:
- Free tier only. No auth, no Cloud, no database. Static app.
- One workspace page + bottom "About the quantum" strip.
- All quantum runs AT BUILD TIME in sandbox; zero runtime Python.
QUANTUM (Guppy + Selene, build-time only):
1. pip install guppylang selene-sim in sandbox.
2. Create quantum/kernel.py: a real .py file with a @guppy-decorated 2-register SWAP-test kernel. Input: two amplitude-encoded vectors (candidate palette, reference palette). Output: fidelity โ [0,1]. Use selene_sim.build() to compile.
3. Create quantum/run.py: import kernel, run 256 shots on Selene emulator over 10 candidate ร 10 reference = 100 grid pairs. Write src/data/quantum-results.json: array of {input: {candidate_idx, reference_idx}, output: {fidelity: <0-1>}} records.
4. Execute quantum/run.py once during build. Commit quantum-results.json. No Python at runtime.
FRONTEND (TanStack Start, route /):
- Import quantum-results.json statically.
- Left panel (Card): candidate picker (10 swatches, Slider or grid). Center (Card): live palette preview from selected record. Right (Card): fidelity as % (0โ100), Badge showing "closest match" (max fidelity in reference set).
- Bottom strip: "Quantum trace" disclosure (Tabs or Collapsible). Show: raw fidelity value + "View Guppy source" toggle that inlines quantum/kernel.py code inline so judges verify real SWAP test on Selene.
- Design: dark background, one accent color (deep purple or teal for art mood), one font family (Geist or Inter), tactile hover/click feedback on palette swatches.
BUILD ORDER (prevent scope creep):
1. Set up quantum/kernel.py + quantum/run.py; test locally, commit quantum-results.json.
2. Create src/data/quantum-results.json import in frontend.
3. Build workspace: Card layout (left picker, center preview, right score) using shadcn components (Card, Slider, Badge, Tabs).
4. Wire picker to JSON; update preview + fidelity on selection.
5. Add "Quantum trace" disclosure with kernel source inline.
6. Style dark theme + accent color + micro-interactions.
7. Deploy.
DELIVERABLE: Every fidelity on screen traces to real Selene output from quantum/kernel.py. Ship in one message.
--- QUANTINUUM APPENDIX (self-contained reference โ paste verbatim) ---
[1] INSTALL (Lovable Linux sandbox):
python -m pip install --no-cache-dir guppylang selene-sim
[2] IMPORTS (use exactly these โ no other quantum libs):
# quantum/kernel.py and quantum/run.py top
from guppylang import guppy
from guppylang.std.builtins import result
from guppylang.std.quantum import qubit, h, cx, rx, ry, rz, measure, discard, t as tgate, tdg
from guppylang.std.angles import angle, pi
from selene_sim import build, Quest
import math, json, sys, tempfile, importlib.util, uuid
from pathlib import Path
[3] HARD RULES (violating any breaks the build):
- @guppy reads source via inspect.getsource โ kernels MUST live in a real .py file on disk. No exec(), no REPL strings, no inline templates.
- Allowed gate set ONLY: h, rx, ry, rz, cx, tgate, tdg. There is NO native ccx/toffoli, cswap, cphase, or crz โ decompose using the snippets in [7].
- Qubit ownership: a qubit passed to a function is moved. You MUST measure() or discard() every qubit exactly once; never reuse after measure.
- Angle hygiene before baking a float into generated source:
theta = ((theta + math.pi) % (2.0 * math.pi)) - math.pi
and write it with repr: f"... {theta!r} ..." (str(float) can truncate).
[4] SELENE SHOT LOOP (canonical):
compiled = my_kernel.compile()
runner = build(compiled)
shots = []
for shot in runner.run_shots(Quest(), n_qubits=N, n_shots=S):
shots.append({str(lbl): int(v) for lbl, v in shot})
# N = MAX number of qubits simultaneously LIVE in the kernel.
# measure(q) releases the slot, so one ancilla reused across k windows still counts as 1.
[5] DRIVER PATTERN โ sweep a kernel over many inputs (closures do NOT work):
ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT))
def run_one(params: dict, shots: int = 256):
# Bake params as literals into a fresh .py file that imports your kernel helpers.
src = (
"from quantum.kernel import guppy, my_helper\n"
"@guppy\n"
"def program() -> None:\n"
f" my_helper({params['a']!r}, {params['b']!r})\n"
)
tmp = Path(tempfile.gettempdir()) / "qprogs"; tmp.mkdir(exist_ok=True)
name = f"prog_{uuid.uuid4().hex[:8]}"
path = tmp / f"{name}.py"; path.write_text(src)
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod # register BEFORE exec_module
spec.loader.exec_module(mod)
runner = build(mod.program.compile())
out = []
for shot in runner.run_shots(Quest(), n_qubits=5, n_shots=shots):
out.append({str(l): int(v) for l, v in shot})
return out
[6] PER-QUBIT INTEGER DECODE (host-side):
# kernel emits: for j in range(n): result(f"x{j}", measure(q[j]))
def decode(rec, n):
x = 0
for j in range(n): x |= (rec.get(f"x{j}", 0) & 1) << j
return x
[7] DECOMPOSITION LIBRARY (copy verbatim into quantum/kernel.py):
# ---- Toffoli (CCX) from H, CX, T, Tdg โ 6-T standard decomposition ----
@guppy
def toffoli(c1: qubit, c2: qubit, tgt: qubit) -> None:
h(tgt)
cx(c2, tgt); tdg(tgt)
cx(c1, tgt); tgate(tgt)
cx(c2, tgt); tdg(tgt)
cx(c1, tgt); tgate(c2); tgate(tgt)
h(tgt)
cx(c1, c2); tgate(c1); tdg(c2)
cx(c1, c2)
# ---- CSWAP (Fredkin) from CX + Toffoli ----
@guppy
def cswap(c: qubit, a: qubit, b: qubit) -> None:
cx(b, a)
toffoli(c, a, b)
cx(b, a)
# ---- Controlled phase exp(i*theta) on |11> from rz + cx ----
@guppy
def cphase(c: qubit, d: qubit, theta: float) -> None:
rz(d, angle(theta / 2.0))
cx(c, d)
rz(d, angle(-theta / 2.0))
cx(c, d)
# ---- Amplitude-encoded feature state (3 floats in [0,1] โ 2-qubit state) ----
@guppy
def prep_features(q0: qubit, q1: qubit, a: float, b: float, c: float) -> None:
ry(q0, angle(a))
ry(q1, angle(b))
cx(q0, q1)
rz(q1, angle(c))
# ---- SWAP test kernel; HOST inverts: F = clamp(2*P(anc=0) - 1, 0, 1) ----
@guppy
def swap_test(ai: float, bi: float, ci: float,
aj: float, bj: float, cj: float) -> None:
anc = qubit()
pi0 = qubit(); pi1 = qubit()
pj0 = qubit(); pj1 = qubit()
prep_features(pi0, pi1, ai, bi, ci)
prep_features(pj0, pj1, aj, bj, cj)
h(anc)
cswap(anc, pi0, pj0)
cswap(anc, pi1, pj1)
h(anc)
result("anc", measure(anc))
discard(pi0); discard(pi1); discard(pj0); discard(pj1)
# n_qubits = 5 for swap_test above.
[8] CLASSICAL CROSS-CHECK (NumPy reference โ commit alongside quantum result):
import numpy as np
I = np.eye(2); X = np.array([[0,1],[1,0]])
def RY(t): c,s = math.cos(t/2), math.sin(t/2); return np.array([[c,-s],[s,c]])
def RZ(t): return np.array([[np.exp(-1j*t/2),0],[0,np.exp(1j*t/2)]])
CX = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]])
def prep_psi(a,b,c):
s = np.kron(RY(a)@np.array([1,0]), RY(b)@np.array([1,0]))
s = CX @ s
s = np.kron(I, RZ(c)) @ s
return s
def classical_fidelity(p, q):
return float(abs(np.vdot(prep_psi(*p), prep_psi(*q)))**2)
# Commit BOTH quantum and classical values per record:
# {"input": [...], "quantum": 0.873, "classical": 0.881, "shots": 256}
[9] FRONTEND HANDOFF (TanStack Start):
# quantum/run.py last step:
Path("src/data/quantum-results.json").write_text(json.dumps({
"records": records, # list of {input, quantum, classical, ...}
"circuit": {"n_qubits": 5, "shots": 256},
"kernel_source": Path("quantum/kernel.py").read_text(), # show on /code tab
}, indent=2))
# In React: import data from "@/data/quantum-results.json" (resolveJsonModule:true).
# Render data.records; render data.kernel_source in a <pre> inside the Quantum trace tab.
[10] BUILD ORDER (do not deviate โ keeps you under 5 credits):
1. Write quantum/kernel.py with the helpers from [7] you actually need.
2. Write quantum/run.py: driver pattern from [5], Selene loop from [4], cross-check from [8], JSON write from [9].
3. Execute once: python quantum/run.py (commit src/data/quantum-results.json).
4. Build the React route: static JSON import, shadcn Card + Tabs + Slider + Badge wiring.
5. Deploy. No Python at runtime; every on-screen number traces to committed Selene output.
[11] OUTPUT CONTRACT โ selene_run v1 (write this exact shape to src/data/quantum-results.json):
{
"schemaVersion": 1,
"experiment": "<idea-slug>",
"title": "<idea title>",
"description": "<one plain-English sentence โ what a non-quantum dev sees>",
"kernel": { "snippet": "<first 40 lines of quantum/kernel.py>", "qubits": <int>, "shotsPerRow": <int> },
"verdict": { "text": "<one-sentence headline of the result>", "good": true },
"metrics": [ { "name": "<label>", "value": <number>, "unit": "<%|bits|Hz|...>", "good": true } ],
"series": [ { "id": "<id>", "kind": "histogram"|"bar"|"line", "title": "...",
"xLabel": "...", "yLabel": "...", "yKeys": ["count"],
"points": [ { "label": "<x>", "values": { "count": <num> } } ] } ]
}
Reason: one schema = one renderer = a polished demo without bespoke React per idea.
All metric / series VALUES must come from real Selene shot data committed in step 3 โ no mocks.
[12] RENDERER โ create src/components/SeleneRunView.tsx with EXACTLY this body (no deps beyond react + tailwind):
import * as React from "react";
type Point = { label: string; values: Record<string, number> };
type Series = { id: string; kind: "histogram"|"bar"|"line"; title: string;
xLabel?: string; yLabel?: string; yKeys: string[]; points: Point[] };
type Metric = { name: string; value: number; unit?: string; good?: boolean };
export type SeleneRun = {
schemaVersion: 1; experiment: string; title: string; description: string;
kernel: { snippet: string; qubits: number; shotsPerRow: number };
verdict: { text: string; good: boolean };
metrics: Metric[]; series: Series[]; notes?: string;
};
const fmt = (n: number) => Math.abs(n) >= 100 ? n.toFixed(0) : Math.abs(n) >= 1 ? n.toFixed(2) : n.toFixed(3);
function Bars({ s }: { s: Series }) {
const max = Math.max(1, ...s.points.flatMap(p => s.yKeys.map(k => p.values[k] ?? 0)));
return (
<div className="space-y-1">
{s.points.map((p, i) => (
<div key={i} className="flex items-center gap-2 text-xs">
<div className="w-20 truncate text-muted-foreground">{p.label}</div>
<div className="flex-1 h-3 bg-muted rounded-sm overflow-hidden">
<div className="h-full bg-primary" style={{ width: `${(100*(p.values[s.yKeys[0]]??0))/max}%` }} />
</div>
<div className="w-12 text-right tabular-nums">{fmt(p.values[s.yKeys[0]]??0)}</div>
</div>
))}
</div>
);
}
function Line({ s }: { s: Series }) {
const W=320, H=120, P=20;
const ys = s.points.map(p => p.values[s.yKeys[0]] ?? 0);
const min = Math.min(...ys), max = Math.max(...ys), span = max - min || 1;
const pts = ys.map((y, i) => {
const x = P + (i*(W-2*P))/Math.max(1, ys.length-1);
const yy = H - P - ((y - min)/span)*(H - 2*P);
return `${x},${yy}`;
}).join(" ");
return (
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-32">
<polyline fill="none" stroke="currentColor" strokeWidth="2" points={pts} className="text-primary" />
</svg>
);
}
export function SeleneRunView({ run }: { run: SeleneRun }) {
return (
<div className="space-y-6">
<header>
<div className="text-xs uppercase tracking-wider text-muted-foreground">{run.experiment}</div>
<h2 className="text-2xl font-semibold">{run.title}</h2>
<p className="text-sm text-muted-foreground">{run.description}</p>
<div className={`mt-2 inline-block px-3 py-1 rounded-full text-xs ${run.verdict.good?"bg-emerald-500/15 text-emerald-400":"bg-amber-500/15 text-amber-400"}`}>
{run.verdict.text}
</div>
</header>
<section className="grid grid-cols-2 md:grid-cols-4 gap-3">
{run.metrics.map((m, i) => (
<div key={i} className="rounded-lg border border-border p-3">
<div className="text-[10px] uppercase tracking-wider text-muted-foreground">{m.name}</div>
<div className="text-xl font-semibold tabular-nums">{fmt(m.value)}<span className="text-xs text-muted-foreground ml-1">{m.unit}</span></div>
</div>
))}
</section>
<section className="space-y-6">
{run.series.map(s => (
<div key={s.id} className="rounded-lg border border-border p-4">
<div className="flex items-baseline justify-between mb-3">
<div className="text-sm font-medium">{s.title}</div>
<div className="text-[10px] text-muted-foreground">{s.xLabel} / {s.yLabel}</div>
</div>
{s.kind === "line" ? <Line s={s} /> : <Bars s={s} />}
</div>
))}
</section>
<footer className="text-[11px] text-muted-foreground">
kernel: {run.kernel.qubits} qubits ยท {run.kernel.shotsPerRow} shots/row
</footer>
</div>
);
}
Then in the route: import data from "@/data/quantum-results.json"; <SeleneRunView run={data as any} />.
Quantum trace tab: <pre>{data.kernel.snippet}</pre>.
[HOOK] SWAP TEST โ state-vector fidelity.
Use swap_test() and prep_features() verbatim from [7].
Host inversion (per pair): p0 = zeros/S; F = max(0.0, min(1.0, 2.0*p0 - 1.0)).
n_qubits = 1 (anc) + 2*reg_size. Each shot emits one ("anc", bit).
selene_run mapping:
metrics: [ {"name":"avg fidelity","value":mean_F,"unit":"%","good":mean_F>0.5},
{"name":"best pair","value":best_F,"unit":"%","good":true} ]
series: [ {"id":"fidelity-hist","kind":"histogram","title":"Fidelity distribution",
"xLabel":"bucket","yLabel":"pairs","yKeys":["count"],
"points":[{"label":f"{b:.1f}","values":{"count":n}} for b,n in hist]},
{"id":"top-pairs","kind":"bar","title":"Top-5 closest pairs",
"xLabel":"pair","yLabel":"F","yKeys":["count"],
"points":[{"label":lbl,"values":{"count":F}} for lbl,F in top5]} ]Market sizing.
TAM
$28.0B
the global art market (~$65B; >300K working visual artists).
SAM
$4.2B
the 15% of that market actively buying palette curation-adjacent software.
SOM
$210M
a realistic 5% capture of the serviceable slice in years 1โ3 via the hackathon launch and creator-led distribution.
Indicative figures for hackathon pitches โ refine with your own research before raising.
Adjacent entries.
palette curation
Field Curation
Reveal the topological shape (clusters, loops, voids) hiding inside palette curation so artists read structure at a glance.
palette curationLoadout Curation
Encode palette curation as an amplitude vector and plot the embedding so artists navigate possibilities visually.
palette curationCompass Curation
Search the combinatorial space of palette curation with a grover oracle that surfaces the right configuration in โn tries.
palette curationPulse Curation
Extract dominant cycles from palette curation via qft so artists see rhythm and repetition that the ear or eye misses.