barsukov 1.3.3__py3-none-any.whl → 1.3.4__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/data/fft.py CHANGED
@@ -1,68 +1,114 @@
1
- ### BEGIN Dependencies ###
2
1
  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
2
 
7
3
 
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 = ''
4
+ def fft(x, y, equidistant_check=True, equidistant_rel_error=1e-4, remove_negative_f=False, mathematica_convention=False, inverse=False):
5
+ """
6
+ Perform Fast Fourier Transform (FFT) or Inverse FFT on one-dimensional or multi-dimensional data,
7
+ with optional handling for non-equidistant sampling, mandatory normalization to continuous Fourier definition.
8
+ Allows for +- in exp for scientific and engineering definition for Fourier Transform.
9
+
10
+ Parameters
11
+ ----------
12
+ x (array_like):
13
+ The time or spatial domain axis (1D array). Must be increasing, doesn't need to be equidistant.
14
+ y (array_like):
15
+ The signal to be transformed. Can be 1D or 2D array (if 2D, the FFT is applied along axis 1).
16
+ equidistant_check (bool, optional):
17
+ If True (default), checks whether `x` is uniformly spaced. If not, interpolates to uniform spacing.
18
+ equidistant_rel_error : float, optional
19
+ Relative tolerance for equidistant spacing check. Default is 1e-4.
20
+ remove_negative_f : bool, optional
21
+ If True, removes negative frequencies from output. Default is False.
22
+ mathematica_convention : bool, optional
23
+ If True, uses Mathematica-style conventions: forward Fourier ~ exp(i2pift).
24
+ If False (default), uses NumPy convention: forward Fourier ~ exp(-i2pift)
25
+ inverse : bool, optional
26
+ If True, computes the inverse FFT. Default is False (forward transform).
27
+
28
+ Returns
29
+ -------
30
+ fft_x : ndarray
31
+ Ordered frequency domain axis corresponding to the transformed data (negative frequencies can be removed if remove_negative_f is True).
32
+ Ordered time domain axis if inverse is True (time axis starts at 0).
33
+ fft_y : ndarray
34
+ Transformed signal. Shape matches `y`.
35
+
36
+ Notes
37
+ -----
38
+ - The function supports both real and complex-valued input `y`.
39
+ - If `x` is not equidistant and `equidistant_check=True`, the function interpolates `y` using `make_equidistant`.
40
+
41
+ Examples
42
+ --------
43
+ >>> t = np.linspace(0, 1, 1000)
44
+ >>> y = np.sin(2 * np.pi * 50 * t)
45
+ >>> f, Y = fft(t, y)
46
+
47
+ >>> # Inverse transform
48
+ >>> t_rec, y_rec = fft(f, Y, inverse=True)
49
+ """
50
+
51
+ x, y = np.array(x), np.array(y)
52
+
53
+ # Handle non-equidistant input
14
54
  if equidistant_check:
15
55
  diffs = np.diff(x)
16
56
  if not np.allclose(diffs, diffs[0], rtol=equidistant_rel_error):
17
57
  # 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)
58
+ x, y = make_equidistant(x, y, step=None)
24
59
 
60
+ # Determine shape and axis
25
61
  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
62
+ n, axis = len(y), -1
63
+ else:
64
+ n, axis = y.shape[1], 1
65
+
66
+ #Determine Convention
67
+ if mathematica_convention is False:
68
+ fft_func, fft_norm = np.fft.fft, "backward"
69
+ ifft_func, ifft_norm = np.fft.ifft, "forward"
30
70
  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.')
71
+ fft_func, fft_norm = np.fft.ifft, "forward"
72
+ ifft_func, ifft_norm = np.fft.fft, "backward"
73
+
74
+ # Compute FFT and normalize
75
+ sample_spacing = (x[-1] - x[0]) / (n-1.0)
76
+
77
+ if inverse is True:
78
+ y = np.fft.ifftshift(y, axes=axis) # Reorder y to match np.fft.ifft convention
79
+ fft_x = np.arange(0, n) / (n * sample_spacing) # Time domain creation
80
+ fft_y = ifft_func(y, axis=axis, norm=ifft_norm) * sample_spacing # Sample Spacing is the Fourier consistent normalization
49
81
  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.')
