Files
2026-09-30 10:45:48 +08:00

135 lines
6.3 KiB
Python

"""Pure synthetic checks for stateless pre-first event validation."""
import unittest
import numpy as np
from validate_events import COLUMN_DTYPES, N_GLOBAL, validate_batch
def fixture():
reference = dict(edge_ids=np.array([7, 20, 99], np.int64),
srcglobals=np.array([3, 4, 3], np.int32), dstglobals=np.array([10, 10, 20], np.int32),
weights=np.array([.001, -.002, .003], np.float64),
target_globals=np.array([10, 20, 30], np.int32), target_first=np.array([30, -1, 0], np.int32),
source_event_keys=np.array([3, 4, 4 * N_GLOBAL + 3], np.int64))
# Arbitrary order, repeated edge at a later tick, negative and positive.
values = dict(edge_id=[99, 20, 7, 99], source_neuron_idx=[3, 4, 3, 3],
target_neuron_idx=[20, 10, 10, 20], source_spike_tick=[4, 0, 0, 0],
arrival_tick=[22, 18, 18, 18], weight_sign=[1, -1, 1, 1],
ref_at_arrival=[0] * 4, weight_SI=[.003, -.002, .001, .003], accepted=[True] * 4)
columns = {name: np.array(values[name], dtype) for name, dtype in COLUMN_DTYPES.items()}
return columns, reference
class EventValidationTests(unittest.TestCase):
def test_unordered_rows_real_edges_signed_weights_and_never_first(self):
columns, ref = fixture()
original = {k: v.copy() for k, v in columns.items()}
result = validate_batch(columns, **ref)
self.assertEqual(result['count'], 4)
np.testing.assert_array_equal(result['per_target_count'], [2, 2, 0])
self.assertEqual(result['per_target_count'].dtype, np.int64)
self.assertFalse(result['unique_cause_claim'])
self.assertFalse(result['cross_batch_completeness_checked'])
for key in columns:
np.testing.assert_array_equal(columns[key], original[key])
def test_empty_valid_but_missing_wrong_dtype_or_shape_still_rejected(self):
columns, ref = fixture()
empty = {name: value[:0] for name, value in columns.items()}
result = validate_batch(empty, **ref)
self.assertEqual(result['count'], 0)
np.testing.assert_array_equal(result['per_target_count'], [0, 0, 0])
bad = empty.copy()
bad['arrival_tick'] = np.empty(0, np.int64)
with self.assertRaises(ValueError):
validate_batch(bad, **ref)
bad = empty.copy()
del bad['accepted']
with self.assertRaises(ValueError):
validate_batch(bad, **ref)
bad = empty.copy()
bad['weight_sign'] = np.empty((0, 1), np.int32)
with self.assertRaises(ValueError):
validate_batch(bad, **ref)
def test_every_edge_field_and_weight_bit_is_checked(self):
for field, value in [('edge_id', 8), ('source_neuron_idx', 4), ('target_neuron_idx', 10),
('weight_sign', -1), ('weight_SI', np.nextafter(.003, np.inf))]:
with self.subTest(field=field):
columns, ref = fixture()
columns[field][0] = value
with self.assertRaises(ValueError):
validate_batch(columns, **ref)
columns, ref = fixture()
ref['weights'][2] = 0.
columns['weight_SI'][[0, 3]] = -0.
with self.assertRaisesRegex(ValueError, 'bits mismatch'):
validate_batch(columns, **ref)
columns['weight_SI'][[0, 3]] = 0.
with self.assertRaisesRegex(ValueError, 'zero-weight'):
validate_batch(columns, **ref)
def test_guard_strict_prefirst_and_source_delay(self):
changes = [('accepted', False), ('ref_at_arrival', 1), ('source_spike_tick', -1),
('source_spike_tick', 3), ('arrival_tick', 21)]
for field, value in changes:
with self.subTest(field=field):
columns, ref = fixture()
columns[field][0] = value
with self.assertRaises(ValueError):
validate_batch(columns, **ref)
for first in (22, 21):
columns, ref = fixture()
ref['target_first'][1] = first
with self.assertRaisesRegex(ValueError, 'strictly before'):
validate_batch(columns, **ref)
def test_source_event_membership_and_target_population(self):
columns, ref = fixture()
ref['source_event_keys'] = ref['source_event_keys'][:2]
with self.assertRaisesRegex(ValueError, 'source spike'):
validate_batch(columns, **ref)
columns, ref = fixture()
ref['target_globals'] = np.array([10, 30], np.int32)
ref['target_first'] = np.array([30, 0], np.int32)
with self.assertRaisesRegex(ValueError, 'target global'):
validate_batch(columns, **ref)
def test_duplicate_rows_rejected_without_sort_assumption(self):
columns, ref = fixture()
for value in columns.values():
value[0] = value[3]
with self.assertRaisesRegex(ValueError, 'Duplicate edge event'):
validate_batch(columns, **ref)
def test_invalid_reference_schema_order_duplicate_range_nonfinite(self):
variants = [dict(edge_ids=np.array([7, 20, 99], np.int32)),
dict(edge_ids=np.array([7, 99, 20], np.int64)),
dict(edge_ids=np.array([7, 20, 20], np.int64)),
dict(srcglobals=np.array([3, 4, N_GLOBAL], np.int32)),
dict(weights=np.array([.001, -.002, np.inf])),
dict(target_first=np.array([30, -2, 0], np.int32)),
dict(source_event_keys=np.array([3, 3, 4], np.int64)),
dict(source_event_keys=np.array([4, 3], np.int64)),
dict(source_event_keys=np.array([-1, 3], np.int64))]
for change in variants:
with self.subTest(change=change):
columns, ref = fixture()
with self.assertRaises(ValueError):
validate_batch(columns, **(ref | change))
def test_optional_design_and_local_row_fields_are_not_assumed(self):
columns, ref = fixture()
columns['incoming_edge_row'] = np.array([1000, 2, 1, 1000], np.int64)
columns['target_is_design'] = np.array([True, False, True, False])
self.assertEqual(validate_batch(columns, **ref)['count'], 4)
bad = columns.copy()
bad['accepted'] = columns['accepted'].astype(np.uint8)
with self.assertRaises(ValueError):
validate_batch(bad, **ref)
if __name__ == '__main__':
unittest.main(verbosity=2)