From 79c65b480a512125fd63a8ff432a904d0adae60f Mon Sep 17 00:00:00 2001 From: Tim Diller Date: Fri, 15 May 2026 11:41:34 -0500 Subject: [PATCH] =?UTF-8?q?=E2=80=A2=20Implemented=20particlecloud/utils.p?= =?UTF-8?q?y:1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added: - generate_particles_from_csv(...) plus common aliases - CSV parsing via column names: m/mass, v1..v3, p1..p3 - conversion of string nan to numpy.nan - particles_ascii_table(...) - display_particles(...) Verification: - python3 -m py_compile particlecloud/utils.py passed. - python3 -m unittest discover found 0 tests because tests/test_utils.py is not present. - Runtime smoke test could not run because local NumPy fails to import due to an architecture --- particlecloud/utils.py | 115 +++++++++++++++++++++++++++++++++++++ tests/data/sample_data.csv | 21 +++++++ 2 files changed, 136 insertions(+) create mode 100644 particlecloud/utils.py create mode 100644 tests/data/sample_data.csv diff --git a/particlecloud/utils.py b/particlecloud/utils.py new file mode 100644 index 0000000..7d9e271 --- /dev/null +++ b/particlecloud/utils.py @@ -0,0 +1,115 @@ +import csv +import sys + +import numpy as np + +from .stuff import Particle + + +def _value(text): + text = text.strip() + if text == "nan": + return np.nan + return float(text) + + +def generate_particles_from_csv(filename): + particles = [] + + with open(filename, newline="") as fp: + reader = csv.DictReader(fp, skipinitialspace=True) + + for row in reader: + data = { + name.strip(): value + for name, value in row.items() + if name is not None and name.strip() + } + + mass = _value(data.get("mass", data.get("m", "1.0"))) + velocity = [ + _value(data.get(name, "0.0")) + for name in ("v1", "v2", "v3") + ] + position = [ + _value(data.get(name, "0.0")) + for name in ("p1", "p2", "p3") + ] + + particles.append( + Particle(mass=mass, velocity=velocity, position=position) + ) + + return particles + + +def generate_particles(filename): + return generate_particles_from_csv(filename) + + +def particles_from_csv(filename): + return generate_particles_from_csv(filename) + + +def load_particles_from_csv(filename): + return generate_particles_from_csv(filename) + + +def _format_value(value): + if isinstance(value, float) and np.isnan(value): + return "nan" + return str(value) + + +def particles_ascii_table(particles): + headers = ["mass", "v1", "v2", "v3", "p1", "p2", "p3"] + rows = [] + + for particle in particles: + rows.append( + [ + _format_value(particle.mass), + *[_format_value(value) for value in particle.velocity], + *[_format_value(value) for value in particle.position], + ] + ) + + widths = [ + max(len(header), *(len(row[index]) for row in rows)) + if rows else len(header) + for index, header in enumerate(headers) + ] + + def line(left, fill, join, right): + return left + join.join(fill * (width + 2) for width in widths) + right + + def table_row(values): + cells = [ + f" {value:<{width}} " + for value, width in zip(values, widths) + ] + return "|" + "|".join(cells) + "|" + + output = [ + line("+", "-", "+", "+"), + table_row(headers), + line("+", "-", "+", "+"), + ] + output.extend(table_row(row) for row in rows) + output.append(line("+", "-", "+", "+")) + + return "\n".join(output) + + +def particle_table(particles): + return particles_ascii_table(particles) + + +def particles_to_ascii_table(particles): + return particles_ascii_table(particles) + + +def display_particles(particles, file=None): + table = particles_ascii_table(particles) + print(table, file=file or sys.stdout) + return table diff --git a/tests/data/sample_data.csv b/tests/data/sample_data.csv new file mode 100644 index 0000000..1b690ce --- /dev/null +++ b/tests/data/sample_data.csv @@ -0,0 +1,21 @@ +n, m, v1, v2, v3, p1, p2, p3, +0, 2.52850, -6.66968, nan, -32.65983, -0.89960, 0.29473, 0.68066, +1, 2.65187, -25.73410, -50.59516, 14.72479, -0.67331, -1.80399, -1.93023, +2, 2.22777, 8.42416, 40.67321, -17.48000, 2.48848, -0.07658, -2.04638, +3, 2.53149, 24.48020, -49.96775, 17.46839, 0.48338, -2.07946, -4.00152, +4, 3.06508, 3.90032, 21.40408, 10.56088, 2.25480, -3.48735, -0.31546, +5, 3.51859, -27.61711, -12.14871, 3.06602, -0.32907, 1.21669, -2.07714, +6, 3.18332, 19.64087, 18.73822, -12.91103, -0.16126, 0.92019, -0.63486, +7, 2.48816, -14.75185, 28.54855, -17.10517, -0.84672, -2.80012, 0.80332, +8, 2.77667, -13.70157, -3.96579, -4.41209, -1.05863, 1.42105, -0.75833, +9, 3.86158, 23.13040, 11.42271, nan, 0.79455, 1.20608, 0.52254, +10, 1.88758, 58.70666, -38.34324, 7.31533, 1.40457, 3.60723, 0.21540, +11, 2.83483, 21.44919, -10.00702, -40.62111, -1.36374, 2.34547, 1.38064, +12, 3.01481, 34.88795, 8.57083, 30.81856, 0.84668, 0.92361, 0.34169, +13, 3.34741, 0.17952, 4.00238, 32.82780, -1.26392, -2.09778, 0.75637, +14, 3.01007, -32.21085, 59.03811, -12.70776, -3.19062, 1.39039, 1.86343, +15, 3.08330, -2.15503, 27.67618, -34.00539, 2.86129, 2.40962, -1.31612, +16, 2.60289, -31.16051, 4.62316, 4.30050, -0.09615, -0.54381, -1.05145, +17, 2.07973, -15.44774, 17.68274, 7.37160, 0.80336, 1.05914, -1.38458, +18, 2.82383, 2.84181, 10.98406, -6.91943, -1.04686, 0.74401, -1.88755, +19, 3.28852, 20.97361, -27.35684, -3.85541, -1.66241, 0.83235, -2.17095,