82
+ fft_x = np.fft.fftfreq(n, d=sample_spacing) # Frequency domain creation
83
+ fft_y = fft_func(y, axis=axis, norm=fft_norm) * sample_spacing
84
+ fft_x, fft_y = np.fft.fftshift(fft_x), np.fft.fftshift(fft_y, axes=axis) # Reorder x, y for plotting
85
+
86
+ # Remove negative frequencies
87
+ if remove_negative_f is True:
88
+ mask = fft_x >= 0
89
+ fft_x = fft_x[mask]
90
+ if y.ndim == 1:
91
+ fft_y = fft_y[mask]
92
+ else:
93
+ fft_y = fft_y[:, mask]
94
+
95
+ return fft_x, fft_y
96
+
97
+ # NOTES For Developer:
98
+ # Mathematica has convention exp(i*2pi*t)
99
+ # np.fft.fft takes x input -3,-2,-1,0,1,2,3 and np.fft.ifft takes x input 0,1,2,3,-3,-2,-1
100
+ # Our fft(np.fft.ifft) takes x input -3,-2,-1,0,1,2,3 and our ifft(np.fft.fft) takes x input 0,1,2,3,-3,-2,-1
101
+ # Numpy outputs in 0,1,2,3,-3,-2,-1 order, our function sorts to -3,-2,-1,0,1,2,3
102
+
103
+ #Tested with Lorentzian, Gaussian, and Rectangular Signals
104
+
105
+ def ifft(x, y, equidistant_check=True, equidistant_rel_error=1e-4, remove_negative_f=False, mathematica_convention=False):
106
+ return fft(x, y, equidistant_check=equidistant_check, equidistant_rel_error=equidistant_rel_error, remove_negative_f=remove_negative_f, mathematica_convention=mathematica_convention, inverse=True)
58
107
 
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
108
 
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
109
 
65
110
  def make_equidistant(x, y, step=None):
111
+ import scipy.interpolate as sp
66
112
  ### Takes one column x and one or more columns y and makes them equidistant in x
67
113
  ### Returns new_x, new_y. The number of points will likely change.
68
114
  if step is None:
@@ -70,18 +116,17 @@ def make_equidistant(x, y, step=None):
70
116
  min_step = np.min(np.diff(x))
71
117
  else:
72
118
  min_step = step
73
-
119
+
74
120
  # Generate the new equidistant x array
75
121
  new_x = np.arange(x[0], x[-1] + min_step, min_step)
76
-
122
+
77
123
  if isinstance(y[0], (list, np.ndarray)): # If y contains multiple columns
78
124
  new_y = []
79
125
  for y_column in y:
80
126
  interpolation_function = sp.interpolate.interp1d(x, y_column, kind='linear', fill_value='extrapolate')
81
127
  new_y.append(interpolation_function(new_x))
82
- new_y = np.array(new_y).T # Transpose to match the original structure
83
128
  else: # If y is a single column
84
129
  interpolation_function = sp.interpolate.interp1d(x, y, kind='linear', fill_value='extrapolate')
85
130
  new_y = interpolation_function(new_x)
