ucndata 5.0.5__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.
ucndata/__init__.py ADDED
@@ -0,0 +1,55 @@
1
+ # init
2
+ # Derek Fujimoto
3
+ # June 2024
4
+
5
+ from .ucnrun import ucnrun
6
+ from .ucncycle import ucncycle
7
+ from .ucnperiod import ucnperiod
8
+ from .ucnbase import ucnbase
9
+ from .ttreeslow import ttreeslow
10
+ from .applylist import applylist
11
+ from .exceptions import *
12
+ from .datetime import to_datetime, from_datetime
13
+
14
+ # path to the directory which contains the root files
15
+ DATADIR = "/data3/ucn/root_files"
16
+
17
+ # detector tree names
18
+ DET_NAMES = {'He3':{'hits': 'UCNHits_He3',
19
+ 'hits_orig': 'UCNHits_He3', # as saved in the root file
20
+ 'charge': 'He3_Charge',
21
+ 'rate': 'He3_Rate',
22
+ 'transitions': 'RunTransitions_He3',
23
+ 'hitsseq': 'hitsinsequence_he3',
24
+ 'hitsseqcumul': 'hitsinsequencecumul_he3',
25
+ },
26
+ 'Li6':{'hits': 'UCNHits_Li6',
27
+ 'hits_orig': 'UCNHits_Li-6', # as saved in the root file
28
+ 'charge': 'Li6_Charge',
29
+ 'rate': 'Li6_Rate',
30
+ 'transitions': 'RunTransitions_Li6',
31
+ 'hitsseq': 'hitsinsequence_li6',
32
+ 'hitsseqcumul': 'hitsinsequencecumul_li6',
33
+ },
34
+ }
35
+
36
+ # needed slow control trees: for checking data quality
37
+ SLOW_TREES = ('BeamlineEpics', 'SequencerTree')
38
+
39
+ # EPICS trees to group into a single slow control tree
40
+ EPICS_TREES = [ 'BeamlineEpics', 'UCN2EpPha5Last', 'UCN2EpicsPhase2B',
41
+ 'UCN2EpPha5Oth', 'UCN2EpicsPhase3', 'UCN2EpPha5Pre',
42
+ 'UCN2EpicsPressures', 'UCN2EpPha5Tmp', 'UCN2EpicsTemperature',
43
+ 'UCN2Epics', 'UCN2Pur', 'UCN2EpicsOthers',
44
+ 'UCN2EpLD2'
45
+ ]
46
+
47
+ # data thresholds for checking data
48
+ DATA_CHECK_THRESH = {'beam_min_current': 0.1, # uA
49
+ 'pileup_cnt_per_ms': 10, # if larger than this, then pileup and delete
50
+ 'pileup_within_first_s': 1, # time frame for pileup in each period
51
+ }
52
+
53
+ # precise cycle defaults
54
+ DEFAULT_CYCLE_TIMES_PRECISE = { 'hw_channel': 10,
55
+ 'detector': 'Li6'}
ucndata/applylist.py ADDED
@@ -0,0 +1,205 @@
1
+ # Array which syntatic sugar to apply functions to each element contained
2
+ # Derek Fujimoto
3
+ # Oct 2024
4
+
5
+ import numpy as np
6
+
7
+ class applylist(list):
8
+ """A list object with the following enhancements:
9
+
10
+ * An `apply` function: like in pandas, `apply` takes a function handle and applies it to every element in the list. This acts recursively, if the list has a depth of more than 1
11
+ * Element access: accessing attributes, if not an attribute of the `applylist`, instead try to fetch attributes of the contained objects. The same for functions. This also works recursively.
12
+ * Numpy-like array slicing: slicing with a `np.ndarray` first converts this object to an array, then does the slice, then converts back. This allows slicing on arrays of indices for re-ordering or booleans for selection based on criteria
13
+ * Arithmetic works element-wise as in numpy arrays
14
+
15
+ Examples:
16
+
17
+ >>> # Slicing
18
+ >>> x = applylist(range(10))
19
+ >>> print(x[3:7])
20
+ [3, 4, 5, 6]
21
+
22
+ >>> # Element access
23
+ >>> x = applylist([ucnrun(1846), ucnrun(1847)])
24
+ >>> print(x.run_number)
25
+ [1846, 1847]
26
+ >>> print(x.beam_current_uA.mean())
27
+ [np.float64(0.16612837637441483), np.float64(0.18927602913972205)]
28
+
29
+ >>> # Arithmetic and comparisons
30
+ >>> x = applylist([1,2,3])
31
+ >>> print(x*2)
32
+ [np.int64(2), np.int64(4), np.int64(6)]
33
+ >>> print(x>2)
34
+ [np.False_, np.False_, np.True_]
35
+ """
36
+
37
+ def __call__(self, *args, **kwargs):
38
+
39
+ # call function of sub-elements
40
+ return applylist([x.__call__(*args, **kwargs) for x in self])
41
+
42
+ # numpy-style comparisons
43
+ def __eq__(self, other): return applylist(np.array(self).__eq__(other))
44
+ def __ne__(self, other): return applylist(np.array(self).__ne__(other))
45
+ def __lt__(self, other): return applylist(np.array(self).__lt__(other))
46
+ def __gt__(self, other): return applylist(np.array(self).__gt__(other))
47
+ def __le__(self, other): return applylist(np.array(self).__le__(other))
48
+ def __ge__(self, other): return applylist(np.array(self).__ge__(other))
49
+
50
+ # numpy-style unary operators
51
+ def __pos__(self): return applylist(np.array(self).__pos__())
52
+ def __neg__(self): return applylist(np.array(self).__neg__())
53
+ def __abs__(self): return applylist(np.array(self).__abs__())
54
+ def __invert__(self): return applylist(np.array(self).__invert__())
55
+ def __round__(self): return applylist(np.array(self).__round__())
56
+ def __floor__(self): return applylist(np.array(self).__floor__())
57
+ def __ceil__(self): return applylist(np.array(self).__ceil__())
58
+ def __trunc__(self): return applylist(np.array(self).__trunc__())
59
+
60
+ # numpy-style arithmetic operators
61
+ def __add__(self, other): return applylist(np.array(self).__add__(other))
62
+ def __sub__(self, other): return applylist(np.array(self).__sub__(other))
63
+ def __mul__(self, other): return applylist(np.array(self).__mul__(other))
64
+ def __floordiv__(self, other): return applylist(np.array(self).__floordiv__(other))
65
+ def __div__(self, other): return applylist(np.array(self).__div__(other))
66
+ def __truediv__(self, other): return applylist(np.array(self).__truediv__(other))
67
+ def __mod__(self, other): return applylist(np.array(self).__mod__(other))
68
+ def __divmod__(self, other): return applylist(np.array(self).__divmod__(other))
69
+ def __pow__(self, other): return applylist(np.array(self).__pow__(other))
70
+ def __and__(self, other): return applylist(np.array(self).__and__(other))
71
+ def __or__(self, other): return applylist(np.array(self).__or__(other))
72
+ def __xor__(self, other): return applylist(np.array(self).__xor__(other))
73
+
74
+ def __getattr__(self, name):
75
+
76
+ # retain default behaviour
77
+ try:
78
+ return super().__getattr__(name)
79
+
80
+ # enhanced behaviour
81
+ except AttributeError as err:
82
+
83
+ # check for scalar numpy datatypes, all contents assumed to be of the same type
84
+ if isinstance(self[0], (int, float, np.integer, np.floating)) or '__array_' in name:
85
+ raise err from None
86
+
87
+ # try to get the contents of subarray
88
+ return applylist([getattr(x, name) for x in self])
89
+
90
+ def __getitem__(self, key):
91
+
92
+ # numpy-like slicing
93
+ if isinstance(key, (np.ndarray, tuple, list, applylist)):
94
+ key = np.asarray(key)
95
+
96
+ # boolean array
97
+ if key.dtype == bool:
98
+ return applylist([i for i, k in zip(self, key) if k])
99
+
100
+ # index array
101
+ elif key.dtype == int:
102
+ return applylist([self[k] for k in key])
103
+
104
+ # default behaviour
105
+ else:
106
+ x = super().__getitem__(key)
107
+ if isinstance(x, list):
108
+ return applylist(x)
109
+ return x
110
+
111
+ def astype(self, typecast):
112
+ """Convert all elements in-place to the given type.
113
+
114
+ Args:
115
+ typecast (type): type to cast each element to (e.g. int, float, str)
116
+
117
+ Returns:
118
+ None: modifies the list in-place
119
+
120
+ Example:
121
+
122
+ >>> x = applylist(np.arange(5))
123
+ >>> print(x)
124
+ [np.int64(0), np.int64(1), np.int64(2), np.int64(3), np.int64(4)]
125
+ >>> x.astype(int)
126
+ >>> print(x)
127
+ [0, 1, 2, 3, 4]
128
+ """
129
+ for i in range(len(self)):
130
+ self[i] = typecast(self[i])
131
+
132
+ def apply(self, fn, inplace=False):
133
+ """Apply function to each element contained, similar to pandas functionality
134
+
135
+ Args:
136
+ fn (function handle): function to apply to each element
137
+ inplace (bool): if `False` return a copy, else act in-place
138
+
139
+ Returns:
140
+ applylist|None: new `applylist` with results if `inplace` is `False`, else `None`
141
+
142
+ Examples:
143
+
144
+ >>> # With return value
145
+ >>> x = applylist(np.arange(5))
146
+ >>> y = x.apply(lambda a: a**2)
147
+ >>> print(x)
148
+ [np.int64(0), np.int64(1), np.int64(2), np.int64(3), np.int64(4)]
149
+ >>> print(y)
150
+ [np.int64(0), np.int64(1), np.int64(4), np.int64(9), np.int64(16)]
151
+
152
+ >>> # Inplace
153
+ >>> x = applylist(np.arange(5))
154
+ >>> x.apply(lambda a: a**2, inplace=True)
155
+ >>> print(x)
156
+ [np.int64(0), np.int64(1), np.int64(4), np.int64(9), np.int64(16)]
157
+ """
158
+
159
+ # decide if inplace
160
+ if inplace: copy = self
161
+ else: copy = self.copy()
162
+
163
+ # do the operation
164
+ for i in range(len(copy)):
165
+ if isinstance(copy[i], applylist):
166
+ copy[i] = copy[i].apply(fn)
167
+ else:
168
+ copy[i] = fn(copy[i])
169
+
170
+ # return the copy
171
+ if not inplace: return copy
172
+
173
+ def copy(self):
174
+ """Return a shallow copy of this applylist.
175
+
176
+ Returns:
177
+ applylist: a new `applylist` with the same elements
178
+
179
+ Example:
180
+
181
+ >>> x = applylist([1, 2, 3])
182
+ >>> y = x.copy()
183
+ >>> y[0] = 99
184
+ >>> print(x)
185
+ [1, 2, 3]
186
+ >>> print(y)
187
+ [99, 2, 3]
188
+ """
189
+ return applylist(super().copy())
190
+
191
+ def transpose(self):
192
+ """Transpose a 2D applylist by converting to np.array and back.
193
+
194
+ Returns:
195
+ applylist: transposed list, where rows and columns are swapped
196
+
197
+ Example:
198
+
199
+ >>> x = applylist([[1,2,3], [4,5,6]])
200
+ >>> print(x)
201
+ [[1, 2, 3], [4, 5, 6]]
202
+ >>> print(x.transpose())
203
+ [array([1, 4]), array([2, 5]), array([3, 6])]
204
+ """
205
+ return applylist(np.array(self).transpose())