pqopen-lib 0.8.1__py3-none-any.whl → 0.8.2__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.
- pqopen/{goertzel.py → auxcalc.py} +24 -1
- pqopen/helper.py +17 -1
- pqopen/powerquality.py +7 -31
- pqopen/powersystem.py +21 -15
- {pqopen_lib-0.8.1.dist-info → pqopen_lib-0.8.2.dist-info}/METADATA +2 -2
- pqopen_lib-0.8.2.dist-info/RECORD +12 -0
- {pqopen_lib-0.8.1.dist-info → pqopen_lib-0.8.2.dist-info}/WHEEL +1 -1
- pqopen_lib-0.8.1.dist-info/RECORD +0 -12
- {pqopen_lib-0.8.1.dist-info → pqopen_lib-0.8.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,7 +9,7 @@ except ImportError:
|
|
|
9
9
|
return func
|
|
10
10
|
float32 = float64 = None
|
|
11
11
|
|
|
12
|
-
@njit
|
|
12
|
+
@njit(cache=True)
|
|
13
13
|
def calc_single_freq(x: np.array, f_hz: float, fs: float) -> tuple[float, float]:
|
|
14
14
|
"""Compute the amplitude and phase of a specific frequency component
|
|
15
15
|
in a signal using the Goertzel algorithm.
|
|
@@ -48,6 +48,29 @@ def calc_single_freq(x: np.array, f_hz: float, fs: float) -> tuple[float, float]
|
|
|
48
48
|
|
|
49
49
|
return amp, phase
|
|
50
50
|
|
|
51
|
+
def calc_rms_trapz(values: np.array, start_frac: float, end_frac: float, frequency: float, samplerate: float):
|
|
52
|
+
u2 = values * values
|
|
53
|
+
s = 0.0
|
|
54
|
+
s += 0.5 * (u2[0] + u2[1]) * start_frac # left edge
|
|
55
|
+
s += 0.5*u2[1:-2].sum() + 0.5*u2[2:-1].sum() # middle
|
|
56
|
+
s += 0.5 * (u2[-2] + u2[-1]) * end_frac # right edge
|
|
57
|
+
return (s * frequency / samplerate) ** 0.5
|
|
58
|
+
|
|
59
|
+
@njit(fastmath=True)
|
|
60
|
+
def fast_interp(y, out_len):
|
|
61
|
+
n = y.size
|
|
62
|
+
scale = (n - 1) / out_len
|
|
63
|
+
out = np.empty(out_len, dtype=y.dtype)
|
|
64
|
+
for i in range(out_len):
|
|
65
|
+
x = i * scale
|
|
66
|
+
j = int(x)
|
|
67
|
+
t = x - j
|
|
68
|
+
if j+1 < n:
|
|
69
|
+
out[i] = (1-t)*y[j] + t*y[j+1]
|
|
70
|
+
else:
|
|
71
|
+
out[i] = y[-1]
|
|
72
|
+
return out
|
|
73
|
+
|
|
51
74
|
# >>>>>>> Pre-Compile for float32 und float64 <<<<<<<
|
|
52
75
|
if float32 is not None and float64 is not None:
|
|
53
76
|
sigs = [
|
pqopen/helper.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
import re
|
|
3
|
+
import numpy as np
|
|
3
4
|
|
|
4
5
|
def floor_timestamp(timestamp: float | int, interval_seconds: int, ts_resolution: str = "us"):
|
|
5
6
|
"""Floor eines Zeitstempels auf ein gegebenes Intervall."""
|
|
@@ -44,4 +45,19 @@ class JsonDecimalLimiter(object):
|
|
|
44
45
|
return self._float_pattern.sub(self._round_float_match, json_string)
|
|
45
46
|
|
|
46
47
|
def _round_float_match(self, m):
|
|
47
|
-
return format(round(float(m.group()), self._decimal_places), f'.{self._decimal_places}f')
|
|
48
|
+
return format(round(float(m.group()), self._decimal_places), f'.{self._decimal_places}f')
|
|
49
|
+
|
|
50
|
+
def create_harm_corr_array(nom_frequency: float, num_harmonics: int, freq_response: tuple, interharm=False):
|
|
51
|
+
if interharm:
|
|
52
|
+
freqs = np.arange(0.5*nom_frequency, nom_frequency*(num_harmonics+1.5), nom_frequency)
|
|
53
|
+
else:
|
|
54
|
+
freqs = np.arange(0, nom_frequency*(num_harmonics+1), nom_frequency)
|
|
55
|
+
freq_response_arr = np.array(freq_response)
|
|
56
|
+
freq_corr = 1/np.interp(freqs, freq_response_arr[:,0], freq_response_arr[:,1])
|
|
57
|
+
return freq_corr
|
|
58
|
+
|
|
59
|
+
def create_fft_corr_array(target_size: int, freq_nyq: float, freq_response: tuple):
|
|
60
|
+
freqs = np.linspace(0, freq_nyq, target_size)
|
|
61
|
+
freq_response_arr = np.array(freq_response)
|
|
62
|
+
freq_corr = 1/np.interp(freqs, freq_response_arr[:,0], freq_response_arr[:,1])
|
|
63
|
+
return freq_corr
|
pqopen/powerquality.py
CHANGED
|
@@ -76,7 +76,7 @@ def resample_and_fft(data: np.ndarray, resample_size: int = None) -> np.ndarray:
|
|
|
76
76
|
np.ndarray: The FFT of the resampled data, scaled by sqrt(2) and normalized by the resample size.
|
|
77
77
|
"""
|
|
78
78
|
if not resample_size:
|
|
79
|
-
resample_size = 2**int(np.
|
|
79
|
+
resample_size = 2**int(np.floor(np.log2(data.size)))
|
|
80
80
|
x_data = np.arange(len(data))
|
|
81
81
|
x = np.linspace(0, len(data), resample_size, endpoint=False)
|
|
82
82
|
data_resampled = np.interp(x, x_data, data)
|
|
@@ -149,11 +149,7 @@ class VoltageFluctuation(object):
|
|
|
149
149
|
def process(self, start_sidx: int, hp_data: np.ndarray, raw_data: np.ndarray):
|
|
150
150
|
"""
|
|
151
151
|
"""
|
|
152
|
-
stage0_tp_filtered_data,
|
|
153
|
-
self.stage0_tp_filter_zi = signal.lfiltic(self.stage0_tp_filter_coeff[0],
|
|
154
|
-
self.stage0_tp_filter_coeff[1],
|
|
155
|
-
stage0_tp_filtered_data[-3:][::-1],
|
|
156
|
-
raw_data[-3:][::-1])
|
|
152
|
+
stage0_tp_filtered_data, self.stage0_tp_filter_zi = signal.lfilter(self.stage0_tp_filter_coeff[0], self.stage0_tp_filter_coeff[1], raw_data, zi=self.stage0_tp_filter_zi)
|
|
157
153
|
|
|
158
154
|
samples_skip_next_start = len(stage0_tp_filtered_data) % self._calc_samplerate_decimation
|
|
159
155
|
stage0_tp_filtered_data = stage0_tp_filtered_data[self._next_reduction_start_idx::self._calc_samplerate_decimation]
|
|
@@ -161,37 +157,17 @@ class VoltageFluctuation(object):
|
|
|
161
157
|
self._next_reduction_start_idx += self._calc_samplerate_decimation - samples_skip_next_start
|
|
162
158
|
if self._next_reduction_start_idx >= self._calc_samplerate_decimation:
|
|
163
159
|
self._next_reduction_start_idx %= self._calc_samplerate_decimation
|
|
164
|
-
stage1_tp_filtered_data,
|
|
165
|
-
self.stage1_tp_filter_zi = signal.lfiltic(self.stage1_tp_filter_coeff[0],
|
|
166
|
-
self.stage1_tp_filter_coeff[1],
|
|
167
|
-
stage1_tp_filtered_data[-2:][::-1],
|
|
168
|
-
hp_data[-2:][::-1])
|
|
160
|
+
stage1_tp_filtered_data, self.stage1_tp_filter_zi = signal.lfilter(self.stage1_tp_filter_coeff[0], self.stage1_tp_filter_coeff[1], hp_data, zi=self.stage1_tp_filter_zi)
|
|
169
161
|
blk_size = int(len(stage0_tp_filtered_data)/len(hp_data))
|
|
170
162
|
for idx,val in enumerate(stage1_tp_filtered_data[:-1]):
|
|
171
163
|
stage0_tp_filtered_data[idx*blk_size:(idx+1)*blk_size] /= val
|
|
172
164
|
stage0_tp_filtered_data[(idx+1)*blk_size:] /= stage1_tp_filtered_data[-1]
|
|
173
165
|
stage2_output = np.power(stage0_tp_filtered_data, 2)
|
|
174
|
-
stage3_hp_filtered_data,
|
|
175
|
-
self.
|
|
176
|
-
|
|
177
|
-
stage3_hp_filtered_data[-2:][::-1],
|
|
178
|
-
stage2_output[-2:][::-1])
|
|
179
|
-
stage3_tp_filtered_data,_ = signal.lfilter(self.stage3_tp_filter_coeff[0], self.stage3_tp_filter_coeff[1], stage3_hp_filtered_data, zi=self.stage3_tp_filter_zi)
|
|
180
|
-
self.stage3_tp_filter_zi = signal.lfiltic(self.stage3_tp_filter_coeff[0],
|
|
181
|
-
self.stage3_tp_filter_coeff[1],
|
|
182
|
-
stage3_tp_filtered_data[-7:][::-1],
|
|
183
|
-
stage3_hp_filtered_data[-7:][::-1])
|
|
184
|
-
stage3_weight_filtered_data,_ = signal.lfilter(self.stage3_weight_filter_coeff[0], self.stage3_weight_filter_coeff[1], stage3_tp_filtered_data, zi=self.stage3_weight_filter_zi)
|
|
185
|
-
self.stage3_weight_filter_zi = signal.lfiltic(self.stage3_weight_filter_coeff[0],
|
|
186
|
-
self.stage3_weight_filter_coeff[1],
|
|
187
|
-
stage3_weight_filtered_data[-len(self.stage3_weight_filter_coeff[1])-1:][::-1],
|
|
188
|
-
stage3_tp_filtered_data[-len(self.stage3_weight_filter_coeff[1])-1:][::-1])
|
|
166
|
+
stage3_hp_filtered_data, self.stage3_hp_filter_zi = signal.lfilter(self.stage3_hp_filter_coeff[0], self.stage3_hp_filter_coeff[1], stage2_output, zi=self.stage3_hp_filter_zi)
|
|
167
|
+
stage3_tp_filtered_data, self.stage3_tp_filter_zi = signal.lfilter(self.stage3_tp_filter_coeff[0], self.stage3_tp_filter_coeff[1], stage3_hp_filtered_data, zi=self.stage3_tp_filter_zi)
|
|
168
|
+
stage3_weight_filtered_data, self.stage3_weight_filter_zi = signal.lfilter(self.stage3_weight_filter_coeff[0], self.stage3_weight_filter_coeff[1], stage3_tp_filtered_data, zi=self.stage3_weight_filter_zi)
|
|
189
169
|
stage3_output = np.power(stage3_weight_filtered_data, 2)
|
|
190
|
-
stage4_tp_filtered_data,
|
|
191
|
-
self.stage4_tp_filter_zi = signal.lfiltic(self.stage4_tp_filter_coeff[0],
|
|
192
|
-
self.stage4_tp_filter_coeff[1],
|
|
193
|
-
stage4_tp_filtered_data[-2:][::-1],
|
|
194
|
-
stage3_output[-2:][::-1])
|
|
170
|
+
stage4_tp_filtered_data, self.stage4_tp_filter_zi = signal.lfilter(self.stage4_tp_filter_coeff[0], self.stage4_tp_filter_coeff[1], stage3_output, zi=self.stage4_tp_filter_zi)
|
|
195
171
|
# Append buffer since steady state
|
|
196
172
|
self.processed_samples += len(raw_data)
|
|
197
173
|
# Steady State after approx. 20 Seconds
|
pqopen/powersystem.py
CHANGED
|
@@ -28,8 +28,8 @@ import json
|
|
|
28
28
|
from daqopen.channelbuffer import AcqBuffer, DataChannelBuffer
|
|
29
29
|
from pqopen.zcd import ZeroCrossDetector
|
|
30
30
|
import pqopen.powerquality as pq
|
|
31
|
-
from pqopen.helper import floor_timestamp
|
|
32
|
-
from pqopen.
|
|
31
|
+
from pqopen.helper import floor_timestamp, create_fft_corr_array
|
|
32
|
+
from pqopen.auxcalc import calc_single_freq, calc_rms_trapz
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
34
34
|
|
|
35
35
|
class PowerSystem(object):
|
|
@@ -78,6 +78,7 @@ class PowerSystem(object):
|
|
|
78
78
|
self._zcd_minimum_frequency = zcd_minimum_freq
|
|
79
79
|
self.nominal_frequency = nominal_frequency
|
|
80
80
|
self.nper = nper
|
|
81
|
+
self._harm_fft_resample_size = 2**int(np.floor(np.log2((self._samplerate / self.nominal_frequency) * self.nper)))
|
|
81
82
|
self._nominal_voltage = None
|
|
82
83
|
self._phases: List[PowerPhase] = []
|
|
83
84
|
self._features = {"harmonics": 0,
|
|
@@ -301,6 +302,17 @@ class PowerSystem(object):
|
|
|
301
302
|
tmp = {channel.name: channel for channel in calc_type.values()}
|
|
302
303
|
self.output_channels.update(tmp)
|
|
303
304
|
|
|
305
|
+
if self._features["harmonics"]:
|
|
306
|
+
for phase in self._phases:
|
|
307
|
+
if phase._u_channel.freq_response:
|
|
308
|
+
phase._u_fft_corr_array = create_fft_corr_array(self._harm_fft_resample_size//2+1,
|
|
309
|
+
self._samplerate/2,
|
|
310
|
+
phase._u_channel.freq_response)
|
|
311
|
+
if phase._i_channel is not None and phase._i_channel.freq_response:
|
|
312
|
+
phase._i_fft_corr_array = create_fft_corr_array(self._harm_fft_resample_size//2+1,
|
|
313
|
+
self._samplerate/2,
|
|
314
|
+
phase._i_channel.freq_response)
|
|
315
|
+
|
|
304
316
|
if self._features["fluctuation"]:
|
|
305
317
|
for phase in self._phases:
|
|
306
318
|
phase._voltage_fluctuation_processor = pq.VoltageFluctuation(samplerate=self._samplerate,
|
|
@@ -403,18 +415,8 @@ class PowerSystem(object):
|
|
|
403
415
|
for phys_type, output_channel in phase._calc_channels["one_period"]["voltage"].items():
|
|
404
416
|
if phys_type == "trms":
|
|
405
417
|
if self._features["rms_trapz_rule"]:
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
u_trapz_values = phase._u_channel.read_data_by_index(phase_period_start_sidx-add_start_idx_sample, phase_period_stop_sidx+add_stop_idx_sample)
|
|
409
|
-
sample_points = np.arange(len(u_values), dtype=np.float64)
|
|
410
|
-
if add_start_idx_sample:
|
|
411
|
-
u_trapz_values[0] = (u_trapz_values[1] - u_trapz_values[0])*(1+self._last_zc_frac) + u_trapz_values[0]
|
|
412
|
-
sample_points = np.insert(sample_points,0,self._last_zc_frac)
|
|
413
|
-
if add_stop_idx_sample:
|
|
414
|
-
u_trapz_values[-1] = (u_trapz_values[-1] - u_trapz_values[-2])*self._last_zc_frac + u_trapz_values[-2]
|
|
415
|
-
sample_points = np.insert(sample_points,len(sample_points),sample_points[-1]+actual_zc_frac)
|
|
416
|
-
integral = np.trapezoid(np.power(u_trapz_values,2), sample_points)
|
|
417
|
-
u_rms = np.sqrt(integral * frequency / self._samplerate)
|
|
418
|
+
u_trapz_values = phase._u_channel.read_data_by_index(phase_period_start_sidx-1, phase_period_stop_sidx+1)
|
|
419
|
+
u_rms = calc_rms_trapz(u_trapz_values, self._last_zc_frac, actual_zc_frac, frequency, self._samplerate)
|
|
418
420
|
else:
|
|
419
421
|
u_rms = np.sqrt(np.mean(np.power(u_values, 2)))
|
|
420
422
|
output_channel.put_data_single(phase_period_stop_sidx, u_rms)
|
|
@@ -477,7 +479,9 @@ class PowerSystem(object):
|
|
|
477
479
|
u_values = phase._u_channel.read_data_by_index(start_sidx, stop_sidx)
|
|
478
480
|
u_rms = np.sqrt(np.mean(np.power(u_values, 2)))
|
|
479
481
|
if self._features["harmonics"]:
|
|
480
|
-
data_fft_U = pq.resample_and_fft(u_values)
|
|
482
|
+
data_fft_U = pq.resample_and_fft(u_values, self._harm_fft_resample_size)
|
|
483
|
+
if phase._u_fft_corr_array is not None:
|
|
484
|
+
data_fft_U *= phase._u_fft_corr_array
|
|
481
485
|
u_h_mag, u_h_phi = pq.calc_harmonics(data_fft_U, self.nper, self._features["harmonics"])
|
|
482
486
|
u_ih_mag = pq.calc_interharmonics(data_fft_U, self.nper, self._features["harmonics"])
|
|
483
487
|
if phase._number == 1: # use phase 1 angle as reference
|
|
@@ -729,6 +733,8 @@ class PowerPhase(object):
|
|
|
729
733
|
self._calc_channels = {}
|
|
730
734
|
self._voltage_fluctuation_processor: pq.VoltageFluctuation = None
|
|
731
735
|
self._mains_signaling_tracer: pq.MainsSignalingVoltageTracer = None
|
|
736
|
+
self._u_fft_corr_array = None
|
|
737
|
+
self._i_fft_corr_array = None
|
|
732
738
|
|
|
733
739
|
def update_calc_channels(self, features: dict):
|
|
734
740
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pqopen-lib
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: A power quality processing library for calculating parameters from waveform data
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaqOpen/pqopen-lib
|
|
6
6
|
Project-URL: Issues, https://github.com/DaqOpen/pqopen-lib/issues
|
|
@@ -10,7 +10,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.11
|
|
13
|
-
Requires-Dist: daqopen-lib
|
|
13
|
+
Requires-Dist: daqopen-lib>=0.7.4
|
|
14
14
|
Requires-Dist: numpy
|
|
15
15
|
Requires-Dist: scipy
|
|
16
16
|
Provides-Extra: numba
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pqopen/__init__.py,sha256=sMVEOm5j6AZYnnEox5PHOUyZlL5TJjpsNMm5ATLx6ec,329
|
|
2
|
+
pqopen/auxcalc.py,sha256=P11Nu9pgJRoPYZDjLk-mXI6Ha02LoTH5bS9FdFTvC9M,2733
|
|
3
|
+
pqopen/eventdetector.py,sha256=NKZU7GbeorZdkYu3ET4lhMaeynw70GhIGO2p1xH4aTA,11962
|
|
4
|
+
pqopen/helper.py,sha256=0msrm6i1v8jj2Z5X8F7wDEW0KD5i91RBNZwPJC05YrA,2533
|
|
5
|
+
pqopen/powerquality.py,sha256=dRVCedWa1QJKHgdiYoIIdvhH_p40cwpgeUePO5u1j28,15953
|
|
6
|
+
pqopen/powersystem.py,sha256=R9nOIv5Qpuc1JPDzP67NGP9LNqB74unpY85yS1CuWPg,48921
|
|
7
|
+
pqopen/storagecontroller.py,sha256=AhVaPgIh4CBKKMJm9Ja0dOjVd4dLppWprxeeg3vrmAk,31266
|
|
8
|
+
pqopen/zcd.py,sha256=jN4jkGgFNBWQWRebefLc1GjUKm1xdYQXav_ajYfkuo4,6054
|
|
9
|
+
pqopen_lib-0.8.2.dist-info/METADATA,sha256=tdCcqLVphJ4slcPgOe9gofVITNC2k4qNMnPwuViNCAE,4787
|
|
10
|
+
pqopen_lib-0.8.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
+
pqopen_lib-0.8.2.dist-info/licenses/LICENSE,sha256=yhYwu9dioytbAvNQa0UBwaBVcALqiOoBViEs4HLW6aU,1064
|
|
12
|
+
pqopen_lib-0.8.2.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
pqopen/__init__.py,sha256=sMVEOm5j6AZYnnEox5PHOUyZlL5TJjpsNMm5ATLx6ec,329
|
|
2
|
-
pqopen/eventdetector.py,sha256=NKZU7GbeorZdkYu3ET4lhMaeynw70GhIGO2p1xH4aTA,11962
|
|
3
|
-
pqopen/goertzel.py,sha256=JhehuEj-xNnwQEh4Ti8dXiGvfDmxbdEoaxIsFswZNcM,2008
|
|
4
|
-
pqopen/helper.py,sha256=bM_wDck5OfeEy96U_2FjCwZuLZRPYyKVeiYD3-vzP6M,1761
|
|
5
|
-
pqopen/powerquality.py,sha256=Qwyaj7BQqQPRivei140mv-Leh6u9uIQzViKLOY7bHyw,17877
|
|
6
|
-
pqopen/powersystem.py,sha256=5gZcZem_mNRqezjC67qlCePht6L8CraPtQNsjPgP7C4,48581
|
|
7
|
-
pqopen/storagecontroller.py,sha256=AhVaPgIh4CBKKMJm9Ja0dOjVd4dLppWprxeeg3vrmAk,31266
|
|
8
|
-
pqopen/zcd.py,sha256=jN4jkGgFNBWQWRebefLc1GjUKm1xdYQXav_ajYfkuo4,6054
|
|
9
|
-
pqopen_lib-0.8.1.dist-info/METADATA,sha256=XSvdYAXMzrjm_FH1qEB5QHoYfsBk3TDldRj-pqbuEGM,4780
|
|
10
|
-
pqopen_lib-0.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
pqopen_lib-0.8.1.dist-info/licenses/LICENSE,sha256=yhYwu9dioytbAvNQa0UBwaBVcALqiOoBViEs4HLW6aU,1064
|
|
12
|
-
pqopen_lib-0.8.1.dist-info/RECORD,,
|
|
File without changes
|