Each single-qubit gate is a rotation of the Bloch sphere. A finite gate set gives you a fixed menu of rotations; composing them lets you reach more angles. The demo below shows the Z-axis angle θ you can reach by composing up to n gates from your chosen set.
<p class="hint">Each single-qubit gate is a Z-axis rotation by a fixed angle. Composing gates from a finite set lets you reach more angles on the unit circle. Toggle gates on/off, pick a depth, then press <b>Run</b> to see how densely your gate set covers the circle.</p>
<div class="controls">
<div class="gate-row">
<label>Gate set:</label>
<div class="gate-btns" id="gate-btns">
<button class="gate-btn active" data-angle="1.0" type="button">T (π/4)</button>
<button class="gate-btn active" data-angle="0.7" type="button">S (π/2)</button>
<button class="gate-btn" data-angle="1.618" type="button">φ</button>
<button class="gate-btn" data-angle="0.4" type="button">R₁</button>
</div>
</div>
<div class="depth-row">
<label for="depth-slider">Depth: <span id="depth-val">4</span></label>
<input id="depth-slider" type="range" min="1" max="8" value="4" />
</div>
</div>
<canvas id="circle" width="300" height="300" aria-label="Unit circle showing reachable rotation angles"></canvas>
<div id="status" class="status"></div>
<div class="btns">
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* layout and visual styles for the gate-set coverage demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .8rem; }
.gate-row, .depth-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
label { font-size: .85rem; font-weight: 600; color: #333; white-space: nowrap; }
.gate-btns { display: flex; gap: .4rem; flex-wrap: wrap; }
.gate-btn { font: 600 13px ui-monospace, monospace; padding: .3rem .65rem;
border: 1.5px solid #adb1b8; background: #e9ecef; color: #444;
border-radius: 6px; cursor: pointer; transition: all .15s; }
.gate-btn.active { background: #1d3557; color: #fff; border-color: #1d3557; }
input[type=range] { flex: 1; min-width: 120px; accent-color: #1d3557; }
canvas { display: block; margin: 0 auto; border-radius: 50%;
background: #f0f4f8; border: 1.5px solid #cdd9e3; }
.status { font-size: .9rem; font-weight: 600; margin: .5rem 0; min-height: 1.3em;
text-align: center; }
.status.ok { color: #0a7d33; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; justify-content: center; margin-top: .3rem; }
button { font: 600 13px system-ui, sans-serif; padding: .42rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Solovay-Kitaev demo: show reachable Z-rotation angles on the unit circle
// when composing up to `depth` gates from the chosen gate set.
// Each gate applies a rotation by +angle or -angle (the gate and its inverse).
const canvas = document.getElementById('circle');
const ctx = canvas.getContext('2d');
const CX = canvas.width / 2, CY = canvas.height / 2, R = 130;
// Active gate angles are read from the DOM on each run.
let activeAngles = [];
function getActiveAngles() {
const btns = document.querySelectorAll('.gate-btn.active');
return Array.from(btns).map(b => parseFloat(b.dataset.angle));
}
document.getElementById('gate-btns').addEventListener('click', e => {
const btn = e.target.closest('.gate-btn');
if (!btn) return;
btn.classList.toggle('active');
// keep at least one gate active
const active = document.querySelectorAll('.gate-btn.active');
if (active.length === 0) btn.classList.add('active');
});
const depthSlider = document.getElementById('depth-slider');
const depthVal = document.getElementById('depth-val');
depthSlider.addEventListener('input', () => { depthVal.textContent = depthSlider.value; });
// BFS over compositions up to the given depth, tracking reached angles.
function computeReachable(angles, depth) {
let reached = new Set([0]);
let frontier = [0];
for (let d = 0; d < depth; d++) {
const next = [];
for (const theta of frontier) {
for (const a of angles) {
// apply gate forward and backward (gate + inverse)
for (const delta of [a, -a]) {
const t = ((theta + delta) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
const key = Math.round(t * 1000);
if (!reached.has(key)) { reached.add(key); next.push(t); }
}
}
}
frontier = next;
if (frontier.length === 0) break;
}
return reached;
}
function drawCircle(reachedKeys) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw the unit circle background
ctx.beginPath();
ctx.arc(CX, CY, R, 0, 2 * Math.PI);
ctx.strokeStyle = '#cdd9e3';
ctx.lineWidth = 1.5;
ctx.stroke();
// draw faint axis lines
ctx.strokeStyle = '#d0d7de';
ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(CX - R, CY); ctx.lineTo(CX + R, CY); ctx.stroke();
ctx.beginPath(); ctx.moveTo(CX, CY - R); ctx.lineTo(CX, CY + R); ctx.stroke();
// draw each reachable angle as a dot on the circle
ctx.fillStyle = '#1d3557';
for (const key of reachedKeys) {
const theta = key / 1000;
const x = CX + R * Math.cos(theta);
const y = CY - R * Math.sin(theta);
ctx.beginPath();
ctx.arc(x, y, 2.2, 0, 2 * Math.PI);
ctx.fill();
}
// highlight the starting point (angle 0) in red
ctx.fillStyle = '#e63946';
ctx.beginPath();
ctx.arc(CX + R, CY, 4, 0, 2 * Math.PI);
ctx.fill();
}
function run() {
const angles = getActiveAngles();
if (angles.length === 0) { return; }
const depth = parseInt(depthSlider.value);
const reached = computeReachable(angles, depth);
drawCircle(reached);
const count = reached.size;
const statusEl = document.getElementById('status');
if (count >= 900) {
statusEl.textContent = fmt('Dense coverage achieved! (%count% points)', { count: count });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('%count% reachable angles — add more gates or increase depth', { count: count });
statusEl.className = 'status info';
}
}
function reset() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw empty circle on reset
ctx.beginPath();
ctx.arc(CX, CY, R, 0, 2 * Math.PI);
ctx.strokeStyle = '#cdd9e3';
ctx.lineWidth = 1.5;
ctx.stroke();
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
}
document.getElementById('btn-run').addEventListener('click', run);
document.getElementById('btn-reset').addEventListener('click', reset);
// draw initial empty circle
reset();
<p class="hint">Each single-qubit gate is a Z-axis rotation by a fixed angle. Composing gates from a finite set lets you reach more angles on the unit circle. Toggle gates on/off, pick a depth, then press <b>Run</b> to see how densely your gate set covers the circle.</p>
<div class="controls">
<div class="gate-row">
<label>Gate set:</label>
<div class="gate-btns" id="gate-btns">
<button class="gate-btn active" data-angle="1.0" type="button">T (π/4)</button>
<button class="gate-btn active" data-angle="0.7" type="button">S (π/2)</button>
<button class="gate-btn" data-angle="1.618" type="button">φ</button>
<button class="gate-btn" data-angle="0.4" type="button">R₁</button>
</div>
</div>
<div class="depth-row">
<label for="depth-slider">Depth: <span id="depth-val">4</span></label>
<input id="depth-slider" type="range" min="1" max="8" value="4" />
</div>
</div>
<canvas id="circle" width="300" height="300" aria-label="Unit circle showing reachable rotation angles"></canvas>
<div id="status" class="status"></div>
<div class="btns">
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* layout and visual styles for the gate-set coverage demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .8rem; }
.gate-row, .depth-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
label { font-size: .85rem; font-weight: 600; color: #333; white-space: nowrap; }
.gate-btns { display: flex; gap: .4rem; flex-wrap: wrap; }
.gate-btn { font: 600 13px ui-monospace, monospace; padding: .3rem .65rem;
border: 1.5px solid #adb1b8; background: #e9ecef; color: #444;
border-radius: 6px; cursor: pointer; transition: all .15s; }
.gate-btn.active { background: #1d3557; color: #fff; border-color: #1d3557; }
input[type=range] { flex: 1; min-width: 120px; accent-color: #1d3557; }
canvas { display: block; margin: 0 auto; border-radius: 50%;
background: #f0f4f8; border: 1.5px solid #cdd9e3; }
.status { font-size: .9rem; font-weight: 600; margin: .5rem 0; min-height: 1.3em;
text-align: center; }
.status.ok { color: #0a7d33; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; justify-content: center; margin-top: .3rem; }
button { font: 600 13px system-ui, sans-serif; padding: .42rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Solovay-Kitaev demo: show reachable Z-rotation angles on the unit circle
// when composing up to `depth` gates from the chosen gate set.
// Each gate applies a rotation by +angle or -angle (the gate and its inverse).
const canvas = document.getElementById('circle');
const ctx = canvas.getContext('2d');
const CX = canvas.width / 2, CY = canvas.height / 2, R = 130;
// Active gate angles are read from the DOM on each run.
let activeAngles = [];
function getActiveAngles() {
const btns = document.querySelectorAll('.gate-btn.active');
return Array.from(btns).map(b => parseFloat(b.dataset.angle));
}
document.getElementById('gate-btns').addEventListener('click', e => {
const btn = e.target.closest('.gate-btn');
if (!btn) return;
btn.classList.toggle('active');
// keep at least one gate active
const active = document.querySelectorAll('.gate-btn.active');
if (active.length === 0) btn.classList.add('active');
});
const depthSlider = document.getElementById('depth-slider');
const depthVal = document.getElementById('depth-val');
depthSlider.addEventListener('input', () => { depthVal.textContent = depthSlider.value; });
// BFS over compositions up to the given depth, tracking reached angles.
function computeReachable(angles, depth) {
let reached = new Set([0]);
let frontier = [0];
for (let d = 0; d < depth; d++) {
const next = [];
for (const theta of frontier) {
for (const a of angles) {
// apply gate forward and backward (gate + inverse)
for (const delta of [a, -a]) {
const t = ((theta + delta) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
const key = Math.round(t * 1000);
if (!reached.has(key)) { reached.add(key); next.push(t); }
}
}
}
frontier = next;
if (frontier.length === 0) break;
}
return reached;
}
function drawCircle(reachedKeys) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw the unit circle background
ctx.beginPath();
ctx.arc(CX, CY, R, 0, 2 * Math.PI);
ctx.strokeStyle = '#cdd9e3';
ctx.lineWidth = 1.5;
ctx.stroke();
// draw faint axis lines
ctx.strokeStyle = '#d0d7de';
ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(CX - R, CY); ctx.lineTo(CX + R, CY); ctx.stroke();
ctx.beginPath(); ctx.moveTo(CX, CY - R); ctx.lineTo(CX, CY + R); ctx.stroke();
// draw each reachable angle as a dot on the circle
ctx.fillStyle = '#1d3557';
for (const key of reachedKeys) {
const theta = key / 1000;
const x = CX + R * Math.cos(theta);
const y = CY - R * Math.sin(theta);
ctx.beginPath();
ctx.arc(x, y, 2.2, 0, 2 * Math.PI);
ctx.fill();
}
// highlight the starting point (angle 0) in red
ctx.fillStyle = '#e63946';
ctx.beginPath();
ctx.arc(CX + R, CY, 4, 0, 2 * Math.PI);
ctx.fill();
}
function run() {
const angles = getActiveAngles();
if (angles.length === 0) { return; }
const depth = parseInt(depthSlider.value);
const reached = computeReachable(angles, depth);
drawCircle(reached);
const count = reached.size;
const statusEl = document.getElementById('status');
if (count >= 900) {
statusEl.textContent = fmt('Dense coverage achieved! (%count% points)', { count: count });
statusEl.className = 'status ok';
} else {
statusEl.textContent = fmt('%count% reachable angles — add more gates or increase depth', { count: count });
statusEl.className = 'status info';
}
}
function reset() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw empty circle on reset
ctx.beginPath();
ctx.arc(CX, CY, R, 0, 2 * Math.PI);
ctx.strokeStyle = '#cdd9e3';
ctx.lineWidth = 1.5;
ctx.stroke();
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
}
document.getElementById('btn-run').addEventListener('click', run);
document.getElementById('btn-reset').addEventListener('click', reset);
// draw initial empty circle
reset();
Notice how the reachable angles spread out as you add more gates to the set or increase the composition depth. The Solovay-Kitaev theorem guarantees that if your gate set is dense in SU(2) — meaning no angle is permanently out of reach — then the gaps shrink doubly exponentially fast with each extra layer, giving you polylogarithmic depth for any target precision ε.
Comments
Loading comments...