qplotter 1.6.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.
- qplot/__init__.py +46 -0
- qplot/__main__.py +232 -0
- qplot/_auxiliary_paths.py +67 -0
- qplot/_build_info.json +1 -0
- qplot/_metadata.py +75 -0
- qplot/_repair.py +14 -0
- qplot/_shutdown_supervisor.py +4049 -0
- qplot/_version.py +12 -0
- qplot/_windows_shutdown_job.py +1166 -0
- qplot/configuration/__init__.py +3 -0
- qplot/configuration/config.py +578 -0
- qplot/configuration/config_schema.json +357 -0
- qplot/configuration/fonts.py +3 -0
- qplot/configuration/scripts.py +255 -0
- qplot/configuration/themes/__init__.py +13 -0
- qplot/configuration/themes/_base.py +592 -0
- qplot/configuration/themes/blank.py +17 -0
- qplot/configuration/themes/dark.py +54 -0
- qplot/configuration/themes/light.py +54 -0
- qplot/datahandling/LoadFromDB.py +224 -0
- qplot/datahandling/__init__.py +47 -0
- qplot/datahandling/_trusted_live_helper.py +812 -0
- qplot/datahandling/_trusted_live_protocol.py +1745 -0
- qplot/datahandling/database.py +2340 -0
- qplot/datahandling/dimensions.py +43 -0
- qplot/datahandling/file_identity.py +461 -0
- qplot/datahandling/parameter_data.py +149 -0
- qplot/datahandling/plot_progress.py +17 -0
- qplot/datahandling/qcodes_cache.py +277 -0
- qplot/datahandling/qcodes_compat.py +14 -0
- qplot/datahandling/readSQL.py +2442 -0
- qplot/datahandling/readonly.py +2566 -0
- qplot/datahandling/source_activity.py +21 -0
- qplot/datahandling/trusted_array_preview.py +148 -0
- qplot/datahandling/trusted_derived_cache.py +1605 -0
- qplot/datahandling/trusted_derived_rendering.py +876 -0
- qplot/datahandling/trusted_heatmap.py +246 -0
- qplot/datahandling/trusted_live.py +2808 -0
- qplot/datahandling/trusted_live_queries.py +5907 -0
- qplot/datahandling/trusted_live_service.py +1919 -0
- qplot/datahandling/trusted_live_supervisor.py +2161 -0
- qplot/datahandling/trusted_plot.py +283 -0
- qplot/datahandling/trusted_presentation.py +1065 -0
- qplot/datahandling/trusted_snapshot.py +1132 -0
- qplot/datahandling/trusted_work_coordinator.py +2157 -0
- qplot/datahandling/trusted_work_scheduler.py +1204 -0
- qplot/diagnostics.py +241 -0
- qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
- qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
- qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
- qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
- qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
- qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
- qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
- qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
- qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
- qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
- qplot/testdata.py +1804 -0
- qplot/tools/__init__.py +13 -0
- qplot/tools/finite_means.py +123 -0
- qplot/tools/general.py +139 -0
- qplot/tools/heatmap_geometry.py +391 -0
- qplot/tools/operation_registry.py +179 -0
- qplot/tools/plot_tools.py +650 -0
- qplot/tools/sample_statistics.py +111 -0
- qplot/tools/worker.py +2838 -0
- qplot/windows/__init__.py +9 -0
- qplot/windows/_colorbar.py +422 -0
- qplot/windows/_commands.py +523 -0
- qplot/windows/_config_persistence.py +123 -0
- qplot/windows/_database_actions.py +4933 -0
- qplot/windows/_dataset_handle.py +221 -0
- qplot/windows/_display_precision.py +48 -0
- qplot/windows/_dragdrop.py +70 -0
- qplot/windows/_export_paths.py +1167 -0
- qplot/windows/_help.py +210 -0
- qplot/windows/_mixed_appearance.py +303 -0
- qplot/windows/_mixed_plots.py +461 -0
- qplot/windows/_native_averaging.py +323 -0
- qplot/windows/_native_transforms.py +551 -0
- qplot/windows/_plot1d_snap.py +612 -0
- qplot/windows/_plot1d_traces.py +2236 -0
- qplot/windows/_plot2d_colorbar.py +1127 -0
- qplot/windows/_plot2d_colorbar_dialog.py +786 -0
- qplot/windows/_plot2d_layers.py +2291 -0
- qplot/windows/_plot2d_sweeps.py +712 -0
- qplot/windows/_plotWin.py +2082 -0
- qplot/windows/_plot_actions.py +2703 -0
- qplot/windows/_plot_annotation.py +186 -0
- qplot/windows/_plot_appearance.py +283 -0
- qplot/windows/_plot_axis_scaling.py +1521 -0
- qplot/windows/_plot_data_viewbox.py +94 -0
- qplot/windows/_plot_drop_axes.py +224 -0
- qplot/windows/_plot_export.py +1445 -0
- qplot/windows/_plot_feedback.py +112 -0
- qplot/windows/_plot_marquee.py +659 -0
- qplot/windows/_plot_refresh.py +950 -0
- qplot/windows/_plot_source_promotion.py +178 -0
- qplot/windows/_plot_state.py +237 -0
- qplot/windows/_plot_status.py +115 -0
- qplot/windows/_plot_tiling.py +35 -0
- qplot/windows/_plot_transform_labels.py +60 -0
- qplot/windows/_preferences.py +724 -0
- qplot/windows/_refresh_interval.py +172 -0
- qplot/windows/_run_controls.py +789 -0
- qplot/windows/_shortcuts.py +47 -0
- qplot/windows/_subplots/__init__.py +8 -0
- qplot/windows/_subplots/subplot1d.py +532 -0
- qplot/windows/_subplots/subplot2d.py +919 -0
- qplot/windows/_svg_export.py +209 -0
- qplot/windows/_trusted_derived_qt.py +1759 -0
- qplot/windows/_widgets/__init__.py +23 -0
- qplot/windows/_widgets/_run_formatting.py +387 -0
- qplot/windows/_widgets/details_tables.py +1119 -0
- qplot/windows/_widgets/dropbox.py +290 -0
- qplot/windows/_widgets/operations.py +460 -0
- qplot/windows/_widgets/preview.py +2997 -0
- qplot/windows/_widgets/run_list_items.py +488 -0
- qplot/windows/_widgets/toolbar.py +111 -0
- qplot/windows/_widgets/treeWidgets.py +2572 -0
- qplot/windows/_window_controls.py +414 -0
- qplot/windows/main.py +1632 -0
- qplot/windows/plot1d.py +313 -0
- qplot/windows/plot2d.py +1935 -0
- qplotter-1.6.0.dist-info/METADATA +322 -0
- qplotter-1.6.0.dist-info/RECORD +130 -0
- qplotter-1.6.0.dist-info/WHEEL +5 -0
- qplotter-1.6.0.dist-info/entry_points.txt +4 -0
- qplotter-1.6.0.dist-info/licenses/LICENSE +21 -0
- qplotter-1.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Bounded numeric heatmap reduction before transferring acquisition rows.
|
|
2
|
+
|
|
3
|
+
Every source query covers a finite primary-key interval of one fixed prefix.
|
|
4
|
+
Only axis values and spatial sufficient statistics cross the helper boundary.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from fractions import Fraction
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from qplot.tools.heatmap_geometry import bounded_grid_shape, spatial_axis_bins
|
|
13
|
+
from qplot.tools.operation_registry import OperationExecutionError
|
|
14
|
+
|
|
15
|
+
from .trusted_live import (
|
|
16
|
+
TrustedLiveQueryError,
|
|
17
|
+
TrustedLiveResultLimitError,
|
|
18
|
+
TrustedQuery,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
SUMMARY_ROWS = 65_536
|
|
22
|
+
# Once an axis has exceeded the exact-cardinality budget, at most one bounded
|
|
23
|
+
# DISTINCT result remains. Larger intervals then amortise helper overhead
|
|
24
|
+
# without doubling the maximum coordinate payload of the original summaries.
|
|
25
|
+
LARGE_SUMMARY_ROWS = 262_144
|
|
26
|
+
# Aggregation returns bounded sufficient statistics, unlike the summary's
|
|
27
|
+
# distinct coordinates and precision replay's raw values. Larger intervals
|
|
28
|
+
# amortise helper/transaction overhead while retaining the reply split limit.
|
|
29
|
+
AGGREGATE_ROWS = 262_144
|
|
30
|
+
MAX_GROUPS = 32_768
|
|
31
|
+
MAX_AXIS_VALUES = 131_072
|
|
32
|
+
MAX_NUMERIC_HEATMAP_CELLS = 2_000_000
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PlotOperationLimitError(TrustedLiveQueryError, OperationExecutionError):
|
|
36
|
+
"""A plot's resolution limit does not invalidate its trusted session."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class NumericHeatmapPlan:
|
|
41
|
+
x: str
|
|
42
|
+
y: str
|
|
43
|
+
z: str
|
|
44
|
+
full_limit: int
|
|
45
|
+
max_cells: int
|
|
46
|
+
max_side: int
|
|
47
|
+
ranges: tuple
|
|
48
|
+
operations: bool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class NumericHeatmap:
|
|
53
|
+
row_count: int
|
|
54
|
+
selected_count: int
|
|
55
|
+
matching_count: int
|
|
56
|
+
completed: bool
|
|
57
|
+
axes: tuple
|
|
58
|
+
grid: np.ndarray
|
|
59
|
+
unique_counts: tuple
|
|
60
|
+
cardinality_exact: tuple
|
|
61
|
+
bounds: tuple
|
|
62
|
+
watermark: int = 0
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def disk_bytes(self):
|
|
66
|
+
# Share the broker's bounded cache accounting with private prefixes.
|
|
67
|
+
return self.grid.nbytes + sum(axis.nbytes for axis in self.axes)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def numeric_heatmap(executor, dataset, plan, watermark, completed):
|
|
71
|
+
from .trusted_plot import identifier
|
|
72
|
+
|
|
73
|
+
table = identifier(dataset.table_name)
|
|
74
|
+
x, y, z = (identifier(name) for name in (plan.x, plan.y, plan.z))
|
|
75
|
+
finite = float(np.finfo(float).max)
|
|
76
|
+
valid = ' AND '.join(f'{name} BETWEEN {-finite} AND {finite}' for name in (x, y, z))
|
|
77
|
+
range_bindings = []
|
|
78
|
+
for name, limits in zip((x, y), plan.ranges, strict=True):
|
|
79
|
+
if limits is not None:
|
|
80
|
+
valid += f' AND {name} BETWEEN ? AND ?'
|
|
81
|
+
range_bindings.extend(limits)
|
|
82
|
+
unique: list[set[int | float] | None] = [set(), set()]
|
|
83
|
+
lower: list[int | float | None] = [None, None]
|
|
84
|
+
upper: list[int | float | None] = [None, None]
|
|
85
|
+
row_count = selected = matching = 0
|
|
86
|
+
executor.report_progress("Scanning coordinates", 0, watermark)
|
|
87
|
+
start = 0
|
|
88
|
+
while start < watermark:
|
|
89
|
+
executor.check_cancelled()
|
|
90
|
+
active_axes = [axis for axis in range(2) if unique[axis] is not None]
|
|
91
|
+
summary_rows = LARGE_SUMMARY_ROWS if len(active_axes) < 2 else SUMMARY_ROWS
|
|
92
|
+
stop = min(start + summary_rows, watermark)
|
|
93
|
+
interval = 'id>? AND id<=?'
|
|
94
|
+
where = f'{interval} AND {valid}'
|
|
95
|
+
bindings = (start, stop, *range_bindings)
|
|
96
|
+
queries = [
|
|
97
|
+
TrustedQuery(f'SELECT COUNT(*), COUNT({z}) FROM {table} WHERE {interval}', (start, stop)),
|
|
98
|
+
TrustedQuery(f'SELECT COUNT(*), MIN({x}), MAX({x}), MIN({y}), MAX({y}) '
|
|
99
|
+
f'FROM {table} WHERE {where}', bindings),
|
|
100
|
+
]
|
|
101
|
+
for axis in active_axes:
|
|
102
|
+
name = (x, y)[axis]
|
|
103
|
+
queries.append(TrustedQuery(
|
|
104
|
+
f'SELECT DISTINCT {name} FROM {table} WHERE {where} LIMIT ?',
|
|
105
|
+
(*bindings, MAX_AXIS_VALUES + 1),
|
|
106
|
+
))
|
|
107
|
+
results = executor.query_batch(tuple(queries))
|
|
108
|
+
count, count_z = results[0].rows[0]
|
|
109
|
+
row_count += count
|
|
110
|
+
selected += count_z
|
|
111
|
+
count, *bounds = results[1].rows[0]
|
|
112
|
+
matching += count
|
|
113
|
+
if count:
|
|
114
|
+
for axis in range(2):
|
|
115
|
+
lo, hi = bounds[2 * axis:2 * axis + 2]
|
|
116
|
+
lower[axis] = lo if lower[axis] is None else min(lower[axis], lo)
|
|
117
|
+
upper[axis] = hi if upper[axis] is None else max(upper[axis], hi)
|
|
118
|
+
for axis, result in zip(active_axes, results[2:], strict=True):
|
|
119
|
+
values = unique[axis]
|
|
120
|
+
assert values is not None
|
|
121
|
+
values.update(row[0] for row in result.rows)
|
|
122
|
+
if len(values) > MAX_AXIS_VALUES:
|
|
123
|
+
unique[axis] = None
|
|
124
|
+
start = stop
|
|
125
|
+
executor.report_progress("Scanning coordinates", stop, watermark)
|
|
126
|
+
yield
|
|
127
|
+
|
|
128
|
+
counts = tuple(len(values) if values is not None else MAX_AXIS_VALUES + 1 for values in unique)
|
|
129
|
+
exact = tuple(values is not None for values in unique)
|
|
130
|
+
if not matching:
|
|
131
|
+
return NumericHeatmap(row_count, selected, 0, completed, (np.array([]), np.array([])),
|
|
132
|
+
np.empty((0, 0)), counts, exact, tuple(zip(lower, upper, strict=True)))
|
|
133
|
+
|
|
134
|
+
# Use the same geometry as the existing SQL heatmap renderer.
|
|
135
|
+
bins = bounded_grid_shape(*counts, max_cells=plan.max_cells, max_side=plan.max_side)
|
|
136
|
+
if bins[0] * bins[1] > MAX_NUMERIC_HEATMAP_CELLS:
|
|
137
|
+
raise TrustedLiveResultLimitError(
|
|
138
|
+
"The numeric heatmap exceeds its bounded aggregation memory budget. "
|
|
139
|
+
"Reduce max_heatmap_grid_cells to at most 2,000,000.")
|
|
140
|
+
axes, groups = [], []
|
|
141
|
+
group_bindings: list[int | float] = []
|
|
142
|
+
for axis, (name, size) in enumerate(zip((x, y), bins, strict=True)):
|
|
143
|
+
if exact[axis] and size >= counts[axis]:
|
|
144
|
+
values = unique[axis]
|
|
145
|
+
assert values is not None
|
|
146
|
+
centres = np.array(sorted(values), dtype=float)
|
|
147
|
+
if np.any(centres[1:] == centres[:-1]):
|
|
148
|
+
raise TrustedLiveQueryError(
|
|
149
|
+
"Distinct recorded coordinates would be merged by floating-point display. "
|
|
150
|
+
"The heatmap cannot be represented at this coordinate precision.")
|
|
151
|
+
axes.append(centres)
|
|
152
|
+
groups.append(name)
|
|
153
|
+
else:
|
|
154
|
+
lo, hi = lower[axis], upper[axis]
|
|
155
|
+
assert lo is not None and hi is not None
|
|
156
|
+
centres, edge, width, scale = spatial_axis_bins(float(lo), float(hi), counts[axis], size)
|
|
157
|
+
axes.append(centres)
|
|
158
|
+
groups.append(f'MIN(CAST(({name} / ? - ?) / ? AS INTEGER), ?)')
|
|
159
|
+
group_bindings.extend((scale, edge, width, size - 1))
|
|
160
|
+
shape = (len(axes[1]), len(axes[0]))
|
|
161
|
+
sums = np.zeros(shape)
|
|
162
|
+
samples = np.zeros(shape, dtype=np.int64)
|
|
163
|
+
minima, maxima = np.full(shape, np.inf), np.full(shape, -np.inf)
|
|
164
|
+
integers = np.zeros(shape, dtype=bool)
|
|
165
|
+
fractional = np.zeros(shape, dtype=bool)
|
|
166
|
+
|
|
167
|
+
def indices(gx, gy):
|
|
168
|
+
return tuple(int(np.searchsorted(axes[axis], value))
|
|
169
|
+
if exact[axis] and bins[axis] >= counts[axis] else int(value)
|
|
170
|
+
for axis, value in ((1, gy), (0, gx)))
|
|
171
|
+
|
|
172
|
+
def source(start, stop):
|
|
173
|
+
sql = (f'SELECT id, {groups[0]} AS gx, {groups[1]} AS gy, {z} AS v '
|
|
174
|
+
f'FROM {table} WHERE id>? AND id<=? AND {valid}')
|
|
175
|
+
return sql, (*group_bindings, start, stop, *range_bindings)
|
|
176
|
+
|
|
177
|
+
# Split dense pages when necessary, keeping response size independent of
|
|
178
|
+
# the configured display size and acquisition length.
|
|
179
|
+
pending: list[tuple[int, int]] = []
|
|
180
|
+
next_start = 0
|
|
181
|
+
aggregated_extent = 0
|
|
182
|
+
executor.report_progress("Building heatmap", 0, watermark, stage=2)
|
|
183
|
+
while pending or next_start < watermark:
|
|
184
|
+
executor.check_cancelled()
|
|
185
|
+
if pending:
|
|
186
|
+
start, stop = pending.pop()
|
|
187
|
+
else:
|
|
188
|
+
start, stop = next_start, min(next_start + AGGREGATE_ROWS, watermark)
|
|
189
|
+
next_start = stop
|
|
190
|
+
sql, bindings = source(start, stop)
|
|
191
|
+
rows = executor.query(
|
|
192
|
+
'SELECT gx, gy, TOTAL(v), COUNT(*), MIN(v), MAX(v), '
|
|
193
|
+
f"MAX(typeof(v)='integer'), MAX(v != CAST(v AS INTEGER)) "
|
|
194
|
+
f"FROM ({sql}) GROUP BY gx, gy LIMIT ?",
|
|
195
|
+
(*bindings, MAX_GROUPS + 1),
|
|
196
|
+
).rows
|
|
197
|
+
if len(rows) > MAX_GROUPS:
|
|
198
|
+
middle = (start + stop) // 2
|
|
199
|
+
pending.extend(((middle, stop), (start, middle)))
|
|
200
|
+
else:
|
|
201
|
+
for number, (gx, gy, total, count, lo, hi, integer, fraction) in enumerate(rows):
|
|
202
|
+
if number % 1024 == 0:
|
|
203
|
+
executor.check_cancelled()
|
|
204
|
+
key = indices(gx, gy)
|
|
205
|
+
sums[key] = float(sums[key]) + (total if total is not None else np.nan)
|
|
206
|
+
samples[key] += count
|
|
207
|
+
minima[key] = min(minima[key], lo)
|
|
208
|
+
maxima[key] = max(maxima[key], hi)
|
|
209
|
+
integers[key] |= bool(integer)
|
|
210
|
+
fractional[key] |= bool(fraction)
|
|
211
|
+
aggregated_extent += stop - start
|
|
212
|
+
executor.report_progress("Building heatmap", aggregated_extent, watermark, stage=2)
|
|
213
|
+
yield
|
|
214
|
+
|
|
215
|
+
largest = np.maximum(np.abs(minima), np.abs(maxima))
|
|
216
|
+
with np.errstate(over='ignore', invalid='ignore'):
|
|
217
|
+
risky = (samples > 0) & (
|
|
218
|
+
~np.isfinite(sums) | ((minima < 0) & (maxima > 0))
|
|
219
|
+
| (largest > (finite / 2) / np.maximum(samples, 1))
|
|
220
|
+
# Fractional page totals can round before division even below the
|
|
221
|
+
# exact-integer boundary. Match the streamed array mean policy.
|
|
222
|
+
| (integers & ((largest * samples >= 2**53) | fractional)))
|
|
223
|
+
totals = {tuple(key): Fraction() for key in np.argwhere(risky)}
|
|
224
|
+
if totals:
|
|
225
|
+
executor.report_progress("Refining numerical precision", 0, watermark, stage=2)
|
|
226
|
+
# Replay original values, never averages of page averages. This keeps
|
|
227
|
+
# cancellation residuals, large integers and near-overflow means.
|
|
228
|
+
for start in range(0, watermark, MAX_GROUPS):
|
|
229
|
+
executor.check_cancelled()
|
|
230
|
+
sql, bindings = source(start, min(start + MAX_GROUPS, watermark))
|
|
231
|
+
for number, (_id, gx, gy, value) in enumerate(executor.query(sql, bindings).rows):
|
|
232
|
+
if number % 1024 == 0:
|
|
233
|
+
executor.check_cancelled()
|
|
234
|
+
key = indices(gx, gy)
|
|
235
|
+
if key in totals:
|
|
236
|
+
totals[key] += Fraction(value)
|
|
237
|
+
executor.report_progress("Refining numerical precision", min(start + MAX_GROUPS, watermark), watermark, stage=2)
|
|
238
|
+
yield
|
|
239
|
+
executor.report_progress("Finalising heatmap", stage=2)
|
|
240
|
+
grid = np.full(shape, np.nan)
|
|
241
|
+
np.divide(sums, samples, out=grid, where=samples > 0)
|
|
242
|
+
for key, total in totals.items():
|
|
243
|
+
executor.check_cancelled()
|
|
244
|
+
grid[key] = float(total / int(samples[key]))
|
|
245
|
+
return NumericHeatmap(row_count, selected, matching, completed, tuple(axes), grid,
|
|
246
|
+
counts, exact, tuple(zip(lower, upper, strict=True)))
|