pyglaze 0.1.2__py3-none-any.whl → 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.
pyglaze/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.2"
1
+ __version__ = "0.2.0"
@@ -457,6 +457,24 @@ class Pulse:
457
457
 
458
458
  return cast(float, np.max(max_estimate) - np.min(min_estimate))
459
459
 
460
+ def estimate_zero_crossing(self: Pulse) -> float:
461
+ """Estimates the zero crossing of the pulse between the maximum and minimum value.
462
+
463
+ Returns:
464
+ float: Estimated zero crossing.
465
+ """
466
+ argmax = np.argmax(self.signal)
467
+ argmin = np.argmin(self.signal)
468
+ if argmax < argmin:
469
+ idx = np.searchsorted(-self.signal[argmax:argmin], 0) + argmax - 1
470
+ else:
471
+ idx = np.searchsorted(self.signal[argmin:argmax], 0) + argmin - 1
472
+
473
+ # To find the zero crossing, solve 0 = s1 + a * (t - t1) for t: t = t1 - s1 / a
474
+ t1, s1 = self.time[idx], self.signal[idx]
475
+ a = (self.signal[idx + 1] - self.signal[idx]) / self.dt
476
+ return cast(float, t1 - s1 / a)
477
+
460
478
  def to_native_dict(self: Pulse) -> dict[str, list[float] | None]:
461
479
  """Converts the Pulse object to a native dictionary.
462
480
 
@@ -1,15 +1,7 @@
1
1
  from .configuration import ForceDeviceConfiguration, Interval, LeDeviceConfiguration
2
- from .delayunit import NonuniformDelay, UniformDelay, list_delayunits, load_delayunit
3
- from .identifiers import get_device_id, list_devices
4
2
 
5
3
  __all__ = [
6
4
  "LeDeviceConfiguration",
7
5
  "ForceDeviceConfiguration",
8
6
  "Interval",
9
- "NonuniformDelay",
10
- "UniformDelay",
11
- "list_delayunits",
12
- "load_delayunit",
13
- "get_device_id",
14
- "list_devices",
15
7
  ]
pyglaze/device/ampcom.py CHANGED
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
7
7
  from enum import Enum
8
8
  from functools import cached_property
9
9
  from math import modf
10
- from typing import TYPE_CHECKING, ClassVar, overload
10
+ from typing import TYPE_CHECKING, Callable, ClassVar, overload
11
11
 
12
12
  import numpy as np
13
13
  import serial
@@ -20,7 +20,6 @@ from pyglaze.device.configuration import (
20
20
  Interval,
21
21
  LeDeviceConfiguration,
22
22
  )
23
- from pyglaze.device.delayunit import Delay, load_delayunit
24
23
  from pyglaze.devtools.mock_device import _mock_device_factory
25
24
  from pyglaze.helpers.utilities import LOGGER_NAME, _BackoffRetry
26
25
 
@@ -67,7 +66,7 @@ class _ForceAmpCom:
67
66
  @cached_property
68
67
  def times(self: _ForceAmpCom) -> FloatArray:
69
68
  return _delay_from_intervals(
70
- delayunit=load_delayunit(self.config.delayunit),
69
+ delayunit=lambda x: x,
71
70
  intervals=self.config.scan_intervals,
72
71
  points_per_interval=_points_per_interval(
73
72
  self.scanning_points, self._squished_intervals
@@ -216,7 +215,6 @@ class _LeAmpCom:
216
215
  __ser: serial.Serial | LeMockDevice = field(init=False)
217
216
 
218
217
  ENCODING: ClassVar[str] = "utf-8"
219
- DAC_BITWIDTH: ClassVar[int] = 4096 # 12-bit DAC
220
218
 
221
219
  OK_RESPONSE: ClassVar[str] = "ACK"
222
220
  START_COMMAND: ClassVar[str] = "G"
@@ -230,30 +228,30 @@ class _LeAmpCom:
230
228
  return self.config.n_points
231
229
 
232
230
  @cached_property
233
- def times(self: _LeAmpCom) -> FloatArray:
234
- return _delay_from_intervals(
235
- delayunit=load_delayunit(self.config.delayunit),
236
- intervals=self.config.scan_intervals,
237
- points_per_interval=_points_per_interval(
238
- self.scanning_points, self._squished_intervals
239
- ),
240
- )
241
-
242
- @cached_property
243
- def scanning_list(self: _LeAmpCom) -> list[int]:
244
- scanning_list: list[int] = []
231
+ def scanning_list(self: _LeAmpCom) -> list[float]:
232
+ scanning_list: list[float] = []
245
233
  for interval, n_points in zip(
246
- self._squished_intervals,
247
- _points_per_interval(self.scanning_points, self._squished_intervals),
234
+ self._intervals,
235
+ _points_per_interval(self.scanning_points, self._intervals),
248
236
  ):
249
- denormalized = self._denormalize_interval(interval)
250
237
  scanning_list.extend(
251
238
  np.linspace(
252
- denormalized[0], denormalized[1], n_points, endpoint=False
253
- ).astype(int),
239
+ interval.lower,
240
+ interval.upper,
241
+ n_points,
242
+ endpoint=len(self._intervals) == 1,
243
+ ),
254
244
  )
255
245
  return scanning_list
256
246
 
247
+ @cached_property
248
+ def bytes_to_receive(self: _LeAmpCom) -> int:
249
+ """Number of bytes to receive for a single scan.
250
+
251
+ We expect to receive 3 arrays of floats (delays, X and Y), each with self.scanning_points elements.
252
+ """
253
+ return self.scanning_points * 12
254
+
257
255
  def __post_init__(self: _LeAmpCom) -> None:
258
256
  self.__ser = _serial_factory(self.config)
259
257
 
@@ -271,39 +269,28 @@ class _LeAmpCom:
271
269
 
272
270
  def write_list_length_and_integration_periods_and_use_ema(self: _LeAmpCom) -> str:
273
271
  self._encode_send_response(self.SEND_SETTINGS_COMMAND)
274
- self._raw_byte_send(
272
+ self._raw_byte_send_ints(
275
273
  [self.scanning_points, self.config.integration_periods, self.config.use_ema]
276
274
  )
277
275
  return self._get_response()
278
276
 
279
277
  def write_list(self: _LeAmpCom) -> str:
280
278
  self._encode_send_response(self.SEND_LIST_COMMAND)
281
- self._raw_byte_send(self.scanning_list)
279
+ self._raw_byte_send_floats(self.scanning_list)
282
280
  return self._get_response()
283
281
 
284
- def start_scan(self: _LeAmpCom) -> tuple[str, np.ndarray]:
282
+ def start_scan(self: _LeAmpCom) -> tuple[str, np.ndarray, np.ndarray, np.ndarray]:
285
283
  self._encode_send_response(self.START_COMMAND)
286
284
  self._await_scan_finished()
287
- Xs, Ys = self._read_scan()
285
+ times, Xs, Ys = self._read_scan()
288
286
 
289
287
  radii, angles = self._convert_to_r_angle(Xs, Ys)
290
-
291
- output_array = np.zeros((self.scanning_points, 3))
292
- output_array[:, 0] = self.times
293
- output_array[:, 1] = radii
294
- output_array[:, 2] = angles
295
-
296
- return self.START_COMMAND, output_array
288
+ return self.START_COMMAND, np.array(times), np.array(radii), np.array(angles)
297
289
 
298
290
  @cached_property
299
- def _squished_intervals(self: _LeAmpCom) -> list[Interval]:
291
+ def _intervals(self: _LeAmpCom) -> list[Interval]:
300
292
  """Intervals squished into effective DAC range."""
301
- return _squish_intervals(
302
- intervals=self.config.scan_intervals or [Interval(lower=0.0, upper=1.0)],
303
- lower_bound=self.config.fs_dac_lower_bound,
304
- upper_bound=self.config.fs_dac_upper_bound,
305
- bitwidth=self.DAC_BITWIDTH,
306
- )
293
+ return self.config.scan_intervals or [Interval(lower=0.0, upper=1.0)]
307
294
 
308
295
  def _convert_to_r_angle(
309
296
  self: _LeAmpCom, Xs: list, Ys: list
@@ -312,11 +299,6 @@ class _LeAmpCom:
312
299
  angle = np.arctan2(np.array(Ys), np.array(Xs))
313
300
  return r, np.rad2deg(angle)
314
301
 
315
- def _denormalize_interval(self: _LeAmpCom, interval: Interval) -> list[int]:
316
- lower = int(interval.lower * self.DAC_BITWIDTH)
317
- upper = int(interval.upper * self.DAC_BITWIDTH)
318
- return [lower, upper]
319
-
320
302
  def _encode_send_response(self: _LeAmpCom, command: str) -> str:
321
303
  self._encode_and_send(command)
322
304
  return self._get_response()
@@ -324,12 +306,18 @@ class _LeAmpCom:
324
306
  def _encode_and_send(self: _LeAmpCom, command: str) -> None:
325
307
  self.__ser.write(command.encode(self.ENCODING))
326
308
 
327
- def _raw_byte_send(self: _LeAmpCom, values: list[int]) -> None:
309
+ def _raw_byte_send_ints(self: _LeAmpCom, values: list[int]) -> None:
328
310
  c = BitArray()
329
311
  for value in values:
330
312
  c.append(BitArray(uintle=value, length=16))
331
313
  self.__ser.write(c.tobytes())
332
314
 
315
+ def _raw_byte_send_floats(self: _LeAmpCom, values: list[float]) -> None:
316
+ c = BitArray()
317
+ for value in values:
318
+ c.append(BitArray(floatle=value, length=32))
319
+ self.__ser.write(c.tobytes())
320
+
333
321
  def _await_scan_finished(self: _LeAmpCom) -> None:
334
322
  time.sleep(self.config._sweep_length_ms * 1.0e-3) # noqa: SLF001, access to private attribute for backwards compatibility
335
323
  status = self._get_status()
@@ -345,26 +333,31 @@ class _LeAmpCom:
345
333
  @_BackoffRetry(
346
334
  backoff_base=1e-2, max_tries=5, logger=logging.getLogger(LOGGER_NAME)
347
335
  )
348
- def _read_scan(self: _LeAmpCom) -> tuple[list[float], list[float]]:
336
+ def _read_scan(self: _LeAmpCom) -> tuple[list[float], list[float], list[float]]:
349
337
  self._encode_and_send(self.FETCH_COMMAND)
338
+ scan_bytes = self.__ser.read(self.bytes_to_receive)
350
339
 
351
- bytes_to_receive = self.scanning_points * 4 + self.scanning_points * 4
352
- scan_bytes = self.__ser.read(bytes_to_receive)
353
- if len(scan_bytes) != bytes_to_receive:
354
- msg = f"received {len(scan_bytes)} bytes, expected {bytes_to_receive}"
340
+ if len(scan_bytes) != self.bytes_to_receive:
341
+ msg = f"received {len(scan_bytes)} bytes, expected {self.bytes_to_receive}"
355
342
  raise serialutil.SerialException(msg)
356
343
 
357
- Xs = [
358
- BitArray(bytes=scan_bytes[d : d + 4]).floatle
359
- for d in range(0, self.scanning_points * 4, 4)
360
- ]
361
- Ys = [
344
+ times = self._bytes_to_floats(scan_bytes, 0, self.scanning_points * 4)
345
+ Xs = self._bytes_to_floats(
346
+ scan_bytes, self.scanning_points * 4, self.scanning_points * 8
347
+ )
348
+ Ys = self._bytes_to_floats(
349
+ scan_bytes, self.scanning_points * 8, self.scanning_points * 12
350
+ )
351
+ return times, Xs, Ys
352
+
353
+ def _bytes_to_floats(
354
+ self: _LeAmpCom, scan_bytes: bytes, from_idx: int, to_idx: int
355
+ ) -> list[float]:
356
+ return [
362
357
  BitArray(bytes=scan_bytes[d : d + 4]).floatle
363
- for d in range(self.scanning_points * 4, self.scanning_points * 8, 4)
358
+ for d in range(from_idx, to_idx, 4)
364
359
  ]
365
360
 
366
- return Xs, Ys
367
-
368
361
  def _get_status(self: _LeAmpCom) -> _LeStatus:
369
362
  msg = self._encode_send_response(self.STATUS_COMMAND)
370
363
  if msg == _LeStatus.SCANNING.value:
@@ -434,7 +427,9 @@ def _squish_intervals(
434
427
 
435
428
 
436
429
  def _delay_from_intervals(
437
- delayunit: Delay, intervals: list[Interval], points_per_interval: list[int]
430
+ delayunit: Callable[[FloatArray], FloatArray],
431
+ intervals: list[Interval],
432
+ points_per_interval: list[int],
438
433
  ) -> FloatArray:
439
434
  """Convert a list of intervals to a list of delay times."""
440
435
  times: list[float] = []
@@ -5,8 +5,6 @@ from abc import ABC, abstractmethod
5
5
  from dataclasses import asdict, dataclass, field
6
6
  from typing import TYPE_CHECKING, ClassVar, TypeVar
7
7
 
8
- from .delayunit import validate_delayunit
9
-
10
8
  if TYPE_CHECKING:
11
9
  from pathlib import Path
12
10
 
@@ -83,7 +81,6 @@ class ForceDeviceConfiguration(DeviceConfiguration):
83
81
  Args:
84
82
  amp_port: The name of the serial port the amp is connected to.
85
83
  sweep_length_ms: The length of the sweep in milliseconds.
86
- delayunit: Name of the delay calculator.
87
84
  scan_intervals: The intervals to scan.
88
85
  integration_periods: The number of integration periods to use.
89
86
  modulation_frequency: The frequency of the modulation in Hz.
@@ -97,7 +94,6 @@ class ForceDeviceConfiguration(DeviceConfiguration):
97
94
 
98
95
  amp_port: str
99
96
  sweep_length_ms: float
100
- delayunit: str
101
97
  scan_intervals: list[Interval] = field(default_factory=lambda: [Interval(0.0, 1.0)])
102
98
  integration_periods: int = 100
103
99
  modulation_frequency: int = 10000 # Hz
@@ -114,9 +110,6 @@ class ForceDeviceConfiguration(DeviceConfiguration):
114
110
  def _sweep_length_ms(self: ForceDeviceConfiguration) -> float:
115
111
  return self.sweep_length_ms
116
112
 
117
- def __post_init__(self: ForceDeviceConfiguration) -> None: # noqa: D105
118
- validate_delayunit(self.delayunit)
119
-
120
113
  def save(self: ForceDeviceConfiguration, path: Path) -> str:
121
114
  """Save a DeviceConfiguration to a file.