86
-
87
- return new_x, new_y
131
+
132
+ return np.array(new_x), np.array(new_y)
barsukov/data/noise.py ADDED
@@ -0,0 +1,276 @@
1
+ import numpy as np
2
+
3
+ K_b = 1.380649e-23
4
+ q = 1.602176634e-19
5
+
6
+ def johnson(time_arr, T, R):
7
+ #CHECKED
8
+ """
9
+ Generates Johnson-Nyquist noise voltage signal in the time domain, for a given time array.
10
+
11
+ Parameters:
12
+ time_arr (array-like): Array of ordered time values in Seconds (assumed to be evenly spaced).
13
+ T (float): Temperature in Kelvin.
14
+ R (float): Resistance in Ohms.
15
+
16
+ Returns:
17
+ np.ndarray: Array of normally distributed noise values with RMS amplitude corresponding to the thermal noise voltage.
18
+ """
19
+ size = len(time_arr)
20
+ Df = 0.5 * (size - 1) / (time_arr[-1] - time_arr[0]) # Nyquist Frequency is this correct. Becomes noise's bandwidth
21
+ # Nyquist Frequency used for bandwidth as it is the maximum resolvable frequency that can be detected. Generating noise purely by itself, before any filtering is being simulated.
22
+
23
+ V_rms = np.sqrt(4 * K_b * T * R * Df)
24
+ return np.random.normal(0, V_rms, size)
25
+
26
+ def shot(time_arr, I, R):
27
+ #CHECKED
28
+ """
29
+ Generates Shot noise voltage signal in the time domain, for a given time array.
30
+
31
+ Parameters:
32
+ time_arr (array-like): Array of ordered time values in Seconds (assumed to be evenly spaced).
33
+ I (float): Current in Amperes.
34
+ R (float): Resistance in Ohms.
35
+
36
+ Returns:
37
+ np.ndarray: Array of normally distributed voltage noise values with RMS amplitude corresponding to shot noise.
38
+
39
+ Note:
40
+ Ideally, shot noise follows a Poisson distribution, but for large mean values (lambda), the Poisson distribution approximates a normal distribution.
41
+ """
42
+
43
+ size = len(time_arr)
44
+ Df = 0.5 * (size - 1) / (time_arr[-1] - time_arr[0])
45
+
46
+ I_rms = np.sqrt(2 * q * I * Df) # Shot noise current
47
+ V_rms = R * I_rms # Recalculating to Shot noise voltage
48
+ return np.random.normal(0, V_rms, size) #poisson should be used, but lamda is too large for np.random.poisson
49
+
50
+ def color(time_arr, V_rms, exponent=1):
51
+ """
52
+ Generates 1/f^(exponent) noise (pink noise by default, exp=1) in the time domain.
53
+
54
+ Parameters:
55
+ time_arr (array-like): Array of ordered time values in Seconds (assumed to be evenly spaced).
56
+ V_rms (float): RMS value of the generated noise.
57
+ exponent (float, optional): Power of the frequency dependence. Default is 1, which gives 1/f noise (pink noise). Set to 0 for white noise, 2 for brown noise, etc...
58
+
59
+ Returns:
60
+ np.ndarray: Array of noise values with PSD proportional to 1/f^(exponent).
61
+ """
62
+ #Generate Guassian White Noise in time domain
63
+ size = len(time_arr)
64
+ dt = (time_arr[-1] - time_arr[0]) / (size-1.0)
65
+ white = np.random.standard_normal(size)
66
+
67
+ #Fourier Transform to Frequency Domain
68
+ freqs = np.fft.rfftfreq(size, d=dt)
69
+ fft = np.fft.rfft(white, norm='backward') * dt
70
+
71
+ #Scale Fourier Transform by 1/f^(exponent/2) for 1/f^exponent PSD (psd proportional to fft^2)
72
+ freqs[0] = freqs[1] #Avoid division by zero
73
+ fft = fft / (freqs**(exponent*0.5))
74
+
75
+ #Convert back to time domain
76
+ ifft = (np.fft.irfft(fft, n=size, norm='forward') / dt).real
77
+ ifft_rms = np.sqrt(np.mean(ifft**2))
78
+
79
+ return V_rms * ifft / ifft_rms #Ensure V_rms is as specified
80
+
81
+
82
+ def rtn(time_arr, tau_up, tau_down, state_up=1, state_down=0, initial_state=None):
83
+ """
84
+ Generate random telegraph noise on a user-supplied time array.
85
+
86
+ Parameters:
87
+ time_arr (np.ndarray): Array of ordered time values in Seconds (assumed to be evenly spaced).
88
+ tau_up (float): Mean dwell time in the 'up' state.
89
+ tau_down (float): Mean dwell time in the 'down' state.
90
+ state_up (float): Value of the up state (default: 1).
91
+ state_down (float): Value of the down state (default: 0).
92
+ initial_state (float): Value of the first state (default: None = random(up, down))
93
+
94
+ Returns:
95
+ signal (np.ndarray): RTN signal array, same shape as time_arr.
96
+
97
+ Notes:
98
+ - PSD of RTN will have lorentzian profile: S(f) = 4*A^2*tau / (1 + (2pi*f*tau)^2),
99
+ with correlation time: tau = tau_up*tau_down / (tau_up+tau_down)
100
+ - Characteristic (roll-off) frequency corresponds to 1 / (2pi*tau)
101
+ """
102
+ if tau_up <= 0 or tau_down <= 0:
103
+ raise ValueError("tau_up and tau_down must be positive to avoid infinite loops.")
104
+
105
+ time_arr = np.asarray(time_arr)
106
+ signal = np.zeros_like(time_arr)
107
+
108
+ if initial_state is None:
109
+ current_state = np.random.choice([state_up,state_down])
110
+ else: current_state = initial_state
111
+
112
+ current_time = time_arr[0]
113
+ i = 0
114
+
115
+ while i < len(time_arr):
116
+ # Sample dwell time
117
+ dwell_time = np.random.exponential(tau_up if current_state == state_up else tau_down)
118
+ dwell_end_time = current_time + dwell_time
119
+
120
+ # Assign current state until dwell time is over
121
+ while i < len(time_arr) and time_arr[i] < dwell_end_time:
122
+ signal[i] = current_state
123
+ i += 1
124
+
125
+ # Flip state
126
+ current_time = dwell_end_time
127
+ current_state = state_down if current_state == state_up else state_up
128
+
129
+ return signal
130
+
131
+
132
+ def bit(signal_arr, bit_depth, measure_min, measure_max, noise_only = False):
133
+ """
134
+ Quantize an analog signal to simulate ADC behavior with given bit depth.
135
+
136
+ Parameters:
137
+ signal_arr (array-like): Input analog signal values.
138
+ bit_depth (int): number of bits used in quantization (e.g., 8, 12, 16).
139
+ measure_min (float): Minimum measurable value of the ADC range.
140
+ measure_max (float): Maximum measurable value of the ADC range.
141
+ noise_only (bool, optional): If True, quantization noise only. If False (default), return the quantized signal.
142
+
143
+ Returns:
144
+ np.ndarray: Quantized signal or Quantization noise, depending on 'noise_only'.
145
+
146
+ Notes:
147
+ - The signal is clipped to the measurement range before quantization.
148
+ - The number of quantization levels is 2^bit_depth.
149
+ """
150
+ levels = int(2**int(bit_depth)) # 1<<int(bit_depth)
151
+ quantization_step = (measure_max - measure_min) / (levels - 1)
152
+
153
+ signal_clipped = np.clip(signal_arr, measure_min, measure_max)
154
+ quantized_signal = np.round((signal_clipped - measure_min) / quantization_step) * quantization_step + measure_min
155
+
156
+ if noise_only is False:
157
+ return quantized_signal
158
+ else:
159
+ return quantized_signal - signal_arr
160
+
161
+
162
+ def psd(time_arr, signal_arr, return_onesided=True):
163
+ """
164
+ Computes the Power Spectral Density (PSD) of a time-domain signal using Welch's method.
165
+
166
+ Parameters:
167
+ time_arr (array-like): Ordered time values in Seconds (assumed to be increasing).
168
+ signal_arr (array-like): Signal values as a function of time.
169
+
170
+ Returns:
171
+ tuple:
172
+ - f_welch (np.ndarray): Array of frequency values corresponding to the PSD.
173
+ - psd_welch (np.ndarray): Power Spectral Density values in (signal_unit)^2/Hz.
174
+ - msg (str): Message describing the PSD normalization and units.
175
+
176
+ Notes:
177
+ - Welch's method averages overlapping FFTs to reduce variance in the PSD estimate.
178
+ - The RMS value of the signal over a bandwidth B can be computed from the PSD as:
179
+ V_rms = sqrt( ∫ PSD(f) df ) over bandwidth.
180
+ - The PSD values follow the convention:
181
+ mean(PSD) ≈ 2 × (b.fft)^2 / total duration
182
+ -When dealing with real signals, (one sided, only positive frequencies) the relationship is psd = np.abs(fft)**2 / total_time * 2, where factor of 2 is applied everywhere except at the DC and Nyquist bins. When dealing with complex signals (positive and negative) the relationship is psd = np.abs(fft)**2 / total_time.
183
+ """
184
+ from scipy import signal as sp_signal
185
+ #make equidistant
186
+ size = len(time_arr)
187
+ sampling_freq = (size-1) / (time_arr[-1] - time_arr[0])
188
+
189
+ f_welch, psd_welch = sp_signal.welch(signal_arr, sampling_freq, nperseg=min(size, 4024), return_onesided=return_onesided)
190
+ return f_welch, psd_welch
191
+
192
+ def noise_help(noise):
193
+ import matplotlib.pyplot as plt
194
+
195
+ doc = {}
196
+
197
+ if noise == "johnson":
198
+ time = np.arange(0,100)
199
+
200
+ doc = {
201
+ "name": "Johnson Noise",
202
+ "time": time,
203
+ "function": johnson,
204
+ "params": [300, 200],
205
+ "text_title_size": 20,
206
+ "text_midbreak_size": 15,
207
+ "text_main_size": 10,
208
+ "text_bullet_size": 10,
209
+ "text_title_x": .5,
210
+ "text_title_y": 9,
211
+ "text_main_x": .3,
212
+ "text_main_y": 8,
213
+ }
214
+
215
+ if noise == "johnson2":
216
+ time = np.arange(0,100)
217
+
218
+ doc = {
219
+ "name": "Johnson Noise",
220
+ "time": time,
221
+ "function": johnson,
222
+ "params": [300, 200],
223
+ "text_title_size": 20,
224
+ "text_midbreak_size": 15,
225
+ "text_main_size": 10,
226
+ "text_bullet_size": 10,
227
+ "text_title_x": .5,
228
+ "text_title_y": 9,
229
+ "text_main_x": .3,
230
+ "text_main_y": 6,
231
+ }
232
+
233
+ if noise == "shot":
234
+ time = np.arange(0,100)
235
+
236
+ doc = {
237
+ "name": "Shot Noise",
238
+ "time": time,
239
+ "function": shot,
240
+ "params": [1, 200],
241
+ "text_title_size": 30,
242
+ "text_midbreak_size": 15,
243
+ "text_main_size": 15,
244
+ "text_bullet_size":10,
245
+ "text_title_x": .5,
246
+ "text_title_y": 9,
247
+ "text_main_x": .5,
248
+ "text_main_y": 8,
249
+ }
250
+
251
+
252
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(22,7))
253
+ fig.suptitle(doc["name"], fontsize=20)
254
+ ax1.plot(doc["time"], doc["function"](doc["time"], *doc["params"]), 'red', label='(t^2)*exp(-(t^2))')
255
+
256
+
257
+ ax1.set(title="2 lines chart", xlabel="t", ylabel="y")
258
+ ax1.legend(loc="upper right")
259
+
260
+
261
+ # Set both x- and y-axis limits to [0, 10] instead of default [0, 1]
262
+
263
+
264
+ ax2.axis([0, 10, 0, 10])
265
+ ax2.tick_params(axis='x', colors='white')
266
+ ax2.tick_params(axis='y', colors='white')
267
+
268
+ ax2.text(doc["text_title_x"], doc["text_title_y"], doc["name"], weight='bold', fontsize=doc["text_title_size"],
269
+ bbox={'facecolor': 'red', 'alpha': 0.3, 'pad': 10})
270
+ ax2.text(doc["text_main_x"], doc["text_main_y"], doc["function"].__doc__, wrap="false", fontsize=doc["text_main_size"])
271
+
272
+
273
+ #use for bullet points#ax2.text(0.5, 5.5, 'Topic 2', weight='bold', fontsize=doc["text_midbreak_size"])
274
+ #use for bullet points#ax2.text(0.5, 4.5, '- Bullet pont 1\n- Bullet point 2', fontsize= doc["text_bullet_size"])
275
+
276
+ #use to display function # ax2.text(2, 3, r'a function to plot: $t^2*exp(-t^2)$', fontsize=12)
barsukov/exp/__init__.py CHANGED
@@ -1 +1,3 @@
1
1
  from . import mwHP
