Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions notebooks/05_p2p_fabric_sharing.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
{
"nbformat": 4,
"nbformat_minor": 5,
"metadata": {
"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"},
"colab": {"name": "knitweb-lens: P2P sharing of circuits, results & systems", "provenance": []}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": ["# P2P sharing of circuits, **results** & **quantum systems**\n",
"\n",
"The quantum layer shares three content-addressed artifact kinds, each in its own CID namespace:\n",
"\n",
"| kind | type | CID |\n",
"|---|---|---|\n",
"| circuit | `QuantumCircuit` | `lcid:` |\n",
"| result | `QuantumResult` | `lres:` |\n",
"| system | `BackendDescriptor` | `lqpu:` |\n",
"\n",
"This notebook runs a circuit, captures its **result**, describes the **backend** it ran on, then shares all three between two nodes by content id — like file-sharing, but for quantum.\n",
"\n",
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Knitweb/lens/blob/main/notebooks/05_p2p_fabric_sharing.ipynb)\n"]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": ["!pip install -q knitweb-lens qiskit qiskit-aer"]
},
{
"cell_type": "markdown",
"metadata": {},
"source": ["## 1. A circuit → its `lcid:`"]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from knitweb_lens.quantum import library, QuantumResult, BackendDescriptor, Store, search\n",
"\n",
"circ = library()['bell_phi_plus']\n",
"print('circuit :', circ.meta.name)\n",
"print('cid :', circ.cid) # lcid:...\n",
"print(circ.qasm)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": ["## 2. Run it → a `QuantumResult` (`lres:`)\n",
"We execute on Qiskit Aer and wrap the measured counts as a content-addressed result that links back to the circuit."]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from qiskit import qasm2, transpile\n",
"from qiskit_aer import AerSimulator\n",
"\n",
"sim = AerSimulator()\n",
"qc = qasm2.loads(circ.qasm)\n",
"counts = sim.run(transpile(qc, sim), shots=1024).result().get_counts()\n",
"counts = {k.replace(' ', ''): int(v) for k, v in counts.items()}\n",
"print('counts:', counts)\n",
"\n",
"result = QuantumResult(circuit_cid=circ.cid, counts=counts, author='demo')\n",
"print('result cid :', result.cid) # lres:...\n",
"print('of circuit :', result.circuit_cid)\n",
"print('most freq :', result.most_frequent)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": ["## 3. Describe the backend → a `BackendDescriptor` (`lqpu:`)"]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"backend = BackendDescriptor(name='aer_simulator', provider='local', n_qubits=32,\n",
" native_gates=['h', 'cx', 'rz', 'ry', 'rx', 'ccx'], simulator=True)\n",
"print('backend cid:', backend.cid) # lqpu:...\n",
"\n",
"# Bind the result to the system it ran on (provenance).\n",
"result = QuantumResult(circuit_cid=circ.cid, counts=counts,\n",
" backend_cid=backend.cid, author='demo')\n",
"print('result now records ran-on:', result.backend_cid)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": ["## 4. Node **A** publishes all three by content id\n",
"Set `KNITWEB_RELAY` to a live Knitweb relay to actually broadcast; otherwise this writes to a local node store and we simulate the peer sync in the next cell."]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os, tempfile\n",
"relay = os.environ.get('KNITWEB_RELAY') # e.g. 'https://relay.5mart.ml'\n",
"node_a = Store(root=tempfile.mkdtemp(prefix='nodeA_'), relay=relay)\n",
"\n",
"for art in (circ, backend, result):\n",
" cid = node_a.put(art)\n",
" print('published', cid)\n",
"\n",
"print('\\nnode A holds', len(node_a), 'artifacts across kinds:')\n",
"for item in node_a.list():\n",
" print(' ', item['kind'], item['cid'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": ["## 5. Node **B** pulls by CID — knowledge crosses the network\n",
"With a relay, node B fetches from it. Without one, we hand B the same store dir to stand in for a completed peer sync — the point is that **the CID is all B needs**."]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"node_b = Store(root=(tempfile.mkdtemp(prefix='nodeB_') if relay else node_a.root), relay=relay)\n",
"\n",
"got_circuit = node_b.get(circ.cid)\n",
"got_result = node_b.get(result.cid)\n",
"got_backend = node_b.get(backend.cid)\n",
"\n",
"print('B pulled circuit:', got_circuit.meta.name)\n",
"print('B pulled result :', got_result.most_frequent, 'from', got_result.circuit_cid)\n",
"print('B pulled backend:', got_backend.name, f'({got_backend.n_qubits}q)')\n",
"\n",
"# Content integrity: the CID B fetched recomputes to the same id A minted.\n",
"assert got_circuit.cid == circ.cid\n",
"assert got_result.cid == result.cid\n",
"assert got_backend.cid == backend.cid\n",
"print('\\nAll CIDs verified — bit-identical across nodes.')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": ["## 6. Query across kinds on node B"]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('results on B :', [i['cid'] for i in node_b.list(kind='result')])\n",
"print('backends on B:', [i['cid'] for i in node_b.list(kind='backend')])\n",
"print('find \"aer\" :', [h['meta']['name'] for h in node_b.find(query='aer', kind='backend')])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": ["## 7. True P2P over the Knitweb fabric\n",
"For real peer-to-peer discovery (anti-entropy sync + a read-only `/interpret` query gateway + a proof-of-useful-work \"run this circuit, return verified counts\" job), a Knitweb **node** weaves these artifacts into the fabric via `knitweb.quantum` (in the [`Knitweb/pulse`](https://github.com/Knitweb/pulse) package):\n",
"\n",
"```python\n",
"from knitweb.quantum import QuantumCircuitRecord, publish, build_lens\n",
"rec = QuantumCircuitRecord(circuit_cid=circ.cid, name='bell', qubits=2, domain='fundamental')\n",
"await node.weave(rec.to_record()) # announces the CID to peers\n",
"# peers can then: query_space(space, 'circuits domain=fundamental') -> [lcid:...]\n",
"```\n",
"\n",
"The `knitweb-lens` SDK mints and stores the artifacts; the fabric carries the lean, queryable descriptors. Same CIDs, everywhere."]
}
]
}
120 changes: 119 additions & 1 deletion web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<style>
:root {
--bg: #0e1117; --surface: #161b22; --border: #30363d;
--text: #e6edf3; --muted: #7d8590; --accent: #58a6ff;
--text: #e6edf3; --muted: #7d8590; --accent: #58a6ff; --accent2: #79c0ff;
--green: #3fb950; --yellow: #d29922; --purple: #bc8cff;
--red: #f85149; --orange: #f0883e;
}
Expand Down Expand Up @@ -75,6 +75,21 @@
.colab-card h3{font-size:12px;color:var(--text)}
.colab-card p{font-size:11px;color:var(--muted);margin-top:4px}
.colab-card a{display:inline-block;margin-top:8px;font-size:11px;color:var(--accent)}

