qplotter 1.6.0b3__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.
Files changed (119) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +63 -0
  4. qplot/_metadata.py +12 -0
  5. qplot/_repair.py +14 -0
  6. qplot/_shutdown_supervisor.py +4049 -0
  7. qplot/_version.py +12 -0
  8. qplot/_windows_shutdown_job.py +1166 -0
  9. qplot/configuration/__init__.py +3 -0
  10. qplot/configuration/config.py +573 -0
  11. qplot/configuration/config_schema.json +319 -0
  12. qplot/configuration/scripts.py +255 -0
  13. qplot/configuration/themes/__init__.py +13 -0
  14. qplot/configuration/themes/_base.py +579 -0
  15. qplot/configuration/themes/blank.py +17 -0
  16. qplot/configuration/themes/dark.py +54 -0
  17. qplot/configuration/themes/light.py +54 -0
  18. qplot/datahandling/LoadFromDB.py +224 -0
  19. qplot/datahandling/__init__.py +47 -0
  20. qplot/datahandling/_trusted_live_helper.py +812 -0
  21. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  22. qplot/datahandling/database.py +2330 -0
  23. qplot/datahandling/dimensions.py +43 -0
  24. qplot/datahandling/file_identity.py +461 -0
  25. qplot/datahandling/parameter_data.py +149 -0
  26. qplot/datahandling/plot_progress.py +17 -0
  27. qplot/datahandling/qcodes_cache.py +277 -0
  28. qplot/datahandling/qcodes_compat.py +14 -0
  29. qplot/datahandling/readSQL.py +2442 -0
  30. qplot/datahandling/readonly.py +2561 -0
  31. qplot/datahandling/source_activity.py +21 -0
  32. qplot/datahandling/trusted_array_preview.py +127 -0
  33. qplot/datahandling/trusted_derived_cache.py +1605 -0
  34. qplot/datahandling/trusted_derived_rendering.py +868 -0
  35. qplot/datahandling/trusted_heatmap.py +241 -0
  36. qplot/datahandling/trusted_live.py +2808 -0
  37. qplot/datahandling/trusted_live_queries.py +5907 -0
  38. qplot/datahandling/trusted_live_service.py +1894 -0
  39. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  40. qplot/datahandling/trusted_plot.py +283 -0
  41. qplot/datahandling/trusted_presentation.py +1065 -0
  42. qplot/datahandling/trusted_snapshot.py +1132 -0
  43. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  44. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  45. qplot/diagnostics.py +241 -0
  46. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  47. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  48. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  49. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  50. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  51. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  52. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  53. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  54. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  55. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  56. qplot/testdata.py +1733 -0
  57. qplot/tools/__init__.py +13 -0
  58. qplot/tools/finite_means.py +114 -0
  59. qplot/tools/general.py +134 -0
  60. qplot/tools/heatmap_geometry.py +381 -0
  61. qplot/tools/operation_registry.py +179 -0
  62. qplot/tools/plot_tools.py +625 -0
  63. qplot/tools/sample_statistics.py +56 -0
  64. qplot/tools/worker.py +2787 -0
  65. qplot/windows/__init__.py +9 -0
  66. qplot/windows/_colorbar.py +422 -0
  67. qplot/windows/_commands.py +523 -0
  68. qplot/windows/_config_persistence.py +123 -0
  69. qplot/windows/_database_actions.py +4904 -0
  70. qplot/windows/_dataset_handle.py +221 -0
  71. qplot/windows/_dragdrop.py +71 -0
  72. qplot/windows/_export_paths.py +1167 -0
  73. qplot/windows/_help.py +169 -0
  74. qplot/windows/_native_averaging.py +173 -0
  75. qplot/windows/_native_transforms.py +389 -0
  76. qplot/windows/_plot1d_snap.py +608 -0
  77. qplot/windows/_plot1d_traces.py +2183 -0
  78. qplot/windows/_plot2d_colorbar.py +1126 -0
  79. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  80. qplot/windows/_plot2d_layers.py +2197 -0
  81. qplot/windows/_plot2d_sweeps.py +712 -0
  82. qplot/windows/_plotWin.py +1993 -0
  83. qplot/windows/_plot_actions.py +2695 -0
  84. qplot/windows/_plot_appearance.py +276 -0
  85. qplot/windows/_plot_axis_scaling.py +1485 -0
  86. qplot/windows/_plot_export.py +1445 -0
  87. qplot/windows/_plot_feedback.py +110 -0
  88. qplot/windows/_plot_marquee.py +656 -0
  89. qplot/windows/_plot_refresh.py +942 -0
  90. qplot/windows/_plot_state.py +239 -0
  91. qplot/windows/_plot_transform_labels.py +60 -0
  92. qplot/windows/_preferences.py +635 -0
  93. qplot/windows/_refresh_interval.py +70 -0
  94. qplot/windows/_run_controls.py +828 -0
  95. qplot/windows/_shortcuts.py +47 -0
  96. qplot/windows/_subplots/__init__.py +8 -0
  97. qplot/windows/_subplots/subplot1d.py +531 -0
  98. qplot/windows/_subplots/subplot2d.py +918 -0
  99. qplot/windows/_svg_export.py +209 -0
  100. qplot/windows/_trusted_derived_qt.py +1759 -0
  101. qplot/windows/_widgets/__init__.py +23 -0
  102. qplot/windows/_widgets/_run_formatting.py +387 -0
  103. qplot/windows/_widgets/details_tables.py +1119 -0
  104. qplot/windows/_widgets/dropbox.py +290 -0
  105. qplot/windows/_widgets/operations.py +460 -0
  106. qplot/windows/_widgets/preview.py +2997 -0
  107. qplot/windows/_widgets/run_list_items.py +488 -0
  108. qplot/windows/_widgets/toolbar.py +111 -0
  109. qplot/windows/_widgets/treeWidgets.py +2572 -0
  110. qplot/windows/_window_controls.py +413 -0
  111. qplot/windows/main.py +1635 -0
  112. qplot/windows/plot1d.py +311 -0
  113. qplot/windows/plot2d.py +1901 -0
  114. qplotter-1.6.0b3.dist-info/METADATA +318 -0
  115. qplotter-1.6.0b3.dist-info/RECORD +119 -0
  116. qplotter-1.6.0b3.dist-info/WHEEL +5 -0
  117. qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
  118. qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
  119. qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,241 @@
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, 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((edge, scale, 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
+
166
+ def indices(gx, gy):
167
+ return tuple(int(np.searchsorted(axes[axis], value))
168
+ if exact[axis] and bins[axis] >= counts[axis] else int(value)
169
+ for axis, value in ((1, gy), (0, gx)))
170
+
171
+ def source(start, stop):
172
+ sql = (f'SELECT id, {groups[0]} AS gx, {groups[1]} AS gy, {z} AS v '
173
+ f'FROM {table} WHERE id>? AND id<=? AND {valid}')
174
+ return sql, (*group_bindings, start, stop, *range_bindings)
175
+
176
+ # Split dense pages when necessary, keeping response size independent of
177
+ # the configured display size and acquisition length.
178
+ pending: list[tuple[int, int]] = []
179
+ next_start = 0
180
+ aggregated_extent = 0
181
+ executor.report_progress("Building heatmap", 0, watermark, stage=2)
182
+ while pending or next_start < watermark:
183
+ executor.check_cancelled()
184
+ if pending:
185
+ start, stop = pending.pop()
186
+ else:
187
+ start, stop = next_start, min(next_start + AGGREGATE_ROWS, watermark)
188
+ next_start = stop
189
+ sql, bindings = source(start, stop)
190
+ rows = executor.query(
191
+ 'SELECT gx, gy, TOTAL(v), COUNT(*), MIN(v), MAX(v), '
192
+ f"MAX(typeof(v)='integer') FROM ({sql}) GROUP BY gx, gy LIMIT ?",
193
+ (*bindings, MAX_GROUPS + 1),
194
+ ).rows
195
+ if len(rows) > MAX_GROUPS:
196
+ middle = (start + stop) // 2
197
+ pending.extend(((middle, stop), (start, middle)))
198
+ else:
199
+ for number, (gx, gy, total, count, lo, hi, integer) in enumerate(rows):
200
+ if number % 1024 == 0:
201
+ executor.check_cancelled()
202
+ key = indices(gx, gy)
203
+ sums[key] = float(sums[key]) + (total if total is not None else np.nan)
204
+ samples[key] += count
205
+ minima[key] = min(minima[key], lo)
206
+ maxima[key] = max(maxima[key], hi)
207
+ integers[key] |= bool(integer)
208
+ aggregated_extent += stop - start
209
+ executor.report_progress("Building heatmap", aggregated_extent, watermark, stage=2)
210
+ yield
211
+
212
+ largest = np.maximum(np.abs(minima), np.abs(maxima))
213
+ with np.errstate(over='ignore', invalid='ignore'):
214
+ risky = (samples > 0) & (
215
+ ~np.isfinite(sums) | ((minima < 0) & (maxima > 0))
216
+ | (largest > (finite / 2) / np.maximum(samples, 1))
217
+ | (integers & (largest * samples >= 2**53)))
218
+ totals = {tuple(key): Fraction() for key in np.argwhere(risky)}
219
+ if totals:
220
+ executor.report_progress("Refining numerical precision", 0, watermark, stage=2)
221
+ # Replay original values, never averages of page averages. This keeps
222
+ # cancellation residuals, large integers and near-overflow means.
223
+ for start in range(0, watermark, MAX_GROUPS):
224
+ executor.check_cancelled()
225
+ sql, bindings = source(start, min(start + MAX_GROUPS, watermark))
226
+ for number, (_id, gx, gy, value) in enumerate(executor.query(sql, bindings).rows):
227
+ if number % 1024 == 0:
228
+ executor.check_cancelled()
229
+ key = indices(gx, gy)
230
+ if key in totals:
231
+ totals[key] += Fraction(value)
232
+ executor.report_progress("Refining numerical precision", min(start + MAX_GROUPS, watermark), watermark, stage=2)
233
+ yield
234
+ executor.report_progress("Finalising heatmap", stage=2)
235
+ grid = np.full(shape, np.nan)
236
+ np.divide(sums, samples, out=grid, where=samples > 0)
237
+ for key, total in totals.items():
238
+ executor.check_cancelled()
239
+ grid[key] = float(total / int(samples[key]))
240
+ return NumericHeatmap(row_count, selected, matching, completed, tuple(axes), grid,
241
+ counts, exact, tuple(zip(lower, upper, strict=True)))