2
+
3
+ from . import smKE
barsukov/exp/mwHP.py CHANGED
@@ -55,6 +55,8 @@ class mwHP:
55
55
  log_in_eq(self, msg, log=log)
56
56
  ### END These functions could be shared across all equipment.
57
57
 
58
+
59
+ ### BEGIN: mwHP Functions:
58
60
  def output(self, state=None, log=None, check=False):
59
61
  ### Always has a return! Which is the state of Output.
60
62
  ### output() reads and returns the state of Output.
@@ -231,6 +233,7 @@ class mwHP:
231
233
  if abs(dutyreal - duty) < 0.03*float(duty): self.log(f'Writing Pulse duty cycle as {dutyreal}.', log=log)
232
234
  else: self.log(f'Warning:Writing Pulse duty cycle as {dutyreal}, but was asked {duty}.', log='important')
233
235
  return freal, dutyreal
236
+ ### END: mwHP Functions:
234
237
 
235
238
 
236
239
  ### BEGIN: OBJ2FILE Tools
@@ -241,8 +244,10 @@ class mwHP:
241
244
  seriable_data = self.__dict__.copy()
242
245
  # take the attributes of unseriable data
243
246
  if self.script is None:
244
- seriable_data['logger'] == 'needsrebuild'
245
- seriable_data['logger_information'] = self.logger.__getargs__()
247
+ if self.logger is not None:
248
+ seriable_data['logger'] == 'needsrebuild'
249
+ seriable_data['logger_information'] = self.logger.__getargs__()
250
+ seriable_data['logger_information']['start_file'] = False
246
251
  else:
