Source code for nlp_shap.masking.codec

"""Packed bitset encoding and stable hashing for coalition masks."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass

import numpy as np
from numpy.typing import NDArray


[docs] @dataclass(frozen=True, slots=True) class PackedMask: """Packed bit representation of one boolean mask.""" words: bytes """Little-endian packed bytes encoding the boolean mask.""" n_bits: int """Original number of bits (mask length) before packing."""
[docs] class MaskCodec: """Encode and decode boolean masks to packed bytes."""
[docs] @staticmethod def normalize(mask: Sequence[bool] | NDArray[np.bool_]) -> NDArray[np.bool_]: """Normalize incoming mask to a 1D boolean array.""" array = np.asarray(mask, dtype=np.bool_) if array.ndim == 0: msg = "mask must contain at least one value" raise ValueError(msg) if array.ndim > 1: if array.shape[0] != 1: msg = "mask must be 1D or have exactly one row" raise ValueError(msg) array = array.reshape(-1) return array
[docs] @staticmethod def pack(mask: Sequence[bool] | NDArray[np.bool_]) -> PackedMask: """Pack a mask into little-endian bytes.""" normalized = MaskCodec.normalize(mask) n_bits = int(normalized.size) packed = np.packbits(normalized.astype(np.uint8), bitorder="little") return PackedMask(words=packed.tobytes(), n_bits=n_bits)
[docs] @staticmethod def unpack(packed: PackedMask) -> tuple[bool, ...]: """Unpack bytes back to a boolean tuple.""" raw = np.frombuffer(packed.words, dtype=np.uint8) bits = np.unpackbits(raw, bitorder="little")[: packed.n_bits] return tuple(bool(value) for value in bits)
[docs] @staticmethod def hash_mask(mask: Sequence[bool] | NDArray[np.bool_]) -> int: """Return a stable hash over packed mask bytes.""" packed = MaskCodec.pack(mask) return hash((packed.words, packed.n_bits))