Skip to content

decompose

Exact-mode channel decomposition (spec sections 3 and 6).

Each term's exact contribution f(x) is reallocated into I (supported), D (under-supported residual) and M (missing-bin routing). D is computed as the residual f - w*f so completeness (C1) holds to float roundoff.

TermChannels dataclass

TermChannels(
    term_idx: int,
    feature_idxs: tuple[int, ...],
    f: ndarray,
    w: ndarray,
    I: ndarray,
    D: ndarray,
    M: ndarray,
)

Per-instance channel values for one term.

aggregate_features

aggregate_features(
    channels: Sequence[TermChannels],
    binned: Sequence[BinnedTerm],
    n_features: int,
    *,
    pair_split: str = "half"
) -> dict[str, np.ndarray]

Aggregate term-level channels to (n_samples, n_features) matrices.

Pair terms split their channels equally between the two participating features (spec 6, pair_split="half"). The oov matrix is the OR over all terms a feature participates in.

Source code in src/triadxai/decompose.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def aggregate_features(
    channels: Sequence[TermChannels],
    binned: Sequence[BinnedTerm],
    n_features: int,
    *,
    pair_split: str = "half",
) -> dict[str, np.ndarray]:
    """Aggregate term-level channels to (n_samples, n_features) matrices.

    Pair terms split their channels equally between the two participating
    features (spec 6, ``pair_split="half"``). The ``oov`` matrix is the OR
    over all terms a feature participates in.
    """
    if pair_split != "half":
        raise ValueError(f"unsupported pair_split {pair_split!r}; v0.1 implements 'half'")
    n_samples = channels[0].f.shape[0]
    out = {key: np.zeros((n_samples, n_features)) for key in ("I", "D", "M")}
    oov = np.zeros((n_samples, n_features), dtype=bool)
    for ch, bt in zip(channels, binned, strict=True):
        share = 1.0 / len(ch.feature_idxs)
        for j in ch.feature_idxs:
            for key in ("I", "D", "M"):
                out[key][:, j] += share * getattr(ch, key)
            oov[:, j] |= bt.oov
    out["oov"] = oov
    return out

decompose_term

decompose_term(
    term: TermData, binned: BinnedTerm, tau2: float
) -> TermChannels

Reallocate one term's exact contributions into I/D/M channels.

Source code in src/triadxai/decompose.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def decompose_term(term: TermData, binned: BinnedTerm, tau2: float) -> TermChannels:
    """Reallocate one term's exact contributions into I/D/M channels."""
    f_hit = term.f[tuple(binned.bin_idx)]
    w = support_weights(term, binned, tau2)
    supported = w * f_hit
    residual = f_hit - supported
    miss = binned.missing
    return TermChannels(
        term_idx=term.term_idx,
        feature_idxs=term.feature_idxs,
        f=f_hit,
        w=w,
        I=np.where(miss, 0.0, supported),
        D=residual,
        M=np.where(miss, supported, 0.0),
    )

support_weights

support_weights(
    term: TermData, binned: BinnedTerm, tau2: float
) -> np.ndarray

Effective support weight w per instance (variance form, spec 3.1).

Empty/unseen bins (zero training mass) get w = 0 regardless of their nominal variance; out-of-range instances are decayed (spec 3.4).

Source code in src/triadxai/decompose.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def support_weights(term: TermData, binned: BinnedTerm, tau2: float) -> np.ndarray:
    """Effective support weight w per instance (variance form, spec 3.1).

    Empty/unseen bins (zero training mass) get w = 0 regardless of their
    nominal variance; out-of-range instances are decayed (spec 3.4).
    """
    idx = tuple(binned.bin_idx)
    v_hit = term.v[idx]
    n_hit = term.n[idx]
    if tau2 <= 0.0:
        w = np.zeros_like(v_hit, dtype=float)
    else:
        w = tau2 / (tau2 + v_hit)
    w = np.where(n_hit <= 0.0, 0.0, w)
    return w * binned.decay