Deminiko
Initial commit: QuantumArchitect-MCP quantum circuit MCP server with Gradio UI
6ce350d
"""
Complexity Score - Calculates circuit complexity metrics.
Includes depth, gate count, width, and derived metrics.
"""
from typing import Any
import math
def score_complexity(circuit_data: dict[str, Any]) -> dict[str, Any]:
"""
Calculate comprehensive complexity metrics for a quantum circuit.
Args:
circuit_data: Circuit dictionary with gates
Returns:
Complexity scores and metrics
"""
gates = circuit_data.get("gates", [])
num_qubits = circuit_data.get("num_qubits", 0)
# Basic metrics
gate_analysis = _analyze_gates(gates, num_qubits)
depth = gate_analysis["depth"]
# Derived metrics
circuit_volume = num_qubits * depth
gate_density = gate_analysis["total_gates"] / circuit_volume if circuit_volume > 0 else 0
# Two-qubit gate ratio (indicator of entanglement complexity)
two_qubit_ratio = (
gate_analysis["two_qubit_gates"] / gate_analysis["total_gates"]
if gate_analysis["total_gates"] > 0 else 0
)
# T-gate count (relevant for fault-tolerant quantum computing)
t_count = gate_analysis["gate_counts"].get("t", 0) + gate_analysis["gate_counts"].get("tdg", 0)
# Calculate overall complexity score (0-100)
complexity_score = _calculate_complexity_score(
num_qubits, depth, gate_analysis["total_gates"],
gate_analysis["two_qubit_gates"], t_count
)
# Complexity class
if complexity_score < 20:
complexity_class = "Trivial"
elif complexity_score < 40:
complexity_class = "Simple"
elif complexity_score < 60:
complexity_class = "Moderate"
elif complexity_score < 80:
complexity_class = "Complex"
else:
complexity_class = "Very Complex"
return {
"complexity_score": round(complexity_score, 1),
"complexity_class": complexity_class,
"basic_metrics": {
"width": num_qubits,
"depth": depth,
"total_gates": gate_analysis["total_gates"],
"single_qubit_gates": gate_analysis["single_qubit_gates"],
"two_qubit_gates": gate_analysis["two_qubit_gates"],
"multi_qubit_gates": gate_analysis["multi_qubit_gates"],
},
"derived_metrics": {
"circuit_volume": circuit_volume,
"gate_density": round(gate_density, 3),
"two_qubit_ratio": round(two_qubit_ratio, 3),
"t_count": t_count,
"cnot_count": gate_analysis["gate_counts"].get("cx", 0) +
gate_analysis["gate_counts"].get("cnot", 0),
},
"gate_breakdown": gate_analysis["gate_counts"],
"qubit_utilization": gate_analysis["qubit_utilization"],
"critical_path": {
"max_depth_qubit": gate_analysis["max_depth_qubit"],
"min_depth_qubit": gate_analysis["min_depth_qubit"],
"depth_variance": gate_analysis["depth_variance"],
},
}
def _analyze_gates(gates: list[dict[str, Any]], num_qubits: int) -> dict[str, Any]:
"""Analyze gate structure of the circuit."""
gate_counts: dict[str, int] = {}
qubit_depths: list[int] = [0] * num_qubits
qubit_gate_counts: list[int] = [0] * num_qubits
single_qubit = 0
two_qubit = 0
multi_qubit = 0
total = 0
two_qubit_names = {"cx", "cnot", "cy", "cz", "swap", "iswap", "cp", "crx", "cry", "crz"}
three_qubit_names = {"ccx", "toffoli", "cswap", "fredkin"}
for gate in gates:
name = gate.get("name", "unknown").lower()
qubits = gate.get("qubits", [])
if name in ("barrier", "measure", "reset"):
continue
gate_counts[name] = gate_counts.get(name, 0) + 1
total += 1
# Update depths
max_depth = max((qubit_depths[q] for q in qubits if q < num_qubits), default=0)
for q in qubits:
if q < num_qubits:
qubit_depths[q] = max_depth + 1
qubit_gate_counts[q] += 1
# Count by type
if name in two_qubit_names:
two_qubit += 1
elif name in three_qubit_names:
multi_qubit += 1
elif len(qubits) == 1:
single_qubit += 1
elif len(qubits) == 2:
two_qubit += 1
else:
multi_qubit += 1
depth = max(qubit_depths) if qubit_depths else 0
# Calculate variance in depth
if num_qubits > 0:
mean_depth = sum(qubit_depths) / num_qubits
variance = sum((d - mean_depth) ** 2 for d in qubit_depths) / num_qubits
else:
variance = 0
return {
"gate_counts": gate_counts,
"total_gates": total,
"single_qubit_gates": single_qubit,
"two_qubit_gates": two_qubit,
"multi_qubit_gates": multi_qubit,
"depth": depth,
"qubit_utilization": {
"depths": qubit_depths,
"gate_counts": qubit_gate_counts,
},
"max_depth_qubit": qubit_depths.index(max(qubit_depths)) if qubit_depths and max(qubit_depths) > 0 else 0,
"min_depth_qubit": qubit_depths.index(min(qubit_depths)) if qubit_depths else 0,
"depth_variance": round(variance, 2),
}
def _calculate_complexity_score(
width: int,
depth: int,
total_gates: int,
two_qubit_gates: int,
t_count: int
) -> float:
"""Calculate overall complexity score (0-100)."""
# Width contribution (log scale)
width_score = min(25, math.log2(width + 1) * 5)
# Depth contribution
depth_score = min(25, math.log2(depth + 1) * 4)
# Gate count contribution
gate_score = min(25, math.log2(total_gates + 1) * 3)
# Two-qubit gate contribution (most impactful for complexity)
two_q_score = min(15, math.log2(two_qubit_gates + 1) * 4)
# T-count contribution (for fault-tolerance)
t_score = min(10, math.log2(t_count + 1) * 3)
return width_score + depth_score + gate_score + two_q_score + t_score
def compare_circuits_complexity(circuits: list[dict[str, Any]]) -> dict[str, Any]:
"""
Compare complexity across multiple circuits.
Args:
circuits: List of circuit dictionaries
Returns:
Comparative analysis
"""
results = []
for i, circuit in enumerate(circuits):
name = circuit.get("name", f"Circuit {i+1}")
score = score_complexity(circuit)
results.append({
"name": name,
"complexity_score": score["complexity_score"],
"class": score["complexity_class"],
"width": score["basic_metrics"]["width"],
"depth": score["basic_metrics"]["depth"],
"gates": score["basic_metrics"]["total_gates"],
})
# Sort by complexity
results.sort(key=lambda x: x["complexity_score"])
return {
"circuits_analyzed": len(circuits),
"comparison": results,
"simplest": results[0] if results else None,
"most_complex": results[-1] if results else None,
}
def estimate_transpilation_overhead(
circuit_data: dict[str, Any],
target_basis: list[str] | None = None
) -> dict[str, Any]:
"""
Estimate overhead from transpiling to a native gate set.
Args:
circuit_data: Circuit dictionary
target_basis: Target native gate set (default: IBM native gates)
Returns:
Estimated transpilation overhead
"""
if target_basis is None:
target_basis = ["cx", "id", "rz", "sx", "x"]
gates = circuit_data.get("gates", [])
# Count gates that need decomposition
needs_decomposition = 0
estimated_additional_gates = 0
# Decomposition estimates (rough)
decomposition_costs = {
"h": 2, # H = Rz(π) · SX · Rz(π/2)
"y": 2, # Y needs decomposition
"s": 1, # S = Rz(π/2)
"sdg": 1,
"t": 1, # T = Rz(π/4)
"tdg": 1,
"rx": 3, # Rx needs decomposition
"ry": 4, # Ry needs decomposition
"swap": 3, # SWAP = 3 CNOTs
"iswap": 4,
"cz": 3, # CZ = H·CX·H
"cy": 4,
"ccx": 15, # Toffoli ≈ 6-15 two-qubit gates
"cswap": 20,
}
for gate in gates:
name = gate.get("name", "").lower()
if name in ("barrier", "measure", "reset"):
continue
if name not in target_basis:
needs_decomposition += 1
estimated_additional_gates += decomposition_costs.get(name, 2)
original_gate_count = len([g for g in gates
if g.get("name", "").lower() not in ("barrier", "measure", "reset")])
estimated_final_count = original_gate_count + estimated_additional_gates - needs_decomposition
overhead_ratio = estimated_final_count / original_gate_count if original_gate_count > 0 else 1
return {
"target_basis": target_basis,
"original_gate_count": original_gate_count,
"gates_needing_decomposition": needs_decomposition,
"estimated_additional_gates": estimated_additional_gates,
"estimated_final_gate_count": estimated_final_count,
"overhead_ratio": round(overhead_ratio, 2),
"overhead_percentage": round((overhead_ratio - 1) * 100, 1),
"note": "Actual transpilation may vary based on optimization level",
}