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/__init__.py +1 -4
- barsukov/app/lock_in_emulator_app.py +293 -0
- barsukov/data/Change_phase.py +160 -0
- barsukov/data/Lock_in_emulator.py +183 -0
- barsukov/data/__init__.py +4 -0
- barsukov/data/constants.py +10 -0
- barsukov/data/fft.py +100 -55
- barsukov/data/noise.py +276 -0
- barsukov/exp/__init__.py +2 -0
- barsukov/exp/mwHP.py +11 -2
- barsukov/exp/smKE.py +148 -0
- barsukov/logger.py +4 -3
- barsukov/script.py +16 -4
- barsukov/time.py +1 -1
- {barsukov-1.3.3.dist-info → barsukov-1.3.4.dist-info}/METADATA +6 -2
- barsukov-1.3.4.dist-info/RECORD +20 -0
- {barsukov-1.3.3.dist-info → barsukov-1.3.4.dist-info}/WHEEL +1 -1
- barsukov-1.3.3.dist-info/RECORD +0 -14
- {barsukov-1.3.3.dist-info → barsukov-1.3.4.dist-info}/top_level.txt +0 -0
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
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
|
-
|
|
245
|
-
|
|
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}')
|