Public Access
normalize list/array handling in data classes
Establish one invariant across the Drawable hierarchy: x is always a numpy ndarray, y is always a list of per-channel ndarrays, and empty data is the same types with zero length. - Drawable: x starts as empty ndarray; min/max helpers return 0 on empty data instead of raising; drop contradictory isinstance(self.y, list) branches - ADC_Result/Accumulation: empty constructors build ndarray x; full constructors coerce x/y/y_square with numpy.asarray; Accumulation get_ydata/get_yerr return empty ndarrays and no longer crash on empty statistics-less accumulations - MeasurementResult: fix get_lineplotdata to return the accumulated means (previously the never-populated self.y, so the GUI line plot could never draw); remove dead float/int branches in CSV and HDF writers - Error_Result: get_xdata/get_ydata return ndarrays Add unittest coverage for the invariant, empty-state accessors, constructor coercion, line plot data, CSV output and HDF round-trip. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
35d040426b
commit
947c4c7f8d
10 files changed
+245
-58
No files matched your search
@@ -316,5 +316,31 @@ class TestADCResult(unittest.TestCase):
|
||||
self.assertRaises(ValueError, adc1.__rfloordiv__, adc2)
|
||||
self.assertRaises(ValueError, adc1.__pow__, adc2)
|
||||
|
||||
def test_empty_state_types(self):
|
||||
"""Empty ADC_Result must hold an ndarray x and a list-of-ndarray y."""
|
||||
adc = ADC_Result()
|
||||
self.assertIsInstance(adc.x, np.ndarray)
|
||||
self.assertIsInstance(adc.y, list)
|
||||
self.assertEqual(adc.x.size, 0)
|
||||
self.assertEqual(adc.get_xmin(), 0)
|
||||
self.assertEqual(adc.get_xmax(), 0)
|
||||
self.assertEqual(adc.get_xminpos(), 0)
|
||||
self.assertEqual(adc.get_ymin(), 0)
|
||||
self.assertEqual(adc.get_ymax(), 0)
|
||||
self.assertEqual(adc.get_yminpos(), 0)
|
||||
|
||||
def test_constructor_coerces_lists(self):
|
||||
"""Plain lists passed to the constructor must become numpy arrays."""
|
||||
x = [0.0, 1.0, 2.0]
|
||||
y = [[10, 20, 30], [15, 25, 35]]
|
||||
index = [(0, 2)]
|
||||
adc = ADC_Result(x, y, index, 1000.0, {"key": "value"}, 1, datetime(2023, 1, 1))
|
||||
self.assertIsInstance(adc.x, np.ndarray)
|
||||
np.testing.assert_array_equal(adc.x, [0.0, 1.0, 2.0])
|
||||
for ch in adc.y:
|
||||
self.assertIsInstance(ch, np.ndarray)
|
||||
np.testing.assert_array_equal(adc.y[0], [10, 20, 30])
|
||||
np.testing.assert_array_equal(adc.y[1], [15, 25, 35])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -523,5 +523,42 @@ class TestAccumulation(unittest.TestCase):
|
||||
self.assertTrue(accu.lock.acquire(timeout=1), "lock leaked after ValueError in write_to_tecmag")
|
||||
accu.lock.release()
|
||||
|
||||
def test_empty_state_accessors(self):
|
||||
"""Empty Accumulation accessors must return ndarrays, min/max helpers 0."""
|
||||
accu = Accumulation()
|
||||
self.assertIsInstance(accu.x, np.ndarray)
|
||||
self.assertEqual(accu.x.size, 0)
|
||||
self.assertIsInstance(accu.get_ydata(), np.ndarray)
|
||||
self.assertEqual(accu.get_ydata().size, 0)
|
||||
self.assertIsInstance(accu.get_yerr(0), np.ndarray)
|
||||
self.assertEqual(accu.get_yerr(0).size, 0)
|
||||
self.assertEqual(accu.get_xmin(), 0)
|
||||
self.assertEqual(accu.get_xmax(), 0)
|
||||
self.assertEqual(accu.get_ymin(), 0)
|
||||
self.assertEqual(accu.get_ymax(), 0)
|
||||
|
||||
def test_get_yerr_without_statistics_on_empty(self):
|
||||
"""get_yerr on an empty accumulation without statistics must not crash."""
|
||||
accu = Accumulation(error=False)
|
||||
err = accu.get_yerr(0)
|
||||
self.assertIsInstance(err, np.ndarray)
|
||||
self.assertEqual(err.size, 0)
|
||||
|
||||
def test_constructor_coerces_lists(self):
|
||||
"""Plain lists passed to the constructor must become numpy arrays."""
|
||||
x = [0.0, 1.0, 2.0]
|
||||
y = [[10, 20, 30], [15, 25, 35]]
|
||||
y_2 = [[100.0, 400.0, 900.0], [225.0, 625.0, 1225.0]]
|
||||
accu = Accumulation(x=x, y=y, y_2=y_2, n=1,
|
||||
index=[(0, 2)], sampl_freq=1000.0, error=True)
|
||||
self.assertIsInstance(accu.x, np.ndarray)
|
||||
np.testing.assert_array_equal(accu.x, [0.0, 1.0, 2.0])
|
||||
for ch in accu.y:
|
||||
self.assertIsInstance(ch, np.ndarray)
|
||||
for ch in accu.y_square:
|
||||
self.assertIsInstance(ch, np.ndarray)
|
||||
np.testing.assert_array_equal(accu.y[0], [10, 20, 30])
|
||||
np.testing.assert_array_equal(accu.y_square[0], [100.0, 400.0, 900.0])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,54 @@
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from damaris.data.Drawable import Drawable
|
||||
|
||||
|
||||
class TestDrawable(unittest.TestCase):
|
||||
def test_initial_types(self):
|
||||
"""x must start as an empty ndarray, y as a list of arrays."""
|
||||
d = Drawable()
|
||||
self.assertIsInstance(d.x, np.ndarray)
|
||||
self.assertEqual(d.x.size, 0)
|
||||
self.assertIsInstance(d.y, list)
|
||||
|
||||
def test_empty_min_max_helpers(self):
|
||||
"""min/max helpers must return 0 instead of raising on empty data."""
|
||||
d = Drawable()
|
||||
self.assertEqual(d.get_xmin(), 0)
|
||||
self.assertEqual(d.get_xmax(), 0)
|
||||
self.assertEqual(d.get_xminpos(), 0)
|
||||
self.assertEqual(d.get_ymin(), 0)
|
||||
self.assertEqual(d.get_ymax(), 0)
|
||||
self.assertEqual(d.get_yminpos(), 0)
|
||||
|
||||
def test_min_max_helpers_with_data(self):
|
||||
d = Drawable()
|
||||
d.x = np.array([-1.0, 0.0, 2.0, 5.0])
|
||||
d.y = [np.array([1.0, -2.0, 3.0]), np.array([0.5, 4.0, -0.5])]
|
||||
self.assertEqual(d.get_xmin(), -1.0)
|
||||
self.assertEqual(d.get_xmax(), 5.0)
|
||||
self.assertEqual(d.get_xminpos(), 2.0)
|
||||
self.assertEqual(d.get_ymin(), -2.0)
|
||||
self.assertEqual(d.get_ymax(), 4.0)
|
||||
self.assertEqual(d.get_yminpos(), 0.5)
|
||||
|
||||
def test_yminpos_without_positive_values(self):
|
||||
d = Drawable()
|
||||
d.x = np.array([1.0])
|
||||
d.y = [np.array([-1.0, -2.0])]
|
||||
self.assertEqual(d.get_yminpos(), 0)
|
||||
|
||||
def test_set_xdata_set_ydata(self):
|
||||
d = Drawable()
|
||||
d.x = np.zeros(3)
|
||||
d.y = [np.zeros(3)]
|
||||
d.set_xdata(1, 5.0)
|
||||
d.set_ydata(0, 1, 7.0)
|
||||
self.assertEqual(d.get_xdata()[1], 5.0)
|
||||
self.assertEqual(d.get_ydata(0)[1], 7.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,23 @@
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from damaris.data.Error_Result import Error_Result
|
||||
|
||||
|
||||
class TestErrorResult(unittest.TestCase):
|
||||
def test_plotdata_are_ndarrays(self):
|
||||
"""get_xdata/get_ydata must return numpy arrays like every Drawable."""
|
||||
er = Error_Result("core failed")
|
||||
self.assertIsInstance(er.get_xdata(), np.ndarray)
|
||||
self.assertIsInstance(er.get_ydata(), np.ndarray)
|
||||
np.testing.assert_array_equal(er.get_xdata(), [0.0])
|
||||
np.testing.assert_array_equal(er.get_ydata(), [0.0])
|
||||
|
||||
def test_add_returns_self(self):
|
||||
er = Error_Result("msg")
|
||||
self.assertIs(er + 1, er)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,8 +1,12 @@
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import tables
|
||||
|
||||
from damaris.data.MeasurementResult import MeasurementResult
|
||||
from damaris.data.MeasurementResult import MeasurementResult, read_from_hdf
|
||||
|
||||
|
||||
class TestMeasurementResult(unittest.TestCase):
|
||||
@@ -30,6 +34,53 @@ class TestMeasurementResult(unittest.TestCase):
|
||||
mr = MeasurementResult("q")
|
||||
self.assertRaises(Exception, mr.__add__, 1)
|
||||
|
||||
def test_get_lineplotdata_returns_means(self):
|
||||
"""get_lineplotdata must return the accumulated means, not self.y."""
|
||||
mr = MeasurementResult("test_quantity")
|
||||
for i in range(3):
|
||||
mr[float(i)] = float(i * i)
|
||||
k, v = mr.get_lineplotdata()
|
||||
self.assertIsInstance(k, np.ndarray)
|
||||
self.assertIsInstance(v, np.ndarray)
|
||||
self.assertEqual(k.shape, v.shape)
|
||||
np.testing.assert_array_equal(v, mr.get_ydata())
|
||||
np.testing.assert_array_equal(v, [0.0, 1.0, 4.0])
|
||||
|
||||
def test_get_lineplotdata_empty(self):
|
||||
"""get_lineplotdata of an empty result must return empty arrays."""
|
||||
mr = MeasurementResult("q")
|
||||
k, v = mr.get_lineplotdata()
|
||||
self.assertEqual(k.shape[0], 0)
|
||||
self.assertEqual(v.shape[0], 0)
|
||||
|
||||
def test_write_to_csv(self):
|
||||
mr = MeasurementResult("q")
|
||||
mr[1.0] = 5.0
|
||||
mr[2.0] = 7.0
|
||||
buf = io.StringIO()
|
||||
mr.write_to_csv(buf)
|
||||
lines = buf.getvalue().splitlines()
|
||||
self.assertEqual(lines[0], "# quantity:q")
|
||||
self.assertEqual(lines[1], "# x y ysigma n")
|
||||
self.assertEqual(len(lines), 4)
|
||||
self.assertEqual(lines[2].split()[3], "1")
|
||||
self.assertEqual(lines[3].split()[3], "1")
|
||||
|
||||
def test_write_to_hdf_roundtrip(self):
|
||||
mr = MeasurementResult("q")
|
||||
mr[1.0] = 5.0
|
||||
mr[2.0] = 7.0
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
path = os.path.join(tmpdir, "test.h5")
|
||||
with tables.open_file(path, mode="w") as h5file:
|
||||
mr.write_to_hdf(h5file, "/", "mr", "test result")
|
||||
with tables.open_file(path, mode="r") as h5file:
|
||||
mr2 = read_from_hdf(h5file.get_node("/mr"))
|
||||
self.assertIsNotNone(mr2)
|
||||
self.assertEqual(mr2.quantity_name, "q")
|
||||
np.testing.assert_array_equal(mr2.get_xdata(), mr.get_xdata())
|
||||
np.testing.assert_array_equal(mr2.get_ydata(), mr.get_ydata())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user