.tabs{max-width:860px;margin:0 auto;padding:6px 24px 0;display:flex;gap:8px}
.tab{background:transparent;border:1px solid var(--border);border-bottom:none;
border-radius:8px 8px 0 0;color:var(--muted);cursor:pointer;font:inherit;font-size:12px;padding:8px 16px}
.tab:hover{color:var(--text)}
.tab.active{color:var(--accent);border-color:var(--accent)55;background:var(--accent)0d}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Alpha suffixes on CSS color variables are invalid and will be ignored by the browser.

Values like var(--accent)55 and var(--accent)0d are not valid CSS colors, so border-color and background won’t apply. If you need transparency, use rgba() or define separate variables (e.g. --accent-border, --accent-bg) with full color values such as #58a6ff55. The same issue applies to the pill styles (var(--green)18, var(--green)44, var(--orange)18, var(--orange)44).

.hist{display:flex;flex-direction:column;gap:3px;margin-top:10px}
.hist-row{display:flex;align-items:center;gap:8px;font-size:10px}
.hist-row .bs{font-family:var(--mono,monospace);color:var(--muted);width:52px;text-align:right}
.hist-bar{height:8px;background:var(--accent);border-radius:2px;min-width:2px}
.hist-row .n{color:var(--muted);width:44px}
.kv{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;margin-top:8px;font-size:11px}
.kv .k{color:var(--muted)} .kv .v{color:var(--text);word-break:break-all}
.pill-sim{font-size:9px;padding:1px 6px;border-radius:3px;background:var(--green)18;border:1px solid var(--green)44;color:var(--green)}
.pill-hw{font-size:9px;padding:1px 6px;border-radius:3px;background:var(--orange)18;border:1px solid var(--orange)44;color:var(--orange)}
Comment on lines +91 to +92

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Pill background/border colors reuse the same invalid color pattern as the active tab styles.

