quantum-debugger 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,219 @@
1
+ """
2
+ State inspection utilities for debugging
3
+ """
4
+
5
+ import numpy as np
6
+ from typing import Dict, List, Tuple
7
+ from quantum_debugger.core.quantum_state import QuantumState
8
+
9
+
10
+ class StateInspector:
11
+ """Utilities for inspecting quantum states during debugging"""
12
+
13
+ @staticmethod
14
+ def get_state_summary(state: QuantumState) -> Dict:
15
+ """
16
+ Get a comprehensive summary of a quantum state
17
+
18
+ Args:
19
+ state: Quantum state to inspect
20
+
21
+ Returns:
22
+ Dictionary with state information
23
+ """
24
+ probabilities = state.get_probabilities()
25
+
26
+ summary = {
27
+ 'num_qubits': state.num_qubits,
28
+ 'dimension': state.dim,
29
+ 'norm': np.linalg.norm(state.state_vector),
30
+ 'entropy': state.entropy(),
31
+ 'is_entangled': state.is_entangled() if state.num_qubits == 2 else None,
32
+ 'max_amplitude': float(np.max(np.abs(state.state_vector))),
33
+ 'nonzero_amplitudes': int(np.sum(np.abs(state.state_vector) > 1e-10)),
34
+ 'most_likely_state': int(np.argmax(probabilities)),
35
+ 'max_probability': float(np.max(probabilities))
36
+ }
37
+
38
+ return summary
39
+
40
+ @staticmethod
41
+ def get_measurement_stats(state: QuantumState) -> Dict[str, float]:
42
+ """
43
+ Get measurement statistics for all computational basis states
44
+
45
+ Args:
46
+ state: Quantum state
47
+
48
+ Returns:
49
+ Dictionary mapping basis states to probabilities
50
+ """
51
+ probabilities = state.get_probabilities()
52
+ stats = {}
53
+
54
+ for i, prob in enumerate(probabilities):
55
+ if prob > 1e-10: # Only include non-negligible probabilities
56
+ binary = format(i, f'0{state.num_qubits}b')
57
+ stats[binary] = float(prob)
58
+
59
+ return dict(sorted(stats.items(), key=lambda x: x[1], reverse=True))
60
+
61
+ @staticmethod
62
+ def get_qubit_probabilities(state: QuantumState, qubit: int) -> Tuple[float, float]:
63
+ """
64
+ Get measurement probabilities for a specific qubit
65
+
66
+ Args:
67
+ state: Quantum state
68
+ qubit: Qubit index
69
+
70
+ Returns:
71
+ (probability of 0, probability of 1)
72
+ """
73
+ prob_0 = state.get_measurement_probability(qubit, 0)
74
+ prob_1 = state.get_measurement_probability(qubit, 1)
75
+ return (prob_0, prob_1)
76
+
77
+ @staticmethod
78
+ def get_amplitude_info(state: QuantumState) -> List[Dict]:
79
+ """
80
+ Get detailed amplitude information for all basis states
81
+
82
+ Args:
83
+ state: Quantum state
84
+
85
+ Returns:
86
+ List of dictionaries with amplitude information
87
+ """
88
+ amplitudes = []
89
+
90
+ for i, amplitude in enumerate(state.state_vector):
91
+ if abs(amplitude) > 1e-10:
92
+ binary = format(i, f'0{state.num_qubits}b')
93
+ amplitudes.append({
94
+ 'basis_state': binary,
95
+ 'index': i,
96
+ 'amplitude': complex(amplitude),
97
+ 'magnitude': float(abs(amplitude)),
98
+ 'phase': float(np.angle(amplitude)),
99
+ 'probability': float(abs(amplitude) ** 2)
100
+ })
101
+
102
+ return sorted(amplitudes, key=lambda x: x['probability'], reverse=True)
103
+
104
+ @staticmethod
105
+ def compare_states(state1: QuantumState, state2: QuantumState) -> Dict:
106
+ """
107
+ Compare two quantum states
108
+
109
+ Args:
110
+ state1: First quantum state
111
+ state2: Second quantum state
112
+
113
+ Returns:
114
+ Dictionary with comparison metrics
115
+ """
116
+ if state1.num_qubits != state2.num_qubits:
117
+ raise ValueError("States must have same number of qubits")
118
+
119
+ fidelity = state1.fidelity(state2)
120
+
121
+ # Calculate trace distance
122
+ trace_distance = np.linalg.norm(
123
+ state1.state_vector - state2.state_vector
124
+ ) / np.sqrt(2)
125
+
126
+ return {
127
+ 'fidelity': float(fidelity),
128
+ 'trace_distance': float(trace_distance),
129
+ 'are_equal': fidelity > 0.9999
130
+ }
131
+
132
+ @staticmethod
133
+ def check_superposition(state: QuantumState, qubit: int, threshold: float = 0.1) -> bool:
134
+ """
135
+ Check if a qubit is in superposition
136
+
137
+ Args:
138
+ state: Quantum state
139
+ qubit: Qubit index
140
+ threshold: Threshold for considering a state in superposition
141
+
142
+ Returns:
143
+ True if qubit is in superposition
144
+ """
145
+ prob_0, prob_1 = StateInspector.get_qubit_probabilities(state, qubit)
146
+
147
+ # In superposition if neither probability is close to 0 or 1
148
+ return threshold < prob_0 < (1 - threshold) and threshold < prob_1 < (1 - threshold)
149
+
150
+ @staticmethod
151
+ def format_state_string(state: QuantumState, max_terms: int = 10) -> str:
152
+ """
153
+ Format quantum state as readable string
154
+
155
+ Args:
156
+ state: Quantum state
157
+ max_terms: Maximum number of terms to show
158
+
159
+ Returns:
160
+ Formatted string representation
161
+ """
162
+ amplitudes = StateInspector.get_amplitude_info(state)
163
+
164
+ if not amplitudes:
165
+ return "|0>"
166
+
167
+ terms = []
168
+ for amp_info in amplitudes[:max_terms]:
169
+ magnitude = amp_info['magnitude']
170
+ phase = amp_info['phase']
171
+ basis = amp_info['basis_state']
172
+
173
+ # Format coefficient
174
+ if abs(phase) < 1e-10:
175
+ coef = f"{magnitude:.3f}"
176
+ elif abs(phase - np.pi) < 1e-10:
177
+ coef = f"-{magnitude:.3f}"
178
+ else:
179
+ coef = f"{magnitude:.3f}e^({phase:.2f}i)"
180
+
181
+ terms.append(f"{coef}|{basis}>")
182
+
183
+ if len(amplitudes) > max_terms:
184
+ terms.append("...")
185
+
186
+ return " + ".join(terms)
187
+
188
+ @staticmethod
189
+ def print_state_info(state: QuantumState):
190
+ """
191
+ Print comprehensive state information to console
192
+
193
+ Args:
194
+ state: Quantum state to inspect
195
+ """
196
+ print("=" * 60)
197
+ print("QUANTUM STATE INSPECTION")
198
+ print("=" * 60)
199
+
200
+ summary = StateInspector.get_state_summary(state)
201
+ print(f"\nState Vector: {StateInspector.format_state_string(state)}")
202
+ print(f"\nNumber of Qubits: {summary['num_qubits']}")
203
+ print(f"Dimension: {summary['dimension']}")
204
+ print(f"Entropy: {summary['entropy']:.4f}")
205
+
206
+ if summary['is_entangled'] is not None:
207
+ print(f"Entangled: {summary['is_entangled']}")
208
+
209
+ print(f"\nMeasurement Statistics:")
210
+ stats = StateInspector.get_measurement_stats(state)
211
+ for basis_state, prob in list(stats.items())[:5]:
212
+ print(f" |{basis_state}>: {prob:.4f} ({prob*100:.2f}%)")
213
+
214
+ print(f"\nPer-Qubit Probabilities:")
215
+ for q in range(state.num_qubits):
216
+ prob_0, prob_1 = StateInspector.get_qubit_probabilities(state, q)
217
+ print(f" Qubit {q}: |0>={prob_0:.4f}, |1>={prob_1:.4f}")
218
+
219
+ print("=" * 60)
@@ -0,0 +1,6 @@
1
+ """Profiler module for quantum circuit analysis"""
2
+
3
+ from quantum_debugger.profiler.profiler import CircuitProfiler
4
+ from quantum_debugger.profiler.metrics import CircuitMetrics
5
+
6
+ __all__ = ["CircuitProfiler", "CircuitMetrics"]
@@ -0,0 +1,129 @@
1
+ """
2
+ Circuit metrics and analysis
3
+ """
4
+
5
+ from typing import Dict, List
6
+ from quantum_debugger.core.circuit import QuantumCircuit
7
+
8
+
9
+ class CircuitMetrics:
10
+ """Container for circuit analysis metrics"""
11
+
12
+ def __init__(self, circuit: QuantumCircuit):
13
+ self.circuit = circuit
14
+ self._compute_metrics()
15
+
16
+ def _compute_metrics(self):
17
+ """Compute all metrics"""
18
+ self.num_qubits = self.circuit.num_qubits
19
+ self.total_gates = len(self.circuit.gates)
20
+ self.depth = self.circuit.depth()
21
+
22
+ # Gate type counts
23
+ self.gate_counts = {}
24
+ for gate in self.circuit.gates:
25
+ name = gate.name
26
+ self.gate_counts[name] = self.gate_counts.get(name, 0) + 1
27
+
28
+ # Special counts
29
+ self.single_qubit_gates = sum(
30
+ 1 for g in self.circuit.gates if g.num_qubits == 1
31
+ )
32
+ self.two_qubit_gates = sum(
33
+ 1 for g in self.circuit.gates if g.num_qubits == 2
34
+ )
35
+ self.three_qubit_gates = sum(
36
+ 1 for g in self.circuit.gates if g.num_qubits == 3
37
+ )
38
+
39
+ self.cnot_count = self.gate_counts.get('CNOT', 0) + self.gate_counts.get('CX', 0)
40
+ self.t_count = self.gate_counts.get('T', 0)
41
+
42
+ # Critical path analysis
43
+ self.critical_path = self._find_critical_path()
44
+
45
+ # Parallelism factor
46
+ self.parallelism = self.total_gates / self.depth if self.depth > 0 else 0
47
+
48
+ def _find_critical_path(self) -> List[int]:
49
+ """Find the critical path (longest dependency chain)"""
50
+ if not self.circuit.gates:
51
+ return []
52
+
53
+ # Track dependencies
54
+ qubit_last_gate = {}
55
+ gate_start_time = []
56
+
57
+ for i, gate in enumerate(self.circuit.gates):
58
+ # Find latest dependency
59
+ start_time = 0
60
+ for q in gate.qubits:
61
+ if q in qubit_last_gate:
62
+ start_time = max(start_time, gate_start_time[qubit_last_gate[q]] + 1)
63
+
64
+ gate_start_time.append(start_time)
65
+
66
+ # Update last gate for each qubit
67
+ for q in gate.qubits:
68
+ qubit_last_gate[q] = i
69
+
70
+ # Find path with maximum start time
71
+ max_time = max(gate_start_time)
72
+ critical_gates = [i for i, t in enumerate(gate_start_time) if t == max_time]
73
+
74
+ return critical_gates
75
+
76
+ def get_summary(self) -> Dict:
77
+ """Get summary dictionary of all metrics"""
78
+ return {
79
+ 'num_qubits': self.num_qubits,
80
+ 'total_gates': self.total_gates,
81
+ 'depth': self.depth,
82
+ 'single_qubit_gates': self.single_qubit_gates,
83
+ 'two_qubit_gates': self.two_qubit_gates,
84
+ 'three_qubit_gates': self.three_qubit_gates,
85
+ 'cnot_count': self.cnot_count,
86
+ 't_count': self.t_count,
87
+ 'parallelism_factor': self.parallelism,
88
+ 'gate_counts': self.gate_counts,
89
+ 'critical_path_length': len(self.critical_path)
90
+ }
91
+
92
+ def estimate_execution_time(self, gate_time: float = 1.0,
93
+ cnot_time: float = 10.0) -> float:
94
+ """
95
+ Estimate execution time on quantum hardware
96
+
97
+ Args:
98
+ gate_time: Time for single-qubit gate (μs)
99
+ cnot_time: Time for CNOT gate (μs)
100
+
101
+ Returns:
102
+ Estimated time in microseconds
103
+ """
104
+ single_time = self.single_qubit_gates * gate_time
105
+ two_time = self.two_qubit_gates * cnot_time
106
+
107
+ return single_time + two_time
108
+
109
+ def estimate_error_rate(self, single_error: float = 0.001,
110
+ cnot_error: float = 0.01) -> float:
111
+ """
112
+ Estimate cumulative error rate
113
+
114
+ Args:
115
+ single_error: Error rate for single-qubit gates
116
+ cnot_error: Error rate for CNOT gates
117
+
118
+ Returns:
119
+ Estimated cumulative error rate
120
+ """
121
+ # Simplified error model (multiplicative)
122
+ single_fidelity = (1 - single_error) ** self.single_qubit_gates
123
+ two_fidelity = (1 - cnot_error) ** self.two_qubit_gates
124
+
125
+ total_fidelity = single_fidelity * two_fidelity
126
+ return 1 - total_fidelity
127
+
128
+ def __repr__(self):
129
+ return f"CircuitMetrics({self.num_qubits} qubits, {self.total_gates} gates, depth {self.depth})"
@@ -0,0 +1,174 @@
1
+ """
2
+ Circuit profiler for performance analysis
3
+ """
4
+
5
+ from typing import Dict, List
6
+ from quantum_debugger.core.circuit import QuantumCircuit
7
+ from quantum_debugger.profiler.metrics import CircuitMetrics
8
+
9
+
10
+ class CircuitProfiler:
11
+ """Profiler for analyzing quantum circuit performance"""
12
+
13
+ def __init__(self, circuit: QuantumCircuit):
14
+ """
15
+ Initialize profiler
16
+
17
+ Args:
18
+ circuit: Quantum circuit to profile
19
+ """
20
+ self.circuit = circuit
21
+ self.metrics = CircuitMetrics(circuit)
22
+
23
+ def analyze(self) -> CircuitMetrics:
24
+ """
25
+ Analyze the circuit and return metrics
26
+
27
+ Returns:
28
+ CircuitMetrics object with analysis results
29
+ """
30
+ return self.metrics
31
+
32
+ def get_optimization_suggestions(self) -> List[str]:
33
+ """
34
+ Get suggestions for circuit optimization
35
+
36
+ Returns:
37
+ List of optimization suggestions
38
+ """
39
+ suggestions = []
40
+
41
+ # High CNOT count
42
+ if self.metrics.cnot_count > self.metrics.num_qubits * 3:
43
+ suggestions.append(
44
+ f"⚠️ High CNOT count ({self.metrics.cnot_count}). "
45
+ "Consider CNOT reduction techniques."
46
+ )
47
+
48
+ # High T count
49
+ if self.metrics.t_count > self.metrics.num_qubits * 2:
50
+ suggestions.append(
51
+ f"⚠️ High T-gate count ({self.metrics.t_count}). "
52
+ "T-gates are expensive on fault-tolerant hardware."
53
+ )
54
+
55
+ # High depth
56
+ if self.metrics.depth > self.metrics.num_qubits * 5:
57
+ suggestions.append(
58
+ f"⚠️ Circuit depth ({self.metrics.depth}) is high. "
59
+ "Consider parallelizing gates."
60
+ )
61
+
62
+ # Low parallelism
63
+ if self.metrics.parallelism < 1.5 and self.metrics.num_qubits > 2:
64
+ suggestions.append(
65
+ f"💡 Low parallelism factor ({self.metrics.parallelism:.2f}). "
66
+ "Look for opportunities to execute gates in parallel."
67
+ )
68
+
69
+ # Consecutive gates on same qubit
70
+ consecutive = self._find_consecutive_single_qubit_gates()
71
+ if consecutive:
72
+ suggestions.append(
73
+ f"💡 Found {len(consecutive)} sequences of consecutive single-qubit gates. "
74
+ "These could be combined into single rotations."
75
+ )
76
+
77
+ if not suggestions:
78
+ suggestions.append("✅ Circuit appears well optimized!")
79
+
80
+ return suggestions
81
+
82
+ def _find_consecutive_single_qubit_gates(self) -> List[tuple]:
83
+ """Find consecutive single-qubit gates on the same qubit"""
84
+ consecutive = []
85
+
86
+ for q in range(self.circuit.num_qubits):
87
+ sequence = []
88
+ for i, gate in enumerate(self.circuit.gates):
89
+ if gate.qubits == [q] and gate.num_qubits == 1:
90
+ sequence.append(i)
91
+ else:
92
+ if len(sequence) >= 2:
93
+ consecutive.append((q, sequence))
94
+ sequence = []
95
+
96
+ if len(sequence) >= 2:
97
+ consecutive.append((q, sequence))
98
+
99
+ return consecutive
100
+
101
+ def compare_with_ideal(self) -> Dict:
102
+ """
103
+ Compare circuit with theoretical ideal
104
+
105
+ Returns:
106
+ Comparison metrics
107
+ """
108
+ # Theoretical minimum for common algorithms
109
+ ideal_depth = self.metrics.num_qubits # Very rough estimate
110
+
111
+ return {
112
+ 'actual_depth': self.metrics.depth,
113
+ 'ideal_depth': ideal_depth,
114
+ 'depth_overhead': self.metrics.depth / ideal_depth if ideal_depth > 0 else 0,
115
+ 'gate_efficiency': self.metrics.parallelism
116
+ }
117
+
118
+ def print_report(self):
119
+ """Print comprehensive profiling report"""
120
+ print("\n" + "=" * 70)
121
+ print(" " * 20 + "CIRCUIT PROFILING REPORT")
122
+ print("=" * 70)
123
+
124
+ # Basic metrics
125
+ print(f"\n📊 BASIC METRICS")
126
+ print(f" Number of Qubits: {self.metrics.num_qubits}")
127
+ print(f" Total Gates: {self.metrics.total_gates}")
128
+ print(f" Circuit Depth: {self.metrics.depth}")
129
+ print(f" Parallelism Factor: {self.metrics.parallelism:.2f}")
130
+
131
+ # Gate breakdown
132
+ print(f"\n🔧 GATE BREAKDOWN")
133
+ print(f" Single-Qubit Gates: {self.metrics.single_qubit_gates}")
134
+ print(f" Two-Qubit Gates: {self.metrics.two_qubit_gates}")
135
+ if self.metrics.three_qubit_gates > 0:
136
+ print(f" Three-Qubit Gates: {self.metrics.three_qubit_gates}")
137
+
138
+ print(f"\n Gate Type Counts:")
139
+ for gate_name, count in sorted(self.metrics.gate_counts.items(),
140
+ key=lambda x: x[1], reverse=True):
141
+ print(f" {gate_name}: {count}")
142
+
143
+ # Special metrics
144
+ print(f"\n⚡ SPECIAL METRICS")
145
+ print(f" CNOT Count: {self.metrics.cnot_count}")
146
+ print(f" T-Gate Count: {self.metrics.t_count}")
147
+ print(f" Critical Path Length: {len(self.metrics.critical_path)} gates")
148
+
149
+ # Performance estimates
150
+ exec_time = self.metrics.estimate_execution_time()
151
+ error_rate = self.metrics.estimate_error_rate()
152
+
153
+ print(f"\n⏱️ PERFORMANCE ESTIMATES")
154
+ print(f" Estimated Execution Time: {exec_time:.2f} μs")
155
+ print(f" Estimated Error Rate: {error_rate*100:.4f}%")
156
+ print(f" Estimated Fidelity: {(1-error_rate)*100:.4f}%")
157
+
158
+ # Optimization suggestions
159
+ suggestions = self.get_optimization_suggestions()
160
+ print(f"\n💡 OPTIMIZATION SUGGESTIONS")
161
+ for suggestion in suggestions:
162
+ print(f" {suggestion}")
163
+
164
+ # Comparison with ideal
165
+ comparison = self.compare_with_ideal()
166
+ print(f"\n📈 COMPARISON WITH IDEAL")
167
+ print(f" Actual Depth: {comparison['actual_depth']}")
168
+ print(f" Theoretical Minimum: {comparison['ideal_depth']}")
169
+ print(f" Depth Overhead: {comparison['depth_overhead']:.2f}x")
170
+
171
+ print("=" * 70 + "\n")
172
+
173
+ def __repr__(self):
174
+ return f"CircuitProfiler({self.metrics})"
@@ -0,0 +1,6 @@
1
+ """Visualization module for quantum states and circuits"""
2
+
3
+ from quantum_debugger.visualization.state_viz import StateVisualizer
4
+ from quantum_debugger.visualization.bloch_sphere import BlochSphere
5
+
6
+ __all__ = ["StateVisualizer", "BlochSphere"]
@@ -0,0 +1,152 @@
1
+ """
2
+ Bloch sphere visualization for single-qubit states
3
+ """
4
+
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ from mpl_toolkits.mplot3d import Axes3D
8
+ from quantum_debugger.core.quantum_state import QuantumState
9
+
10
+
11
+ class BlochSphere:
12
+ """Bloch sphere visualization for single qubits"""
13
+
14
+ @staticmethod
15
+ def plot(state: QuantumState, qubit: int = 0, figsize: tuple = (8, 8)):
16
+ """
17
+ Plot qubit state on Bloch sphere
18
+
19
+ Args:
20
+ state: Quantum state
21
+ qubit: Index of qubit to visualize (for multi-qubit systems)
22
+ figsize: Figure size
23
+ """
24
+ # Get Bloch vector
25
+ x, y, z = state.bloch_vector(qubit)
26
+
27
+ # Create figure
28
+ fig = plt.figure(figsize=figsize)
29
+ ax = fig.add_subplot(111, projection='3d')
30
+
31
+ # Draw sphere
32
+ u = np.linspace(0, 2 * np.pi, 50)
33
+ v = np.linspace(0, np.pi, 50)
34
+ x_sphere = np.outer(np.cos(u), np.sin(v))
35
+ y_sphere = np.outer(np.sin(u), np.sin(v))
36
+ z_sphere = np.outer(np.ones(np.size(u)), np.cos(v))
37
+
38
+ ax.plot_surface(x_sphere, y_sphere, z_sphere,
39
+ color='lightblue', alpha=0.2, linewidth=0)
40
+
41
+ # Draw axes
42
+ axis_length = 1.3
43
+ ax.plot([0, axis_length], [0, 0], [0, 0], 'k-', linewidth=1, alpha=0.5)
44
+ ax.plot([0, 0], [0, axis_length], [0, 0], 'k-', linewidth=1, alpha=0.5)
45
+ ax.plot([0, 0], [0, 0], [0, axis_length], 'k-', linewidth=1, alpha=0.5)
46
+
47
+ # Labels
48
+ ax.text(axis_length, 0, 0, 'X', fontsize=14, fontweight='bold')
49
+ ax.text(0, axis_length, 0, 'Y', fontsize=14, fontweight='bold')
50
+ ax.text(0, 0, axis_length, '|0⟩', fontsize=14, fontweight='bold')
51
+ ax.text(0, 0, -axis_length, '|1⟩', fontsize=14, fontweight='bold')
52
+
53
+ # Draw state vector
54
+ ax.quiver(0, 0, 0, x, y, z, color='red', arrow_length_ratio=0.15,
55
+ linewidth=3, label='State Vector')
56
+
57
+ # Draw projection onto XY plane
58
+ ax.plot([x, x], [y, y], [0, z], 'r--', alpha=0.5, linewidth=1)
59
+ ax.plot([0, x], [0, y], [0, 0], 'r--', alpha=0.5, linewidth=1)
60
+
61
+ # Mark poles
62
+ ax.scatter([0], [0], [1], color='blue', s=100, marker='o', label='|0⟩')
63
+ ax.scatter([0], [0], [-1], color='orange', s=100, marker='o', label='|1⟩')
64
+
65
+ # Mark current state
66
+ ax.scatter([x], [y], [z], color='red', s=150, marker='*',
67
+ label=f'Qubit {qubit}', zorder=10)
68
+
69
+ # Settings
70
+ ax.set_xlim([-1.2, 1.2])
71
+ ax.set_ylim([-1.2, 1.2])
72
+ ax.set_zlim([-1.2, 1.2])
73
+ ax.set_xlabel('X', fontsize=12)
74
+ ax.set_ylabel('Y', fontsize=12)
75
+ ax.set_zlabel('Z', fontsize=12)
76
+ ax.set_title(f'Bloch Sphere - Qubit {qubit}', fontsize=16, fontweight='bold')
77
+ ax.legend(loc='upper right')
78
+
79
+ # Set viewing angle
80
+ ax.view_init(elev=20, azim=45)
81
+
82
+ plt.tight_layout()
83
+ plt.show()
84
+
85
+ @staticmethod
86
+ def plot_trajectory(states: list, qubit: int = 0, figsize: tuple = (8, 8)):
87
+ """
88
+ Plot trajectory of state evolution on Bloch sphere
89
+
90
+ Args:
91
+ states: List of QuantumState objects
92
+ qubit: Qubit index to visualize
93
+ figsize: Figure size
94
+ """
95
+ # Get Bloch vectors for all states
96
+ vectors = [s.bloch_vector(qubit) for s in states]
97
+ xs, ys, zs = zip(*vectors)
98
+
99
+ # Create figure
100
+ fig = plt.figure(figsize=figsize)
101
+ ax = fig.add_subplot(111, projection='3d')
102
+
103
+ # Draw sphere
104
+ u = np.linspace(0, 2 * np.pi, 50)
105
+ v = np.linspace(0, np.pi, 50)
106
+ x_sphere = np.outer(np.cos(u), np.sin(v))
107
+ y_sphere = np.outer(np.sin(u), np.sin(v))
108
+ z_sphere = np.outer(np.ones(np.size(u)), np.cos(v))
109
+
110
+ ax.plot_surface(x_sphere, y_sphere, z_sphere,
111
+ color='lightblue', alpha=0.15, linewidth=0)
112
+
113
+ # Draw axes
114
+ axis_length = 1.3
115
+ ax.plot([0, axis_length], [0, 0], [0, 0], 'k-', linewidth=1, alpha=0.5)
116
+ ax.plot([0, 0], [0, axis_length], [0, 0], 'k-', linewidth=1, alpha=0.5)
117
+ ax.plot([0, 0], [0, 0], [0, axis_length], 'k-', linewidth=1, alpha=0.5)
118
+
119
+ # Labels
120
+ ax.text(axis_length, 0, 0, 'X', fontsize=14, fontweight='bold')
121
+ ax.text(0, axis_length, 0, 'Y', fontsize=14, fontweight='bold')
122
+ ax.text(0, 0, axis_length, '|0⟩', fontsize=14, fontweight='bold')
123
+ ax.text(0, 0, -axis_length, '|1⟩', fontsize=14, fontweight='bold')
124
+
125
+ # Draw trajectory
126
+ ax.plot(xs, ys, zs, 'r-', linewidth=2, alpha=0.7, label='Trajectory')
127
+
128
+ # Mark start and end
129
+ ax.scatter([xs[0]], [ys[0]], [zs[0]], color='green', s=150,
130
+ marker='o', label='Start', zorder=10)
131
+ ax.scatter([xs[-1]], [ys[-1]], [zs[-1]], color='red', s=150,
132
+ marker='*', label='End', zorder=10)
133
+
134
+ # Mark intermediate points
135
+ if len(states) > 2:
136
+ ax.scatter(xs[1:-1], ys[1:-1], zs[1:-1], color='orange',
137
+ s=50, alpha=0.6)
138
+
139
+ # Settings
140
+ ax.set_xlim([-1.2, 1.2])
141
+ ax.set_ylim([-1.2, 1.2])
142
+ ax.set_zlim([-1.2, 1.2])
143
+ ax.set_xlabel('X', fontsize=12)
144
+ ax.set_ylabel('Y', fontsize=12)
145
+ ax.set_zlabel('Z', fontsize=12)
146
+ ax.set_title(f'State Evolution - Qubit {qubit}', fontsize=16, fontweight='bold')
147
+ ax.legend(loc='upper right')
148
+
149
+ ax.view_init(elev=20, azim=45)
150
+
151
+ plt.tight_layout()
152
+ plt.show()