These were not too critical errors as we normal only do + and -
Build Debian Packages / build (trixie, debian13) (push) Successful in 13m14s
Build Debian Packages / build (bookworm, debian12) (push) Successful in 13m23s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled

operations in the experiments.

- Identified and fixed multiple logic and syntax errors in the `Accumulation` class similar to those recently found in `ADC_Result`.
- Implemented missing arithmetic operators (`*`, `/`, `//`) and their inplace/reverse counterparts for the `Accumulation` class.
- Verified that all arithmetic operations correctly maintain statistical data (sum of squares) where mathematically possible.
This commit is contained in:
2026-07-04 16:26:56 +02:00
parent 4fffcc8e3b
commit efb15606c6
2 changed files with 158 additions and 20 deletions
+144 -7
View File
@@ -617,15 +617,18 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
else: else:
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error) r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error)
r.job_id = self.job_id r.job_id = self.job_id
r.job_ids = self.job_ids.copy()
r.time_period = self.time_period[:] if self.time_period is not None else None
if self.common_descriptions is not None:
r.common_descriptions = self.common_descriptions.copy()
self.lock.release() self.lock.release()
return r return r
# ADC_Result # ADC_Result
elif isinstance(other, ADC_Result): elif isinstance(other, ADC_Result):
# Other empty (return) # Other empty (return copy of self)
# todo: this is seems to be bugy!!!! (Achim)
if not other.contains_data(): if not other.contains_data():
return return self + 0
# Self empty (copy ADC_Result) # Self empty (copy ADC_Result)
if not self.contains_data(): if not self.contains_data():
tmp_y = [] tmp_y = []
@@ -691,9 +694,9 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
# Accumulation # Accumulation
elif isinstance(other, Accumulation): elif isinstance(other, Accumulation):
# Other empty (return) # Other empty (return copy of self)
if not other.contains_data(): if not other.contains_data():
return return self + 0
# Self empty (copy) # Self empty (copy)
if not self.contains_data(): if not self.contains_data():
@@ -776,7 +779,141 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
def __rsub__(self, other): def __rsub__(self, other):
"""Redefining other - self""" """Redefining other - self"""
return self.__neg__(self.__add__(-other)) return (-self) + other
def __mul__(self, other):
"""Redefining self * other (scalar)"""
if isinstance(other, (int, float)):
if not self.contains_data():
raise ValueError("Accumulation: You cant multiply an empty accumulation")
self.lock.acquire()
tmp_y = []
tmp_ysquare = []
for i in range(self.get_number_of_channels()):
tmp_y.append(self.y[i] * other)
if self.uses_statistics():
tmp_ysquare.append(self.y_square[i] * (other**2))
if self.uses_statistics():
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, y_2 = tmp_ysquare, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error)
else:
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error)
r.job_id = self.job_id
r.job_ids = self.job_ids.copy()
r.time_period = self.time_period[:] if self.time_period is not None else None
if self.common_descriptions is not None:
r.common_descriptions = self.common_descriptions.copy()
self.lock.release()
return r
else:
raise ValueError(f"ValueError: Cannot multiply \"{other.__class__}\" to Accumulation!")
def __rmul__(self, other):
"""Redefining other (scalar) * self"""
return self.__mul__(other)
def __imul__(self, other):
"""Redefining self *= other (scalar)"""
if isinstance(other, (int, float)):
if not self.contains_data():
raise ValueError("Accumulation: You cant multiply an empty accumulation")
self.lock.acquire()
for i in range(self.get_number_of_channels()):
self.y[i] *= other
if self.uses_statistics():
self.y_square[i] *= (other**2)
self.lock.release()
return self
else:
raise ValueError(f"ValueError: Cannot multiply \"{other.__class__}\" to Accumulation!")
def __truediv__(self, other):
"""Redefining self / other (scalar)"""
if isinstance(other, (int, float)):
if not self.contains_data():
raise ValueError("Accumulation: You cant divide an empty accumulation")
self.lock.acquire()
tmp_y = []
tmp_ysquare = []
for i in range(self.get_number_of_channels()):
tmp_y.append(self.y[i] / other)
if self.uses_statistics():
tmp_ysquare.append(self.y_square[i] / (other**2))
if self.uses_statistics():
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, y_2 = tmp_ysquare, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error)
else:
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error)
r.job_id = self.job_id
r.job_ids = self.job_ids.copy()
r.time_period = self.time_period[:] if self.time_period is not None else None
if self.common_descriptions is not None:
r.common_descriptions = self.common_descriptions.copy()
self.lock.release()
return r
else:
raise ValueError(f"ValueError: Cannot divide \"{other.__class__}\" to Accumulation!")
def __itruediv__(self, other):
"""Redefining self /= other (scalar)"""
if isinstance(other, (int, float)):
if not self.contains_data():
raise ValueError("Accumulation: You cant divide an empty accumulation")
self.lock.acquire()
for i in range(self.get_number_of_channels()):
self.y[i] /= other
if self.uses_statistics():
self.y_square[i] /= (other**2)
self.lock.release()
return self
else:
raise ValueError(f"ValueError: Cannot divide \"{other.__class__}\" to Accumulation!")
def __floordiv__(self, other):
"""Redefining self // other (scalar)"""
if isinstance(other, float):
raise ValueError("ValueError: Cannot use floor division (//) on floats! Use \"/\" instead of \"//\"! ")
if isinstance(other, int):
if not self.contains_data():
raise ValueError("Accumulation: You cant divide an empty accumulation")
self.lock.acquire()
tmp_y = []
for i in range(self.get_number_of_channels()):
tmp_y.append(self.y[i] // other)
# Note: Statistics (y_square) cannot be easily maintained with floor division
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = False)
r.job_id = self.job_id
r.job_ids = self.job_ids.copy()
r.time_period = self.time_period[:] if self.time_period is not None else None
if self.common_descriptions is not None:
r.common_descriptions = self.common_descriptions.copy()
self.lock.release()
return r
else:
raise ValueError(f"ValueError: Cannot divide \"{other.__class__}\" to Accumulation!")
def __ifloordiv__(self, other):
"""Redefining self //= other (scalar)"""
if isinstance(other, float):
raise ValueError("ValueError: Cannot use floor division (//) on floats! Use \"/\" instead of \"//\"! ")
if isinstance(other, int):
if not self.contains_data():
raise ValueError("Accumulation: You cant divide an empty accumulation")
self.lock.acquire()
for i in range(self.get_number_of_channels()):
self.y[i] //= other
if self.uses_statistics():
# Invalidate statistics
self.y_square = []
self.use_error = False
self.lock.release()
return self
else:
raise ValueError(f"ValueError: Cannot divide \"{other.__class__}\" to Accumulation!")
def __iadd__(self, other): def __iadd__(self, other):
@@ -863,7 +1000,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
elif isinstance(other, Accumulation): elif isinstance(other, Accumulation):
# Other empty (return) # Other empty (return)
if not other.contains_data(): if not other.contains_data():
return return self
# Self empty (copy) # Self empty (copy)
if not self.contains_data(): if not self.contains_data():
+14 -13
View File
@@ -233,7 +233,6 @@ class TestAccumulation(unittest.TestCase):
for i in range(adc.get_nChannels()): for i in range(adc.get_nChannels()):
np.testing.assert_array_equal(accu.y[i], y[i] - 10 - 10.) np.testing.assert_array_equal(accu.y[i], y[i] - 10 - 10.)
@unittest.skip("Not implmented")
def test_operator_mul_scalar(self): def test_operator_mul_scalar(self):
""" """
Test the functionality of __mul and __rmul__ Test the functionality of __mul and __rmul__
@@ -254,33 +253,35 @@ class TestAccumulation(unittest.TestCase):
for i in range(adc.get_nChannels()): for i in range(adc.get_nChannels()):
np.testing.assert_array_equal(accu.y[i], y[i] * 10 * 10.0) np.testing.assert_array_equal(accu.y[i], y[i] * 10 * 10.0)
@unittest.skip("Not implmented")
def test_operator_truediv_scalar(self): def test_operator_truediv_scalar(self):
""" """
Test the functionality of __div__ and __rdiv__ Test the functionality of __truediv__ and __itruediv__
""" """
adc = self.create_adc_result() adc = self.create_adc_result()
y = adc.y y = adc.y
accu = Accumulation()
accu += adc
for i in range(adc.get_nChannels()): for i in range(adc.get_nChannels()):
np.testing.assert_array_equal(adc.y[i]/10, y[i] / 10, strict=True) np.testing.assert_array_equal(accu.y[i]/10, y[i] / 10)
adc /= 10 accu /= 10
for i in range(adc.get_nChannels()): for i in range(adc.get_nChannels()):
np.testing.assert_array_equal(adc.y[i], y[i] / 10, strict=True) np.testing.assert_array_equal(accu.y[i], y[i] / 10)
@unittest.skip("Not implmented")
def test_operator_floordiv_scalar(self): def test_operator_floordiv_scalar(self):
""" """
Test the functionality of __div__ and __rdiv__ Test the functionality of __floordiv__ and __ifloordiv__
""" """
adc = self.create_adc_result() adc = self.create_adc_result()
y = adc.y y = adc.y
accu = Accumulation()
accu += adc
for i in range(adc.get_nChannels()): for i in range(adc.get_nChannels()):
np.testing.assert_array_equal(adc.y[i]//10, y[i] // 10, strict=True) np.testing.assert_array_equal(accu.y[i]//10, y[i] // 10)
adc //= 10 accu //= 10
for i in range(adc.get_nChannels()): for i in range(adc.get_nChannels()):
np.testing.assert_array_equal(adc.y[i], y[i] // 10, strict=True) np.testing.assert_array_equal(accu.y[i], y[i] // 10)
self.assertRaises(ValueError, adc.__floordiv__, 1.0) self.assertRaises(ValueError, accu.__floordiv__, 1.0)
self.assertRaises(ValueError, adc.__rfloordiv__, 1.0) self.assertRaises(ValueError, accu.__ifloordiv__, 1.0)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()