barsukov 0.0.1__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.

Potentially problematic release.


This version of barsukov might be problematic. Click here for more details.

barsukov/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ # Modules:
2
+ from . import time
3
+ from . import formula
4
+ from . import data
5
+
6
+ # Objects/Functions:
7
+ from .script import Script
8
+ from .logger import Logger
9
+
10
+ # Equipment Objects:
11
+ from .exp.mwHP import mwHP
12
+
13
+ __all__ = ["time", "formula", "data", "Script", "Logger", "mwHP"]
14
+
15
+
File without changes
barsukov/data/fft.py ADDED
@@ -0,0 +1,87 @@
1
+ ### BEGIN Dependencies ###
2
+ import numpy as np
3
+ import scipy as sp
4
+ from barsukov.logger import debug # Un-comment before implementing in pip
5
+ ### END Dependencies ###
6
+
7
+
8
+ def fft(x, y, equidistant_check=True, equidistant_rel_error=1e-4, remove_negative_f=False, inverse=False):
9
+ ### Takes: x=list of real floats, y=list of data or list of lists of data. Data can be real or complex.
10
+ ### Returns: freqs, fft_y_norm, msg
11
+ ### This fft used to give wrong sign of the imaginary component because of the "wrong" definition of fft in np and sp
12
+ ### Now it has been corrected to the Mathematica definition of fft = int ... exp(2pi f t)
13
+ msg = ''
14
+ if equidistant_check:
15
+ diffs = np.diff(x)
16
+ if not np.allclose(diffs, diffs[0], rtol=equidistant_rel_error):
17
+ # x is not equidistant, must start interpolating
18
+ x,y = make_equidistant(x, y, step=None)
19
+ y = y.T
20
+ msg += debug('fft(x,y) made x,y equidistant.')
21
+
22
+ y = np.array(y)
23
+ #print(y)
24
+
25
+ if y.ndim == 1:
26
+ # y is 1D, treat it as a single column
27
+ n = len(y) # Number of points in the column
28
+ if inverse is False: fft_y = np.fft.ifft(y) * n # np fft has the "wrong" imag sign
29
+ else: fft_y = np.fft.fft(y) # That's why fft and ifft are inverted in this code
30
+ else:
31
+ # y is 2D, treat it as multiple columns
32
+ n = y.shape[1] # Number of points in each column
33
+ if inverse is False: fft_y = np.fft.ifft(y, axis=1) * n # np fft has the "wrong" imag sign
34
+ else: fft_y = np.fft.fft(y, axis=1) # That's why fft and ifft are inverted in this code
35
+
36
+ sample_spacing = ( x[-1] - x[0] ) / (n-1)
37
+ #print(n, sample_spacing, x[1] - x[0])
38
+ fft_y_norm = fft_y * sample_spacing # This normalizes FFT to mathematically correct
39
+ freqs = np.fft.fftfreq(n, d=sample_spacing)
40
+
41
+ if remove_negative_f is False:
42
+ sorted_indices = np.argsort(freqs)
43
+ freqs = freqs[sorted_indices]
44
+ if isinstance(fft_y_norm[0], (list, np.ndarray)): # If fft_y_norm contains multiple columns
45
+ fft_y_norm = [x[sorted_indices] for x in fft_y_norm]
46
+ else: # If fft_y_norm is a single column
47
+ fft_y_norm = fft_y_norm[sorted_indices]
48
+ msg += debug('fft(x,y) sorted negative and positive frequencies.')
49
+ else:
50
+ mask = freqs >= 0 # Boolean array with Falses for negative frequencies, effectively removing them
51
+ freqs = freqs[mask]
52
+ # If fft_y_norm contains multiple columns:
53
+ if isinstance(fft_y_norm[0], (list, np.ndarray)):
54
+ fft_y_norm = [x[mask] for x in fft_y_norm]
55
+ else: # If fft_y_norm is a single column
56
+ fft_y_norm = fft_y_norm[mask]
57
+ msg += debug('fft(x,y) removed negative frequencies.')
58
+
59
+ msg += debug('freqs, fft_y_norm, msg = fft(x,y) is done.\nThe forward fft approximates the mathematically correct integral over ...exp(+i2pift).\nNow do not forget to apply np.abs(fft_y_norm), np.angle(fft_y_norm), fft_y_norm.real, fft_y_norm.imag')
60
+ return freqs, fft_y_norm, msg
61
+
62
+ def ifft(x, y, equidistant_check=True, equidistant_rel_error=1e-4, remove_negative_f=False):
63
+ return fft(x, y, equidistant_check=equidistant_check, equidistant_rel_error=equidistant_rel_error, remove_negative_f=remove_negative_f, inverse=True)
64
+
65
+ def make_equidistant(x, y, step=None):
66
+ ### Takes one column x and one or more columns y and makes them equidistant in x
67
+ ### Returns new_x, new_y. The number of points will likely change.
68
+ if step is None:
69
+ # Calculate the smallest difference between consecutive elements
70
+ min_step = np.min(np.diff(x))
71
+ else:
72
+ min_step = step
73
+
74
+ # Generate the new equidistant x array
75
+ new_x = np.arange(x[0], x[-1] + min_step, min_step)
76
+
77
+ if isinstance(y[0], (list, np.ndarray)): # If y contains multiple columns
78
+ new_y = []
79
+ for y_column in y:
80
+ interpolation_function = sp.interpolate.interp1d(x, y_column, kind='linear', fill_value='extrapolate')
81
+ new_y.append(interpolation_function(new_x))
82
+ new_y = np.array(new_y).T # Transpose to match the original structure
83
+ else: # If y is a single column
84
+ interpolation_function = sp.interpolate.interp1d(x, y, kind='linear', fill_value='extrapolate')
85
+ new_y = interpolation_function(new_x)
86
+
87
+ return new_x, new_y
@@ -0,0 +1 @@
1
+ from . import mwHP
@@ -0,0 +1,119 @@
1
+ ### BEGIN Dependencies ###
2
+ import numpy as np
3
+ import sys
4
+ from barsukov.time import *
5
+ ### END Dependencies
6
+
7
+
8
+
9
+
10
+
11
+
12
+ ### BEGIN Helper functions
13
+
14
+ def log_in_eq(eq_obj, msg, log='default'): # FINISHED 2024/10/26
15
+ decorated_msg = str(eq_obj.msg_deco) + ' ' + msg
16
+ if eq_obj.logger is None:
17
+ if log=='no': return
18
+ else: print(time_stamp() + ' ' + decorated_msg)
19
+ else:
20
+ eq_obj.logger.log(decorated_msg, log)
21
+
22
+ def initialize_gpib(eq_obj):
23
+ # Initializes a visa.open_resource(). Returns rm.open_resource(). Exits if error.
24
+ if eq_obj.rm is None: # eq_obj has no ResourceManager
25
+ if eq_obj.script is None: # If there is no Script
26
+ eq_obj.log('Visa ResourceManager and Script have not been passed to me. I will attempt to initialize visa myself.', log='important')
27
+ try:
28
+ import pyvisa as visa
29
+ eq_obj.rm = visa.ResourceManager()
30
+ eq_obj.log('I just set my self.rm = visa.ResourceManager.', log='screen')
31
+ except:
32
+ eq_obj.log('I failed to initialize set my self.rm = visa.ResourceManager. I am sys-exiting.', log='important')
33
+ sys.exit()
34
+ else: # If there is a Script
35
+ if eq_obj.script.rm is None: # If Script has no ResourceManager
36
+ eq_obj.log('I see Script but it does not have rm. I am asking Script to initialize visa.ResourceManager and pass it to me.', log='important')
37
+ try:
38
+ eq_obj.rm = eq_obj.script.init_rm() # Script will try to init ResourceManager
39
+ #print('eq_obj.rm initialized as ', eq_obj.rm)
40
+ except:
41
+ eq_obj.log('Error while Script was initializing visa.ResourceManager. I am sys-exiting.', log='important')
42
+ sys.exit()
43
+ else:
44
+ eq_obj.log('Script has visa.ResourceManager. I am grabbing it.', log='screen')
45
+ eq_obj.rm = eq_obj.script.rm
46
+ if eq_obj.rm is None: # Just to double check if rm is in fact there.
47
+ eq_obj.log('My last check showed that my self.rm is still None. I am sys-exiting.', log='important')
48
+ sys.exit()
49
+ # Now, we assume there is a resource manager
50
+ if (eq_obj.gpib is None) or (eq_obj.gpib_card is None):
51
+ eq_obj.log('GPIB card number or GPIB address is not set.', log='important')
52
+ sys.exit()
53
+ try:
54
+ #print(eq_obj.rm)
55
+ eq_obj.log('I am trying to rm.open_resource().', log='screen')
56
+ y = eq_obj.rm.open_resource(f'GPIB{eq_obj.gpib_card}::{eq_obj.gpib}')
57
+ #print('y=',y)
58
+ return y
59
+ except:
60
+ eq_obj.log(f'I could not initialize rm.open_resource() for GPIB {eq_obj.gpib_card}::{eq_obj.gpib}. Also check visa_rm, just in case.', log='important')
61
+ sys.exit()
62
+
63
+ ### END Helper functions
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+ ### BEGIN Functions that are likely unnecessary
88
+
89
+ def query_float(eq_obj, cmd):
90
+ ### Returns float or np.nan
91
+ try:
92
+ q = eq_obj.eq.query(cmd)
93
+ q = float(q) # Will it work with all equipment?
94
+ return q
95
+ except:
96
+ eq_obj.log(f'Error while quering: \"{cmd}\".', log='important')
97
+ return np.nan
98
+
99
+ def write_float(eq_obj, cmd, value, digits, limits):
100
+ value_differs = ''
101
+ try:
102
+ value = float(value)
103
+ value_round = round(value, digits)
104
+ if value_round < limits[0]:
105
+ value_round = limits[0]
106
+ if value_round > limits[1]:
107
+ value_round = limits[1]
108
+ if value_round != value:
109
+ value_differs = f' But requested {value}.'
110
+ cmd = ''.join( [value_round if el is None else el for el in cmd] )
111
+ eq_obj.eq.write(cmd)
112
+ write_error = False
113
+ return write_error, value_differs
114
+ except:
115
+ write_error = True
116
+ eq_obj.log(f'Error while writing: \"{cmd}\".', log='important')
117
+ return write_error, value_differs
118
+
119
+ ### END Functions that are likely unnecessary
barsukov/exp/mwHP.py ADDED
@@ -0,0 +1,357 @@
1
+ ### BEGIN Dependencies ###
2
+ import numpy as np
3
+ import sys
4
+ from barsukov.exp.exp_utils import *
5
+ ### END Dependencies
6
+
7
+ # STATUS: f() finished, nor test it and then add more funcitons
8
+
9
+ class mwHP:
10
+ def __init__(self, gpib=None, visa_rm=None, logger=None, gpib_card=0, log='default', script=None):
11
+ # Pass the Script object, if available.
12
+ # If Script has no visa_rm or if no Script is passed, you'll need to pass the visa_rm=visa.ResourceManager manually.
13
+ # If Script has no logger or if no Script is passed, you can pass the logger manually.
14
+ # If no logger is passed, will simply print to screen.
15
+ # Change log from 'default' to 'screen', 'file', 'both', 'no'.
16
+ # gpib_card is per default 0. You can change, if you have multiple.
17
+
18
+ self.script = script
19
+ self.logger = logger
20
+ self.eq_default_log = log
21
+ self.rm = visa_rm
22
+ self.gpib_card = gpib_card
23
+ self.gpib = gpib
24
+
25
+ self.msg_deco = f'[mwHP {self.gpib_card}::{self.gpib}]'
26
+ #print( 'visa_rm is ', visa_rm )
27
+ #print( 'script is ', script )
28
+ self.eq = initialize_gpib(self) # This will initialize self.eq = visa.open_resource()
29
+ self.log( f'Initialized: {self.identify()}', log='important' ) # This is the 'welcome message' and a check if communication works.
30
+
31
+ self.f_digits = 9 # Digits of precision of mw frequency
32
+ self.f_limits = [0.01, 20.5] # Lower and upper GHz limits
33
+ self.p_digits = 1 #TODO -- Digits of precision of mw power
34
+ self.p_limits = [-15.0, 17.0] #TODO -- Lower and upper __ limits
35
+ self.pulsef_digits = 3 # digits in KHz
36
+ self.pulsew_digits = 3 # digits in ms
37
+ self.pulsef_limits = [0.016, 500]
38
+ self.pulsew_limits = [0.001, 65.53] # digits in ms
39
+ self.pulsedc_limits = [0, 100]
40
+
41
+ ### BEGIN The definition of the following functions may be specific to this equipment.
42
+ def query(self, cmd):
43
+ return self.eq.query(cmd)
44
+
45
+ def write(self, cmd):
46
+ return self.eq.write(cmd)
47
+
48
+ def identify(self):
49
+ return str(self.eq.query('*IDN?'))
50
+ ### END The definition of the following functions may be specific to this equipment.
51
+
52
+ def log(self, msg, log=None):
53
+ if log is None: log=self.eq_default_log
54
+ log_in_eq(self, msg, log=log)
55
+ ### END These functions could be shared across all equipment.
56
+
57
+
58
+ def f(self, f=None, log=None, check=False):
59
+ ### Always has a return! Which is frequency in GHz.
60
+ ### f() reads and returns the frequency in GHz.
61
+ ### f(10) writes frequency to 10 GHz.
62
+ ### f(10) returns the frequency that was actually sent to equipment.
63
+ ### f(10, check=True) returns the frequency queried after writing.
64
+ ### Do set log='no' to avoid latency for repeated calls.
65
+ if log is None: log=self.eq_default_log
66
+ if f is None:
67
+ try:
68
+ y = self.eq.query('freq?')
69
+ y = float(y)/1e9 # Is the frequency returned in GHz??
70
+ self.log(f'Reading f as {y} GHz.', log=log)
71
+ return y
72
+ except:
73
+ self.log(f'Error while reading Frequency.', log='important')
74
+ return np.nan
75
+ else:
76
+ try:
77
+ x = round(f, self.f_digits) # rounding the digits
78
+ x = max(self.f_limits[0], min(x, self.f_limits[1])) # sets x within f_limits
79
+ self.eq.write(f'freq {x} GHz')
80
+ if check: y = self.f(log='no')
81
+ else: y = x
82
+ print(y, f)
83
+ if abs(y-f)<10.0**(-self.f_digits): self.log(f'Writing f as {x}.', log=log) #################### THIS IS WRONG!!!! IT SHOULD BE abs(y-f)<(10**self.f_digits)
84
+ else: self.log(f'Warning: writing Frequency as {x}, but was asked {f}.', log='important')
85
+ return y
86
+ except:
87
+ self.log(f'Error while writing Frequency as {f}.', log='important')
88
+ return np.nan
89
+
90
+ def p(self, p=None, log=None, check=False):
91
+ ### Always has a return! Which is the power in dBm.
92
+ ### p() reads and returns the power in dBm.
93
+ ### p(1) writes power to 1 dBm.
94
+ ### p(1) returns the power that was actually sent to equipment.
95
+ ### p(1, check=True) returns the power queried after writing
96
+ ### Do set log='no' to avoid latency for repeated calls.
97
+ if log is None: log=self.eq_default_log
98
+ if p is None:
99
+ try:
100
+ y = self.eq.query('pow?')
101
+ y = float(y)
102
+ self.log(f'Reading p as {y} dBm.', log=log)
103
+ return y
104
+ except:
105
+ self.log(f'Error while reading Power.', log='important')
106
+ return np.nan
107
+ else:
108
+ try:
109
+ x = round(p, self.p_digits) # rounding the digits
110
+ x = max(self.p_limits[0], min(x, self.p_limits[1])) # sets x within p_limits
111
+ self.eq.write(f'pow {x} dBm')
112
+ if check: y = self.p(log='no')
113
+ else: y = x
114
+ if abs(y-p)<10**(-self.p_digits): self.log(f'Writing p as {x}.', log=log)
115
+ else: self.log(f'Warning: writing Power as {x}, but was asked {p}.', log='important')
116
+ return y
117
+ except:
118
+ self.log(f'Error while writing Power as {p}.', log='important')
119
+ return np.nan
120
+
121
+ def output(self, state=None, log=None, check=False):
122
+ ### Always has a return! Which is the state of Output.
123
+ ### output() reads and returns the state of Output.
124
+ ### output(1) writes state of Output to ON.
125
+ ### output(1) returns the state that was actually sent to equipment.
126
+ ### output(1, check=True) returns the state queried after writing
127
+ if log is None: log=self.eq_default_log
128
+ if state is None:
129
+ try:
130
+ y = self.eq.query('output?')
131
+ y = int(y)
132
+ self.log(f'Output is {y}.')
133
+ return y
134
+ except:
135
+ self.log(f'Error while reading Output.', log='important')
136
+ return np.nan
137
+ else:
138
+ if (state == 1) or (state == 'on') or (state=='ON') or (state=='On'): sstate = 1
139
+ else: sstate = 0
140
+ try:
141
+ self.eq.write(f'output {sstate}')
142
+ if check: y=self.output(log='no')
143
+ else: y = sstate
144
+ if y == state: self.log(f'Output set to {sstate}.')
145
+ else: self.log(f'Warning: Setting Output to {sstate}, but was asked for {state}.')
146
+ return y
147
+ except:
148
+ self.log(f'Error while changing Output state.', log='important')
149
+ return np.nan
150
+
151
+ #def sweep(self, mode, state=None, step=None, dwell=None, minm=None, maxm=None, log=None, check=False):
152
+ #if log is None: log=self.eq_default_log
153
+ #if (mode == 'pow') or (mode == 'freq'): ### Sweep function can write to power or frequency
154
+ #if state is None: ### sweep(pow) or sweep(freq) reads and returns if the sweep mode is on or off
155
+ #try:
156
+ #y = self.eq.query(f'{mode} swe?')
157
+ #y = float(y)
158
+ #self.log(f'{mode} Sweep state is {y}.')
159
+ #return y
160
+ #except:
161
+ #self.log(f'Error while reading {mode} Sweep state.', log='important')
162
+ #return np.nan
163
+ #else:
164
+ #if (state == 1) or (state == 'on') or (state=='ON') or (state=='On'): sstate = 'swe' ### sweep(pow, 1) or sweep (freq, 1) turns the sweep mode for pow/freq ON
165
+ #else: sstate = 'fix'
166
+ #try:
167
+ #self.eq.write(f'{mode} {sstate}')
168
+ #if check: y=self.output(log='no')
169
+ #else: y = sstate
170
+ #if y == state:
171
+ #self.log(f'{mode} Sweep state set to {sstate}.')
172
+ #if (step=None) or (dwell=None) or (maxm=None) or (minm=None):
173
+ #self.log(f'Warning: missing arguements to conduct {mode} Sweep.') ### indicates that while power mode on, sweep
174
+ #else:
175
+ #if (mode == 'pow'): level = 'dBm'
176
+ #elif (mode == 'freq'): level = 'GHz'
177
+ #dwellunit = 'us' ### is it in microseconds?
178
+ #self.eq.write(f'{mode} step {step} {level})
179
+ #self.eq.write(f'{mode} dwel1 {dwell} {dwellunit})
180
+ #self.eq.write(f'{mode} star {minm} {level}')
181
+ #self.eq.write(f'{mode} stop {maxm} {level}')
182
+ ### write checks to see if values are actually written to Output
183
+ #self.eq.write(f'init imm')
184
+ #return y
185
+ #else:
186
+ #self.log(f'Warning: Setting {mode} Sweep state to {sstate}, but was asked for {state}.')
187
+ #return y
188
+ #except:
189
+ #self.log(f'Error while changing {mode} Sweep state.', log='important')
190
+ #return np.nan
191
+ #else:
192
+ #self.log(f'Warning: mode for sweep was not specified.', log=log)
193
+ #return np.nan
194
+
195
+
196
+
197
+
198
+ def pulse(self, f=None, duty=None, log=None):
199
+ if log is None: log=self.eq_default_log
200
+ if f is None and duty is None:
201
+ T = float(self.eq.query('puls:per?')) * 10.0**3
202
+ f = 1.0 / T
203
+ w = float(self.eq.query('puls:widt?')) * 10.0**3
204
+ duty = w / T * 100.0
205
+ y = self.eq.query('pulm:stat?')
206
+ y = int(y)
207
+ x = self.eq.query('pulm:sour?')
208
+ x = x[:-1].lower()
209
+ self.log(f'Pulse Frequency {f} KHz, duty-cycle {duty}%. state {y}, source {x}.', log=log)
210
+ return f, duty
211
+
212
+ else:
213
+ if f is None and duty is not None:
214
+ duty_write = max(self.pulsedc_limits[0], min(float(duty), self.pulsedc_limits[1]))
215
+ T = float(self.eq.query('puls:per?')) * 10.0**3
216
+ w = duty_write * T / 100.0
217
+ self.eq.write(f'puls:widt {w} ms')
218
+
219
+ elif f is not None and duty is None:
220
+ f_write = max(self.pulsef_limits[0], min(float(f), self.pulsef_limits[1]))
221
+ duty_write = 50.0
222
+ T = 1.0 / f
223
+ w = duty_write * T / 100.0
224
+ self.eq.write(f'puls:per {T} ms')
225
+ self.eq.write(f'puls:widt {w} ms')
226
+
227
+ elif f is not None and duty is not None:
228
+ f_write = max(self.pulsef_limits[0], min(float(f), self.pulsef_limits[1]))
229
+ duty_write = max(self.pulsedc_limits[0], min(float(duty), self.pulsedc_limits[1]))
230
+ T = 1.0 / f
231
+ w = duty_write * T / 100.0
232
+ self.eq.write(f'puls:per {T} ms')
233
+ self.eq.write(f'puls:widt {w} ms')
234
+
235
+ check = self.pulse()
236
+ return check
237
+
238
+
239
+ #def pulse(self, on_off=None, freq=None, duty_cycle=None, int_ext=None, log=None):
240
+ ### Always has a return! Which is the state, freq, duty cycle, and source of pulse
241
+ ### Frequency in KHz, Duty Cycle in %, source int(ernal) or ext(ernal)
242
+ ### pulse() reads and returns state, frequency, duty cycle, and source of pulse
243
+ ### pulse(1,1,50,int) writes state ON, frequency 1KHz, duty cycle 50%, and internal mode to Pulse
244
+ ### pulse(1,1,50,int) returns the state, frequency, duty cycle, and source that was actually sent to the equipment
245
+ ### pulse(1,1,50,int) returns the state, frequency, duty cycle, and source queried after writing
246
+ #if log is None: log=self.eq_default_log
247
+ #if (on_off is None) and (freq is None) and (duty_cycle is None) and (int_ext is None):
248
+ ### Accesser Code - returns whats on equipment object
249
+ #try:
250
+ #y = self.eq.query('pulm:stat?')
251
+ #y = int(y)
252
+ #f = float(self.eq.query('puls:freq?')) / 10.0**3
253
+ # returns in Khz
254
+ #dc = float(self.eq.query('puls:widt?')) * f * 10.0**5
255
+ # returns in %
256
+ #x = self.eq.query('pulm:sour?')
257
+ #x = x[:-1].lower()
258
+ # returns in lowercase
259
+ #self.log(f'Pulse state is {y}, frequency {f} KHz, duty-cycle {dc}%, source {x}.', log=log)
260
+ #return [y, f, dc, x]
261
+ #except:
262
+ #self.log(f'Error while reading Pulse state.', log='important')
263
+ #return np.nan
264
+ #else:
265
+ ### Mutator Code - Writes to equipment object
266
+ #if (on_off == 1) or (on_off == 'on') or (on_off=='ON') or (on_off=='On'): state = 1
267
+ #else: state = 0
268
+ #error = False
269
+ #if (int_ext == 'int') or (int_ext == 'ext') or (int_ext == 'INT') or (int_ext == 'EXT'):
270
+ ### writes mode (int/ext)
271
+ #try:
272
+ #self.eq.write(f'pulm:sour {int_ext}')
273
+ #y = self.eq.query('pulm:sour?')
274
+ #y = y[:-1].lower()
275
+ #if (str(y) == str(int_ext.lower())): self.log(f'Setting Pulse to {int_ext}.', log=log)
276
+ #else: self.log(f'Warning:Setting Pulse to {y[3]}, but was asked {int_ext}.', log='important')
277
+ #except:
278
+ #self.log(f'Error while changing Pulse Source.', log='important')
279
+ #return np.nan
280
+ #elif (int_ext is not None): self.log(f'Error: Invalid Pulse Source.', log='important')
281
+ #if (freq is None) and (duty_cycle is not None):
282
+ ### if frequency and duty cycle not in ratio, changes duty cycle to acceptable value
283
+ #try:
284
+ #writeduty_cycle = float(duty_cycle)
285
+ #writeduty_cycle = max(self.pulsedc_limits[0], min(writeduty_cycle, self.pulsedc_limits[1])) # sets writeduty_cycle within pulsedc_limits
286
+ #y = float(self.eq.query(f'puls:freq?')) / 1000.0 #returns in KHz
287
+ #writefreq = y
288
+ #width = writeduty_cycle * 0.01 / writefreq # units of ms
289
+ #width = max(self.pulsew_limits[0], min(width, self.pulsew_limits[1]))
290
+ #wdth = ceil(width * 1000.0) / 1000.0
291
+ #idth = round(width, self.pulsew_digits)
292
+ #lf.eq.write(f'puls:widt {width} ms')
293
+ #except:
294
+ #self.log(f'Error while changing Duty Cycle within limits.', log='important')
295
+ #error = True
296
+ #return np.nan
297
+ #elif (duty_cycle is None) and (freq is not None):
298
+ ### if frequency and duty cycle not in ratio, changes frequency to acceptable value
299
+ # try:
300
+ # writeduty_cycle = 50.0
301
+ # writefreq = round(freq, self.pulsef_digits) # rounding the digits
302
+ # writefreq = max(self.pulsef_limits[0], min(writefreq, self.pulsef_limits[1])) # sets writeduty_cycle within pulsedc_limits
303
+ # print(writefreq)
304
+ # width = writeduty_cycle * 0.01 / writefreq # units of ms
305
+ # width = max(self.pulsew_limits[0], min(width, self.pulsew_limits[1]))
306
+ # width = ceil(width * 1000.0) / 1000.0
307
+ #width = round(width, self.pulsew_digits)
308
+ # print(width)
309
+ # self.eq.write(f'puls:freq {writefreq} KHz')
310
+ # self.eq.write(f'puls:widt {width} ms')
311
+ # except:
312
+ # print(width)
313
+ # self.log(f'Error while changing Pulse Frequency within limits.', log='important')
314
+ # error = True
315
+ # return np.nan
316
+ #elif (freq is not None) and (duty_cycle is not None):
317
+ ### writing both duty cycle and frequency
318
+ # writeduty_cycle = float(duty_cycle) # rounding the digits
319
+ # writeduty_cycle = max(self.pulsedc_limits[0], min(writeduty_cycle, self.pulsedc_limits[1])) # sets writeduty_cycle within pulsedc_limits
320
+ # writefreq = round(freq, self.pulsef_digits) # rounding the digits
321
+ # writefreq = max(self.pulsef_limits[0], min(writefreq, self.pulsef_limits[1])) # sets writeduty_cycle within pulsedc_limits
322
+ # try:
323
+ # width = writeduty_cycle * 0.01 / writefreq # units of ms
324
+ #width = max(self.pulsew_limits[0], min(width, self.pulsew_limits[1]))
325
+ # width = ceil(width * 1000.0) / 1000.0
326
+ # width = round(width, self.pulsew_digits)
327
+ #self.eq.write(f'puls:freq {writefreq} KHz')
328
+ #self.eq.write(f'puls:widt {width} ms')
329
+ #xcept:
330
+ # self.log(f'Error while writing Duty Cycle.', log='important')
331
+ # self.log(f'Error while writing Pulse Frequency.', log='important')
332
+ # error = True
333
+ # return np.nan
334
+ #y = self.pulse(log='no')
335
+ ### returns the actual parameters written to machine to be compared
336
+ #if (freq is not None) and (error == False):
337
+ ### Checker Code
338
+ # if abs(y[1] - freq)< 10**(-self.pulsef_digits): self.log(f'Writing Pulse Frequency as {y[1]}.', log=log)
339
+ # else: self.log(f'Warning:Writing Pulse Frequency as {y[1]}, but was asked {freq}.', log='important')
340
+ #if (duty_cycle is not None) and (error == False):
341
+ ### Checker Code
342
+ # if abs(y[2] - duty_cycle)<10**(-3): self.log(f'Writing Pulse duty cycle as {y[2]}.', log=log)
343
+ # else: self.log(f'Warning:Writing Pulse duty cycle as {y[2]}, but was asked {duty_cycle}.', log='important')
344
+ #if (on_off is not None):
345
+ # try:
346
+ # self.eq.write(f'pulm:stat {on_off}')
347
+ # x = self.eq
348
+ # y = self.pulse(log='no')
349
+ # if y[0] == state: self.log(f'Pulse state set to {on_off}.')
350
+ # else: self.log(f'Warning: Setting Pulse state to {sstate}, but was asked for {on_off}.')
351
+ #xept:
352
+ # self.log(f'Error while changing Pulse state.', log='important')
353
+ # error = True
354
+ # return np.nan
355
+ #if (error == False): return y
356
+ #else: return np.nan
357
+
barsukov/logger.py ADDED
@@ -0,0 +1,123 @@
1
+ ### BEGIN Dependencies ###
2
+ import os
3
+ from barsukov.time import time_stamp
4
+ ### END Dependencies ###
5
+
6
+ class Logger:
7
+ """
8
+ ### Don't create multiple Logger objects.
9
+ ### Logger can be opened and closed with start() and close() methods.
10
+ ### Restarting the logger will log to the same file, unless self.full_file_path has been changed by hand.
11
+ ### start=True will start the logger automatically, but be careful
12
+ ### about the full_folder_path=os.getcwd() in init.
13
+ ### Log options are 'screen', 'file', 'both', 'no'. The logger default is 'both'.
14
+ ### The log='important' is for the Logger.log() method only. Try not to use it.
15
+ ### Default hyerarchy is logger-default overriden by object-default overriden by instance.
16
+ ### Instances, that log errors, will usually use 'both'.
17
+ ###
18
+ """
19
+ def __init__(self,
20
+ description=None, # Will be passed by Script. If not, write a brief description!
21
+ full_folder_path=os.getcwd(), # Will be passed by Script. If not, specify!
22
+ full_file_path=None, # Specify only if you want to log into an already existing file.
23
+ log='both', # Logger default will be passed by Script. If not, you may choose to change.
24
+ start_file=True
25
+ ):
26
+
27
+ ### Initializing all variables before setting/getting them ###
28
+ self.full_file_path = full_file_path # If changed by hand later, needs start() to take effect
29
+ self.description = description # If changed by hand later, needs set_full_file_path and start() to take effect
30
+ self.log_mode = log # Default log mode can be changed by hand any time, no restart needed.
31
+
32
+ if self.full_file_path is not None:
33
+ self.full_folder_path = None
34
+ else:
35
+ self.full_folder_path = full_folder_path # If changed by hand later, needs set_full_file_path and start() to take effect
36
+ self.set_full_file_path(description=description, full_folder_path=full_folder_path)
37
+
38
+ self.file_error = False # If a problem with file, will be set to True. Just in case for later.
39
+ # If problem is remedied, you need to set file_error to False by hand.
40
+
41
+ #
42
+ if start_file:
43
+ self.start_file()
44
+ else:
45
+ self.file = None
46
+
47
+ self.log(f'Logger initialization complete.', log='important')
48
+
49
+ def set_full_file_path(self, description=None, full_folder_path=None):
50
+ ### Checking if optional arguments are filled or if defaults are provided ###
51
+ if description is None:
52
+ description = self.description
53
+ if full_folder_path is None:
54
+ full_folder_path = self.full_folder_path
55
+ #
56
+ ### Create a file name like log_timeStamp_description_.txt ###
57
+ if not (description is None or description == ''):
58
+ description = '_' + description
59
+ else:
60
+ description = ''
61
+ file_name = 'log_' + time_stamp() + description + '_.txt'
62
+ #print(file_name)
63
+ #print(full_folder_path)
64
+ self.full_file_path = os.path.join(full_folder_path, file_name)
65
+
66
+ def start_file(self):
67
+ try:
68
+ self.file = open(self.full_file_path, 'a')
69
+ self.log('Logging file started.', log='important')
70
+ except:
71
+ print(f'{time_stamp()} Logger failed to open the log file \"{self.full_file_path}\".', log='important')
72
+
73
+ def close_file(self): # If closed, you'll need to restart before logging.
74
+ self.log('Logger is closing log file.', log='important')
75
+ self.file.close()
76
+ self.file = None
77
+
78
+ def decorated_msg(self, msg):
79
+ decorated_msg = time_stamp() + ' ' + msg + '\n'
80
+ return decorated_msg
81
+
82
+ def write_to_file(self, msg):
83
+ if self.file:
84
+ self.file.write(msg)
85
+ ### flushing the log file to make sure it's written ###
86
+ self.file.flush()
87
+ os.fsync(self.file)
88
+ else:
89
+ self.file_error = True
90
+ print(f'{time_stamp()} Logger is trying to write to a closed or non-existent file.')
91
+
92
+ def log(self, msg, log='default'):
93
+ ### This is the main function. Log options: 'screen', 'file', 'both', 'no', 'default', 'important'
94
+ if log == 'important' and self.log_mode == 'no':
95
+ log = 'screen'
96
+ elif log == 'important' and self.log_mode == 'file':
97
+ log = 'both'
98
+ elif (log == 'important') or(log == 'default') or (log is None):
99
+ log = self.log_mode
100
+
101
+ decorated_message = self.decorated_msg(msg)
102
+
103
+ if log == 'both':
104
+ print(self.decorated_msg(msg))
105
+ self.write_to_file(decorated_message)
106
+ elif log == 'file':
107
+ self.write_to_file(decorated_message)
108
+ elif log == 'no':
109
+ pass
110
+ else: # log == 'screen' or anything else
111
+ print(decorated_message)
112
+
113
+
114
+
115
+ ### Use this function in other libraries if needed for debugging -- Not really needed
116
+ DEBUG = False
117
+ def debug(msg):
118
+ if DEBUG:
119
+ print(msg)
120
+ return msg+'\n'
121
+ else:
122
+ return msg
123
+ ###
barsukov/obj2file.py ADDED
@@ -0,0 +1,107 @@
1
+ # This is unfinished
2
+
3
+
4
+ #import pickle
5
+ import os
6
+ #import glob
7
+
8
+ import obj_name, time_stamp
9
+
10
+ class Obj2File:
11
+ def __init__(self, logger=None, log='default', script=None):
12
+ self.script = script
13
+ self.logger = logger
14
+ self.default_log = log
15
+ self.msg_deco = f'[Obj2File]'
16
+
17
+ if self.logger is None:
18
+ try: self.logger = self.script.logger
19
+ except: pass
20
+
21
+ self.mtj_calib_directory = '/Users/alexandrakorotneva/Desktop/2024-11-18 obj import test'
22
+ self.tosave = {}
23
+
24
+
25
+ def log(self, msg, log=None):
26
+ if log is None: log = self.default_log
27
+ if self.logger is not None:
28
+ try:
29
+ self.logger.log(f'{self.msg_deco} {msg}', log=log)
30
+ return
31
+ except: pass
32
+ print(f'{time_stamp()} {self.msg_deco} {msg}')
33
+
34
+ def save(self, obj, directory=None, full_file=None, short_file=None, log='default'):
35
+ if full_file is None:
36
+ if directory is None: directory = self.script.full_folder_path #current measurement folder
37
+ if short_file is None: short_file = f'{time_stamp()}_{obj_name(obj)}.pkl'
38
+ full_file = os.path.join(directory, short_file)
39
+ try:
40
+ with open(full_file, 'wb') as file:
41
+ pickle.dump(obj, file)
42
+ self.log(f'Object {obj_name(obj)} is written to file {full_file}.', log=log)
43
+ except:
44
+ self.log('Error: could not write object to file.', log='important')
45
+
46
+ def load(self, directory=None, full_file=None, short_file=None, log='default'):
47
+ if full_file is None:
48
+ if directory is None:
49
+ directory = self.script.full_folder_path
50
+ if short_file is None:
51
+ files = [f for f in glob.glob(os.path.join(directory, '*.pkl'))]
52
+ short_file = files[0]
53
+ full_file = os.path.join(directory, short_file)
54
+
55
+ try:
56
+ with open(filepath, 'rb') as file:
57
+ toload = pickle.load(file)
58
+ self.log(f'Object is uploaded from file {full_file}.', log=log)
59
+ except:
60
+ self.log('Error: could not upload object from file.', log='important')
61
+ return toload
62
+
63
+
64
+
65
+
66
+ def mtj_fieldcalib(self, directory=None, upd=False, log='default', spl_ind=None):
67
+ if directory is None: directory = self.mtj_calib_directory
68
+ else:
69
+ if upd:
70
+ self.mtj_calib_directory = directory
71
+ self.log(f'MTJ station field calibration directory has been updated to {directory}', log='important')
72
+
73
+ files = [f for f in glob.glob(os.path.join(directory, '*.pkl'))]
74
+ if len(files)==1:
75
+ short_file = files[0]
76
+ else:
77
+ ##
78
+
79
+ full_file = os.path.join(directory, short_file)
80
+ splines = self.load(full_file=full_file, log=log)
81
+
82
+ if spl_ind is None:
83
+ return: splines
84
+ elif spl_ind=='c':
85
+ return: splines['cheap_to_H']
86
+ elif spl_ind=='v':
87
+ return: Vsplines['V_to_H']
88
+ elif spl_ind=='h':
89
+ return: splines['H_to_V']
90
+
91
+ def pack_tosave(self, obj, obj_name=None, overwrite=False, save=False):
92
+ #prepares a dictionary {'obj name':obj, ...}
93
+ tosave = self.tosave
94
+ if obj_name is None: obj_name = obj_name(obj)
95
+
96
+ if overwrite:
97
+ tosave.update({obj_name : obj})
98
+ else:
99
+ add = tosave.setdefault(obj_name, obj) #for already excisting name returns corresponding element and doesn't overwrite
100
+ if add!=obj:
101
+ print(f'Overwriting attempt!')
102
+
103
+ self.tosave = tosave
104
+ if save:
105
+ self.save(tosave)
106
+ return tosave
107
+
barsukov/script.py ADDED
@@ -0,0 +1,82 @@
1
+ from barsukov.time import *
2
+ from barsukov.logger import Logger
3
+
4
+ import sys
5
+ import os
6
+
7
+ class Script():
8
+ def __init__(self,
9
+ ### Please ALWAYS specify the following:
10
+ operator='Anon', # ib, Rundong, Sasha, Ameerah, Alex, or AlexH if ambiguous
11
+ station='No Stn', # qd, ppms, mseppms, data, orange, ...
12
+ sample='No Sample', # Use sample name from the sample table, e.g. "cro2410a1" or "yig2207"
13
+ description='No Description', # Briefly: what are you doing. Sets the folder name.
14
+ # i.e. 'Testing modulation' or 'fH OOP long average'
15
+ project_folder = os.getcwd(), # Full path to your project folder. Please follow this convention:
16
+ # D:/Rundong/Projects/AFM sims/2024-07-06 Autooscillations
17
+ # Will grab the current directory if not specified.
18
+
19
+ ### Optional:
20
+ log='both', # This is the default log setting which will be passed to the logger.
21
+ ### It will be overriden by other objects,
22
+ ### which in turn will be overriden by methods.
23
+ ### Choose here and everywhere from screen, file, both, no.
24
+
25
+ ### Usually, it's better not to change these:
26
+ log_full_folder_path=None,
27
+ log_full_file_path=None
28
+ ):
29
+
30
+ self.operator = operator
31
+ self.station = station
32
+ self.sample = sample
33
+ self.description = description
34
+ self.project_folder = project_folder
35
+
36
+ self.rm = None
37
+
38
+ ### Creating the sub-project folder
39
+ self.folder_name = date() + '_' + self.station + '_' + self.operator + '_' + self.sample + '_' + self.description
40
+ self.full_folder_path = os.path.join(self.project_folder, self.folder_name)
41
+ os.makedirs(self.full_folder_path, exist_ok=True)
42
+
43
+ ### Starting the logger
44
+ if log_full_folder_path is None:
45
+ self.logger = Logger(
46
+ description=self.operator + '_' + self.description,
47
+ full_file_path=log_full_file_path,
48
+ log=log, # Script.log becomes Logger's default
49
+ start_file=True)
50
+ else:
51
+ self.logger = Logger(
52
+ description=self.operator + '_' + self.description,
53
+ full_folder_path=log_full_folder_path,
54
+ full_file_path=log_full_file_path,
55
+ log=log, # Script.log becomes Logger's default
56
+ start_file=True)
57
+
58
+ self.logger.log(f'Script object initialized. Logger started.', log='both')
59
+
60
+ def log(self, msg, log='default'): # default means the default of the logger
61
+ self.logger.log(msg, log=log)
62
+
63
+
64
+ ### BEGIN: Equipment related stuff
65
+
66
+ def init_rm(self):
67
+ # Imports pyvisa as method library #
68
+ import pyvisa as visa # ----------------XXX Can this be done like this??????????????
69
+ try:
70
+ self.rm = visa.ResourceManager()
71
+ except:
72
+ self.rm = None
73
+ self.log('Script could not import pyvisa.', log='important')
74
+ sys.exit()
75
+ self.log(f'Script started pyvisa.ResourceManager.', log='both')
76
+ return self.rm
77
+
78
+ ### BEGIN Equipment devices
79
+
80
+ def mwHP(self, **kwargs):
81
+ from barsukov.src.exp.mwHP import mwHP as eq
82
+ return eq(**kwargs, script=self, logger=self.logger)
barsukov/time.py ADDED
@@ -0,0 +1,16 @@
1
+ ### BEGIN Dependencies ###
2
+ import datetime
3
+ from pytz import timezone
4
+ ### END Dependencies ###
5
+
6
+ TIMEZONE = timezone('America/Los_Angeles')
7
+
8
+ def time_stamp(): # YYYY-MM-DD_HH-MM-SSS, last digit is 1/10th of the second
9
+ now = datetime.datetime.now(TIMEZONE)
10
+ formatted_datetime = now.strftime(f"%Y-%m-%d_%H-%M-%S") + str(int( now.microsecond / 100000 ))
11
+ return formatted_datetime
12
+
13
+ def date(): # YYYY-MM-DD
14
+ now = datetime.datetime.now(TIMEZONE)
15
+ formatted_datetime = now.strftime(f"%Y-%m-%d")
16
+ return formatted_datetime
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.2
2
+ Name: barsukov
3
+ Version: 0.0.1
4
+ Summary: Experiment Automation Package
5
+ Author-email: Igor Barsukov <igorb@ucr.edu>
6
+ Maintainer-email: Steven Castaneda <scast206@ucr.edu>
7
+ Project-URL: Homepage, https://barsukov.ucr.edu
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pytz>=2014.10
13
+ Requires-Dist: numpy>=1.20.0
14
+ Requires-Dist: scipy>=1.15.1
15
+
16
+ # Barsukov
17
+
18
+ Barsukov is a Python library for experiment automation.
19
+
20
+ ## Installation
21
+
22
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install barsukov.
23
+
24
+ ```bash
25
+ pip install barsukov
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ #
32
+ #
33
+ #
34
+ #
35
+ #
36
+ #
37
+ #
38
+ ```
39
+
40
+ ## Contributing
41
+
42
+ -
43
+ -
44
+ -
45
+
46
+ ## License
47
+
48
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -0,0 +1,14 @@
1
+ barsukov/__init__.py,sha256=gFsPiZHKeV_2ssxYqUO-xAa22CpXepwuFbTghauXzo4,280
2
+ barsukov/logger.py,sha256=kWSiJJSjI4EtCxW5fyAww-6BQAFnCk-Gv8OIdtXH5pg,5275
3
+ barsukov/obj2file.py,sha256=kNEDM7wcAUbeg7qbztZrKsxcOQEi2JmB-qQzaOO50Cc,3879
4
+ barsukov/script.py,sha256=R-8ai8LMO3m4quoHrKXBgoLTlDGX7AeRj6XbVZEunbA,3383
5
+ barsukov/time.py,sha256=6YVvay-ISO5VpyHG8WGUelGfaN1N18ktRrP37MVbdyU,552
6
+ barsukov/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ barsukov/data/fft.py,sha256=VwY1h0ZB8xy_mxoI8zPESRLM7nk5NbiGi3wC6vg6T-k,4452
8
+ barsukov/exp/__init__.py,sha256=urLfGpap40kN9ULi53JB0NT-iMsZDSFdBmdSq3ckB0E,19
9
+ barsukov/exp/exp_utils.py,sha256=jpmSt-4XGwK4QWR8SEi1s0JPwUavO8Smmc4y6bO0giU,4091
10
+ barsukov/exp/mwHP.py,sha256=auZmIgxtbZyzS_x0jk5uhNHioAuJKnAUHeB-fJoSF3w,19023
11
+ barsukov-0.0.1.dist-info/METADATA,sha256=3aX_sWC9hbJcqxKSA2eSpvIVLdKgQG0T_-XPm7RhESM,858
12
+ barsukov-0.0.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
13
+ barsukov-0.0.1.dist-info/top_level.txt,sha256=Js5sHbNjP0UNMB9O5HtCHZqlfHabuNS8nTsHbg-1DDQ,9
14
+ barsukov-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ barsukov