These background and border values (var(--green)18, var(--green)44, and the orange equivalents) are not valid CSS colors, so browsers will ignore them and the pills won’t display as intended. Please replace them with valid color tokens (e.g., dedicated --pill-sim-bg / --pill-sim-border variables using proper hex or rgba values).

</style>
</head>
<body>
Expand Down Expand Up @@ -120,8 +135,15 @@ <h3>04 Qiskit & PennyLane</h3>
</div>
</div>

<div class="tabs" id="tabs">
<button class="tab active" data-tab="circuits" onclick="setTab('circuits',this)">⬡ Circuits</button>
<button class="tab" data-tab="results" onclick="setTab('results',this)">▤ Results</button>
<button class="tab" data-tab="backends" onclick="setTab('backends',this)">⚛ Backends</button>
</div>

<div class="controls">
<input class="search-input" id="search" placeholder="Search circuits…" oninput="render()">
<span id="domain-row" style="display:contents">
<button class="chip active" data-domain="" onclick="setDomain('',this)">All</button>
<button class="chip" data-domain="fundamental" onclick="setDomain('fundamental',this)">fundamental</button>
<button class="chip" data-domain="algorithms" onclick="setDomain('algorithms',this)">algorithms</button>
Expand All @@ -131,9 +153,11 @@ <h3>04 Qiskit & PennyLane</h3>
<button class="chip" data-domain="optimization" onclick="setDomain('optimization',this)">optimization</button>
<button class="chip" data-domain="simulation" onclick="setDomain('simulation',this)">simulation</button>
<button class="chip" data-domain="communication" onclick="setDomain('communication',this)">communication</button>
</span>
</div>

<div class="count" id="count">100 circuits</div>
<div id="tab-note" class="count" style="display:none"></div>
<div class="grid" id="grid"></div>

<div class="modal-overlay" id="overlay" onclick="if(event.target===this)closeModal()">
Expand Down Expand Up @@ -276,6 +300,26 @@ <h2 id="m-name">—</h2>
};

let _domain = '';
let _tab = 'circuits';

// Example shared artifacts (lres:/lqpu:). On a page served by a live node these
// are fetched from the relay; here they illustrate what a peer would discover.
const RESULTS = [
{circuit:'bell_phi_plus', circuit_cid:'lcid:e86fd8b3…', shots:1024, backend:'aer_simulator',
counts:{'00':508,'11':516}, desc:'Bell state — perfectly correlated'},
{circuit:'ghz_3', circuit_cid:'lcid:7a1c…', shots:2048, backend:'aer_simulator',
counts:{'000':1017,'111':1031}, desc:'3-qubit GHZ — all-0 or all-1'},
{circuit:'grover_2q', circuit_cid:'lcid:9f4b…', shots:1024, backend:'ibm_kyiv',
counts:{'11':903,'00':47,'01':39,'10':35}, desc:'Grover marks |11⟩'},
{circuit:'qrng_4', circuit_cid:'lcid:c2a0…', shots:1600, backend:'aer_simulator',
counts:{'0000':98,'0101':110,'1111':94,'1010':101,'0011':96,'1100':105,'0110':100,'1001':102}, desc:'4-bit quantum RNG — uniform'},
];
const BACKENDS = [
{name:'aer_simulator', provider:'local', qubits:32, sim:true, gates:['h','x','cx','rz','ry','rx','ccx'], desc:'Statevector simulator (bundled)'},
{name:'ibm_kyiv', provider:'IBM', qubits:127, sim:false, gates:['ecr','id','rz','sx','x'], desc:'Eagle r3 superconducting QPU'},
{name:'ionq_aria', provider:'IonQ', qubits:25, sim:false, gates:['gpi','gpi2','ms'], desc:'Trapped-ion, all-to-all connectivity'},
{name:'quantinuum_h2', provider:'Quantinuum', qubits:56, sim:false, gates:['u1q','rz','zz'], desc:'Trapped-ion, high fidelity'},
];

function setDomain(d, btn) {
_domain = d;
Expand All @@ -284,7 +328,32 @@ <h2 id="m-name">—</h2>
render();
}

