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,283 @@
1
+ """Explicit plot data captured through the application's trusted broker.
2
+
3
+ Only a run's result table is staged, never database pages or unrelated runs.
4
+ The private spool lets the existing precision-preserving QCoDeS decoder and
5
+ bounded heatmap aggregator operate on one immutable acquisition prefix. No
6
+ source transaction survives a page, a BLOB chunk, or time spent processing it.
7
+ """
8
+
9
+ import sqlite3
10
+ import tempfile
11
+ from pathlib import Path
12
+ from typing import Any, cast
13
+
14
+ from qcodes.dataset.data_set_cache import DataSetCacheWithDBBackend
15
+ from qcodes.dataset.descriptions.versioning.converters import new_to_old
16
+ from qcodes.dataset.descriptions.versioning.serialization import from_json_to_current
17
+ from qcodes.dataset.sqlite.connection import AtomicConnection
18
+
19
+ from .trusted_live import TrustedLiveQueryError, TrustedQuery
20
+
21
+ PAGE_ROWS = 8192
22
+ BLOB_CHUNK_BYTES = 256 * 1024
23
+ RECORD_PROGRESS_UNITS = 1_000_000
24
+ MAX_PARAMETERS = 256
25
+
26
+
27
+ def identifier(name):
28
+ return '"' + str(name).replace('"', '""') + '"'
29
+
30
+
31
+ def _report_array_progress(executor, phase, row_id, watermark, copied_bytes, record_bytes):
32
+ # Byte progress advances within this record's share of the captured range,
33
+ # instead of repeatedly resetting the bar to 0% for each stored array.
34
+ completed_units = (row_id - 1) * RECORD_PROGRESS_UNITS
35
+ if record_bytes:
36
+ completed_units += copied_bytes * RECORD_PROGRESS_UNITS // record_bytes
37
+ executor.report_progress(phase, completed_units, watermark * RECORD_PROGRESS_UNITS)
38
+
39
+
40
+ class TrustedPlotDataset:
41
+ """Connection-free run description and viewer-owned QCoDeS cache."""
42
+
43
+ def __init__(self, service, row):
44
+ (self.run_id, self.guid, self.table_name, description,
45
+ completed, self.name, self.exp_name, self.sample_name) = row
46
+ self.service = service
47
+ self.path_to_db = service.database_instance.logical_path
48
+ self.description_json = description
49
+ self.description = from_json_to_current(description)
50
+ self._parameters = list(new_to_old(self.description.interdeps).paramspecs)
51
+ if not 0 < len(self._parameters) <= MAX_PARAMETERS:
52
+ raise ValueError("Plot parameter count exceeds the supported bound.")
53
+ self._completed = bool(completed)
54
+ self.number_of_results = 0
55
+ # Only qPlot's cache accessors are used; no DataSet database methods.
56
+ self.cache = DataSetCacheWithDBBackend(cast(Any, self))
57
+ self.cache.prepare()
58
+
59
+ @property
60
+ def completed(self):
61
+ return self._completed
62
+
63
+ @property
64
+ def running(self):
65
+ return not self._completed
66
+
67
+ @property
68
+ def paramspecs(self):
69
+ return {parameter.name: parameter for parameter in self._parameters}
70
+
71
+ def get_parameters(self):
72
+ return list(self._parameters)
73
+
74
+
75
+ def plot_dataset(executor, service, guid):
76
+ rows = executor.query(
77
+ "SELECT r.run_id, r.guid, r.result_table_name, r.run_description, "
78
+ "r.is_completed, r.name, e.name, e.sample_name "
79
+ "FROM runs r JOIN experiments e ON r.exp_id=e.exp_id "
80
+ "WHERE r.guid=? LIMIT 2", (guid,),
81
+ ).rows
82
+ if len(rows) != 1:
83
+ raise ValueError("The requested plot GUID does not identify one run.")
84
+ return TrustedPlotDataset(service, rows[0])
85
+
86
+
87
+ class PlotPrefix:
88
+ """Owned private storage, shared only after its writer has closed."""
89
+
90
+ def __init__(self):
91
+ self.directory = tempfile.TemporaryDirectory(prefix="qplot-plot-")
92
+ self.path = Path(self.directory.name) / "results.db"
93
+ self.row_count = 0
94
+ self.decode_bytes = 0
95
+ self.watermark = 0
96
+ self.disk_bytes = 0
97
+
98
+ def connect(self, *, decode_arrays=False, cancelled=lambda: False):
99
+ if decode_arrays:
100
+ from qcodes.dataset.sqlite import database as qcodes_database
101
+
102
+ from .readonly import _register_qcodes_sqlite_types
103
+
104
+ _register_qcodes_sqlite_types(qcodes_database)
105
+ conn = sqlite3.connect(
106
+ self.path.as_uri() + "?mode=ro&immutable=1", uri=True,
107
+ factory=AtomicConnection,
108
+ detect_types=sqlite3.PARSE_DECLTYPES if decode_arrays else 0,
109
+ )
110
+ conn.execute("PRAGMA query_only=ON")
111
+ conn.set_progress_handler(lambda: int(cancelled()), 1000)
112
+ return conn
113
+
114
+
115
+ def plot_prefix(executor, dataset, heatmap_plan=None):
116
+ """Capture a run in resumable steps, returning its immutable private spool.
117
+
118
+ Yields occur with no source transaction open, after each numeric page or
119
+ array chunk. Only the broker dispatcher may advance or close this iterator.
120
+ """
121
+ table = identifier(dataset.table_name)
122
+ executor.report_progress("Checking plot data")
123
+ metadata, extent = executor.query_batch((
124
+ TrustedQuery(
125
+ "SELECT guid, result_table_name, run_description, is_completed "
126
+ "FROM runs WHERE run_id=?", (dataset.run_id,),
127
+ ),
128
+ TrustedQuery(f"SELECT MAX(id) FROM {table}"),
129
+ ))
130
+ expected = (dataset.guid, dataset.table_name, dataset.description_json)
131
+ if len(metadata.rows) != 1 or metadata.rows[0][:3] != expected:
132
+ raise TrustedLiveQueryError("The plot run identity changed.")
133
+ completed = bool(metadata.rows[0][3])
134
+ watermark = extent.rows[0][0] or 0
135
+ cache_key = (dataset.run_id, dataset.guid, dataset.description_json, heatmap_plan)
136
+ retained = dataset.service._plot_prefix_cache.get(cache_key)
137
+ if completed and retained is not None and retained.watermark == watermark:
138
+ return retained
139
+ specs = dataset.get_parameters()
140
+ names = [identifier(spec.name) for spec in specs]
141
+ if any(spec.type not in {"numeric", "array", "complex", "text"} for spec in specs):
142
+ raise ValueError("Unsupported plot storage type.")
143
+ if heatmap_plan is not None and watermark > heatmap_plan.full_limit:
144
+ plan = heatmap_plan
145
+ if any(dataset.paramspecs[name].type != "numeric" for name in (plan.x, plan.y, plan.z)):
146
+ raise ValueError("The numeric heatmap plan requires numeric parameters.")
147
+ count = 0
148
+ executor.report_progress("Checking plot size")
149
+ for start in range(0, watermark, 65_536):
150
+ count += executor.query(
151
+ f"SELECT COUNT(*) FROM {table} WHERE id>? AND id<=? "
152
+ f"AND {identifier(plan.z)} IS NOT NULL",
153
+ (start, min(start + 65_536, watermark)),
154
+ ).rows[0][0]
155
+ yield
156
+ if count > plan.full_limit:
157
+ break
158
+ if count > plan.full_limit:
159
+ from .trusted_heatmap import PlotOperationLimitError, numeric_heatmap
160
+ if plan.operations:
161
+ raise PlotOperationLimitError(
162
+ "This heatmap exceeds max_full_heatmap_points. "
163
+ "Disable operations or increase max_full_heatmap_points.")
164
+ heatmap = yield from numeric_heatmap(executor, dataset, plan, watermark, completed)
165
+ final = executor.query(
166
+ "SELECT guid, result_table_name, run_description FROM runs WHERE run_id=?",
167
+ (dataset.run_id,),
168
+ ).rows
169
+ if final != (expected,):
170
+ raise TrustedLiveQueryError("The plot run identity changed during capture.")
171
+ heatmap.watermark = watermark
172
+ if completed and heatmap.disk_bytes <= 512 * 1024 * 1024:
173
+ _cache_prefix(dataset.service, cache_key, heatmap)
174
+ return heatmap
175
+ prefix = PlotPrefix()
176
+ prefix.watermark = watermark
177
+ executor.report_progress("Reading plot data", 0, watermark)
178
+ # This is a newly created, private database. It contains no source metadata
179
+ # besides the completion observation needed by the existing cache loader.
180
+ try:
181
+ with sqlite3.connect(prefix.path) as target:
182
+ def interrupted():
183
+ try:
184
+ executor.check_cancelled()
185
+ return 0
186
+ except (InterruptedError, TimeoutError):
187
+ return 1
188
+ target.set_progress_handler(interrupted, 1000)
189
+ target.execute("PRAGMA cache_size=-2048")
190
+ target.execute("CREATE TABLE runs(run_id INTEGER PRIMARY KEY, is_completed INTEGER)")
191
+ target.execute("INSERT INTO runs VALUES (?, ?)", (dataset.run_id, completed))
192
+ columns = ", ".join(f"{name} {spec.type}" for name, spec in zip(names, specs, strict=True))
193
+ target.execute(f"CREATE TABLE {table}(id INTEGER PRIMARY KEY, {columns})")
194
+ # BLOB lengths travel separately; even a multi-gigabyte array never
195
+ # becomes a scalar in the reader, IPC, or this process.
196
+ values_sql = ", ".join(
197
+ f"CASE WHEN typeof({name})='blob' THEN NULL ELSE {name} END, "
198
+ f"CASE WHEN typeof({name})='blob' THEN length({name}) ELSE NULL END"
199
+ for name in names
200
+ )
201
+ last_id = 0
202
+ page_size = min(PAGE_ROWS, max(1, 65536 // (2 * len(names))))
203
+ while last_id < watermark:
204
+ rows = executor.query(
205
+ f"SELECT id, {values_sql} FROM {table} "
206
+ "WHERE id>? AND id<=? ORDER BY id LIMIT ?",
207
+ (last_id, watermark, page_size),
208
+ ).rows
209
+ if not rows:
210
+ break
211
+ for row in rows:
212
+ executor.check_cancelled()
213
+ row_id = row[0]
214
+ expressions = ["?"]
215
+ bindings = [row_id]
216
+ for index in range(len(names)):
217
+ value, size = row[1 + 2 * index:3 + 2 * index]
218
+ expressions.append("?" if size is None else "zeroblob(?)")
219
+ bindings.append(value if size is None else size)
220
+ prefix.decode_bytes += 128 + (size or 0) + (
221
+ 4 * len(value) if isinstance(value, str) else 0)
222
+ target.execute(
223
+ f"INSERT INTO {table} VALUES ({','.join(expressions)})", bindings,
224
+ )
225
+ record_bytes = sum(row[2 + 2 * index] or 0 for index in range(len(names)))
226
+ copied_bytes = 0
227
+
228
+ for index in range(len(names)):
229
+ size = row[2 + 2 * index]
230
+ if size is None:
231
+ continue
232
+ with target.blobopen(dataset.table_name, specs[index].name, row_id) as blob:
233
+ array_phase = f"Reading array {specs[index].name} (record {prefix.row_count + 1:,})"
234
+ _report_array_progress(
235
+ executor, array_phase, row_id, watermark, copied_bytes, record_bytes,
236
+ )
237
+ for offset in range(0, size, BLOB_CHUNK_BYTES):
238
+ chunk = executor.query(
239
+ "SELECT qplot_read_blob(?, ?, ?, ?, ?)",
240
+ (dataset.table_name, specs[index].name, row_id,
241
+ offset, min(BLOB_CHUNK_BYTES, size - offset)),
242
+ ).rows
243
+ if len(chunk) != 1 or len(chunk[0][0]) != min(BLOB_CHUNK_BYTES, size - offset):
244
+ raise TrustedLiveQueryError("A captured array record changed.")
245
+ blob.write(chunk[0][0])
246
+ copied_bytes += len(chunk[0][0])
247
+ _report_array_progress(
248
+ executor, array_phase, row_id, watermark, copied_bytes, record_bytes,
249
+ )
250
+ yield
251
+ prefix.row_count += 1
252
+ last_id = rows[-1][0]
253
+ target.commit()
254
+ executor.report_progress("Reading plot data", last_id, watermark)
255
+ if last_id < watermark:
256
+ yield
257
+ executor.report_progress("Validating plot data")
258
+ final = executor.query(
259
+ "SELECT guid, result_table_name, run_description FROM runs WHERE run_id=?",
260
+ (dataset.run_id,),
261
+ ).rows
262
+ if final != (expected,):
263
+ raise TrustedLiveQueryError("The plot run identity changed during capture.")
264
+ # Context managers commit but do not close sqlite3 connections.
265
+ target.close()
266
+ prefix.disk_bytes = prefix.path.stat().st_size
267
+ if completed and prefix.disk_bytes <= 512 * 1024 * 1024:
268
+ _cache_prefix(dataset.service, cache_key, prefix)
269
+ return prefix
270
+ except BaseException:
271
+ if "target" in locals():
272
+ target.close()
273
+ prefix.directory.cleanup()
274
+ raise
275
+
276
+
277
+ def _cache_prefix(service, key, prefix):
278
+ cache = service._plot_prefix_cache
279
+ cache.pop(key, None)
280
+ while cache and (len(cache) >= 8 or
281
+ sum(item.disk_bytes for item in cache.values()) + prefix.disk_bytes > 512 * 1024 * 1024):
282
+ cache.pop(next(iter(cache)))
283
+ cache[key] = prefix