Carrier Pricing
Encode print pricing as an amplitude vector and plot the embedding so artists navigate possibilities visually.
Amplitude encodingยท embeddings
Section ยท Quantum
full primer โThe hook.
Print pricing is encoded as a quantum vector and projected onto a 2D map; painters navigate the option space visually instead of guessing parameters.
Why this primitiveAmplitude encoding is the right primitive here because print pricing reduces to a embeddings problem; the kernel returns a result you can drop straight into the UI.
Kernel
an amplitude-encoding kernel that loads a normalized feature vector into log2(d) qubits and measures expectation values
Drives the UI as
a low-dimensional embedding scatter that updates as inputs change
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 โ
~22.0 KB408 lines1 msg ยท ~5 credits
Build a one-page web app called "Carrier Pricing" for painters, illustrators, generative artists, gallerists.
Headline: Encode print pricing as an amplitude vector and plot the embedding so artists navigate possibilities visually.
CONSTRAINTS (read first):
- I am on Lovable's FREE plan with ~5 credits. Build this in ONE message. Do NOT add features beyond what is listed.
- No accounts, no Lovable Cloud, no database, no auth. Static app only.
- One page: a workspace. Plus a small "About the quantum" strip at the bottom.
REAL QUANTUM (Quantinuum Guppy + Selene) โ run AT BUILD TIME in the Lovable Linux sandbox, not at runtime:
1. In the sandbox, run: pip install "guppylang>=1.0"
2. Create quantum/kernel.py containing an amplitude-encoding kernel that loads a normalized feature vector into log2(d) qubits and measures expectation values. It MUST be a real .py file on disk (Guppy reads source via inspect.getsource โ REPL/exec strings fail).
3. Create quantum/run.py that imports the @guppy kernel, compiles it, and runs it on the Selene emulator (โค8 qubits, 256 shots) over 32 feature vectors, each loaded into 5 qubits = 32 expectation rows. Write the real results to src/data/quantum-results.json with shape: an array of { input, output } records where output contains expectation values per qubit.
4. Execute quantum/run.py ONCE during the build so src/data/quantum-results.json is committed real Selene output. Do not call Python at runtime โ Cloudflare Workers cannot run Python.
FRONTEND (TanStack Start, single route /):
- Import src/data/quantum-results.json statically.
- Workspace UI: left = input picker tied to the precomputed grid (print pricing); center = live visual built from the selected record; right = a low-dimensional embedding scatter that updates as inputs change driven by the JSON.
- "Quantum trace" disclosure: show the raw expectation values per qubit for the active record and a "View Guppy source" toggle that prints the contents of quantum/kernel.py inline, so judges see this is genuine Amplitude encoding on Selene, not a fake.
- Design: dark workspace, ONE accent color matched to the Visual Art mood, ONE typeface family, tactile micro-interaction when the user picks a new record.
Deliverable: a single deploy where every number on screen traces back to a real Selene shot from quantum/kernel.py. Ship in one message.
--- QUANTINUUM APPENDIX (self-contained reference โ paste verbatim) ---
[1] INSTALL (Lovable Linux sandbox, Python >= 3.12):
python -m pip install --no-cache-dir "guppylang>=1.0" numpy
# Guppy v1 is a BREAKING release: the Selene emulator now ships INSIDE guppylang.
# Do NOT pip-install the old standalone emulator package, and do NOT use pre-v1 syntax.
[2] IMPORTS (Guppy v1 โ use exactly these, no other quantum libs):
# quantum/kernel.py and quantum/run.py top
from guppylang import guppy, OptimizationLevel
from guppylang.std.builtins import array, output # `output`, NOT `result`
from guppylang.std.quantum import qubit, h, cx, rx, ry, rz, measure, measure_array, discard, t as tgate, tdg
from guppylang.std.angles import angle, pi
from selene_sim import Quest # error models still live here
from selene_sim.error_models import (
IdealErrorModel, DepolarizingErrorModel, SimpleLeakageErrorModel,
)
import math, json, sys, tempfile, importlib.util, uuid
from pathlib import Path
[3] HARD RULES (violating any breaks the build):
- GUPPY v1: use output("tag", v) โ `result()` no longer exists. measure(q) returns a
Measurement object; call .read() before using it in output(...) or an `if`.
Run via my_kernel.emulator(...).with_shots(S).run() โ the pre-v1 build() helper is gone.
- @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 (Guppy v1 emulator builder โ the ONLY correct form):
res = (
my_kernel # the @guppy program itself, NOT .compile()
.emulator(n_qubits=N)
.with_shots(S)
.with_seed(7)
.with_simulator(Quest())
.with_error_model(IdealErrorModel())
.run()
)
shots = []
for shot in res: # EmulatorResult is iterable
shots.append({str(tag): int(v) for tag, v in shot.entries})
# 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.
# The pre-v1 build(prog.compile()) + shot-loop helper NO LONGER EXISTS.
# Other builder methods: with_n_processes, with_timeout, with_verbose, with_progress_bar.
# ALWAYS pin .with_seed(<int>) โ an unseeded run is not reproducible evidence.
# If you report GATE COUNTS, also pin program.with_opt_level(OptimizationLevel.Classical);
# v1 optimises on compile and will otherwise flatter your numbers.
[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)
res = (
mod.program
.emulator(n_qubits=5)
.with_shots(shots)
.with_simulator(Quest())
.run()
)
out = []
for shot in res:
out.append({str(l): int(v) for l, v in shot.entries})
return out
[6] PER-QUBIT INTEGER DECODE (host-side):
# kernel emits: for j in range(n): output(f"x{j}", measure(q[j]).read())
# measure() returns a Measurement in v1 โ .read() gives the bool/bit.
# Compiler error if you forget: "Values of type 'Measurement' cannot be passed to 'output' directly".
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)
output("anc", measure(anc).read())
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> } } ] } ]
}
Also include provenance so the result is checkable:
"extras": { "shots": <int>, "seed": <int>, "nQubits": <int>,
"errorModel": "IdealErrorModel"|"DepolarizingErrorModel(...)",
"guppy": ">=1.0", "generatedAt": "<ISO date>" }
NO ESCAPE HATCH: extras is for non-rendered metadata ONLY. Anything the user is
meant to SEE belongs in metrics[] or series[] โ if you need extras to render, the
mapping is wrong. Render the provenance line as plain text under the chart.
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>.
[13] SKILL v2 GOTCHAS (must-follow)
- angle(x) is HALFTURNS, not radians. angle(0.5) == ฯ/2 (S gate).
For a radian ฮธ, write angle(ฮธ / math.pi). Never pass raw radians.
- If a source formula contains an explicit ฯ (e.g. t = ฯ/(16ยทh)),
divide the ฯ out BEFORE angle(): angle(1/16), NOT angle(math.pi/16).
- Only three Selene noise models exist:
from selene_sim.error_models import (
IdealErrorModel, DepolarizingErrorModel, SimpleLeakageErrorModel,
)
There is NO coherent / T1-T2 model โ do not import one.
- Optional "noisy vs ideal" second pass (adds one extra series):
run the same kernel twice, once with IdealErrorModel() and once with
DepolarizingErrorModel(p_1q=1e-4, p_2q=1.29e-3, p_meas=0.9e-3),
emit both under series[].points so the chart shows the gap.
- Run the CLASSICAL baseline BEFORE writing any quantum code and keep its number.
An encoding bug that scores a perfect 1.000 on every input is caught by the
baseline, never by the circuit.
- Shot-noise pass bar is 4*sqrt(0.5/shots) โ see [16]. Do NOT use 3*sqrt(p(1-p)/n);
it produces false FAILs whenever a probability sits near 0 or 1.
- Hardware footer line (render below the chart, one <div> only):
"Runs today on Helios (98 qubits). Roadmap: Sol โ Apollo โ Lumos."
[14] BUILD SAFETY (Lovable sandbox + production runtime)
- Results ship as a COMMITTED JSON file only. Do NOT create a
createServerFn, API route, or handler that shells out to Python or
reads a cache directory: production stubs child_process and blocks
arbitrary filesystem reads, so it builds and then fails live.
- If you install deps with `pip install --target .pydeps ...`, add
`.pydeps/` to .gitignore FIRST. An unignored vendored dep tree makes
the build time out and roll back, wasting credits.
- Make the sweep resumable: before moving to the next row, write it to
_cache_run/<row-tag>.json and skip rows whose cache file exists.
Assemble src/data/quantum-results.json from the cache at the end.
Keep the grid small (<= 100 rows, <= 256 shots/row) so one pass fits.
[15] IF THE TURN ROLLS BACK (Lovable orchestration)
- A big Guppy/Selene turn can fail with "An internal error occurred". That is
a task-transaction rollback: every file write from that turn is discarded.
Do not debug the app โ nothing broke. Re-do the work in smaller gates.
- Author in ATOMIC GATES, one per message, each ending in a saved artifact:
(a) quantum/kernel.py + a smoke run of a handful of shots
(b) quantum/run.py resumable driver (per-row cache)
(c) the cached sweep -> src/data/quantum-results.json
(d) the React route reading the committed JSON
A rollback then costs one gate, not the whole build.
- Persistence canary: on a fresh session, make one trivial edit and end the
turn. If it persists, larger gates are safe.
[16] EVIDENCE & VERIFICATION (do this, it is what makes the demo credible)
- STEP 0, before any quantum code: compute the CLASSICAL baseline for your task
(cosine similarity, brute-force search, direct FFT โ whatever the honest
non-quantum answer is). Print it, keep it, and show it next to the quantum
number in the UI. If the quantum result is identical to a trivial baseline on
every input, your encoding is degenerate โ fix the encoding, not the chart.
- PASS BAR for any probability comparison (quantum shots vs NumPy reference):
import math
tol = 4.0 * math.sqrt(0.5 / shots) # shots = shots PER CELL
ok = abs(p_measured - p_expected) <= tol
At 256 shots tol ~= 0.088. Use this exact form. The textbook
3*sqrt(p(1-p)/n) form collapses to ~0 near p=0 or p=1 and reports false FAILs.
- The verdict.good flag in [11] MUST be computed from that comparison
(e.g. good = all(cells_ok)), never hand-written as true.
- ANGLE SANITY CELL (30 seconds, saves the whole sweep): before the full run,
do one 256-shot single-qubit check โ prepare h(q); rz(q, angle(0.5)); h(q),
measure, and assert P(1) ~= 0.5 within tol. If it comes out 0 or 1 you passed
radians into angle() somewhere. Halfturns, always (see [13]).
- Report shots + seed + tolerance in the UI next to the verdict. A number
without its shot count and seed is a rumour, not a result.
[17] OPTIONAL โ LIVE SHOT STREAM (stretch goal, ONLY if you still have credits)
Skip this unless the committed JSON demo from [1]-[12] is already working. It is
polish, not proof.
- Selene/Python cannot run in production. For a live "watch the shots land" panel,
port ONE small kernel (<= 4 qubits, gates H / RX / RZ / CX / measure) to a pure
TypeScript statevector mini-sim in src/lib/mini-sim.ts: complex amplitudes as two
Float64Array's, one closed-form unitary per gate, projective measure with collapse
(compute P(0), draw vs rng(), zero the dead branch, renormalise).
- Mirror the Guppy gate ORDER exactly and return the same labelled keys your
Python driver passes to output(...). Same kernel, two runtimes.
- Stream it from src/routes/api/public/shots.ts as Server-Sent Events
(`data: ${JSON.stringify(shot)}\n\n`), seeded from a URL param so a session is
reproducible. The client shows a "Live-verified" badge once >= 500 streamed shots
land within the [16] tolerance of the analytic probability, "Drift" otherwise.
- HARD LIMIT: <= 4 qubits and no noise model. Anything bigger, anything needing real
compilation or a noise model, and anything that IS the claim stays in the committed
Selene JSON. The stream is a UX layer on top of an already-verified experiment.
[HOOK] AMPLITUDE ENCODING โ feature vector โ state, read Z-expectations.
Kernel: ry(qj, angle(fj)) # halfturns: fjโ[0,1] gives ฯยทfj rad rotation per feature; cx ladder q0โq1โโฆ; rz on tail for non-trivial phase.
output("z{j}", measure(q[j]).read()) for each j.
Host: per-qubit expectation E_j = (#0 - #1) / S โ [-1, +1].
Cross-check with the prep_psi() NumPy snippet in [8] and E_j = <ฯ|Z_j|ฯ>.
selene_run mapping:
metrics: [ {"name":"embedding norm","value":norm,"unit":""},
{"name":"qubits","value":n,"unit":""} ]
series: [ {"id":"expectations","kind":"bar","title":"โจZ_jโฉ per qubit",
"xLabel":"qubit","yLabel":"โจZโฉ","yKeys":["count"],
"points":[{"label":f"q{j}","values":{"count":Ej}} for j,Ej in enumerate(expectations)]} ]Market sizing.
TAM
$14.0B
the global art market (~$65B; >300K working visual artists).
SAM
$1.7B
the 12% of that market actively buying print pricing-adjacent software.
SOM
$17M
a realistic 1% 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
Loadout Curation
Encode palette curation as an amplitude vector and plot the embedding so artists navigate possibilities visually.
composition planningEncoded Planning
Encode composition planning as an amplitude vector and plot the embedding so artists navigate possibilities visually.
style transferCipher Transfer
Encode style transfer as an amplitude vector and plot the embedding so artists navigate possibilities visually.
gallery curationLatent Curation
Encode gallery curation as an amplitude vector and plot the embedding so artists navigate possibilities visually.