Spaces:
Running
Running
| """ | |
| 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", | |
| } | |