122
115
 
@@ -172,7 +165,6 @@ class LeDeviceConfiguration(DeviceConfiguration):
172
165
 
173
166
  Args:
174
167
  amp_port: The name of the serial port the amp is connected to.
175
- delayunit: Name of the delay calculator.
176
168
  use_ema: Whether to use en exponentially moving average filter during lockin detection.
177
169
  n_points: The number of points to scan.
178
170
  scan_intervals: The intervals to scan.
@@ -181,21 +173,15 @@ class LeDeviceConfiguration(DeviceConfiguration):
181
173
  """
182
174
 
183
175
  amp_port: str
184
- delayunit: str
185
176
  use_ema: bool = True
186
177
  n_points: int = 1000
187
178
  scan_intervals: list[Interval] = field(default_factory=lambda: [Interval(0.0, 1.0)])
188
179
  integration_periods: int = 10
189
180
  amp_timeout_seconds: float = 0.2
181
+ modulation_frequency: int = 10000 # Hz
190
182
 
191
- modulation_frequency: ClassVar[int] = 10000 # Hz
192
- fs_dac_lower_bound: ClassVar[int] = 300
193
- fs_dac_upper_bound: ClassVar[int] = 3700
194
183
  amp_baudrate: ClassVar[int] = 1000000 # bit/s
195
184
 
196
- def __post_init__(self: LeDeviceConfiguration) -> None: # noqa: D105
197
- validate_delayunit(self.delayunit)
198
-
199
185
  @property
200
186
  def _sweep_length_ms(self: LeDeviceConfiguration) -> float:
201
187
  return self.n_points * self._time_constant_ms
@@ -195,7 +195,7 @@ class LeMockDevice(MockDevice):
195
195
  self.n_scanning_points: int | None = None
196
196
  self.integration_periods: int | None = None
197
197
  self.use_ema: bool | None = None
198
- self.scanning_list: list[int] | None = None
198
+ self.scanning_list: list[float] | None = None
199
199
  self._scan_start_time: float | None = None
200
200
 
201
201
  def write(self: LeMockDevice, input_bytes: bytes) -> None:
@@ -297,7 +297,7 @@ class LeMockDevice(MockDevice):
297
297
  self.state = _LeMockState.RECEIVED_SETTINGS
298
298
 
299
299
  def _handle_waiting_for_list(self: LeMockDevice, input_bytes: bytes) -> None:
300
- self.scanning_list = self._decode_ints(input_bytes)
300
+ self.scanning_list = self._decode_floats(input_bytes)
301
301
  self.state = _LeMockState.RECEIVED_LIST
302
302
 
303
303
  def _decode_ints(self: LeMockDevice, input_bytes: bytes) -> list[int]:
@@ -307,6 +307,13 @@ class LeMockDevice(MockDevice):
307
307
  for i in range(0, len(input_bytes), 2)
308
308
  ]
309
309
 
310
+ def _decode_floats(self: LeMockDevice, input_bytes: bytes) -> list[float]:
311
+ # Convert every four bytes to a 32-bit float (assuming little-endian format)
312
+ return [
313
+ struct.unpack("<f", input_bytes[i : i + 4])[0]
314
+ for i in range(0, len(input_bytes), 4)
315
+ ]
316
+
310
317
  def _scan_has_finished(self: LeMockDevice) -> bool:
311
318
  if not self.is_scanning:
312
319
  return True
@@ -329,7 +336,12 @@ class LeMockDevice(MockDevice):
329
336
  self.n_failures += 1
330
337
  numbers = np.array([])
331
338
  else:
332
- numbers = self.rng.random(2 * len(self.scanning_list))
339
+ numbers = np.concatenate(
340
+ (
341
+ np.array(self.scanning_list) * 100e-12, # mock time values
342
+ self.rng.random(2 * len(self.scanning_list)),
343
+ )
344
+ )
333
345
 
334
346
  # Each scanning point will generate an X and a Y value (lockin detection)
335
347
  return struct.pack("<" + "f" * len(numbers), *numbers)
@@ -198,11 +198,7 @@ class LeScanner(_ScannerImplementation[LeDeviceConfiguration]):
198
198
  Returns:
199
199
  Unprocessed scan.
200
200
  """