247
252
  seriable_data['script'] == 'needsrebuild'
248
253
  seriable_data['script_information'] = self.script.__getstate__()
@@ -259,5 +264,9 @@ class mwHP:
259
264
  self.script = Script(**seriable_data['script_information'])
260
265
  if self.logger == 'needsrebuild':
261
266
  self.logger = Logger(**seriable_data['logger_information'])
267
+ if (self.script is not None):
268
+ self.log(f'I am using Script saved in memory: {self.script.folder_name}.', log='screen')
269
+ elif (self.logger is not None):
270
+ self.log(f'I am using Logger saved in memory: {self.logger.description}.', log='screen')
262
271
  eq_reconnect(self)
263
272
  ### END: OBJ2FILE Tools
barsukov/exp/smKE.py ADDED
@@ -0,0 +1,148 @@
1
+ ### BEGIN Dependencies ###
2
+ import numpy as np
3
+ import sys
4
+ from barsukov.exp.exp_utils import *
5
+ ### END Dependencies
6
+
7
+
8
+ class smKE:
9
+ def __init__(self, gpib=None, visa_rm=None, logger=None, gpib_card=0, log='default', script=None):
10
+ # Pass the Script object, if available.
11
+ # If Script has no visa_rm or if no Script is passed, you'll need to pass the visa_rm=visa.ResourceManager manually.
12
+ # If Script has no logger or if no Script is passed, you can pass the logger manually.
13
+ # If no logger is passed, will simply print to screen.
14
+ # Change log from 'default' to 'screen', 'file', 'both', 'no'.
15
+ # gpib_card is per default 0. You can change, if you have multiple.
16
+
17
+ self.script = script
18
+ self.logger = logger
19
+ self.eq_default_log = log
20
+ self.rm = visa_rm
21
+ self.gpib_card = gpib_card
22
+ self.gpib = gpib
23
+
24
+ self.msg_deco = f'[smKE {self.gpib_card}::{self.gpib}]'
25
+ self.eq = initialize_gpib(self) # This will initialize self.eq = visa.open_resource()
26
+ self.log( f'Initialized: {self.identify()}', log='important' ) # This is the 'welcome message' and a check if communication works.
27
+
28
+ # Digits, x_Min, x_Max, Range_min, Range_max, units, function
29
+ self.I_params = [10, -1.05, 1.05, 1e-6, 1, 'A', 'curr']
30
+ self.V_params = [10, -210.0, 210.0, 0, 210.0, 'V', 'volt'] #digits and range undetermined
31
+ self.R_params = [10, 0.0, 2.1e8, 'R'] #digits undetermined
32
+
33
+ ### BEGIN The definition of the following functions may be specific to this equipment.
34
+ def query(self, cmd):
35
+ return self.eq.query(cmd)
36
+
37
+ def write(self, cmd):
38
+ return self.eq.write(cmd)
39
+
40
+ def identify(self):
41
+ return str(self.eq.query('*IDN?'))
42
+ ### END The definition of the following functions may be specific to this equipment.
43
+ def disconnect(self):
44
+ eq_disconnect(self)
45
+
46
+ def reconnect(self):
47
+ eq_reconnect(self)
48
+
49
+ def log(self, msg, log=None):
50
+ if log is None: log=self.eq_default_log
51
+ log_in_eq(self, msg, log=log)
52
+ ### END These functions could be shared across all equipment.\
53
+
54
+ def source(self, function=None, scale=None, level=None, cmpl_scale=None, cmpl_level=None, log=None):
55
+ if log is None: log=self.eq_default_log
56
+ if function is None:
57
+ try:
58
+ f = self.eq.query('sour:func?')
59
+ f = str(f)[:-1]
60
+
61
+ s = self.eq.query(f'sour:{f}:rang?')
62
+ s = float(s) / 1.05
63
+ exp = np.floor(np.log10(abs(s)))
64
+ s = np.ceil(s / 10**exp) * 10**exp
65
+
66
+ l = self.eq.query(f'sour:{f}:lev?')
67
+ l = float(l)
68
+
69
+ if f == 'CURR': source_param, compliance_param = self.I_params, self.V_params
70
+ if f == 'VOLT': source_param, compliance_param = self.V_params, self.I_params
71
+
72
+ cs = self.eq.query(f'sens:{source_param[6]}:rang?')
73
+ cs = float(cs)
74
+
75
+ cl = self.eq.query(f'sens:{source_param[6]}:prot?')
76
+ cl = float(cl)
77
+
78
+ self.log(f'Source function is {f}, scale is {s} {source_param[5]}, level is {l} {source_param[5]}. Compliance scale is {cs} {compliance_param[5]}, level is {cl} {compliance_param[5]}')
79
+ return f, s, l, cs, cl
80
+
81
+ except:
82
+ self.log(f'Error while reading source function.', log='important')
83
+ return np.nan
84
+ else:
85
+ if (function == 'curr') or (function == 'current'): source_param, compliance_param = self.I_params, self.V_params
86
+ else: source_param, compliance_param, = self.V_params, self.I_params
87
+ try:
88
+ if level > scale: level = scale #level must be equal to or less than scale
89
+ if cmpl_level > cmpl_scale: cmpl_level = cmpl_scale
90
+
91
+ f = source_param[6]
92
+ self.eq.write(f'sour:func {f}')
93
+ self.eq.write(f'sour:{f}:mode fix')
94
+
95
+ s = max(source_param[3], min(float(scale), source_param[4]))
96
+ self.eq.write(f'sour:{f}:rang {s}')
97
+
98
+ l = max(source_param[1], min(float(level), source_param[2]))
99
+ self.eq.write(f'sour:{f}:lev {l}')
100
+
101
+ cs = max(compliance_param[3], min(float(cmpl_scale), compliace_param[4]))
102
+ self.eq.write(f'sens:{compliance_param[6]}rang:auto off')
103
+ self.eq.write(f'sens:{compliance_param[6]}:rang: {cs}')
104
+
105
+ cl = max(compliance_param[1], min(float(cmpl_level), compliance_param[2]))
106
+ self.eq.write(f'sens:{compliance_param[6]}:prot:rsyn on')
107
+ self.eq.write(f'sens:{compliance_param[6]}:prot {cmpl_level}')
108
+
109
+ freal, sreal, lreal, csreal, clreal = self.source()
110
+ return freal, sreal, lreal, csreal, clreal
111
+
112
+ except:
113
+ self.log(f'Error while changing Source.', log='important')
114
+ return np.nan
115
+
116
+ def measure(self, function=None, four_wire=None, res_mode=None, res_guard=None, res_compensation=None, log=None):
117
+ if log is None: log=self.eq_default_log
118
+ if function is None:
119
+ try:
120
+ f = self.eq.query('sens:func?')
121
+ f = str(f)[:-1]
122
+
123
+ ws = self.eq.query('syst:rsen?')
124
+ ws = float(ws)
125
+ if ws: ws = 'on'
126
+ else: ws = 'off'
127
+
128
+ #self.log(f'Measure function
129
+
130
+ if 'RES' in f:
131
+ rm = self.eq.query('res:mode?')
132
+ rm = str(f)[:-1]
133
+
134
+ rg = self.eq.query('syst:guard?')
135
+ rg = str(f)[:-1]
136
+
137
+ rc = self.eq.query('res:ocom?')
138
+ if ws: ws = 'on'
139
+ else: ws = 'off'
140
+
141
+ except:
142
+ return 0
143
+
144
+ self.eq.write(f'sens:func:off:all')
145
+ self.eq.write(f'sens:func:on "{function}"')
146
+ self.eq.write(f'{function}:mode {source}')
147
+ self.eq.write(f'syst:rsen {sense_mode}')
148
+ self.eq.write(f'syst:guard {guard}')