BlockLayout2D.to_dense: require floats when the layout has holes
Holes are filled with NaN, which bool/str silently mangled and integers rejected with a conversion error. Sparse layouts now raise a clear ValueError for non-floating arrays; full layouts keep any dtype. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
aecee19c19
commit
cc64abcafe
@@ -319,14 +319,18 @@ class BlockLayout2D:
|
|||||||
return blocked
|
return blocked
|
||||||
|
|
||||||
def to_dense(self, blocked):
|
def to_dense(self, blocked):
|
||||||
"""Unpack owner cores only; halo contents are ignored, holes are NaN."""
|
"""Unpack owner cores only; halo contents are ignored, holes are NaN.
|
||||||
|
|
||||||
|
Full layouts accept any dtype; layouts with holes require floats.
|
||||||
|
"""
|
||||||
blocked = self._check_blocked(blocked)
|
blocked = self._check_blocked(blocked)
|
||||||
flat = blocked.reshape(self.nblocks, NQ, -1)
|
flat = blocked.reshape(self.nblocks, NQ, -1)
|
||||||
owned = self.owned.ravel()
|
owned = self.owned.ravel()
|
||||||
|
if not owned.all() and not np.issubdtype(blocked.dtype, np.floating):
|
||||||
|
raise ValueError("a layout with holes unpacks floating arrays only (holes are NaN)")
|
||||||
ob, oc = np.divmod(self.owner.ravel()[owned].astype(np.intp), flat.shape[2])
|
ob, oc = np.divmod(self.owner.ravel()[owned].astype(np.intp), flat.shape[2])
|
||||||
dense = np.empty((NQ, owned.size), dtype=blocked.dtype)
|
dense = np.empty((NQ, owned.size), dtype=blocked.dtype)
|
||||||
if not owned.all(): # full layouts keep any dtype; holes need a float NaN
|
dense[:, ~owned] = np.nan
|
||||||
dense[:, ~owned] = np.nan
|
|
||||||
dense[:, owned] = flat[ob, :, oc].T
|
dense[:, owned] = flat[ob, :, oc].T
|
||||||
return dense.reshape(NQ, *self.shape)
|
return dense.reshape(NQ, *self.shape)
|
||||||
|
|
||||||
|
|||||||
@@ -422,6 +422,18 @@ class SparseBlockLayoutTests(unittest.TestCase):
|
|||||||
self.assertEqual(layout.block_cell(0, 0, 0), address)
|
self.assertEqual(layout.block_cell(0, 0, 0), address)
|
||||||
self.assertEqual(address, layout.owner[0, 0])
|
self.assertEqual(address, layout.owner[0, 0])
|
||||||
|
|
||||||
|
def test_to_dense_dtype_contract(self):
|
||||||
|
full = BlockLayout2D.build(6, 8, 3, 4)
|
||||||
|
blocked = np.arange(np.prod(full.blocked_shape), dtype=np.int64).reshape(full.blocked_shape)
|
||||||
|
dense = full.to_dense(blocked) # legacy: full layouts keep any dtype
|
||||||
|
self.assertEqual(dense.dtype, np.int64)
|
||||||
|
np.testing.assert_array_equal(dense, full.to_dense(blocked.astype(np.float64)))
|
||||||
|
sparse = BlockLayout2D.build(6, 8, 3, 4, origins=[(0, 0), (3, 4)])
|
||||||
|
for dtype in (np.int64, np.uint16, np.bool_):
|
||||||
|
with self.subTest(dtype=dtype), self.assertRaises(ValueError):
|
||||||
|
sparse.to_dense(np.zeros(sparse.blocked_shape, dtype=dtype))
|
||||||
|
self.assertEqual(sparse.to_dense(np.zeros(sparse.blocked_shape, np.float32)).dtype, np.float32)
|
||||||
|
|
||||||
def test_invalid_origins_rejected(self):
|
def test_invalid_origins_rejected(self):
|
||||||
for origins in ([(0, 4), (0, 0)], # not row-major
|
for origins in ([(0, 4), (0, 0)], # not row-major
|
||||||
[(3, 0), (0, 4)], # not row-major across rows
|
[(3, 0), (0, 4)], # not row-major across rows
|
||||||
|
|||||||
Reference in New Issue
Block a user