quantum-study-utils 0.2.0__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.
- quantum_study_utils-0.2.0.dist-info/METADATA +513 -0
- quantum_study_utils-0.2.0.dist-info/RECORD +14 -0
- quantum_study_utils-0.2.0.dist-info/WHEEL +5 -0
- quantum_study_utils-0.2.0.dist-info/licenses/LICENSE +21 -0
- quantum_study_utils-0.2.0.dist-info/top_level.txt +1 -0
- quantum_utils/__init__.py +42 -0
- quantum_utils/display/__init__.py +48 -0
- quantum_utils/display/_utils.py +137 -0
- quantum_utils/display/display.py +388 -0
- quantum_utils/display/formatters.py +197 -0
- quantum_utils/display/templates.py +225 -0
- quantum_utils/formatting.py +136 -0
- quantum_utils/measurements.py +268 -0
- quantum_utils/visualization.py +371 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Formatters for quantum states and numbers in LaTeX notation.
|
|
3
|
+
|
|
4
|
+
Converts quantum objects into mathematically-formatted strings suitable
|
|
5
|
+
for display in Jupyter notebooks and educational contexts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from typing import List, Tuple
|
|
10
|
+
from ._utils import (
|
|
11
|
+
as_complex_array,
|
|
12
|
+
validate_qubit_count,
|
|
13
|
+
amplitude_is_near_zero,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AcademicNumberFormatter:
|
|
18
|
+
"""Format numbers using academic/analytic notation."""
|
|
19
|
+
|
|
20
|
+
SQRT2_INV = 1.0 / np.sqrt(2)
|
|
21
|
+
EPSILON = 1e-10
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def format(number: complex, precision: int = 3) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Format a real or complex number using analytic notation.
|
|
27
|
+
|
|
28
|
+
Special handling for:
|
|
29
|
+
- ±1/√2: Uses LaTeX fraction notation
|
|
30
|
+
- Integers: No decimal places
|
|
31
|
+
- Pure imaginary: Shows 'i' notation
|
|
32
|
+
- Complex: Shows full form
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
number: Number to format (converted to complex)
|
|
36
|
+
precision: Decimal precision for non-special values
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
LaTeX-formatted string representation
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> fmt = AcademicNumberFormatter()
|
|
43
|
+
>>> fmt.format(1/np.sqrt(2))
|
|
44
|
+
'\\frac{1}{\\sqrt{2}}'
|
|
45
|
+
>>> fmt.format(1+2j)
|
|
46
|
+
'(1.000+2.000i)'
|
|
47
|
+
"""
|
|
48
|
+
number = complex(number)
|
|
49
|
+
|
|
50
|
+
# Handle purely real numbers
|
|
51
|
+
if np.isclose(number.imag, 0, atol=AcademicNumberFormatter.EPSILON):
|
|
52
|
+
real = number.real
|
|
53
|
+
|
|
54
|
+
# Check for ±1/√2
|
|
55
|
+
if np.isclose(abs(real), AcademicNumberFormatter.SQRT2_INV):
|
|
56
|
+
return (
|
|
57
|
+
r"\frac{1}{\sqrt{2}}" if real > 0
|
|
58
|
+
else r"-\frac{1}{\sqrt{2}}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Check for integers
|
|
62
|
+
if np.isclose(real, round(real)):
|
|
63
|
+
return str(int(round(real)))
|
|
64
|
+
|
|
65
|
+
# Decimal approximation
|
|
66
|
+
return f"{real:.{precision}g}"
|
|
67
|
+
|
|
68
|
+
# Handle purely imaginary numbers
|
|
69
|
+
if np.isclose(number.real, 0, atol=AcademicNumberFormatter.EPSILON):
|
|
70
|
+
return f"{number.imag:.{precision}g}i"
|
|
71
|
+
|
|
72
|
+
# Handle full complex numbers
|
|
73
|
+
sign = "+" if number.imag >= 0 else "-"
|
|
74
|
+
return f"({number.real:.{precision}g}{sign}{abs(number.imag):.{precision}g}i)"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class DiracExpressionGenerator:
|
|
78
|
+
"""Convert statevectors into Dirac notation (ket) expressions."""
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def generate(statevector) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Convert a statevector into a sum of computational basis kets.
|
|
84
|
+
|
|
85
|
+
Filters out near-zero amplitudes and formats non-zero terms using
|
|
86
|
+
academic number notation. Terms are concatenated with appropriate
|
|
87
|
+
signs.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
statevector: Statevector or Qiskit Statevector object
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dirac notation expression as string
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If not a 1D vector
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> sv = np.array([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)])
|
|
100
|
+
>>> gen = DiracExpressionGenerator()
|
|
101
|
+
>>> gen.generate(sv)
|
|
102
|
+
'\\frac{1}{\\sqrt{2}}|00\\rangle + \\frac{1}{\\sqrt{2}}|11\\rangle'
|
|
103
|
+
"""
|
|
104
|
+
vector = as_complex_array(statevector)
|
|
105
|
+
|
|
106
|
+
if vector.ndim != 1:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Expected 1D statevector, got {vector.ndim}D array"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
n_qubits = validate_qubit_count(len(vector))
|
|
112
|
+
terms: List[str] = []
|
|
113
|
+
formatter = AcademicNumberFormatter()
|
|
114
|
+
|
|
115
|
+
for index, amplitude in enumerate(vector):
|
|
116
|
+
# Skip near-zero amplitudes
|
|
117
|
+
if amplitude_is_near_zero(amplitude):
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# Format basis state
|
|
121
|
+
ket = rf"|{index:0{n_qubits}b}\rangle"
|
|
122
|
+
|
|
123
|
+
# Handle special amplitude cases
|
|
124
|
+
if np.isclose(amplitude, 1):
|
|
125
|
+
term = ket
|
|
126
|
+
elif np.isclose(amplitude, -1):
|
|
127
|
+
term = f"-{ket}"
|
|
128
|
+
else:
|
|
129
|
+
term = f"{formatter.format(amplitude)}{ket}"
|
|
130
|
+
|
|
131
|
+
terms.append(term)
|
|
132
|
+
|
|
133
|
+
if not terms:
|
|
134
|
+
return "0"
|
|
135
|
+
|
|
136
|
+
# Join terms with proper signs
|
|
137
|
+
expression = " + ".join(terms)
|
|
138
|
+
# Replace leading "+ -" with "-"
|
|
139
|
+
expression = expression.replace(" + -", " - ")
|
|
140
|
+
|
|
141
|
+
return expression
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class LatexMatrixFormatter:
|
|
145
|
+
"""Format matrices in LaTeX matrix notation."""
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def format_vector(vector: np.ndarray) -> str:
|
|
149
|
+
"""
|
|
150
|
+
Format a 1D vector in LaTeX matrix notation.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
vector: 1D numpy array
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
LaTeX bmatrix string
|
|
157
|
+
"""
|
|
158
|
+
formatter = AcademicNumberFormatter()
|
|
159
|
+
rows = r" \\ ".join(formatter.format(elem) for elem in vector)
|
|
160
|
+
return rf"\begin{{bmatrix}} {rows} \end{{bmatrix}}"
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def format_matrix(matrix: np.ndarray) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Format a 2D matrix in LaTeX matrix notation.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
matrix: 2D numpy array
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
LaTeX bmatrix string
|
|
172
|
+
"""
|
|
173
|
+
formatter = AcademicNumberFormatter()
|
|
174
|
+
rows = r" \\ ".join(
|
|
175
|
+
" & ".join(formatter.format(elem) for elem in row)
|
|
176
|
+
for row in matrix
|
|
177
|
+
)
|
|
178
|
+
return rf"\begin{{bmatrix}} {rows} \end{{bmatrix}}"
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def format_full_statevector(statevector) -> Tuple[str, str]:
|
|
182
|
+
"""
|
|
183
|
+
Format a statevector as both Dirac notation and column vector.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
statevector: Input statevector
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Tuple of (dirac_expression, matrix_string)
|
|
190
|
+
"""
|
|
191
|
+
vector = as_complex_array(statevector)
|
|
192
|
+
validate_qubit_count(len(vector))
|
|
193
|
+
|
|
194
|
+
dirac = DiracExpressionGenerator.generate(vector)
|
|
195
|
+
matrix = LatexMatrixFormatter.format_vector(vector)
|
|
196
|
+
|
|
197
|
+
return dirac, matrix
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTML templates for display components.
|
|
3
|
+
|
|
4
|
+
Centralizes HTML markup to improve maintainability and enable theme customization.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import html
|
|
8
|
+
from typing import Dict, List
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class HTMLTheme:
|
|
14
|
+
"""Configuration for HTML display styling."""
|
|
15
|
+
|
|
16
|
+
# Data display box
|
|
17
|
+
data_border_color: str = "#4A90E2"
|
|
18
|
+
data_padding: str = "12px"
|
|
19
|
+
|
|
20
|
+
# Bar chart styling
|
|
21
|
+
bar_height: str = "13px"
|
|
22
|
+
bar_color: str = "#8866b1"
|
|
23
|
+
bar_bg_color: str = "#e9e9e9"
|
|
24
|
+
bar_border_radius: str = "5px"
|
|
25
|
+
|
|
26
|
+
# Container styling
|
|
27
|
+
container_border_color: str = "#ddd"
|
|
28
|
+
container_padding: str = "14px"
|
|
29
|
+
container_border_radius: str = "7px"
|
|
30
|
+
|
|
31
|
+
# Text styling
|
|
32
|
+
label_font_weight: str = "bold"
|
|
33
|
+
unit_font_size: str = "12px"
|
|
34
|
+
unit_color: str = "#666"
|
|
35
|
+
|
|
36
|
+
# Heatmap sizing
|
|
37
|
+
heatmap_min_size: int = 4
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class HTMLBuilder:
|
|
41
|
+
"""Helper class for building HTML strings safely with escaping."""
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def safe_text(text: str) -> str:
|
|
45
|
+
"""Escape HTML special characters."""
|
|
46
|
+
return html.escape(str(text))
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def div(content: str, style: str = "", **attrs) -> str:
|
|
50
|
+
"""Create a div element with optional styling."""
|
|
51
|
+
attr_str = "".join(f' {k}="{HTMLBuilder.safe_text(str(v))}"'
|
|
52
|
+
for k, v in attrs.items() if v is not None)
|
|
53
|
+
style_attr = f' style="{style}"' if style else ""
|
|
54
|
+
return f"<div{style_attr}{attr_str}>{content}</div>"
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def code(text: str) -> str:
|
|
58
|
+
"""Create a code element with safe escaping."""
|
|
59
|
+
return f"<code>{HTMLBuilder.safe_text(text)}</code>"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RawDataTemplate:
|
|
63
|
+
"""Template for displaying raw numpy arrays."""
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def render(name: str, array_string: str,
|
|
67
|
+
theme: HTMLTheme = None) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Render raw array data with label.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
name: Variable name or label
|
|
73
|
+
array_string: String representation of array (already escaped)
|
|
74
|
+
theme: HTMLTheme configuration
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
HTML string
|
|
78
|
+
"""
|
|
79
|
+
if theme is None:
|
|
80
|
+
theme = HTMLTheme()
|
|
81
|
+
|
|
82
|
+
style = (
|
|
83
|
+
f"padding:{theme.data_padding};"
|
|
84
|
+
f"border-left:4px solid {theme.data_border_color};"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
content = (
|
|
88
|
+
f"<b>{HTMLBuilder.safe_text(name)}</b> "
|
|
89
|
+
f"<code>{array_string}</code>"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return HTMLBuilder.div(content, style=style)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BarChartTemplate:
|
|
96
|
+
"""Template for displaying bar charts as HTML."""
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def render_bar(state: str, value: float, width_percent: float,
|
|
100
|
+
theme: HTMLTheme = None) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Render a single bar in the chart.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
state: State label (e.g., '|0⟩')
|
|
106
|
+
value: Numeric value to display
|
|
107
|
+
width_percent: Bar width as percentage (0-100)
|
|
108
|
+
theme: HTMLTheme configuration
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
HTML string for single bar
|
|
112
|
+
"""
|
|
113
|
+
if theme is None:
|
|
114
|
+
theme = HTMLTheme()
|
|
115
|
+
|
|
116
|
+
width_percent = max(0.0, min(100.0, width_percent))
|
|
117
|
+
|
|
118
|
+
bar_style = (
|
|
119
|
+
f"width:{width_percent:.4f}%;"
|
|
120
|
+
f"height:{theme.bar_height};"
|
|
121
|
+
f"background:{theme.bar_color};"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
container_style = (
|
|
125
|
+
f"flex:1;"
|
|
126
|
+
f"background:{theme.bar_bg_color};"
|
|
127
|
+
f"border-radius:{theme.bar_border_radius};"
|
|
128
|
+
f"overflow:hidden;"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
row_style = (
|
|
132
|
+
"display:flex;"
|
|
133
|
+
"align-items:center;"
|
|
134
|
+
"gap:10px;"
|
|
135
|
+
"margin:7px 0;"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
bar_html = HTMLBuilder.div("", style=bar_style)
|
|
139
|
+
container_html = HTMLBuilder.div(bar_html, style=container_style)
|
|
140
|
+
|
|
141
|
+
value_style = "min-width:95px;text-align:right;"
|
|
142
|
+
value_html = HTMLBuilder.div(
|
|
143
|
+
f"{value:.2f}",
|
|
144
|
+
style=value_style
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
state_html = HTMLBuilder.div(
|
|
148
|
+
HTMLBuilder.code(f"|{state}⟩"),
|
|
149
|
+
style="width:55px;"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
row_content = state_html + container_html + value_html
|
|
153
|
+
|
|
154
|
+
return HTMLBuilder.div(row_content, style=row_style)
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def render_chart(data: Dict[str, float], label: str,
|
|
158
|
+
suffix: str = "", unit: str = "",
|
|
159
|
+
theme: HTMLTheme = None) -> str:
|
|
160
|
+
"""
|
|
161
|
+
Render complete bar chart.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
data: Dict mapping state labels to values
|
|
165
|
+
label: Chart title/label
|
|
166
|
+
suffix: Suffix for values (e.g., '%')
|
|
167
|
+
unit: Unit description
|
|
168
|
+
theme: HTMLTheme configuration
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
HTML string for complete chart
|
|
172
|
+
"""
|
|
173
|
+
if theme is None:
|
|
174
|
+
theme = HTMLTheme()
|
|
175
|
+
|
|
176
|
+
bars = []
|
|
177
|
+
for state in sorted(data.keys()):
|
|
178
|
+
value = data[state]
|
|
179
|
+
# Display value with suffix
|
|
180
|
+
display_value = value if not suffix else value # suffix shown separately
|
|
181
|
+
bar_width = max(0.0, min(100.0, value))
|
|
182
|
+
|
|
183
|
+
bar_html = BarChartTemplate.render_bar(
|
|
184
|
+
state, display_value, bar_width, theme
|
|
185
|
+
)
|
|
186
|
+
bars.append(bar_html)
|
|
187
|
+
|
|
188
|
+
container_style = (
|
|
189
|
+
f"max-width:550px;"
|
|
190
|
+
f"padding:{theme.container_padding};"
|
|
191
|
+
f"border:1px solid {theme.container_border_color};"
|
|
192
|
+
f"border-radius:{theme.container_border_radius};"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
label_html = f"<b>{HTMLBuilder.safe_text(label)}</b>"
|
|
196
|
+
unit_html = (
|
|
197
|
+
f'<div style="font-size:{theme.unit_font_size};'
|
|
198
|
+
f'color:{theme.unit_color};">'
|
|
199
|
+
f"{HTMLBuilder.safe_text(unit)}</div>"
|
|
200
|
+
if unit else ""
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
bars_html = "".join(bars)
|
|
204
|
+
content = label_html + unit_html + bars_html
|
|
205
|
+
|
|
206
|
+
return HTMLBuilder.div(content, style=container_style)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class NormalizationNoticeTemplate:
|
|
210
|
+
"""Template for normalization status notice."""
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def render(norm_squared: float) -> str:
|
|
214
|
+
"""
|
|
215
|
+
Render normalization status.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
norm_squared: ⟨ψ|ψ⟩ value
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
HTML string
|
|
222
|
+
"""
|
|
223
|
+
return (
|
|
224
|
+
f'<small>Normalized state: ⟨ψ|ψ⟩ = {norm_squared:.4f}</small>'
|
|
225
|
+
)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Formatting utilities for quantum states and measurement results.
|
|
4
|
+
|
|
5
|
+
Provides text-based representations for quantum statevectors
|
|
6
|
+
and measurement counts.
|
|
7
|
+
|
|
8
|
+
Compatible with NumPy, Qiskit Statevector and standard Python.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
from typing import Sequence
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_statevector(
|
|
18
|
+
statevector: Sequence[complex],
|
|
19
|
+
precision: int = 4,
|
|
20
|
+
threshold: float = 1e-10,
|
|
21
|
+
) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Format a quantum statevector using computational basis notation.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
statevector:
|
|
27
|
+
Sequence of complex amplitudes or Qiskit Statevector.
|
|
28
|
+
precision:
|
|
29
|
+
Number of decimal places.
|
|
30
|
+
threshold:
|
|
31
|
+
Minimum amplitude magnitude to display.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Formatted string representation.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if hasattr(statevector, "data"):
|
|
38
|
+
statevector = statevector.data
|
|
39
|
+
|
|
40
|
+
amplitudes = np.asarray(statevector, dtype=complex)
|
|
41
|
+
|
|
42
|
+
if amplitudes.ndim != 1:
|
|
43
|
+
raise ValueError("Statevector must be one-dimensional.")
|
|
44
|
+
|
|
45
|
+
dimension = len(amplitudes)
|
|
46
|
+
|
|
47
|
+
if dimension == 0 or dimension & (dimension - 1):
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"Statevector length must be a power of two."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if precision < 0:
|
|
53
|
+
raise ValueError("Precision must be non-negative.")
|
|
54
|
+
|
|
55
|
+
if threshold < 0:
|
|
56
|
+
raise ValueError("Threshold must be non-negative.")
|
|
57
|
+
|
|
58
|
+
num_qubits = max(1, dimension.bit_length() - 1)
|
|
59
|
+
|
|
60
|
+
results = []
|
|
61
|
+
|
|
62
|
+
for index, amplitude in enumerate(amplitudes):
|
|
63
|
+
|
|
64
|
+
if abs(amplitude) <= threshold:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
bitstring = format(index, f"0{num_qubits}b")
|
|
68
|
+
|
|
69
|
+
real = float(amplitude.real)
|
|
70
|
+
imag = float(amplitude.imag)
|
|
71
|
+
|
|
72
|
+
if abs(imag) <= threshold:
|
|
73
|
+
formatted = f"{real:.{precision}f}"
|
|
74
|
+
|
|
75
|
+
elif abs(real) <= threshold:
|
|
76
|
+
formatted = f"{imag:.{precision}f}j"
|
|
77
|
+
|
|
78
|
+
else:
|
|
79
|
+
sign = "+" if imag >= 0 else "-"
|
|
80
|
+
formatted = (
|
|
81
|
+
f"{real:.{precision}f}"
|
|
82
|
+
f"{sign}"
|
|
83
|
+
f"{abs(imag):.{precision}f}j"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
results.append(
|
|
87
|
+
f"|{bitstring}⟩: {formatted}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return "\n".join(results) if results else "Zero statevector"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def format_measurement_results(
|
|
94
|
+
counts: Mapping[str, int],
|
|
95
|
+
precision: int = 2,
|
|
96
|
+
) -> str:
|
|
97
|
+
"""
|
|
98
|
+
Format quantum measurement counts and percentages.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
counts:
|
|
102
|
+
Mapping of measured bitstrings to shot counts.
|
|
103
|
+
precision:
|
|
104
|
+
Decimal places for percentages.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Formatted measurement summary.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
if not counts:
|
|
111
|
+
return "No measurements available."
|
|
112
|
+
|
|
113
|
+
if any(count < 0 for count in counts.values()):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
"Measurement counts cannot be negative."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
total_shots = sum(counts.values())
|
|
119
|
+
|
|
120
|
+
if total_shots == 0:
|
|
121
|
+
return "No measurement shots recorded."
|
|
122
|
+
|
|
123
|
+
results = []
|
|
124
|
+
|
|
125
|
+
for bitstring in sorted(counts):
|
|
126
|
+
|
|
127
|
+
count = counts[bitstring]
|
|
128
|
+
percentage = 100 * count / total_shots
|
|
129
|
+
|
|
130
|
+
results.append(
|
|
131
|
+
f"|{bitstring}⟩: "
|
|
132
|
+
f"{count} "
|
|
133
|
+
f"({percentage:.{precision}f}%)"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return "\n".join(results)
|