FireEcho / quantum /circuit.py
Joysulem's picture
Upload 3258 files
b5bff9c verified
"""
FireEcho Quantum Gold - Circuit Builder
Provides a high-level interface for constructing quantum circuits
similar to Qiskit/Cirq but optimized for FireEcho's SM120 backend.
Example:
qc = QuantumCircuit(3)
qc.h(0) # Hadamard on qubit 0
qc.cx(0, 1) # CNOT: control=0, target=1
qc.cx(0, 2) # CNOT: control=0, target=2
# Result: GHZ state (|000⟩ + |111⟩)/√2
"""
from dataclasses import dataclass, field
from typing import List, Tuple, Optional, Union, Any
import math
@dataclass
class QuantumRegister:
"""
A register of qubits.
Args:
size: Number of qubits in the register
name: Optional name for the register
"""
size: int
name: str = "q"
def __getitem__(self, idx: int) -> int:
"""Get qubit index."""
if idx < 0 or idx >= self.size:
raise IndexError(f"Qubit index {idx} out of range [0, {self.size})")
return idx
def __len__(self) -> int:
return self.size
def __iter__(self):
return iter(range(self.size))
@dataclass
class Gate:
"""Representation of a quantum gate operation."""
name: str
targets: Tuple[int, ...]
params: Tuple[float, ...] = ()
def __repr__(self):
if self.params:
param_str = ", ".join(f"{p:.4f}" for p in self.params)
return f"{self.name}({param_str}) @ {self.targets}"
return f"{self.name} @ {self.targets}"
class QuantumCircuit:
"""
Quantum circuit builder for FireEcho Quantum Gold.
Supports all standard gates and provides methods for
circuit visualization, optimization, and execution.
Args:
num_qubits: Number of qubits in the circuit
name: Optional circuit name
Example:
# Create a Bell state
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
# Execute
sim = QuantumSimulator()
state = sim.run(qc)
"""
def __init__(self, num_qubits: int, name: str = "circuit"):
self.num_qubits = num_qubits
self.name = name
self.gates: List[Gate] = []
self.register = QuantumRegister(num_qubits)
def _validate_qubit(self, qubit: int, name: str = "qubit"):
"""Validate qubit index."""
if qubit < 0 or qubit >= self.num_qubits:
raise ValueError(
f"{name} index {qubit} out of range [0, {self.num_qubits})"
)
# =========================================================================
# Single-Qubit Gates
# =========================================================================
def h(self, qubit: int) -> 'QuantumCircuit':
"""
Hadamard gate.
Creates superposition: H|0⟩ = (|0⟩+|1⟩)/√2
"""
self._validate_qubit(qubit)
self.gates.append(Gate("H", (qubit,)))
return self
def x(self, qubit: int) -> 'QuantumCircuit':
"""
Pauli-X (NOT) gate.
Bit flip: X|0⟩ = |1⟩, X|1⟩ = |0⟩
"""
self._validate_qubit(qubit)
self.gates.append(Gate("X", (qubit,)))
return self
def y(self, qubit: int) -> 'QuantumCircuit':
"""
Pauli-Y gate.
Y|0⟩ = i|1⟩, Y|1⟩ = -i|0⟩
"""
self._validate_qubit(qubit)
self.gates.append(Gate("Y", (qubit,)))
return self
def z(self, qubit: int) -> 'QuantumCircuit':
"""
Pauli-Z gate.
Phase flip: Z|0⟩ = |0⟩, Z|1⟩ = -|1⟩
"""
self._validate_qubit(qubit)
self.gates.append(Gate("Z", (qubit,)))
return self
def s(self, qubit: int) -> 'QuantumCircuit':
"""S gate (√Z, π/2 phase)."""
self._validate_qubit(qubit)
self.gates.append(Gate("S", (qubit,)))
return self
def sdg(self, qubit: int) -> 'QuantumCircuit':
"""S-dagger gate (S†, -π/2 phase)."""
self._validate_qubit(qubit)
self.gates.append(Gate("SDG", (qubit,)))
return self
def t(self, qubit: int) -> 'QuantumCircuit':
"""T gate (π/4 phase)."""
self._validate_qubit(qubit)
self.gates.append(Gate("T", (qubit,)))
return self
def tdg(self, qubit: int) -> 'QuantumCircuit':
"""T-dagger gate (T†, -π/4 phase)."""
self._validate_qubit(qubit)
self.gates.append(Gate("TDG", (qubit,)))
return self
def rx(self, theta: float, qubit: int) -> 'QuantumCircuit':
"""
Rotation around X-axis.
Rx(θ) = exp(-iθX/2)
"""
self._validate_qubit(qubit)
self.gates.append(Gate("RX", (qubit,), (theta,)))
return self
def ry(self, theta: float, qubit: int) -> 'QuantumCircuit':
"""
Rotation around Y-axis.
Ry(θ) = exp(-iθY/2)
"""
self._validate_qubit(qubit)
self.gates.append(Gate("RY", (qubit,), (theta,)))
return self
def rz(self, theta: float, qubit: int) -> 'QuantumCircuit':
"""
Rotation around Z-axis.
Rz(θ) = exp(-iθZ/2)
"""
self._validate_qubit(qubit)
self.gates.append(Gate("RZ", (qubit,), (theta,)))
return self
def p(self, phi: float, qubit: int) -> 'QuantumCircuit':
"""
Phase gate.
P(φ) = [[1, 0], [0, e^(iφ)]]
"""
self._validate_qubit(qubit)
self.gates.append(Gate("P", (qubit,), (phi,)))
return self
def u(self, theta: float, phi: float, lam: float, qubit: int) -> 'QuantumCircuit':
"""
General single-qubit unitary gate.
U(θ,φ,λ) = Rz(φ) Ry(θ) Rz(λ)
"""
self._validate_qubit(qubit)
self.gates.append(Gate("U", (qubit,), (theta, phi, lam)))
return self
# =========================================================================
# Two-Qubit Gates
# =========================================================================
def cx(self, control: int, target: int) -> 'QuantumCircuit':
"""
CNOT (Controlled-NOT) gate.
Flips target qubit when control is |1⟩.
"""
self._validate_qubit(control, "control")
self._validate_qubit(target, "target")
if control == target:
raise ValueError("Control and target must be different qubits")
self.gates.append(Gate("CX", (control, target)))
return self
def cnot(self, control: int, target: int) -> 'QuantumCircuit':
"""Alias for cx()."""
return self.cx(control, target)
def cy(self, control: int, target: int) -> 'QuantumCircuit':
"""Controlled-Y gate."""
self._validate_qubit(control, "control")
self._validate_qubit(target, "target")
self.gates.append(Gate("CY", (control, target)))
return self
def cz(self, control: int, target: int) -> 'QuantumCircuit':
"""
Controlled-Z gate.
Applies Z to target when control is |1⟩.
"""
self._validate_qubit(control, "control")
self._validate_qubit(target, "target")
self.gates.append(Gate("CZ", (control, target)))
return self
def swap(self, qubit1: int, qubit2: int) -> 'QuantumCircuit':
"""SWAP gate - exchanges two qubits."""
self._validate_qubit(qubit1, "qubit1")
self._validate_qubit(qubit2, "qubit2")
self.gates.append(Gate("SWAP", (qubit1, qubit2)))
return self
def cp(self, phi: float, control: int, target: int) -> 'QuantumCircuit':
"""Controlled phase gate."""
self._validate_qubit(control, "control")
self._validate_qubit(target, "target")
self.gates.append(Gate("CP", (control, target), (phi,)))
return self
def crx(self, theta: float, control: int, target: int) -> 'QuantumCircuit':
"""Controlled Rx rotation."""
self._validate_qubit(control, "control")
self._validate_qubit(target, "target")
self.gates.append(Gate("CRX", (control, target), (theta,)))
return self
def cry(self, theta: float, control: int, target: int) -> 'QuantumCircuit':
"""Controlled Ry rotation."""
self._validate_qubit(control, "control")
self._validate_qubit(target, "target")
self.gates.append(Gate("CRY", (control, target), (theta,)))
return self
def crz(self, theta: float, control: int, target: int) -> 'QuantumCircuit':
"""Controlled Rz rotation."""
self._validate_qubit(control, "control")
self._validate_qubit(target, "target")
self.gates.append(Gate("CRZ", (control, target), (theta,)))
return self
# =========================================================================
# Three-Qubit Gates
# =========================================================================
def ccx(self, control1: int, control2: int, target: int) -> 'QuantumCircuit':
"""
Toffoli (CCX) gate.
Flips target when both controls are |1⟩.
"""
self._validate_qubit(control1, "control1")
self._validate_qubit(control2, "control2")
self._validate_qubit(target, "target")
self.gates.append(Gate("CCX", (control1, control2, target)))
return self
def toffoli(self, control1: int, control2: int, target: int) -> 'QuantumCircuit':
"""Alias for ccx()."""
return self.ccx(control1, control2, target)
def cswap(self, control: int, target1: int, target2: int) -> 'QuantumCircuit':
"""
Fredkin (CSWAP) gate.
Swaps target1 and target2 when control is |1⟩.
"""
self._validate_qubit(control, "control")
self._validate_qubit(target1, "target1")
self._validate_qubit(target2, "target2")
self.gates.append(Gate("CSWAP", (control, target1, target2)))
return self
def fredkin(self, control: int, target1: int, target2: int) -> 'QuantumCircuit':
"""Alias for cswap()."""
return self.cswap(control, target1, target2)
# =========================================================================
# Measurement
# =========================================================================
def measure(self, qubit: int) -> 'QuantumCircuit':
"""Mark qubit for measurement."""
self._validate_qubit(qubit)
self.gates.append(Gate("MEASURE", (qubit,)))
return self
def measure_all(self) -> 'QuantumCircuit':
"""Mark all qubits for measurement."""
for i in range(self.num_qubits):
self.measure(i)
return self
# =========================================================================
# Barriers and Identity
# =========================================================================
def barrier(self, *qubits: int) -> 'QuantumCircuit':
"""
Insert a barrier (prevents gate fusion across barrier).
If no qubits specified, applies to all.
"""
if not qubits:
qubits = tuple(range(self.num_qubits))
for q in qubits:
self._validate_qubit(q)
self.gates.append(Gate("BARRIER", qubits))
return self
def id(self, qubit: int) -> 'QuantumCircuit':
"""Identity gate (no-op, useful for timing)."""
self._validate_qubit(qubit)
self.gates.append(Gate("I", (qubit,)))
return self
# =========================================================================
# Circuit Composition
# =========================================================================
def compose(self, other: 'QuantumCircuit', qubits: Optional[List[int]] = None) -> 'QuantumCircuit':
"""
Append another circuit to this one.
Args:
other: Circuit to append
qubits: Qubit mapping (if circuits have different sizes)
"""
if qubits is None:
if other.num_qubits != self.num_qubits:
raise ValueError(
f"Circuit sizes don't match: {self.num_qubits} vs {other.num_qubits}. "
"Provide qubit mapping."
)
qubits = list(range(self.num_qubits))
for gate in other.gates:
mapped_targets = tuple(qubits[t] for t in gate.targets)
self.gates.append(Gate(gate.name, mapped_targets, gate.params))
return self
def inverse(self) -> 'QuantumCircuit':
"""
Return the inverse (adjoint) of this circuit.
Reverses gate order and applies gate inverses.
"""
inv = QuantumCircuit(self.num_qubits, f"{self.name}_inv")
for gate in reversed(self.gates):
name = gate.name
targets = gate.targets
params = gate.params
# Handle inverse of each gate type
if name in ("H", "X", "Y", "Z", "CX", "CZ", "SWAP", "CCX", "CSWAP", "I"):
# Self-inverse gates
inv.gates.append(Gate(name, targets, params))
elif name == "S":
inv.gates.append(Gate("SDG", targets))
elif name == "SDG":
inv.gates.append(Gate("S", targets))
elif name == "T":
inv.gates.append(Gate("TDG", targets))
elif name == "TDG":
inv.gates.append(Gate("T", targets))
elif name in ("RX", "RY", "RZ", "P", "CP", "CRX", "CRY", "CRZ"):
# Negate rotation angle
inv.gates.append(Gate(name, targets, (-params[0],)))
elif name == "U":
# U†(θ,φ,λ) = U(-θ,-λ,-φ)
inv.gates.append(Gate("U", targets, (-params[0], -params[2], -params[1])))
elif name in ("BARRIER", "MEASURE"):
# Keep as-is
inv.gates.append(Gate(name, targets, params))
else:
raise ValueError(f"Unknown gate for inverse: {name}")
return inv
# =========================================================================
# Circuit Properties
# =========================================================================
@property
def depth(self) -> int:
"""
Circuit depth (number of gate layers).
Gates on different qubits can execute in parallel.
"""
if not self.gates:
return 0
qubit_depths = [0] * self.num_qubits
for gate in self.gates:
if gate.name in ("BARRIER", "MEASURE"):
continue
max_depth = max(qubit_depths[t] for t in gate.targets)
new_depth = max_depth + 1
for t in gate.targets:
qubit_depths[t] = new_depth
return max(qubit_depths) if qubit_depths else 0
@property
def size(self) -> int:
"""Total number of gates (excluding barriers)."""
return sum(1 for g in self.gates if g.name not in ("BARRIER", "MEASURE"))
def count_ops(self) -> dict:
"""Count occurrences of each gate type."""
counts = {}
for gate in self.gates:
counts[gate.name] = counts.get(gate.name, 0) + 1
return counts
# =========================================================================
# Visualization
# =========================================================================
def draw(self, output: str = "text", figsize: tuple = None):
"""
Draw the circuit.
Args:
output: Output format:
- 'text': ASCII text representation
- 'mpl': Matplotlib figure
- 'latex': LaTeX string (for papers)
figsize: Figure size for matplotlib (width, height)
Returns:
String (text/latex) or matplotlib Figure (mpl)
"""
if output == "text":
return self._draw_text()
elif output == "mpl":
return self._draw_matplotlib(figsize)
elif output == "latex":
return self._draw_latex()
else:
raise ValueError(f"Unsupported output format: {output}. Use 'text', 'mpl', or 'latex'")
def _draw_text(self) -> str:
"""ASCII art circuit diagram."""
lines = [f"QuantumCircuit '{self.name}' ({self.num_qubits} qubits, depth={self.depth})"]
lines.append("=" * 60)
# Build wire diagram
wire_chars = {q: [] for q in range(self.num_qubits)}
for gate in self.gates:
# Add gate to wire diagram
targets = gate.targets
name = gate.name
# Determine gate width
if name in ('H', 'X', 'Y', 'Z', 'S', 'T', 'I'):
symbol = f"[{name}]"
elif name in ('RX', 'RY', 'RZ'):
symbol = f"[{name[1]}({gate.params[0]:.2f})]"
elif name == 'CX':
symbol = None # Special handling
elif name == 'CZ':
symbol = None # Special handling
elif name == 'SWAP':
symbol = None # Special handling
else:
symbol = f"[{name}]"
if len(targets) == 1:
q = targets[0]
wire_chars[q].append(symbol or f"[{name}]")
for other in range(self.num_qubits):
if other != q:
wire_chars[other].append("─" * len(symbol or f"[{name}]"))
elif len(targets) == 2:
q1, q2 = min(targets), max(targets)
if name == 'CX':
ctrl_q = targets[0]
targ_q = targets[1]
for q in range(self.num_qubits):
if q == ctrl_q:
wire_chars[q].append("─●─")
elif q == targ_q:
wire_chars[q].append("─⊕─")
elif q1 < q < q2:
wire_chars[q].append("─│─")
else:
wire_chars[q].append("───")
elif name == 'SWAP':
for q in range(self.num_qubits):
if q in targets:
wire_chars[q].append("─×─")
elif q1 < q < q2:
wire_chars[q].append("─│─")
else:
wire_chars[q].append("───")
else:
for q in range(self.num_qubits):
if q in targets:
wire_chars[q].append(f"[{name}]")
else:
wire_chars[q].append("─" * (len(name) + 2))
# Render wires
lines.append("")
for q in range(self.num_qubits):
wire = f"q{q}: ─" + "".join(wire_chars[q][:20]) + "─" # Limit width
if len(wire_chars[q]) > 20:
wire += "..."
lines.append(wire)
lines.append("")
lines.append("=" * 60)
lines.append(f"Gate counts: {self.count_ops()}")
return "\n".join(lines)
def _draw_matplotlib(self, figsize: tuple = None):
"""
Draw circuit using matplotlib.
Returns matplotlib Figure object.
"""
try:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
except ImportError:
raise ImportError("matplotlib required for 'mpl' output. Install with: pip install matplotlib")
if figsize is None:
figsize = (max(12, self.depth * 0.5), max(4, self.num_qubits * 0.8))
fig, ax = plt.subplots(figsize=figsize)
# Layout parameters
wire_spacing = 1.0
gate_width = 0.6
gate_height = 0.6
x_spacing = 0.8
# Draw qubit wires
max_x = max(self.depth + 2, 5)
for q in range(self.num_qubits):
y = (self.num_qubits - 1 - q) * wire_spacing
ax.plot([0, max_x * x_spacing], [y, y], 'k-', linewidth=1)
ax.text(-0.5, y, f'q{q}', ha='right', va='center', fontsize=10)
# Track x position for each qubit
x_pos = [1.0] * self.num_qubits
# Draw gates
for gate in self.gates:
name = gate.name
targets = gate.targets
if name in ('BARRIER', 'MEASURE'):
continue
# Find x position (max of involved qubits)
gate_x = max(x_pos[q] for q in targets)
if len(targets) == 1:
q = targets[0]
y = (self.num_qubits - 1 - q) * wire_spacing
# Draw gate box
rect = patches.FancyBboxPatch(
(gate_x - gate_width/2, y - gate_height/2),
gate_width, gate_height,
boxstyle="round,pad=0.02",
facecolor='white',
edgecolor='black',
linewidth=1.5
)
ax.add_patch(rect)
# Gate label
label = name
if name in ('RX', 'RY', 'RZ') and gate.params:
label = f"{name}\n({gate.params[0]:.2f})"
ax.text(gate_x, y, label, ha='center', va='center', fontsize=8)
x_pos[q] = gate_x + x_spacing
elif len(targets) == 2:
q1, q2 = targets
y1 = (self.num_qubits - 1 - q1) * wire_spacing
y2 = (self.num_qubits - 1 - q2) * wire_spacing
if name == 'CX':
# Control dot
ax.plot(gate_x, y1, 'ko', markersize=8)
# Target circle with plus
circle = patches.Circle((gate_x, y2), 0.2, fill=False,
edgecolor='black', linewidth=2)
ax.add_patch(circle)
ax.plot([gate_x, gate_x], [y2-0.2, y2+0.2], 'k-', linewidth=2)
ax.plot([gate_x-0.2, gate_x+0.2], [y2, y2], 'k-', linewidth=2)
# Vertical line
ax.plot([gate_x, gate_x], [y1, y2], 'k-', linewidth=1)
elif name == 'CZ':
ax.plot(gate_x, y1, 'ko', markersize=8)
ax.plot(gate_x, y2, 'ko', markersize=8)
ax.plot([gate_x, gate_x], [y1, y2], 'k-', linewidth=1)
elif name == 'SWAP':
ax.plot(gate_x, y1, 'x', markersize=12, mew=2, color='black')
ax.plot(gate_x, y2, 'x', markersize=12, mew=2, color='black')
ax.plot([gate_x, gate_x], [y1, y2], 'k-', linewidth=1)
else:
# Generic two-qubit gate
y_min, y_max = min(y1, y2), max(y1, y2)
rect = patches.FancyBboxPatch(
(gate_x - gate_width/2, y_min - gate_height/4),
gate_width, y_max - y_min + gate_height/2,
boxstyle="round,pad=0.02",
facecolor='lightblue',
edgecolor='black',
linewidth=1.5
)
ax.add_patch(rect)
ax.text(gate_x, (y1 + y2) / 2, name, ha='center', va='center', fontsize=8)
x_pos[q1] = gate_x + x_spacing
x_pos[q2] = gate_x + x_spacing
elif len(targets) == 3:
# Three-qubit gate (CCX, CSWAP)
ys = [(self.num_qubits - 1 - q) * wire_spacing for q in targets]
y_min, y_max = min(ys), max(ys)
if name == 'CCX':
ax.plot(gate_x, ys[0], 'ko', markersize=8)
ax.plot(gate_x, ys[1], 'ko', markersize=8)
circle = patches.Circle((gate_x, ys[2]), 0.2, fill=False,
edgecolor='black', linewidth=2)
ax.add_patch(circle)
ax.plot([gate_x, gate_x], [y_min, y_max], 'k-', linewidth=1)
else:
rect = patches.FancyBboxPatch(
(gate_x - gate_width/2, y_min - gate_height/4),
gate_width, y_max - y_min + gate_height/2,
boxstyle="round,pad=0.02",
facecolor='lightyellow',
edgecolor='black',
linewidth=1.5
)
ax.add_patch(rect)
ax.text(gate_x, (y_min + y_max) / 2, name, ha='center', va='center', fontsize=8)
for q in targets:
x_pos[q] = gate_x + x_spacing
# Configure axes
ax.set_xlim(-1, max_x * x_spacing + 1)
ax.set_ylim(-wire_spacing, self.num_qubits * wire_spacing)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title(f"Circuit: {self.name} ({self.num_qubits} qubits, depth {self.depth})")
plt.tight_layout()
return fig
def _draw_latex(self) -> str:
"""Generate LaTeX/quantikz code for the circuit."""
lines = [r"\begin{quantikz}"]
for q in range(self.num_qubits):
wire = [r"\lstick{$q_" + str(q) + r"$}"]
for gate in self.gates:
if q not in gate.targets:
wire.append(r"\qw")
elif len(gate.targets) == 1:
name = gate.name
if name == 'H':
wire.append(r"\gate{H}")
elif name == 'X':
wire.append(r"\gate{X}")
elif name == 'Y':
wire.append(r"\gate{Y}")
elif name == 'Z':
wire.append(r"\gate{Z}")
elif name == 'S':
wire.append(r"\gate{S}")
elif name == 'T':
wire.append(r"\gate{T}")
elif name in ('RX', 'RY', 'RZ'):
angle = gate.params[0]
wire.append(rf"\gate{{{name}({angle:.2f})}}")
else:
wire.append(rf"\gate{{{name}}}")
elif len(gate.targets) == 2:
if gate.name == 'CX':
if gate.targets[0] == q:
wire.append(r"\ctrl{" + str(gate.targets[1] - q) + r"}")
else:
wire.append(r"\targ{}")
else:
wire.append(rf"\gate{{{gate.name}}}")
wire.append(r"\qw")
lines.append(" & ".join(wire) + r" \\")
lines.append(r"\end{quantikz}")
return "\n".join(lines)
def __repr__(self):
return f"QuantumCircuit(num_qubits={self.num_qubits}, gates={len(self.gates)}, depth={self.depth})"
def __str__(self):
return self.draw()
def __len__(self):
return len(self.gates)
def copy(self) -> 'QuantumCircuit':
"""Return a copy of this circuit."""
qc = QuantumCircuit(self.num_qubits, self.name)
qc.gates = [Gate(g.name, g.targets, g.params) for g in self.gates]
return qc