"""CSV storage for the eight one-dimensional terminal-path arrays. Float columns use Python's binary64 round-trip representation. Integer columns are restored as int8 (cause) and int32 (retained_marks). The string nan denotes an unavailable terminal mark on a path that did not terminate at a jump. """ from __future__ import annotations import csv from pathlib import Path import numpy as np DTYPES = { 'cause': np.dtype('int8'), 'time_lower': np.dtype('float64'), 'time_upper': np.dtype('float64'), 'pre_h': np.dtype('float64'), 'terminal_mark': np.dtype('float64'), 'final_v': np.dtype('float64'), 'retained_marks': np.dtype('int32'), 'occupation_left_001': np.dtype('float64'), } def write_paths(path: Path, paths: dict[str, np.ndarray]) -> None: """Write one record per path, preserving the schema and binary64 values.""" if set(paths) != set(DTYPES): raise ValueError('Unexpected path-array fields') n = len(paths['cause']) for name, dtype in DTYPES.items(): if paths[name].shape != (n,) or paths[name].dtype != dtype: raise ValueError(f'Unexpected shape or dtype for {name}') path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) with path.open('w', newline='', encoding='utf-8') as stream: writer = csv.writer(stream, lineterminator='\n') writer.writerow(DTYPES) for i in range(n): writer.writerow([ str(int(paths[name][i])) if dtype.kind == 'i' else repr(float(paths[name][i])) for name, dtype in DTYPES.items() ]) def read_paths(path: Path) -> dict[str, np.ndarray]: """Read CSV fields with explicit dtypes and round-trip float parsing.""" values = {name: [] for name in DTYPES} with Path(path).open(newline='', encoding='utf-8') as stream: reader = csv.reader(stream) if next(reader, None) != list(DTYPES): raise ValueError('Unexpected path CSV header') for row in reader: if len(row) != len(DTYPES): raise ValueError('Unexpected number of fields in path CSV') for (name, dtype), value in zip(DTYPES.items(), row): values[name].append(int(value) if dtype.kind == 'i' else float(value)) return {name: np.asarray(values[name], dtype=dtype) for name, dtype in DTYPES.items()}