function setTab(t, btn) {
_tab = t;
document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// domain chips + circuit search only make sense on the circuits tab
document.getElementById('domain-row').style.display = (t === 'circuits') ? 'contents' : 'none';
const search = document.getElementById('search');
search.placeholder = t === 'circuits' ? 'Search circuits…'
: t === 'results' ? 'Search results…' : 'Search backends…';
const note = document.getElementById('tab-note');
if (t === 'circuits') { note.style.display = 'none'; }
else {
note.style.display = '';
note.textContent = 'Example shared artifacts — a node serving this page streams live '
+ t + ' from the relay.';
}
render();
}

function render() {
if (_tab === 'results') return renderResults();
if (_tab === 'backends') return renderBackends();
return renderCircuits();
}

function renderCircuits() {
const q = document.getElementById('search').value.toLowerCase();
const filtered = CIRCUITS.filter(c => {
if (_domain && c.domain !== _domain) return false;
Expand All @@ -311,6 +380,55 @@ <h2 id="m-name">—</h2>
});
}

function renderResults() {
const q = document.getElementById('search').value.toLowerCase();
const rows = RESULTS.filter(r => !q || r.circuit.includes(q) || r.backend.toLowerCase().includes(q));
Comment on lines +383 to +385

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Search in renderResults mixes lowercased query with a case-sensitive circuit name comparison.

The query is lower-cased, but only r.backend is compared case-insensitively; r.circuit.includes(q) stays case-sensitive. For consistent search behavior, also lower-case r.circuit, e.g. r.circuit.toLowerCase().includes(q).

document.getElementById('count').textContent = `${rows.length} result${rows.length===1?'':'s'}`;
const grid = document.getElementById('grid');
grid.innerHTML = '';
rows.forEach(r => {
const max = Math.max(...Object.values(r.counts));
const bars = Object.entries(r.counts).sort((a,b)=>b[1]-a[1]).map(([bs,n]) =>
`<div class="hist-row"><span class="bs">${_esc(bs)}</span>
<span class="hist-bar" style="width:${Math.round(120*n/max)}px"></span>
<span class="n">${n}</span></div>`).join('');
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `
<div class="card-name">${_esc(r.circuit)}</div>
<div class="card-domain" style="color:var(--green)">result · ${r.shots} shots · ${_esc(r.backend)}</div>
<div class="card-desc">${_esc(r.desc)}</div>
<div class="hist">${bars}</div>
<div class="card-meta"><span class="tag">${_esc(r.circuit_cid)}</span></div>`;
grid.appendChild(div);
});
}

function renderBackends() {
const q = document.getElementById('search').value.toLowerCase();
const rows = BACKENDS.filter(b => !q || b.name.toLowerCase().includes(q)
|| b.provider.toLowerCase().includes(q) || b.gates.join(' ').includes(q));
Comment on lines +409 to +410

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Search in renderBackends treats gate names case-sensitively while other fields are case-insensitive.

b.name and b.provider are compared in lower case, but b.gates.join(' ').includes(q) is not, so gate matching is case-sensitive. Consider normalizing the gates string with .toLowerCase() before includes(q) to keep search behavior consistent.

Suggested change
const rows = BACKENDS.filter(b => !q || b.name.toLowerCase().includes(q)
|| b.provider.toLowerCase().includes(q) || b.gates.join(' ').includes(q));
const rows = BACKENDS.filter(b => !q || b.name.toLowerCase().includes(q)
|| b.provider.toLowerCase().includes(q) || b.gates.join(' ').toLowerCase().includes(q));

document.getElementById('count').textContent = `${rows.length} backend${rows.length===1?'':'s'}`;
const grid = document.getElementById('grid');
grid.innerHTML = '';
rows.forEach(b => {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `
<div class="card-name">${_esc(b.name)}
<span class="${b.sim?'pill-sim':'pill-hw'}">${b.sim?'simulator':'hardware'}</span></div>
<div class="card-domain" style="color:var(--accent2)">${_esc(b.provider)}</div>
<div class="card-desc">${_esc(b.desc)}</div>
<div class="kv">
<span class="k">qubits</span><span class="v">${b.qubits}</span>
<span class="k">native</span><span class="v">${b.gates.map(_esc).join(', ')}</span>
</div>
<div class="card-meta"><span class="qubits">${b.qubits}q</span>
${b.gates.slice(0,3).map(g=>`<span class="tag">${_esc(g)}</span>`).join('')}</div>`;
grid.appendChild(div);
});
}

function _esc(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
Expand Down
Loading