201
- _, responses = self._ampcom.start_scan()
202
-
203
- time = responses[:, 0]
204
- radius = responses[:, 1]
205
- theta = responses[:, 2]
201
+ _, time, radius, theta = self._ampcom.start_scan()
206
202
  self._phase_estimator.update_estimate(radius=radius, theta=theta)
207
203
 
208
204
  return UnprocessedWaveform.from_polar_coords(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyglaze
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Pyglaze is a library used to operate the devices of Glaze Technologies
5
5
  Author: GLAZE Technologies ApS
6
6
  License: BSD 3-Clause License
@@ -0,0 +1,25 @@
1
+ pyglaze/__init__.py,sha256=Zn1KFblwuFHiDRdRAiRnDBRkbPttWh44jKa5zG2ov0E,22
2
+ pyglaze/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ pyglaze/datamodels/__init__.py,sha256=DJLByl2C7pC4RM4Uh6PW-McM5RIGBjcopzGywCKhSlI,111
4
+ pyglaze/datamodels/pulse.py,sha256=BoW_GDvkwEpn_UYUFPoUfi5E_oCnvv8cU4nZKG7Ph2U,20911
5
+ pyglaze/datamodels/waveform.py,sha256=n31DhJHFeBNZ3hHqQUiCGXssm5Dc8wV6tGPkhmFYB4Q,5809
6
+ pyglaze/device/__init__.py,sha256=5RjCHuFKMi9g2KLUkxixO9hNpAgkUBcOURNTuhAdoUk,177
7
+ pyglaze/device/ampcom.py,sha256=mtsprV51UeRwqkvbbLRbO2WeRd2XdFcU4_Q8AMul28g,16523
8
+ pyglaze/device/configuration.py,sha256=gh_eerX8TdXx3LnFxHieJqOpfDfE9cV6Xgm5WYVnvO0,7994
9
+ pyglaze/devtools/__init__.py,sha256=9EW20idoaZv_5GuSgDmfpTPjfCZ-Rl27EV3oJebmwnQ,90
10
+ pyglaze/devtools/mock_device.py,sha256=XJ1jwOOYPDqynX3QDYfEJk9n3sTMmefNPAe9N3M1T5Q,13522
11
+ pyglaze/devtools/thz_pulse.py,sha256=xp-T9psdOrUMtSUFu8HEwQJVu_aMixJdZHtg_BCVu_k,923
12
+ pyglaze/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ pyglaze/helpers/types.py,sha256=p9xSAP5Trr1FcCWl7ynCWqDOUZKgMQYzMUXSwDpAKHg,599
14
+ pyglaze/helpers/utilities.py,sha256=n_x9Tqm305MUorS29O6CoJM8Mi4apo2bsN_odrRaVAw,2423
15
+ pyglaze/interpolation/__init__.py,sha256=WCxHPsiI7zvJykp-jfytoEbO4Tla-YIF6A7fjDfcDvU,72
16
+ pyglaze/interpolation/interpolation.py,sha256=rQWzPD7W8TXETps7VZI0gcfAOCWO8pGL1HhhBnyxaMw,735
17
+ pyglaze/scanning/__init__.py,sha256=uCBaeDTufOrC9KWf30ICqcmvFg_YT85olb3M9jkvZRg,99
18
+ pyglaze/scanning/_asyncscanner.py,sha256=n4PhRBSZLFSSeGgBkgVwFsdY4rnduXJnQo6TQ0ujVkw,5224
19
+ pyglaze/scanning/client.py,sha256=3qrQStkeLQzCeu4yMHJ_ENLGQ7E5GMc4CP9J55rk-ug,1817
20
+ pyglaze/scanning/scanner.py,sha256=PSjXVpSpHpYIl-sW34pIThocbM9GSHJ_E4gGcsePeTw,8139
21
+ pyglaze-0.2.0.dist-info/LICENSE,sha256=LCP3sGBX7LxuQopcjeug1fW4tngWCHF4zB7QCgB28xM,1504
22
+ pyglaze-0.2.0.dist-info/METADATA,sha256=ZWjcLSzB9CD-_MmxsXdaX2tEcQbia3zLTH2kp5cnp1E,3498
23
+ pyglaze-0.2.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
24
+ pyglaze-0.2.0.dist-info/top_level.txt,sha256=X7d5rqVVuWNmtK4-Uh4sgOLlqye8vaHZOr5RYba0REo,8
25
+ pyglaze-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (72.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,151 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import pickle
4
- from abc import ABC, abstractmethod
5
- from dataclasses import asdict, dataclass
6
- from datetime import datetime
7
- from pathlib import Path
8
- from typing import TYPE_CHECKING, Callable, cast
9
- from uuid import UUID, uuid4
10
-
11
- import numpy as np
12
-
13
- if TYPE_CHECKING:
14
- from pyglaze.helpers.types import FloatArray
15
-
16
- __all__ = ["UniformDelay", "NonuniformDelay", "list_delayunits", "load_delayunit"]
17
-
18
- _DELAYUNITS_PATH = Path(__file__).parent / "_delayunit_data"
19
-
20
-
21
- def validate_delayunit(name: str) -> None:
22
- delayunits = list_delayunits()
23
- if name not in delayunits:
24
- msg = f"Unknown delayunit '{name}'. Valid options are: {', '.join(delayunits)}."
25
- raise ValueError(msg)
26
-
27
-
28
- def list_delayunits() -> list[str]:
29
- """List all available delayunits.
30
-
31
- Returns:
32
- A list of all available delayunits.
33
-
34
- """
35
- return [delayunit.stem for delayunit in _DELAYUNITS_PATH.iterdir()]
36
-
37
-
38
- def load_delayunit(name: str) -> Delay:
39
- """Load a delayunit by name.
40
-
41
- Args:
42
- name: The name of the delayunit to load.
43
-
44
- Returns:
45
- The loaded delayunit.
46
- """
47
- try:
48
- return _load_delayunit_from_path(_DELAYUNITS_PATH / f"{name}.pickle")
49
- except FileNotFoundError as e:
50
- msg = f"Unknown delayunit requested ('{name}'). Known units are: {list_delayunits()}"
51
- raise NameError(msg) from e
52
-
53
-
54
- def _load_delayunit_from_path(path: Path) -> Delay:
55
- with Path(path).open("rb") as f:
56
- _dict: dict = pickle.load(f)
57
- delay_type = _dict.pop("type")
58
- return cast(Delay, globals()[delay_type](**_dict))
59
-
60
-
61
- @dataclass(frozen=True)
62
- class Delay(ABC):
63
- friendly_name: str
64
- unique_id: UUID
65
- creation_time: datetime
66
-
67
- def __call__(self: Delay, x: FloatArray) -> FloatArray:
68
- if np.max(x) > 1.0 or np.min(x) < 0.0:
69
- msg = "All values of 'x' must be between 0 and 1."
70
- raise ValueError(msg)
71
-
72
- return self._call(x)
73
-
74
- @property
75
- def filename(self: Delay) -> str:
76
- return f"{self.friendly_name}-{self.creation_time.strftime('%Y-%m-%d')}.pickle"
77
-
78
- @abstractmethod
79
- def _call(self: Delay, x: FloatArray) -> FloatArray: ...
80
-
81
- def save(self: Delay, path: Path) -> None:
82
- with Path(path).open("wb") as f:
83
- pickle.dump({"type": self.__class__.__name__, **asdict(self)}, f)
84
-
85
-
86
- @dataclass(frozen=True)
87
- class UniformDelay(Delay):
88
- """A delay calculator that calculates equidisant delays."""
89
-
90
- time_window: float
91
-
92
- def _call(self: UniformDelay, x: FloatArray) -> FloatArray:
93
- return x * self.time_window
94
-
95
- @classmethod
96
- def new(cls: type[UniformDelay], time_window: float, friendly_name: str) -> Delay:
97
- """Create a new Delay object.
98
-
99
- Args:
100
- time_window: The time window for the delay.
101
- friendly_name: The friendly name of the delay.
102
-
103
- Returns:
104
- Delay: The newly created Delay object.
105
- """
106
- return cls(
107
- friendly_name=friendly_name,
108
- unique_id=uuid4(),
109
- creation_time=datetime.now(), # noqa: DTZ005
110
- time_window=time_window,
111
- )
112
-
113
-
114
- @dataclass(frozen=True)
115
- class NonuniformDelay(Delay):
116
- """A delay calculator that calculates non-equidistant delays."""
117
-
118
- time_window: float
119
- residual_interpolator: Callable[[FloatArray], FloatArray]
120
-
121
- def _call(self: NonuniformDelay, x: FloatArray) -> FloatArray:
122
- return (
123
- x * self.time_window
124
- + self.residual_interpolator(x)
125
- - self.residual_interpolator(np.asarray(0.0))
126
- )
127
-
128
- @classmethod
129
- def new(
130
- cls: type[NonuniformDelay],
131
- friendly_name: str,
132
- time_window: float,
133
- residual_interpolator: Callable[[FloatArray], FloatArray],
134
- ) -> Delay:
135
- """Create a new NonuniformDelay object.
136
-
137
- Args:
138
- friendly_name: The friendly name of the NonuniformDelay object.
139
- time_window: The time window of the pulse.
140
- residual_interpolator: a residual interpolator for calculating the nonuniform part of the delay.
141
-
142
- Returns:
143
- Delay: The newly created Delay object.
144
- """
145
- return cls(
146
- friendly_name=friendly_name,
147
- unique_id=uuid4(),
148
- creation_time=datetime.now(), # noqa: DTZ005
149
- time_window=time_window,
150
- residual_interpolator=residual_interpolator,
151
- )
@@ -1,41 +0,0 @@
1
- from typing import Literal, get_args
2
- from uuid import UUID
3
-
4
- from typing_extensions import TypeAlias
5
-
6
- DeviceName: TypeAlias = Literal["glaze1", "glaze2", "carmen"]
7
-
8
-
9
- def _device_ids() -> dict[DeviceName, UUID]:
10
- return {
11
- "glaze1": UUID("5042dbda-e9bc-4216-a614-ac56d0a32023"),
12
- "glaze2": UUID("66fa482a-1ef4-4076-a883-72d7bf43e151"),
13
- "carmen": UUID("6a54db26-fa88-4146-b04f-b84b945bfea8"),
14
- }
15
-
16
-
17
- def list_devices() -> list[DeviceName]:
18
- """List all available devices.
19
-
20
- Returns:
21
- A list of all available devices.
22
- """
23
- return list(_device_ids().keys())
24
-
25
-
26
- def get_device_id(name: DeviceName) -> UUID:
27
- """Get the UUID of a device by its name.
28
-
29
- Args:
30
- name: The name of the device.
31
-
32
- Returns:
33
- Unique identifier of a device.
34
- """
35
- try:
36
- return _device_ids()[name]
37
- except KeyError as e:
38
- msg = (
39
- f"Device {name} does not exist. Possible values are {get_args(DeviceName)}"
40
- )
41
- raise ValueError(msg) from e
@@ -1,32 +0,0 @@
1
- pyglaze/__init__.py,sha256=YvuYzWnKtqBb-IqG8HAu-nhIYAsgj9Vmc_b9o7vO-js,22
2
- pyglaze/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- pyglaze/datamodels/__init__.py,sha256=DJLByl2C7pC4RM4Uh6PW-McM5RIGBjcopzGywCKhSlI,111
4
- pyglaze/datamodels/pulse.py,sha256=4dZ9jmakDZ_2lG9OBEl32_QKlS3Cj1ixxaSlvjyutbY,20169
5
- pyglaze/datamodels/waveform.py,sha256=n31DhJHFeBNZ3hHqQUiCGXssm5Dc8wV6tGPkhmFYB4Q,5809
6
- pyglaze/device/__init__.py,sha256=NbN0I1U3mc_kNz-HyRCGRQQSi5z5peEZOIrH3z-m52Y,445
7
- pyglaze/device/ampcom.py,sha256=_cPfgXb78uuj-GZkNPNDjifhQvqfkAxYycY8n_E2bvk,16767
8
- pyglaze/device/configuration.py,sha256=8_q7l4DTsU_dfrZd384XYGyzmA8ZuOho98pFulzByeY,8511
9
- pyglaze/device/delayunit.py,sha256=TO1FRhB3TepJn4QRNP-AKykWJVD9sx0Q9crL3xMudIk,4415
10
- pyglaze/device/identifiers.py,sha256=-LBAhlYsxBGSMevUIqW3KxckdOzOT9HcKIBOjT3XN-U,1022
11
- pyglaze/device/_delayunit_data/carmen-nonuniform-2023-10-20.pickle,sha256=5qcz-YwPfP5s2f5xlQ02er4dtZULWxXXJsac5tQsJRE,20400
12
- pyglaze/device/_delayunit_data/g1-linearized-2023-04-04.pickle,sha256=N2QX4WSCrh7QihOqfczIYgk_XPx1IxY2zxp0gs9H0RY,209
13
- pyglaze/device/_delayunit_data/g2-linearized-2023-04-04.pickle,sha256=eJ1A-NsZxN3pDd0UsqCyY9-pEzcXAYdsu_vxBx8ODq0,210
14
- pyglaze/device/_delayunit_data/g2-nonuniform-2023-04-04.pickle,sha256=1Tyd22YzAqw2r6BPVemfE1XuJ8jZj8XYQik5Ne8-s9g,1784
15
- pyglaze/device/_delayunit_data/mock_delay.pickle,sha256=tu-Uqytg8bWarBaXXRMD4jVU6MUr1Sz6dswJ6_6nbOA,207
16
- pyglaze/devtools/__init__.py,sha256=9EW20idoaZv_5GuSgDmfpTPjfCZ-Rl27EV3oJebmwnQ,90
17
- pyglaze/devtools/mock_device.py,sha256=TglmqvwX3FtdcE8cB_-3RNu2_QFtuJ-1chW8WTED3rc,13049
18
- pyglaze/devtools/thz_pulse.py,sha256=xp-T9psdOrUMtSUFu8HEwQJVu_aMixJdZHtg_BCVu_k,923
19
- pyglaze/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- pyglaze/helpers/types.py,sha256=p9xSAP5Trr1FcCWl7ynCWqDOUZKgMQYzMUXSwDpAKHg,599
21
- pyglaze/helpers/utilities.py,sha256=n_x9Tqm305MUorS29O6CoJM8Mi4apo2bsN_odrRaVAw,2423
22
- pyglaze/interpolation/__init__.py,sha256=WCxHPsiI7zvJykp-jfytoEbO4Tla-YIF6A7fjDfcDvU,72
23
- pyglaze/interpolation/interpolation.py,sha256=rQWzPD7W8TXETps7VZI0gcfAOCWO8pGL1HhhBnyxaMw,735
24
- pyglaze/scanning/__init__.py,sha256=uCBaeDTufOrC9KWf30ICqcmvFg_YT85olb3M9jkvZRg,99
25
- pyglaze/scanning/_asyncscanner.py,sha256=n4PhRBSZLFSSeGgBkgVwFsdY4rnduXJnQo6TQ0ujVkw,5224
26
- pyglaze/scanning/client.py,sha256=3qrQStkeLQzCeu4yMHJ_ENLGQ7E5GMc4CP9J55rk-ug,1817
27
- pyglaze/scanning/scanner.py,sha256=TGHTO4qoLeKzb_hCuqEEAlKicbDvozY-3GiSfxiatXI,8226
28
- pyglaze-0.1.2.dist-info/LICENSE,sha256=LCP3sGBX7LxuQopcjeug1fW4tngWCHF4zB7QCgB28xM,1504
29
- pyglaze-0.1.2.dist-info/METADATA,sha256=kVOBOygqiA8ZZHdrO09wi8pkkM2qhTP-3NKUIgiXSM4,3498
30
- pyglaze-0.1.2.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
31
- pyglaze-0.1.2.dist-info/top_level.txt,sha256=X7d5rqVVuWNmtK4-Uh4sgOLlqye8vaHZOr5RYba0REo,8
32
- pyglaze-0.1.2.dist-info/RECORD,,