fpfind 3.3.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.
- fpfind/__init__.py +14 -0
- fpfind/_postinstall +5 -0
- fpfind/apps/fpplot.py +286 -0
- fpfind/apps/g2_two_timestamps.py +38 -0
- fpfind/apps/generate_freqcd_testcase.py +150 -0
- fpfind/apps/generate_freqdetuneg2.py +50 -0
- fpfind/apps/show-timestamps +16 -0
- fpfind/apps/show-timestamps-hex +16 -0
- fpfind/apps/tsviz.py +50 -0
- fpfind/fpfind.py +1008 -0
- fpfind/freqcd +81 -0
- fpfind/freqcd.c +549 -0
- fpfind/freqservo.py +333 -0
- fpfind/lib/_logging.py +177 -0
- fpfind/lib/constants.py +98 -0
- fpfind/lib/getopt.c +106 -0
- fpfind/lib/getopt.h +12 -0
- fpfind/lib/parse_epochs.py +699 -0
- fpfind/lib/parse_timestamps.py +1316 -0
- fpfind/lib/typing.py +27 -0
- fpfind/lib/utils.py +716 -0
- fpfind-3.3.0.data/scripts/freqcd +81 -0
- fpfind-3.3.0.data/scripts/show-timestamps +16 -0
- fpfind-3.3.0.data/scripts/show-timestamps-hex +16 -0
- fpfind-3.3.0.dist-info/METADATA +126 -0
- fpfind-3.3.0.dist-info/RECORD +29 -0
- fpfind-3.3.0.dist-info/WHEEL +4 -0
- fpfind-3.3.0.dist-info/entry_points.txt +7 -0
- fpfind-3.3.0.dist-info/licenses/LICENSE +339 -0
fpfind/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
import fpfind.lib
|
|
4
|
+
from fpfind.lib.constants import NP_PRECISEFLOAT, TSRES
|
|
5
|
+
from fpfind.lib.utils import get_overlap
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"fpfind",
|
|
9
|
+
"NP_PRECISEFLOAT",
|
|
10
|
+
"TSRES",
|
|
11
|
+
"get_overlap", # used in tests
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
VERSION = version("fpfind")
|
fpfind/_postinstall
ADDED
fpfind/apps/fpplot.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""NOT FOR PRODUCTION USE - but in main branch for ease of maintenance ;)
|
|
3
|
+
|
|
4
|
+
Changelog:
|
|
5
|
+
2024-02-02, Justin: Init
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import kochen # v0.2024.4
|
|
13
|
+
import kochen.logging
|
|
14
|
+
import kochen.scriptutil
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
import numpy as np
|
|
17
|
+
from S15lib.g2lib.g2lib import histogram
|
|
18
|
+
|
|
19
|
+
from fpfind.lib.parse_timestamps import read_a1_overlapping
|
|
20
|
+
from fpfind.lib.utils import (
|
|
21
|
+
get_first_overlapping_epoch,
|
|
22
|
+
get_timestamp_pattern,
|
|
23
|
+
normalize_timestamps,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
_ENABLE_BREAKPOINT = False
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def plotter(
|
|
31
|
+
alice,
|
|
32
|
+
bob,
|
|
33
|
+
freq,
|
|
34
|
+
time,
|
|
35
|
+
width,
|
|
36
|
+
resolution,
|
|
37
|
+
window=800,
|
|
38
|
+
save=False,
|
|
39
|
+
normalize=False,
|
|
40
|
+
decimation=1,
|
|
41
|
+
):
|
|
42
|
+
bob = (bob - time) / (1 + freq)
|
|
43
|
+
ys, xs, stats = histogram(
|
|
44
|
+
alice,
|
|
45
|
+
bob,
|
|
46
|
+
duration=width,
|
|
47
|
+
resolution=resolution,
|
|
48
|
+
statistics=True,
|
|
49
|
+
window=window,
|
|
50
|
+
)
|
|
51
|
+
# Custom breakpoint for experimentation
|
|
52
|
+
if _ENABLE_BREAKPOINT:
|
|
53
|
+
globals().update(locals()) # write all local variables to global scope
|
|
54
|
+
raise # noqa: PLE0704
|
|
55
|
+
|
|
56
|
+
if decimation > 1:
|
|
57
|
+
ys = ys[::decimation]
|
|
58
|
+
xs = xs[::decimation]
|
|
59
|
+
|
|
60
|
+
elapsed = (alice[-1] - alice[0]) * 1e-9
|
|
61
|
+
_center = ys[500:1500]
|
|
62
|
+
_sides = np.array(list(ys[:500]) + list(ys[1500:]))
|
|
63
|
+
coincidences = np.sum(_center) - np.sum(_sides) * len(_center) / len(_sides)
|
|
64
|
+
|
|
65
|
+
s1 = len(alice) / elapsed
|
|
66
|
+
s2 = len(bob) / elapsed
|
|
67
|
+
c = coincidences / elapsed
|
|
68
|
+
print("Totals:")
|
|
69
|
+
print(f" S1: {len(alice):d}")
|
|
70
|
+
print(f" S2: {len(bob):d}")
|
|
71
|
+
print(f" C: {coincidences:.0f}")
|
|
72
|
+
print(f"Time elapsed: {elapsed:.3f}s")
|
|
73
|
+
print(f" s1: {s1:.0f}")
|
|
74
|
+
print(f" s2: {s2:.0f}")
|
|
75
|
+
print(f" sg: {(len(alice) * len(bob)) ** 0.5 / elapsed:.0f}")
|
|
76
|
+
print(f" c: {c:.0f}")
|
|
77
|
+
print(f"Efficiency: {100 * c / np.sqrt(s1 * s2):.1f}%")
|
|
78
|
+
|
|
79
|
+
# Normalize?
|
|
80
|
+
plt.subplots(figsize=(5, 3.5))
|
|
81
|
+
if normalize:
|
|
82
|
+
accs = s1 * s2 * elapsed * resolution * 1e-9
|
|
83
|
+
yerrs = np.sqrt(ys) / accs
|
|
84
|
+
ys = ys / accs
|
|
85
|
+
print(f"Normalizing by background = {accs}")
|
|
86
|
+
plt.errorbar(xs, ys, yerrs, fmt=".", markersize=3, elinewidth=1)
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
plt.plot(xs, ys, linewidth=1)
|
|
90
|
+
|
|
91
|
+
plt.xlabel("Delay, $\\tau$ (ns)")
|
|
92
|
+
plt.ylabel("$g^{(2)}(\\tau)$")
|
|
93
|
+
plt.tight_layout()
|
|
94
|
+
if save:
|
|
95
|
+
plt.xlim(np.min(xs), np.max(xs))
|
|
96
|
+
plt.tight_layout()
|
|
97
|
+
plt.savefig(save, dpi=250)
|
|
98
|
+
plt.show()
|
|
99
|
+
globals().update(locals())
|
|
100
|
+
raise # noqa: PLE0704
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def attenuate(ts, transmission=1):
|
|
104
|
+
if transmission >= 1:
|
|
105
|
+
return ts
|
|
106
|
+
mask = np.random.random(size=ts.size) < transmission
|
|
107
|
+
return ts[mask]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main(): # noqa: PLR0915
|
|
111
|
+
global _ENABLE_BREAKPOINT # noqa: PLW0603
|
|
112
|
+
parser = kochen.scriptutil.generate_default_parser(__doc__, "fpfind")
|
|
113
|
+
|
|
114
|
+
# Boilerplate
|
|
115
|
+
# fmt: off
|
|
116
|
+
pgroup_config = parser.add_argument_group("display/configuration")
|
|
117
|
+
pgroup_config.add_argument(
|
|
118
|
+
"-h", "--help", action="store_true",
|
|
119
|
+
help="Show this help message and exit")
|
|
120
|
+
pgroup_config.add_argument(
|
|
121
|
+
"-v", "--verbosity", action="count", default=0,
|
|
122
|
+
help="Specify debug verbosity, e.g. -vv for more verbosity")
|
|
123
|
+
pgroup_config.add_argument(
|
|
124
|
+
"-L", "--logging", metavar="",
|
|
125
|
+
help="Log to file, if specified. Log level follows verbosity.")
|
|
126
|
+
pgroup_config.add_argument(
|
|
127
|
+
"--quiet", action="store_true",
|
|
128
|
+
help="Suppress errors, but will not block logging")
|
|
129
|
+
pgroup_config.add_argument(
|
|
130
|
+
"--config", metavar="", is_config_file_arg=True,
|
|
131
|
+
help="Path to configuration file")
|
|
132
|
+
pgroup_config.add_argument(
|
|
133
|
+
"--save", metavar="", is_write_out_config_file_arg=True,
|
|
134
|
+
help="Path to configuration file for saving, then immediately exit")
|
|
135
|
+
pgroup_config.add_argument(
|
|
136
|
+
"--experiment", action="store_true",
|
|
137
|
+
help=argparse.SUPPRESS)
|
|
138
|
+
|
|
139
|
+
# Timestamp importing arguments
|
|
140
|
+
pgroup_ts = parser.add_argument_group("importing timestamps")
|
|
141
|
+
pgroup_ts.add_argument(
|
|
142
|
+
"-t", "--reference", metavar="",
|
|
143
|
+
help="Timestamp file in 'a1' format, from low-count side (reference)")
|
|
144
|
+
pgroup_ts.add_argument(
|
|
145
|
+
"-T", "--target", metavar="",
|
|
146
|
+
help="Timestamp file in 'a1' format, from high-count side")
|
|
147
|
+
pgroup_ts.add_argument(
|
|
148
|
+
"-X", "--legacy", action="store_true",
|
|
149
|
+
help="Parse raw timestamps in legacy mode (default: %(default)s)")
|
|
150
|
+
pgroup_ts.add_argument(
|
|
151
|
+
"-Z", "--skip-duration", metavar="", type=float, default=0,
|
|
152
|
+
help="Specify initial duration to skip, in seconds (default: %(default)s)")
|
|
153
|
+
|
|
154
|
+
# Epoch importing arguments
|
|
155
|
+
pgroup_ep = parser.add_argument_group("importing epochs")
|
|
156
|
+
pgroup_ep.add_argument(
|
|
157
|
+
"-d", "--sendfiles", metavar="",
|
|
158
|
+
help="SENDFILES, from low-count side (reference)")
|
|
159
|
+
pgroup_ep.add_argument(
|
|
160
|
+
"-D", "--t1files", metavar="",
|
|
161
|
+
help="T1FILES, from high-count side")
|
|
162
|
+
pgroup_ep.add_argument(
|
|
163
|
+
"-e", "--first-epoch", metavar="",
|
|
164
|
+
help="Specify filename of first overlapping epoch, optional")
|
|
165
|
+
pgroup_ep.add_argument(
|
|
166
|
+
"-n", "--num-epochs", metavar="", type=int, default=1,
|
|
167
|
+
help="Specify number of epochs to import (default: %(default)d)")
|
|
168
|
+
pgroup_ep.add_argument(
|
|
169
|
+
"-z", "--skip-epochs", metavar="", type=int, default=0,
|
|
170
|
+
help="Specify number of initial epochs to skip (default: %(default)d)")
|
|
171
|
+
|
|
172
|
+
# Epoch importing arguments
|
|
173
|
+
pgroup_chselect = parser.add_argument_group("channel selection")
|
|
174
|
+
pgroup_chselect.add_argument(
|
|
175
|
+
"-m", "--reference-pattern", metavar="", type=int,
|
|
176
|
+
help="Pattern mask for selecting detector events from low-count side")
|
|
177
|
+
pgroup_chselect.add_argument(
|
|
178
|
+
"-M", "--target-pattern", metavar="", type=int,
|
|
179
|
+
help="Pattern mask for selecting detector events from high-count side")
|
|
180
|
+
|
|
181
|
+
# Plotting parameters
|
|
182
|
+
pgroup = parser.add_argument_group("plotting")
|
|
183
|
+
pgroup.add_argument(
|
|
184
|
+
"--df", type=float, default=0.0,
|
|
185
|
+
help="Specify clock skew, in absolute units (default: %(default)f)")
|
|
186
|
+
pgroup.add_argument(
|
|
187
|
+
"--dt", type=float, default=0.0,
|
|
188
|
+
help="Specify time delay, in units of ns (default: %(default)f)")
|
|
189
|
+
pgroup.add_argument(
|
|
190
|
+
"--width", type=float, default=1000,
|
|
191
|
+
help="Specify one-sided width of histogram, in units of ns (default: %(default)f)") # noqa: E501
|
|
192
|
+
pgroup.add_argument(
|
|
193
|
+
"-r", "--resolution", "--final-res", type=float, default=1,
|
|
194
|
+
help="Specify resolution of histogram, in units of ns (default: %(default)f)")
|
|
195
|
+
pgroup.add_argument(
|
|
196
|
+
"--duration", type=float, default=1,
|
|
197
|
+
help="Specify duration of timestamps to use, units of s (default: %(default)f)")
|
|
198
|
+
pgroup.add_argument(
|
|
199
|
+
"--save-plot",
|
|
200
|
+
help="Specify filename to save the plot to")
|
|
201
|
+
pgroup.add_argument(
|
|
202
|
+
"--normalize", action="store_true",
|
|
203
|
+
help="Normalize g(2) plot")
|
|
204
|
+
pgroup.add_argument(
|
|
205
|
+
"--decimation", type=int, default=1,
|
|
206
|
+
help="Take only every N-th point, for clarity (default: %(default)d)")
|
|
207
|
+
# fmt: on
|
|
208
|
+
|
|
209
|
+
# Parse arguments and configure logging
|
|
210
|
+
args = kochen.scriptutil.parse_args_or_help(parser, ignore_unknown=True)
|
|
211
|
+
kochen.logging.set_default_handlers(logger, file=args.logging)
|
|
212
|
+
kochen.logging.set_logging_level(logger, args.verbosity)
|
|
213
|
+
logger.debug("%s", args)
|
|
214
|
+
|
|
215
|
+
# Set experimental mode
|
|
216
|
+
if args.experiment:
|
|
217
|
+
_ENABLE_BREAKPOINT = True
|
|
218
|
+
|
|
219
|
+
# Obtain timestamps needed for fpplot
|
|
220
|
+
# alice: low count side - chopper - HeadT2 - sendfiles (reference)
|
|
221
|
+
# bob: high count side - chopper2 - HeadT1 - t1files
|
|
222
|
+
if args.sendfiles is not None and args.t1files is not None:
|
|
223
|
+
logger.info(" Reading from epoch directories...")
|
|
224
|
+
_is_reading_ts = False
|
|
225
|
+
|
|
226
|
+
# Automatically choose first overlapping epoch if not supplied manually
|
|
227
|
+
first_epoch, available_epochs = get_first_overlapping_epoch(
|
|
228
|
+
args.sendfiles,
|
|
229
|
+
args.t1files,
|
|
230
|
+
first_epoch=args.first_epoch,
|
|
231
|
+
return_length=True,
|
|
232
|
+
)
|
|
233
|
+
if available_epochs < args.num_epochs + args.skip_epochs:
|
|
234
|
+
logger.warning(" Insufficient epochs")
|
|
235
|
+
|
|
236
|
+
# Read epochs
|
|
237
|
+
alice, aps = get_timestamp_pattern(
|
|
238
|
+
args.sendfiles, "T2", first_epoch, args.skip_epochs, args.num_epochs
|
|
239
|
+
)
|
|
240
|
+
bob, bps = get_timestamp_pattern(
|
|
241
|
+
args.t1files, "T1", first_epoch, args.skip_epochs, args.num_epochs
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
elif args.target is not None and args.reference is not None:
|
|
245
|
+
logger.info(" Reading from timestamp files...")
|
|
246
|
+
_is_reading_ts = True
|
|
247
|
+
(alice, bob), (aps, bps) = read_a1_overlapping(
|
|
248
|
+
args.reference,
|
|
249
|
+
args.target,
|
|
250
|
+
legacy=args.legacy,
|
|
251
|
+
duration=args.duration + args.skip_duration,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
else:
|
|
255
|
+
logger.error("Timestamp files/epochs must be supplied with -tT/-dD")
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
|
|
258
|
+
# Select events only from specified channels
|
|
259
|
+
if args.reference_pattern is not None:
|
|
260
|
+
alice = alice[(aps & args.reference_pattern).astype(bool)]
|
|
261
|
+
if args.target_pattern is not None:
|
|
262
|
+
bob = bob[(bps & args.target_pattern).astype(bool)]
|
|
263
|
+
|
|
264
|
+
# Normalize timestamps to common time reference near start, so that
|
|
265
|
+
# frequency compensation will not shift the timing difference too far
|
|
266
|
+
skip = args.skip_duration if _is_reading_ts else 0
|
|
267
|
+
alice, bob = normalize_timestamps(alice, bob, skip=skip)
|
|
268
|
+
|
|
269
|
+
alice = attenuate(alice, 1)
|
|
270
|
+
bob = attenuate(bob, 1)
|
|
271
|
+
|
|
272
|
+
plotter(
|
|
273
|
+
alice,
|
|
274
|
+
bob,
|
|
275
|
+
args.df,
|
|
276
|
+
args.dt,
|
|
277
|
+
2 * args.width,
|
|
278
|
+
resolution=args.resolution,
|
|
279
|
+
save=args.save_plot,
|
|
280
|
+
normalize=args.normalize,
|
|
281
|
+
decimation=args.decimation,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import parse_timestamps as parser
|
|
4
|
+
|
|
5
|
+
Ta = 2**27
|
|
6
|
+
N = 2**18
|
|
7
|
+
# binwidth = 32
|
|
8
|
+
|
|
9
|
+
def g2(arr1, arr2, start_time, delta_t):
|
|
10
|
+
maxdelay = 10000000
|
|
11
|
+
arr1 = arr1[np.where((arr1 > start_time) & (arr1 <= start_time + Ta))]
|
|
12
|
+
arr2 = arr2[np.where((arr2 > start_time) & (arr2 <= start_time + Ta))]
|
|
13
|
+
arr3 = np.zeros(N, dtype = np.int32)
|
|
14
|
+
delay = 0
|
|
15
|
+
|
|
16
|
+
for event in arr2:
|
|
17
|
+
while arr1.size > 1:
|
|
18
|
+
if (event - arr1[0] >= maxdelay):
|
|
19
|
+
# np.delete(arr1, 0)
|
|
20
|
+
arr1 = arr1[1:]
|
|
21
|
+
# del arr1[:1]
|
|
22
|
+
else: break
|
|
23
|
+
for stamp in arr1:
|
|
24
|
+
delay = event - stamp
|
|
25
|
+
arr3[int(delay // delta_t) % N] += 1
|
|
26
|
+
|
|
27
|
+
return arr3
|
|
28
|
+
|
|
29
|
+
# alice1 = parser.read_a1('./data/1_rawevents/raw_alice_20221109170747.dat', legacy = True)
|
|
30
|
+
# bob1 = parser.read_a1('./data/1_rawevents/raw_bob_20221109170747.dat', legacy = True)
|
|
31
|
+
# start = max(alice1[0], bob1[0])
|
|
32
|
+
# alice_pro = alice1[np.where((alice1 > start) & (alice1 <= start + Ta))]
|
|
33
|
+
# bob_pro = bob1[np.where((bob1 > start) & (bob1 <= start + Ta))]
|
|
34
|
+
# print(alice_pro.size)
|
|
35
|
+
# print(bob_pro.size)
|
|
36
|
+
# g2alicebob = g2(alice_pro, bob_pro)
|
|
37
|
+
# plt.plot(g2alicebob)
|
|
38
|
+
# plt.show()
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Companion script to create sample binary timestamps for freqcd.c
|
|
3
|
+
# Justin, 2023-02-03
|
|
4
|
+
#
|
|
5
|
+
# Examples:
|
|
6
|
+
#
|
|
7
|
+
# 1. Generate legacy timestamps and save to file:
|
|
8
|
+
#
|
|
9
|
+
# ./generate_freqcd_testcase.py \
|
|
10
|
+
# -o .input \
|
|
11
|
+
# -t 0 1000000000 2000000000 \
|
|
12
|
+
# -x
|
|
13
|
+
# ./freqcd -f 1000000 < .input
|
|
14
|
+
#
|
|
15
|
+
# 2. Pipe timestamps directly into freqcd
|
|
16
|
+
#
|
|
17
|
+
# ./generate_freqcd_testcase.py -t 0 1000000000 2000000000 |\
|
|
18
|
+
# ./freqcd -f 1000000 |\
|
|
19
|
+
# ./freqcd -f -999933 -o .output
|
|
20
|
+
#
|
|
21
|
+
# 3. Dynamically generate timestamps via command line and save to file
|
|
22
|
+
#
|
|
23
|
+
# ./generate_freqcd_testcase.py -o .input
|
|
24
|
+
#
|
|
25
|
+
# 4. Read timestamps from event supplied via command line or file
|
|
26
|
+
#
|
|
27
|
+
# cat .input | ./generate_freqcd_testcase.py
|
|
28
|
+
# ./generate_freqcd_testcase.py .input
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import pathlib
|
|
33
|
+
import secrets
|
|
34
|
+
import struct
|
|
35
|
+
import sys
|
|
36
|
+
import warnings
|
|
37
|
+
|
|
38
|
+
import fpfind.lib.parse_timestamps as ts_parser
|
|
39
|
+
|
|
40
|
+
warnings.simplefilter(action="once", category=UserWarning)
|
|
41
|
+
|
|
42
|
+
LEGACY = False
|
|
43
|
+
|
|
44
|
+
def get_event(timestamp, detectors: int = 0b0001):
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
Timestamps are not restricted to the (1 << 54) cap, to reflect the
|
|
48
|
+
filespec of truncating the excess significant bits. Use case in
|
|
49
|
+
having user see that the timestamps have indeed overflowed, e.g. for
|
|
50
|
+
verifying overflow response. A warning will be issued for precaution.
|
|
51
|
+
"""
|
|
52
|
+
assert isinstance(timestamp, int) and (0 <= timestamp)
|
|
53
|
+
if timestamp >= (1 << 54):
|
|
54
|
+
warnings.warn(
|
|
55
|
+
f"Timestamp overflow detected, "
|
|
56
|
+
"will truncate accordingly."
|
|
57
|
+
)
|
|
58
|
+
timestamp = timestamp & 0x3FFFFFFFFFFFFF
|
|
59
|
+
assert isinstance(detectors, int) and (0 <= detectors < 16)
|
|
60
|
+
event = (timestamp << 10) + detectors
|
|
61
|
+
if LEGACY:
|
|
62
|
+
event = ((event & 0xFFFFFFFF) << 32) + (event >> 32)
|
|
63
|
+
return struct.pack("=Q", event)
|
|
64
|
+
|
|
65
|
+
def fwrite(event, filename: str = ".freqcd.input"):
|
|
66
|
+
with open(filename, "ab") as f:
|
|
67
|
+
f.write(event)
|
|
68
|
+
|
|
69
|
+
def owrite(event):
|
|
70
|
+
sys.stdout.buffer.write(event)
|
|
71
|
+
sys.stdout.flush()
|
|
72
|
+
|
|
73
|
+
def main():
|
|
74
|
+
global LEGACY
|
|
75
|
+
parser = argparse.ArgumentParser(description="Generates binary timestamp events for testing freqcd.")
|
|
76
|
+
parser.add_argument("-o", help="output file, defaults to stdout stream")
|
|
77
|
+
parser.add_argument("-t", type=int, nargs="+", help="list of timestamps in decimal")
|
|
78
|
+
parser.add_argument("-x", action="store_true", help="legacy format")
|
|
79
|
+
parser.add_argument("infile", nargs="?", help="optional filename of events ('-' if reading from stdin)")
|
|
80
|
+
|
|
81
|
+
# Print help if no arguments supplied
|
|
82
|
+
if len(sys.argv) == 1:
|
|
83
|
+
parser.print_help(sys.stderr)
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
|
|
86
|
+
args = parser.parse_args()
|
|
87
|
+
if args.x:
|
|
88
|
+
LEGACY = True
|
|
89
|
+
|
|
90
|
+
# Modify method of writing
|
|
91
|
+
write = owrite
|
|
92
|
+
if args.o:
|
|
93
|
+
write = lambda e: fwrite(e, args.o)
|
|
94
|
+
open(args.o, "wb").close() # truncate file
|
|
95
|
+
|
|
96
|
+
# Different methods of reading timestamps
|
|
97
|
+
# 1. Read decimal timestamps from command line
|
|
98
|
+
# e.g. "-t 0 1000 2000"
|
|
99
|
+
if args.t:
|
|
100
|
+
for ts in args.t:
|
|
101
|
+
write(get_event(ts))
|
|
102
|
+
|
|
103
|
+
# 2. Read raw timestamps from file and print as decimal
|
|
104
|
+
# e.g. "filename.dat"
|
|
105
|
+
# 3. Read raw timestamps from stdin
|
|
106
|
+
# e.g. "-"
|
|
107
|
+
elif args.infile:
|
|
108
|
+
inputfile = args.infile
|
|
109
|
+
|
|
110
|
+
# Direct reading from stdin currently not supported
|
|
111
|
+
# instead creating a temporary file to store binary data
|
|
112
|
+
HAS_TEMP_FILE = args.infile == "-"
|
|
113
|
+
if HAS_TEMP_FILE:
|
|
114
|
+
inputfile = pathlib.Path(secrets.token_hex(5) + "_tempinput")
|
|
115
|
+
|
|
116
|
+
# Read binary timestamps from file
|
|
117
|
+
try:
|
|
118
|
+
if HAS_TEMP_FILE:
|
|
119
|
+
with open(inputfile, "wb") as f:
|
|
120
|
+
f.write(sys.stdin.buffer.read())
|
|
121
|
+
|
|
122
|
+
# Parsing binary timestamps
|
|
123
|
+
ts, _ = ts_parser.read_a1(inputfile, args.x)
|
|
124
|
+
for t in ts:
|
|
125
|
+
print(int(t * ts_parser.TSRES.PS4.value))
|
|
126
|
+
|
|
127
|
+
# Clean up
|
|
128
|
+
finally:
|
|
129
|
+
if HAS_TEMP_FILE:
|
|
130
|
+
inputfile.unlink()
|
|
131
|
+
|
|
132
|
+
# 4. Read decimal timestamps from interactive prompt,
|
|
133
|
+
# note output file in this case should be supplied.
|
|
134
|
+
# This is only for testing purposes.
|
|
135
|
+
else:
|
|
136
|
+
# Read from stdin
|
|
137
|
+
print("Enter timestamps in decimal separated by newlines.")
|
|
138
|
+
print("Press Ctrl-C to stop input.")
|
|
139
|
+
try:
|
|
140
|
+
while True:
|
|
141
|
+
try:
|
|
142
|
+
event = get_event(int(input()))
|
|
143
|
+
write(event)
|
|
144
|
+
except ValueError:
|
|
145
|
+
pass
|
|
146
|
+
except KeyboardInterrupt:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Justin, 2022-10-07
|
|
3
|
+
# From single timestamp g(2) file, generate increasingly frequency detuned timestamping.
|
|
4
|
+
#
|
|
5
|
+
# Worked out the theory with Darren:
|
|
6
|
+
# Given a clock rate of f Hz, and the detuning offset denoted D,
|
|
7
|
+
# the detuned frequency is f + D, where D/f = detuning ratio (e.g. 1e-9).
|
|
8
|
+
#
|
|
9
|
+
# Here's an example to illustrate the relationship:
|
|
10
|
+
# The device keeps time via the clock rate. Suppose the clock fed to it
|
|
11
|
+
# is 10Hz, then the device which registers 10 clock ticks as 1 second will
|
|
12
|
+
# read out 1 second. The same device with 15 Hz will then read 1.5 seconds,
|
|
13
|
+
# i.e. faster clock => timestamp is faster relative to actual time.
|
|
14
|
+
#
|
|
15
|
+
# Given a detuning ratio R, the frequency fed to the device is now
|
|
16
|
+
# f + R*f, so the effective timestamp has an additional (1+R) product.
|
|
17
|
+
|
|
18
|
+
import parse_timestamps as parser
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
DATA_DIR = "./Clock-sync/20221007_freqdetuneg2"
|
|
22
|
+
FILE = DATA_DIR + "/singletimestampnodelay.a1.dat"
|
|
23
|
+
|
|
24
|
+
# Read file
|
|
25
|
+
t, p = parser.read_a1(FILE, legacy=True)
|
|
26
|
+
|
|
27
|
+
# Perform frequency detuning of timestamp clock
|
|
28
|
+
def detune(t: np.ndarray, p: np.ndarray, channel: int, ratio: float = 0, offset: float = 0):
|
|
29
|
+
"""Performs frequency detuning of timestamp clock on one channel.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
t: Timestamp data
|
|
33
|
+
p: Detector channel pattern
|
|
34
|
+
channel: Choice of channel, follows detector pattern, i.e. 0b0100 for channel 3
|
|
35
|
+
ratio: Frequency detuning ratio, i.e. 0 is no detuning
|
|
36
|
+
offset: Performs fixed timing offset, optional
|
|
37
|
+
"""
|
|
38
|
+
tc, pc = t[p==channel], p[p==channel]
|
|
39
|
+
tc = (tc-t[0])*(1+ratio) + t[0] + offset # assume both devices are synchronized t
|
|
40
|
+
return tc, pc
|
|
41
|
+
|
|
42
|
+
parser.write_a2(DATA_DIR + "/c1_a2.dat", t[p==1], p[p==1], legacy=True)
|
|
43
|
+
parser.write_a1(DATA_DIR + "/c4e10.dat", *detune(t, p, 8, 1e-10), legacy=True)
|
|
44
|
+
parser.write_a1(DATA_DIR + "/c4e9.dat", *detune(t, p, 8, 1e-9), legacy=True)
|
|
45
|
+
parser.write_a1(DATA_DIR + "/c4e8.dat", *detune(t, p, 8, 1e-8), legacy=True)
|
|
46
|
+
parser.write_a1(DATA_DIR + "/c4e7.dat", *detune(t, p, 8, 1e-7), legacy=True)
|
|
47
|
+
parser.write_a1(DATA_DIR + "/c4e6.dat", *detune(t, p, 8, 1e-6), legacy=True)
|
|
48
|
+
parser.write_a1(DATA_DIR + "/c4e5.dat", *detune(t, p, 8, 1e-5), legacy=True)
|
|
49
|
+
parser.write_a1(DATA_DIR + "/c4e4.dat", *detune(t, p, 8, 1e-4), legacy=True)
|
|
50
|
+
parser.write_a1(DATA_DIR + "/c4e3.dat", *detune(t, p, 8, 1e-3), legacy=True)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Prints 'a1' timestamp files as binary text
|
|
3
|
+
# Justin, 2025-08-27
|
|
4
|
+
#
|
|
5
|
+
# Useful for downstream textual processing, e.g. regex-filtering with grep.
|
|
6
|
+
# Pass '-' as argument to read from standard input instead of a file.
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
# 00000000: 01011000110100001111011100001000 00000000000000000000000000000111
|
|
10
|
+
# 00000008: 00000010110000111111001100100001 00000000000000000000000000001010
|
|
11
|
+
# 00000010: 01011111000110000100101100001000 00000000000000000000000000010000
|
|
12
|
+
# 00000018: 01101111101000111111101100101000 00000000000000000000000000010000
|
|
13
|
+
# ...
|
|
14
|
+
|
|
15
|
+
test -z "$1" && { echo "usage: show-timestamps {<FILE>|-}"; exit 1; }
|
|
16
|
+
xxd -e -g 4 -c 8 "$1" | cut -b11-27 | xxd -r -p - | xxd -b -g 4 -c 8 | cut -b1-75
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Prints 'a1' timestamp files as hexadecimal text
|
|
3
|
+
# Justin, 2025-08-27
|
|
4
|
+
#
|
|
5
|
+
# Useful for downstream textual processing, e.g. regex-filtering with grep.
|
|
6
|
+
# Pass '-' as argument to read from standard input instead of a file.
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
# 00000000: 58d0f708 00000007
|
|
10
|
+
# 00000008: 02c3f321 0000000a
|
|
11
|
+
# 00000010: 5f184b08 00000010
|
|
12
|
+
# 00000018: 6fa3fb28 00000010
|
|
13
|
+
# ...
|
|
14
|
+
|
|
15
|
+
test -z "$1" && { echo "usage: show-timestamps-hex {<FILE>|-}"; exit 1; }
|
|
16
|
+
xxd -e -g 4 -c 8 "$1" | cut -b1-27
|
fpfind/apps/tsviz.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Visualize timestamp file
|
|
3
|
+
# Justin, 2025-04-07
|
|
4
|
+
#
|
|
5
|
+
# Currently hardcoded with read_a1(legacy=True).
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
import fpfind.lib.parse_timestamps as parser
|
|
13
|
+
|
|
14
|
+
if len(sys.argv) < 2:
|
|
15
|
+
print("usage: tsviz.py <FILE> [legacy]")
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
legacy = len(sys.argv) >= 3
|
|
19
|
+
filename = sys.argv[1]
|
|
20
|
+
ts, ps = parser.read_a1(filename, legacy=legacy, ignore_rollover=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def generate_xy(ts, ps, ch):
|
|
24
|
+
"""
|
|
25
|
+
Args:
|
|
26
|
+
ch: Channel number, i.e. between 1 and 4 inclusive.
|
|
27
|
+
"""
|
|
28
|
+
p = int(2 ** (ch - 1))
|
|
29
|
+
xs = ts[(ps & p).astype(bool)]
|
|
30
|
+
ys = np.ones(xs.size) * ch
|
|
31
|
+
return xs, ys
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
args = ["x"]
|
|
35
|
+
kwargs = {"markersize": 3}
|
|
36
|
+
|
|
37
|
+
fig, ax = plt.subplots(figsize=(12, 3))
|
|
38
|
+
plt.plot(*generate_xy(ts, ps, 1), *args, **kwargs)
|
|
39
|
+
plt.plot(*generate_xy(ts, ps, 2), *args, **kwargs)
|
|
40
|
+
plt.plot(*generate_xy(ts, ps, 3), *args, **kwargs)
|
|
41
|
+
plt.plot(*generate_xy(ts, ps, 4), *args, **kwargs)
|
|
42
|
+
|
|
43
|
+
plt.ylim([-1, 6])
|
|
44
|
+
plt.yticks([1, 2, 3, 4])
|
|
45
|
+
plt.gca().invert_yaxis()
|
|
46
|
+
plt.ylabel("Channel")
|
|
47
|
+
plt.xlabel("Time (ns)")
|
|
48
|
+
plt.tight_layout()
|
|
49
|
+
|
|
50
|
+
plt.show()
|