uor_matmul_codec/codecs.rs
1//! The tiers (§6.2).
2//!
3//! Instantiations, not special cases. Each is roughly twenty lines, and none of
4//! them contains any arithmetic that the others do not: a tier names a decode,
5//! and the accumulation downstream of it is the same accumulation for every
6//! tier in this file.
7
8use core::marker::PhantomData;
9
10use bytemuck::TransparentWrapper as _;
11use uor_matmul_core::{Alphabet, Bound, FloatElement, IntegerElement, Whole};
12
13use crate::tier::{Codec, Enumerable, IndexStream, TierId};
14
15/// How many codes a `u16`-indexed table can actually be reached with.
16///
17/// A property of the code type, not a limit on the table: a `Grid<70000>` is a
18/// well-formed decode whose entries past this are unreachable, and saying so is
19/// how the enumeration stays honest about its own size.
20const U16_CODES: usize = u16::MAX as usize + 1;
21
22/// The three parameters `Offset` carries without storing: the input bound, the
23/// output bound, and the element type. Invariant in none of them, so the marker
24/// is a function pointer rather than a `PhantomData<T>`.
25type OffsetMarker<BdIn, BdOut, E> = PhantomData<fn() -> (BdIn, BdOut, E)>;
26
27/// The same marker for the width-parameterized constant-table tiers: the
28/// element, the bound, and the code width, none of them stored.
29type TierMarker<E, Bd, K> = PhantomData<fn() -> (E, Bd, K)>;
30
31// ---------------------------------------------------------------------------
32// Identity
33// ---------------------------------------------------------------------------
34
35/// Decoding is a validated copy.
36///
37/// Zero-sized. The validation is the alphabet wrap itself, performed once at
38/// the boundary by `as_alphabet_full` (which cannot fail) or `as_alphabet`
39/// (which reports the observed bound rather than failing), so the code type is
40/// [`Alphabet`] and there is nothing left for the decode to check (§5.2, §6.2).
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub struct Identity;
43
44impl<E: IntegerElement, Bd: Bound> Codec<E, Bd> for Identity {
45 type Code = Alphabet<E, Bd>;
46 const MAX_BLOCK: usize = 1;
47 const TIER: TierId = TierId::Identity;
48
49 fn decode_element(&self, code: Self::Code, _i: usize) -> Alphabet<E, Bd> {
50 code
51 }
52
53 fn decode_seq(&self, codes: &[Self::Code], out: &mut [Alphabet<E, Bd>]) -> usize {
54 out[..codes.len()].copy_from_slice(codes);
55 codes.len()
56 }
57}
58
59// `Identity` does not implement `Enumerable`, deliberately. Its code space is
60// the alphabet itself --- `2^32` values over `i32` --- and its block is one, so
61// a table indexed by it would be both unholdable and pointless: `tabulation_pays`
62// refuses `MAX_BLOCK == 1` on op count anyway. The absence is enforced by the
63// type system rather than by a runtime refusal, which is why the tabulated
64// traversal needs no branch for it (§4).
65
66// ---------------------------------------------------------------------------
67// Grid
68// ---------------------------------------------------------------------------
69
70/// Any lookup codec, of any code width.
71///
72/// The 16-entry i4 grid is `Grid<16>`; a 65536-entry one is `Grid<65536>`. The
73/// library carries no hardcoded table size, and no size is privileged.
74///
75/// The code space of an `N`-entry grid is `Z/N`, so an arbitrary `u16` indexes
76/// it modulo `N`. That is not a clamp and not an error path: it is what
77/// "index into a table of `N` entries" means, and it is why this decode is
78/// total on all `2^16` codes (C6).
79#[derive(Clone, Copy, Debug)]
80pub struct Grid<'a, E: IntegerElement, Bd: Bound, const N: usize> {
81 table: &'a [Alphabet<E, Bd>; N],
82}
83
84impl<'a, E: IntegerElement, Bd: Bound, const N: usize> Grid<'a, E, Bd, N> {
85 /// Borrow a decode table. The table is `Alphabet<E, Bd>`, so its image is
86 /// in the alphabet by construction and there is nothing to validate.
87 pub const fn new(table: &'a [Alphabet<E, Bd>; N]) -> Self {
88 Self { table }
89 }
90
91 /// The table.
92 pub const fn table(&self) -> &'a [Alphabet<E, Bd>; N] {
93 self.table
94 }
95}
96
97impl<E: IntegerElement, Bd: Bound, const N: usize> Codec<E, Bd> for Grid<'_, E, Bd, N> {
98 type Code = u16;
99 const MAX_BLOCK: usize = 1;
100 const TIER: TierId = TierId::Grid;
101
102 fn decode_element(&self, code: Self::Code, _i: usize) -> Alphabet<E, Bd> {
103 self.table[(code as usize) % N]
104 }
105}
106
107impl<E: IntegerElement, Bd: Bound, const N: usize> Enumerable<E, Bd> for Grid<'_, E, Bd, N> {
108 // The reachable code space, which is the table size unless the table is
109 // wider than the code type can address.
110 const CODE_SPACE: usize = if N < U16_CODES { N } else { U16_CODES };
111
112 fn code_at(index: usize) -> Self::Code {
113 // `index < CODE_SPACE <= U16_CODES`, so the cast is the identity on the
114 // enumeration's own domain. The `%` is what makes it total off it, and
115 // the `max(1)` keeps it total for a table with no entries at all --- a
116 // codec `tabulation_pays` refuses, so no traversal reaches it.
117 (index % Self::CODE_SPACE.max(1)) as u16
118 }
119
120 fn index_of(code: Self::Code) -> usize {
121 // The same reduction `decode_element` performs, so equal indices and
122 // equal decodes are the same relation.
123 (code as usize) % Self::CODE_SPACE.max(1)
124 }
125
126 fn as_index_stream(codes: &[u16]) -> Option<IndexStream<'_>> {
127 // `index_of` is `% CODE_SPACE`, which is `& (CODE_SPACE - 1)` exactly
128 // when the space is a power of two --- and then the stored `u16` is the
129 // index, masked. At any other entry count the two differ and the
130 // traversal builds the stream, at the same bytes.
131 Self::CODE_SPACE
132 .is_power_of_two()
133 .then_some(IndexStream::U16(codes))
134 }
135}
136
137// ---------------------------------------------------------------------------
138// Packed
139// ---------------------------------------------------------------------------
140
141/// Unpacks `P` sub-codes from one stored byte, then defers to `C`.
142///
143/// The i4 tier is `Packed<Grid<16>, 2>`. **Low sub-code first** --- normative,
144/// pinned by case `nibble-order` and asserted by `CK-03`.
145///
146/// `P` is arbitrary: two nibbles, four two-bit codes, eight one-bit codes. The
147/// sub-code width is `8 / P`, so `P` must divide 8, which [`Packed::new`]
148/// checks once at construction.
149#[derive(Clone, Copy, Debug)]
150pub struct Packed<C, const P: usize> {
151 inner: C,
152}
153
154impl<C, const P: usize> Packed<C, P> {
155 /// Bits per sub-code.
156 pub const SUB_BITS: u32 = (8 / P) as u32;
157
158 /// Wrap `inner`, unpacking `P` sub-codes per byte.
159 ///
160 /// `None` when `P` does not divide 8, which means no such packing exists.
161 pub fn new(inner: C) -> Option<Self> {
162 if P == 0 || 8 % P != 0 {
163 return None;
164 }
165 Some(Self { inner })
166 }
167}
168
169impl<E, Bd, C, const P: usize> Codec<E, Bd> for Packed<C, P>
170where
171 E: IntegerElement,
172 Bd: Bound,
173 C: Codec<E, Bd>,
174 C::Code: From<u8>,
175{
176 type Code = u8;
177 const MAX_BLOCK: usize = P * C::MAX_BLOCK;
178 const TIER: TierId = TierId::Packed;
179 const IS_FIXED_WIDTH: bool = C::IS_FIXED_WIDTH;
180
181 fn decode_element(&self, code: Self::Code, i: usize) -> Alphabet<E, Bd> {
182 let bits = Self::SUB_BITS;
183 let mask: u8 = if bits >= 8 {
184 u8::MAX
185 } else {
186 (1u8 << bits) - 1
187 };
188 // Low sub-code first. This is normative: a reader that took the high
189 // sub-code first would decode a different matrix from the same bytes,
190 // so the order is pinned rather than left to convention (CK-03).
191 let p = i / C::MAX_BLOCK;
192 let sub = (code >> (bits * p as u32)) & mask;
193 self.inner
194 .decode_element(C::Code::from(sub), i % C::MAX_BLOCK)
195 }
196}
197
198/// How many distinct sub-codes one field of a packed byte can store.
199///
200/// The inner codec's own space, unless the field is narrower than that space ---
201/// a `Grid<256>` packed two to a byte can only ever be handed a nibble, so its
202/// reachable sub-space is sixteen and not two hundred and fifty-six. Getting
203/// this wrong in either direction breaks a law: too large and `code_at` names
204/// codes the byte cannot hold, too small and `index_of` collides.
205const fn sub_space(inner: usize, sub_bits: u32) -> usize {
206 let per_field = 1usize << sub_bits;
207 if inner < per_field {
208 inner
209 } else {
210 per_field
211 }
212}
213
214impl<E, Bd, C, const P: usize> Enumerable<E, Bd> for Packed<C, P>
215where
216 E: IntegerElement,
217 Bd: Bound,
218 C: Enumerable<E, Bd>,
219 C::Code: From<u8>,
220{
221 // Mixed radix in the sub-space, which is at most `2^(SUB_BITS * P) = 256`,
222 // so this cannot overflow whatever `P` and `C` are.
223 const CODE_SPACE: usize = sub_space(C::CODE_SPACE, Self::SUB_BITS).pow(P as u32);
224
225 fn code_at(index: usize) -> Self::Code {
226 let bits = Self::SUB_BITS;
227 let radix = sub_space(C::CODE_SPACE, bits).max(1);
228 let mut rest = index % Self::CODE_SPACE.max(1);
229 let mut code = 0u8;
230 for field in 0..P {
231 let digit = rest % radix;
232 rest /= radix;
233 // Low sub-code first, the same order `decode_element` reads them in.
234 code |= (digit as u8) << (bits * field as u32);
235 }
236 code
237 }
238
239 fn index_of(code: Self::Code) -> usize {
240 let bits = Self::SUB_BITS;
241 let mask: u8 = if bits >= 8 {
242 u8::MAX
243 } else {
244 (1u8 << bits) - 1
245 };
246 let radix = sub_space(C::CODE_SPACE, bits).max(1);
247 let mut index = 0usize;
248 let mut place = 1usize;
249 for field in 0..P {
250 let sub = (code >> (bits * field as u32)) & mask;
251 let digit = C::index_of(C::Code::from(sub)) % radix;
252 index += digit * place;
253 place *= radix;
254 }
255 index
256 }
257}
258
259// ---------------------------------------------------------------------------
260// Book
261// ---------------------------------------------------------------------------
262
263/// Any codebook: `N` entries of `BLK` alphabet elements each.
264///
265/// E8 is `Book<256, 8>`. Nothing privileges that shape; `BLK` and `N` are
266/// parameters, and no quality claim attaches to any table (N3).
267#[derive(Clone, Copy, Debug)]
268pub struct Book<
269 'a,
270 E: IntegerElement,
271 Bd: Bound,
272 const N: usize,
273 const BLK: usize,
274 K: SymbolCode = u16,
275> {
276 table: &'a [[Alphabet<E, Bd>; BLK]; N],
277 // The width is a parameter of the decode, not of the table: the struct
278 // stores nothing of it.
279 _code: PhantomData<fn() -> K>,
280}
281
282impl<'a, E: IntegerElement, Bd: Bound, const N: usize, const BLK: usize, K: SymbolCode>
283 Book<'a, E, Bd, N, BLK, K>
284{
285 /// Borrow a codebook. On an embedded target this is a pointer into flash.
286 pub const fn new(table: &'a [[Alphabet<E, Bd>; BLK]; N]) -> Self {
287 Self {
288 table,
289 _code: PhantomData,
290 }
291 }
292
293 /// The codebook.
294 pub const fn table(&self) -> &'a [[Alphabet<E, Bd>; BLK]; N] {
295 self.table
296 }
297}
298
299impl<E: IntegerElement, Bd: Bound, const N: usize, const BLK: usize, K: SymbolCode> Codec<E, Bd>
300 for Book<'_, E, Bd, N, BLK, K>
301{
302 type Code = K;
303 const MAX_BLOCK: usize = BLK;
304 const TIER: TierId = TierId::Book;
305
306 fn decode_element(&self, code: Self::Code, i: usize) -> Alphabet<E, Bd> {
307 self.table[K::index(code) % N][i % BLK]
308 }
309
310 fn decode_into(&self, code: Self::Code, out: &mut [Alphabet<E, Bd>]) -> usize {
311 out[..BLK].copy_from_slice(&self.table[K::index(code) % N]);
312 BLK
313 }
314}
315
316impl<E: IntegerElement, Bd: Bound, const N: usize, const BLK: usize, K: SymbolCode>
317 Enumerable<E, Bd> for Book<'_, E, Bd, N, BLK, K>
318{
319 const CODE_SPACE: usize = if N < K::CODES { N } else { K::CODES };
320
321 fn code_at(index: usize) -> Self::Code {
322 K::at(index % Self::CODE_SPACE.max(1))
323 }
324
325 fn index_of(code: Self::Code) -> usize {
326 K::index(code) % Self::CODE_SPACE.max(1)
327 }
328
329 fn as_index_stream(codes: &[K]) -> Option<IndexStream<'_>> {
330 // `index_of` is `% CODE_SPACE`, which is `& (CODE_SPACE - 1)` exactly
331 // when the space is a power of two --- and then the stored code, at
332 // either width, is the index, masked. At any other entry count the two
333 // differ and the traversal builds the stream, at the same bytes.
334 K::index_stream(codes, Self::CODE_SPACE)
335 }
336}
337
338// ---------------------------------------------------------------------------
339// Sign
340// ---------------------------------------------------------------------------
341
342/// Weights in `{-1, +1}`, one bit per element, with no table at all.
343///
344/// The `Packed<Grid<2>,8>` spelling of the same decode (`CK-13`) tabulates but
345/// can never answer [`Enumerable::as_index_stream`]: a packed byte's index is
346/// a mixed-radix decomposition of it, so the tabulated traversal builds the
347/// index stream it gathers from. Measured, that build is a fifth to a third of
348/// the work at a one-row tile (ANALYSIS.md, "What the missing index stream
349/// costs the sign composition"). This tier spells the same decode with the
350/// code *being* the index --- `Code = u16`, one bit per element of the block
351/// --- so the operand's own memory is the stream and there is nothing to
352/// build. `CK-11` pins the two spellings byte for byte.
353///
354/// The codebook is the constant `{-1, +1}`, so there is nothing to borrow and
355/// no lifetime: the decode is a bit test. **Bit `t` of the code is element
356/// `t`; set is `+1`, clear is `-1`, low bit first** --- the order `Packed`
357/// reads its sub-codes in (`CK-03`), so a `Sign` code spells the block the
358/// composition spells from the same byte value. The convention is normative:
359/// the encoder side is out of tree, and this decode is the whole contract
360/// with it.
361#[derive(Clone, Copy, Debug)]
362pub struct Sign<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode = u16> {
363 _marker: TierMarker<E, Bd, K>,
364}
365
366impl<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode> Sign<E, Bd, BLK, K> {
367 /// The one non-existence check: an alphabet that does not admit `+-1` has
368 /// no sign codec. Decided at construction, before any arithmetic, like
369 /// every other non-existence in this library (C6).
370 pub const fn new() -> Option<Self> {
371 if Bd::VALUE < 1 || E::FULL < 1 {
372 return None;
373 }
374 Some(Self {
375 _marker: PhantomData,
376 })
377 }
378}
379
380impl<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode> Codec<E, Bd>
381 for Sign<E, Bd, BLK, K>
382{
383 type Code = K;
384 const MAX_BLOCK: usize = BLK;
385 const TIER: TierId = TierId::Sign;
386
387 fn decode_element(&self, code: Self::Code, i: usize) -> Alphabet<E, Bd> {
388 let bit = i % BLK;
389 // The guard keeps the shift total past the code type's own width: at
390 // `BLK > K::BITS` a code holds no bit for those positions, and they
391 // decode to `-1` like any clear bit --- the same rule at either width.
392 let v = if bit < K::BITS as usize && (K::index(code) >> bit) & 1 == 1 {
393 E::ONE
394 } else {
395 // `0 - 1`: exact for every integer element, never a wrap.
396 E::ZERO.sub(E::ONE)
397 };
398 // `new` established that the declared alphabet admits `+-1`, so the
399 // wrap cannot put a value outside its bound: it is that O(1) check
400 // being cashed in, exactly as `Offset`'s decode cashes in its own.
401 Alphabet::wrap(v)
402 }
403}
404
405impl<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode> Enumerable<E, Bd>
406 for Sign<E, Bd, BLK, K>
407{
408 // Every bit pattern of the block, capped at what the code type can
409 // address --- the same honesty about the code type's width as `Grid`'s.
410 const CODE_SPACE: usize = if BLK < K::BITS as usize {
411 1 << BLK
412 } else {
413 K::CODES
414 };
415
416 // The whole point of the tier: the code *is* the index, and the decode is
417 // the bit test. The Gray-walk build reads this flag and nothing else.
418 const SIGN_BIT_BOOK: bool = true;
419
420 fn code_at(index: usize) -> Self::Code {
421 K::at(index % Self::CODE_SPACE.max(1))
422 }
423
424 fn index_of(code: Self::Code) -> usize {
425 // A mask, not a remainder: the space is a power of two at every block
426 // width, and the stored code *is* the index, which is the whole point
427 // of the tier.
428 K::index(code) & (Self::CODE_SPACE - 1)
429 }
430
431 fn as_index_stream(codes: &[K]) -> Option<IndexStream<'_>> {
432 // Always --- not at the power-of-two entry counts, as `Grid` and
433 // `Book` answer, but at every block width, because the code addresses
434 // the enumeration unconditionally: the space is a power of two at
435 // every `BLK`. This is the answer `Packed` can never give, and the gap
436 // this tier exists to close.
437 K::index_stream(codes, Self::CODE_SPACE)
438 }
439}
440
441// ---------------------------------------------------------------------------
442// Ternary
443// ---------------------------------------------------------------------------
444
445/// Weights in `{-1, 0, +1}`, two bits per element, with no table at all.
446///
447/// The `Packed<Grid<4>,4>` spelling of the same decode (`CK-10`) tabulates but
448/// can never answer [`Enumerable::as_index_stream`]: a packed byte's index is
449/// a mixed-radix decomposition of it, so the tabulated traversal builds the
450/// index stream it gathers from. This tier spells the same decode with the
451/// code *being* the index --- `Code = u16`, one base-4 digit per element of
452/// the block, and a base-4 digit stream is a power-of-two code space (4^BLK =
453/// 2^(2BLK)) --- so the operand's own memory is the stream and there is
454/// nothing to build. `CK-12` pins the two spellings byte for byte.
455///
456/// The codebook is the constant `{-1, 0, +1}`, so there is nothing to borrow
457/// and no lifetime: the decode is a two-bit field test. **Digit `t` of the
458/// code --- bits `2t` and `2t+1` --- is element `t`; 0 is `-1`, 1 is `0`, 2 is
459/// `+1`, and 3, the dead digit no ternary encoder emits, decodes to `0`, low
460/// digit first** --- the order `Packed` reads its sub-codes in (`CK-03`) and
461/// the decode the spelling's `[-1, 0, +1, dead]` table gives every digit
462/// (`CK-10`), so a `Ternary` code spells the block the composition spells from
463/// the same byte value. The convention is normative: the encoder side is out
464/// of tree, and this decode is the whole contract with it.
465#[derive(Clone, Copy, Debug)]
466pub struct Ternary<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode = u16> {
467 _marker: TierMarker<E, Bd, K>,
468}
469
470impl<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode> Ternary<E, Bd, BLK, K> {
471 /// The one non-existence check: an alphabet that does not admit `+-1` has
472 /// no ternary codec --- `0` is in every alphabet. Decided at construction,
473 /// before any arithmetic, like every other non-existence in this library
474 /// (C6).
475 pub const fn new() -> Option<Self> {
476 if Bd::VALUE < 1 || E::FULL < 1 {
477 return None;
478 }
479 Some(Self {
480 _marker: PhantomData,
481 })
482 }
483}
484
485impl<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode> Codec<E, Bd>
486 for Ternary<E, Bd, BLK, K>
487{
488 type Code = K;
489 const MAX_BLOCK: usize = BLK;
490 const TIER: TierId = TierId::Ternary;
491
492 fn decode_element(&self, code: Self::Code, i: usize) -> Alphabet<E, Bd> {
493 let shift = 2 * (i % BLK);
494 // The guard keeps the shift total past the code type's own width: at
495 // `BLK > K::BITS / 2` a code holds no digit for those positions, and
496 // they decode to `-1` like any zero field --- the same rule at either
497 // width.
498 let digit = if shift < K::BITS as usize {
499 (K::index(code) >> shift) & 3
500 } else {
501 0
502 };
503 let v = match digit {
504 // `0 - 1`: exact for every integer element, never a wrap.
505 0 => E::ZERO.sub(E::ONE),
506 2 => E::ONE,
507 // 1 is the zero digit, and 3 the dead one --- which also decodes
508 // to 0, a priced duplicate of it rather than an error (`CK-10`).
509 _ => E::ZERO,
510 };
511 // `new` established that the declared alphabet admits `+-1`, so the
512 // wrap cannot put a value outside its bound: it is that O(1) check
513 // being cashed in, exactly as `Sign`'s decode cashes in its own.
514 Alphabet::wrap(v)
515 }
516}
517
518impl<E: IntegerElement, Bd: Bound, const BLK: usize, K: SymbolCode> Enumerable<E, Bd>
519 for Ternary<E, Bd, BLK, K>
520{
521 // 4^BLK, capped at what the code type can address --- a power of two at
522 // every block width, the same honesty about the code type's width as
523 // `Grid`'s, and what makes the index stream unconditional below.
524 const CODE_SPACE: usize = if 2 * BLK < K::BITS as usize {
525 1 << (2 * BLK)
526 } else {
527 K::CODES
528 };
529
530 fn code_at(index: usize) -> Self::Code {
531 K::at(index % Self::CODE_SPACE.max(1))
532 }
533
534 fn index_of(code: Self::Code) -> usize {
535 // A mask, not a remainder: the space is a power of two at every block
536 // width, and the stored code *is* the index, which is the whole point
537 // of the tier.
538 K::index(code) & (Self::CODE_SPACE - 1)
539 }
540
541 fn as_index_stream(codes: &[K]) -> Option<IndexStream<'_>> {
542 // Always, as `Sign` answers and for one reason more: 4^BLK is 2^(2BLK),
543 // so the code addresses the enumeration unconditionally. This is the
544 // answer `Packed` can never give, and the gap this tier exists to close.
545 K::index_stream(codes, Self::CODE_SPACE)
546 }
547}
548
549// ---------------------------------------------------------------------------
550// Offset
551// ---------------------------------------------------------------------------
552
553/// `d(c) - z`: asymmetric quantization as a codec composition, not a feature.
554///
555/// A zero point is a codec, which is why the library needs no separate
556/// "asymmetric" mode and no branch for one (§1.3).
557///
558/// Decoding widens the alphabet: an inner value bounded by `BdIn` and a zero
559/// point of magnitude `|z|` produce a value bounded by `BdIn + |z|`. That is
560/// why the output bound is a separate parameter, and why [`Offset::new`]
561/// checks, once and in O(1), that the declared output bound covers the image
562/// and that no value overflows the element type. A `None` means no such codec
563/// exists --- the same category as a non-conformant shape, decided before any
564/// arithmetic (C6).
565#[derive(Clone, Copy, Debug)]
566pub struct Offset<E: IntegerElement, BdIn: Bound, BdOut: Bound, C: Codec<E, BdIn>> {
567 inner: C,
568 zero: E,
569 _marker: OffsetMarker<BdIn, BdOut, E>,
570}
571
572impl<E: IntegerElement, BdIn: Bound, BdOut: Bound, C: Codec<E, BdIn>> Offset<E, BdIn, BdOut, C> {
573 /// Compose `inner` with the zero point `zero`.
574 ///
575 /// `None` when `BdIn + |zero|` exceeds the declared output bound or the
576 /// element type's own range, in which case the decode this describes does
577 /// not land in the alphabet it claims to.
578 pub fn new(inner: C, zero: E) -> Option<Self> {
579 let image = BdIn::VALUE.checked_add(zero.magnitude())?;
580 if image > BdOut::VALUE || image > E::FULL {
581 return None;
582 }
583 Some(Self {
584 inner,
585 zero,
586 _marker: PhantomData,
587 })
588 }
589
590 /// The zero point.
591 pub const fn zero(&self) -> E {
592 self.zero
593 }
594}
595
596impl<E, BdIn, BdOut, C> Codec<E, BdOut> for Offset<E, BdIn, BdOut, C>
597where
598 E: IntegerElement,
599 BdIn: Bound,
600 BdOut: Bound,
601 C: Codec<E, BdIn>,
602{
603 type Code = C::Code;
604 const MAX_BLOCK: usize = C::MAX_BLOCK;
605 const TIER: TierId = TierId::Offset;
606 const IS_FIXED_WIDTH: bool = C::IS_FIXED_WIDTH;
607
608 fn decode_element(&self, code: Self::Code, i: usize) -> Alphabet<E, BdOut> {
609 let inner = self.inner.decode_element(code, i).get();
610 // Exact: `new` established that `BdIn + |zero| <= min(BdOut, E::FULL)`,
611 // so this subtraction neither overflows the element type nor leaves the
612 // declared output alphabet. The wrap is therefore not an unchecked
613 // assertion; it is the O(1) check in `new` being cashed in.
614 Alphabet::<E, BdOut>::wrap(inner.sub(self.zero))
615 }
616}
617
618impl<E, BdIn, BdOut, C> Enumerable<E, BdOut> for Offset<E, BdIn, BdOut, C>
619where
620 E: IntegerElement,
621 BdIn: Bound,
622 BdOut: Bound,
623 C: Enumerable<E, BdIn>,
624{
625 // A zero point relabels the *image*, never the code space: the same codes
626 // name the same blocks, shifted. So the enumeration is the inner one,
627 // unchanged, and a tabulated asymmetric quantization needs nothing new.
628 const CODE_SPACE: usize = C::CODE_SPACE;
629
630 fn code_at(index: usize) -> Self::Code {
631 C::code_at(index)
632 }
633
634 fn index_of(code: Self::Code) -> usize {
635 C::index_of(code)
636 }
637
638 fn as_index_stream(codes: &[Self::Code]) -> Option<IndexStream<'_>> {
639 // A zero point relabels the image, never the code space, so whether the
640 // code addresses the enumeration is the inner codec's answer unchanged.
641 C::as_index_stream(codes)
642 }
643}
644
645// ---------------------------------------------------------------------------
646// Runs
647// ---------------------------------------------------------------------------
648
649/// Sparse storage as a codec, not as a separate algorithm and not as a separate
650/// crate (D-16).
651///
652/// A run is `(length, code)`: `length` consecutive decoded elements, all of
653/// them `d(code)`. A gap is a run whose code decodes to the alphabet's zero, so
654/// the zeros are *explicit* and the arithmetic downstream stays dense. No
655/// sparse-specific speedup is claimed; the benefit is residency, and `CG-03`
656/// measures it like any other codec's.
657///
658/// A code is a *run index*, and the run's length is what [`Codec::decode_len`]
659/// reports. `MAX_RUN` is the longest run the caller declares, so this tier is
660/// variable-length within a compile-time bound --- which is exactly what the
661/// `MAX_BLOCK` spelling is for (S4, S5b).
662#[derive(Clone, Copy, Debug)]
663pub struct Runs<'a, E: IntegerElement, Bd: Bound, C: Codec<E, Bd>, const MAX_RUN: usize> {
664 runs: &'a [(u32, C::Code)],
665 inner: C,
666 _marker: PhantomData<fn() -> (E, Bd)>,
667}
668
669impl<'a, E: IntegerElement, Bd: Bound, C: Codec<E, Bd>, const MAX_RUN: usize>
670 Runs<'a, E, Bd, C, MAX_RUN>
671{
672 /// Borrow a run list.
673 ///
674 /// `None` when a run is empty or longer than `MAX_RUN`, both of which mean
675 /// the list describes no stream. Decided at construction, before any
676 /// arithmetic, like every other non-existence in this library (C6).
677 pub fn new(inner: C, runs: &'a [(u32, C::Code)]) -> Option<Self> {
678 if runs
679 .iter()
680 .any(|&(len, _)| len == 0 || len as usize > MAX_RUN)
681 {
682 return None;
683 }
684 Some(Self {
685 runs,
686 inner,
687 _marker: PhantomData,
688 })
689 }
690
691 /// The number of stored runs, which is the residency this tier buys.
692 pub const fn run_count(&self) -> usize {
693 self.runs.len()
694 }
695
696 /// The decoded length of the whole run list.
697 ///
698 /// `CK-06` asserts this equals the declared row width, which is the
699 /// invariant that lets a variable-length tier live inside `Codec`.
700 pub fn decoded_len(&self) -> usize {
701 self.runs.iter().map(|&(len, _)| len as usize).sum()
702 }
703}
704
705impl<E: IntegerElement, Bd: Bound, C: Codec<E, Bd>, const MAX_RUN: usize> Codec<E, Bd>
706 for Runs<'_, E, Bd, C, MAX_RUN>
707{
708 type Code = u32;
709 const MAX_BLOCK: usize = MAX_RUN;
710 const TIER: TierId = TierId::Runs;
711 // The one variable-length tier: a run is as long as it is.
712 const IS_FIXED_WIDTH: bool = false;
713
714 fn decode_len(&self, run: Self::Code) -> usize {
715 self.runs
716 .get(run as usize)
717 .map_or(0, |&(len, _)| len as usize)
718 }
719
720 fn decode_element(&self, run: Self::Code, i: usize) -> Alphabet<E, Bd> {
721 match self.runs.get(run as usize) {
722 // Every element of a run decodes the run's code. For an inner tier
723 // that is itself a block codec, the position within the block is
724 // `i % C::MAX_BLOCK`, so a run of an E8 codeword repeats the whole
725 // codeword rather than its first element.
726 Some(&(_, code)) => self.inner.decode_element(code, i % C::MAX_BLOCK),
727 // Past the end of the run list: the alphabet's zero, explicitly.
728 None => Alphabet::ZERO,
729 }
730 }
731}
732
733// `Runs` does not implement `Enumerable`. A run code carries a *length*, so the
734// table would have to be indexed by `(value, length)` rather than by code, and
735// the length is artifact-local data rather than the codec's fixed enumerable
736// space. Such a pair is not the `code -> fixed block` declaration tabulation
737// requires. The absence is structural at the traversal's type boundary, not a
738// performance capability withheld behind a runtime refusal (§4).
739
740// ---------------------------------------------------------------------------
741// Transcode
742// ---------------------------------------------------------------------------
743
744/// A total map from one code space to another.
745///
746/// This is the first half of the upstream `transcode_decode` composite: the
747/// part that is a relabelling rather than a decode.
748pub trait CodeMap<In: Copy, Out: Copy>: Send + Sync {
749 /// Relabel. Total on the whole input code space.
750 fn map(&self, code: In) -> Out;
751}
752
753impl<In: Copy, Out: Copy, F> CodeMap<In, Out> for F
754where
755 F: Fn(In) -> Out + Send + Sync,
756{
757 fn map(&self, code: In) -> Out {
758 self(code)
759 }
760}
761
762/// The upstream `transcode_decode` composite: relabel, then decode.
763///
764/// Closed under composition, which is what makes a tier change a *change of
765/// artifact* rather than a change of arithmetic (`CK-04`, `CL-MM03`).
766///
767/// The plan spells this `Transcode<C1, C2>`; the first parameter is a
768/// [`CodeMap`] rather than a second [`Codec`], because a decode into the
769/// alphabet cannot be the input of another decode --- only a relabelling can.
770#[derive(Clone, Copy, Debug)]
771pub struct Transcode<M, C, In> {
772 map: M,
773 inner: C,
774 _marker: PhantomData<fn() -> In>,
775}
776
777impl<M, C, In> Transcode<M, C, In> {
778 /// Compose a relabelling with a decode.
779 pub const fn new(map: M, inner: C) -> Self {
780 Self {
781 map,
782 inner,
783 _marker: PhantomData,
784 }
785 }
786}
787
788impl<E, Bd, M, C, In> Codec<E, Bd> for Transcode<M, C, In>
789where
790 E: IntegerElement,
791 Bd: Bound,
792 C: Codec<E, Bd>,
793 M: CodeMap<In, C::Code>,
794 In: Copy + Send + Sync + 'static,
795{
796 type Code = In;
797 const MAX_BLOCK: usize = C::MAX_BLOCK;
798 const TIER: TierId = TierId::Transcode;
799 const IS_FIXED_WIDTH: bool = C::IS_FIXED_WIDTH;
800
801 fn decode_element(&self, code: Self::Code, i: usize) -> Alphabet<E, Bd> {
802 self.inner.decode_element(self.map.map(code), i)
803 }
804}
805
806// `Transcode` does not implement `Enumerable`, and the reason is the shape of
807// this repository's spelling rather than anything about tabulation. The plan
808// spells the composite `Transcode<C1, C2>` with two codecs; here the first
809// parameter is a [`CodeMap`] --- a *function* --- because a decode into the
810// alphabet cannot be the input of another decode, only a relabelling can. A
811// function's domain cannot be enumerated and `code_at` would need its inverse.
812//
813// Nothing is lost. `decode(c) = inner.decode(map(c))`, so the table over the
814// inner code space *is* the table for the composite: a caller tabulates `C` and
815// reaches the entry with `C::index_of(map(code))`. The relabelling is a property
816// of the code stream, and tabulation has already left the code stream behind by
817// the time it reads the table.
818
819// ---------------------------------------------------------------------------
820// Arena
821// ---------------------------------------------------------------------------
822
823/// A code type whose own order is an enumeration's order.
824///
825/// `u16` and `u8`, and nothing else: the width is a fact about the artifact's
826/// storage, and the two widths are the two residencies a codec has a use for
827/// --- two bytes a symbol, and one where the code space fits a byte.
828///
829/// The trait exists so that [`Arena`], [`Book`], [`Sign`] and [`Ternary`] are
830/// one tier at two widths rather than two tiers with one decode: the table
831/// read, the bit fields, the enumeration, and the canonicalization are written
832/// once, against these methods.
833pub trait SymbolCode: Copy + Send + Sync + 'static {
834 /// How many distinct codes the type holds.
835 const CODES: usize;
836
837 /// The type's own width in bits, for the shift guard a bit-field decode
838 /// totals against: a position past it reads as a clear field, at either
839 /// width.
840 const BITS: u32;
841
842 /// The code at `index` in the type's own order. Total below `CODES`.
843 fn at(index: usize) -> Self;
844
845 /// Where `code` sits in the type's own order.
846 fn index(code: Self) -> usize;
847
848 /// The stored code stream read as the index stream it already is, when it
849 /// is one.
850 ///
851 /// `index_of` is `% space` at every tier here, which is `& (space - 1)`
852 /// exactly when the space is a power of two --- and then the stored code,
853 /// masked, *is* the index, at either width, and the traversal borrows it.
854 /// At any other entry count the two differ and the traversal builds the
855 /// stream, at the same bytes. The variant names the width: a byte stream
856 /// answers [`IndexStream::U8`], and the gather dispatches on it once.
857 fn index_stream(codes: &[Self], space: usize) -> Option<IndexStream<'_>>;
858}
859
860impl SymbolCode for u16 {
861 const CODES: usize = U16_CODES;
862 const BITS: u32 = u16::BITS;
863
864 fn at(index: usize) -> Self {
865 // `index < CODE_SPACE <= CODES` at every call site, so the cast is the
866 // identity on the enumeration's own domain.
867 index as u16
868 }
869
870 fn index(code: Self) -> usize {
871 code as usize
872 }
873
874 fn index_stream(codes: &[Self], space: usize) -> Option<IndexStream<'_>> {
875 space.is_power_of_two().then_some(IndexStream::U16(codes))
876 }
877}
878
879impl SymbolCode for u8 {
880 const CODES: usize = u8::MAX as usize + 1;
881 const BITS: u32 = u8::BITS;
882
883 fn at(index: usize) -> Self {
884 // As `u16`'s: total on the enumeration's own domain.
885 index as u8
886 }
887
888 fn index(code: Self) -> usize {
889 code as usize
890 }
891
892 fn index_stream(codes: &[Self], space: usize) -> Option<IndexStream<'_>> {
893 space.is_power_of_two().then_some(IndexStream::U8(codes))
894 }
895}
896
897/// A codebook of one artifact's distinct bit patterns.
898///
899/// Mechanically the arena is a one-element block grid, and its decode is the
900/// same table read. What makes it a tier rather than a [`Grid`] is the
901/// construction discipline the token carries into the kappa manifest: the
902/// table is the source stream's distinct symbols in canonical order, built by
903/// [`canonicalize`], so two artifacts holding the same values share a
904/// codebook --- and an address --- whatever order their streams stored them
905/// in (§6.4, `CK-10`).
906///
907/// The element type is a float: the symbols are bit patterns, and the bound is
908/// [`Whole`] because membership is discharged by the table's construction and
909/// no magnitude question applies to a float (§5.2b).
910///
911/// The code width is a parameter, `u16` by default and `u8` where the
912/// artifact's distinct patterns fit a byte (`CK-14`): one type at two
913/// residencies, not two tiers. At a power-of-two code space the stored code
914/// masked *is* the index at either width, so both spellings answer their own
915/// stream to the tabulated gather --- `IndexStream::U16` and
916/// `IndexStream::U8` --- and the traversal builds nothing.
917#[derive(Clone, Copy, Debug)]
918pub struct Arena<'a, E: FloatElement, const N: usize, K: SymbolCode = u16> {
919 table: &'a [Alphabet<E, Whole<E>>; N],
920 // The width is a parameter of the decode, not of the table: the struct
921 // stores nothing of it.
922 _code: PhantomData<fn() -> K>,
923}
924
925impl<'a, E: FloatElement, const N: usize, K: SymbolCode> Arena<'a, E, N, K> {
926 /// Borrow a canonical codebook. The canonicalization is [`canonicalize`]'s
927 /// discipline; like [`Grid`], the borrow itself validates nothing (§6.2).
928 pub const fn new(table: &'a [Alphabet<E, Whole<E>>; N]) -> Self {
929 Self {
930 table,
931 _code: PhantomData,
932 }
933 }
934
935 /// The codebook.
936 pub const fn table(&self) -> &'a [Alphabet<E, Whole<E>>; N] {
937 self.table
938 }
939}
940
941impl<E: FloatElement, const N: usize, K: SymbolCode> Codec<E, Whole<E>> for Arena<'_, E, N, K> {
942 type Code = K;
943 const MAX_BLOCK: usize = 1;
944 const TIER: TierId = TierId::Arena;
945
946 fn decode_element(&self, code: Self::Code, _i: usize) -> Alphabet<E, Whole<E>> {
947 // The same total reduction `Grid` performs: an arbitrary code indexes
948 // a table of `N` entries modulo `N` (C6).
949 self.table[K::index(code) % N]
950 }
951}
952
953impl<E: FloatElement, const N: usize, K: SymbolCode> Enumerable<E, Whole<E>>
954 for Arena<'_, E, N, K>
955{
956 // The reachable code space, as for `Grid`: the table size unless the table
957 // is wider than the code type can address.
958 const CODE_SPACE: usize = if N < K::CODES { N } else { K::CODES };
959
960 fn code_at(index: usize) -> Self::Code {
961 K::at(index % Self::CODE_SPACE.max(1))
962 }
963
964 fn index_of(code: Self::Code) -> usize {
965 // The same reduction `decode_element` performs, so equal indices and
966 // equal decodes are the same relation.
967 K::index(code) % Self::CODE_SPACE.max(1)
968 }
969
970 fn as_index_stream(codes: &[K]) -> Option<IndexStream<'_>> {
971 K::index_stream(codes, Self::CODE_SPACE)
972 }
973}
974
975/// Sort `values` into canonical arena order and move the distinct symbols to
976/// the front, returning how many there are. The codebook is `&values[..n]`.
977///
978/// Canonical order is unsigned order on bit patterns, and equality is pattern
979/// equality: `-0.0` and `+0.0` are distinct symbols, two NaNs with different
980/// payloads are distinct symbols, and deciding either takes no float
981/// comparison (`CK-10`, `CU-01`). The sort is a heapsort: in place,
982/// allocation-free, and `O(n log n)` whatever the stream, so an adversarial
983/// order meets the same bound as a friendly one.
984pub fn canonicalize<E: FloatElement>(values: &mut [E]) -> usize {
985 heapsort_by_pattern(values);
986 // Equal patterns are adjacent after the sort; keep the first of each run.
987 let mut n = 0usize;
988 for i in 0..values.len() {
989 if n == 0 || values[i].symbol_bits() != values[n - 1].symbol_bits() {
990 values[n] = values[i];
991 n += 1;
992 }
993 }
994 n
995}
996
997/// Heapsort by bit pattern: a total order with no float comparisons.
998fn heapsort_by_pattern<E: FloatElement>(values: &mut [E]) {
999 fn sift_down<E: FloatElement>(values: &mut [E], mut root: usize, end: usize) {
1000 loop {
1001 let child = 2 * root + 1;
1002 if child >= end {
1003 return;
1004 }
1005 let mut swap = root;
1006 if values[swap].symbol_bits() < values[child].symbol_bits() {
1007 swap = child;
1008 }
1009 if child + 1 < end && values[swap].symbol_bits() < values[child + 1].symbol_bits() {
1010 swap = child + 1;
1011 }
1012 if swap == root {
1013 return;
1014 }
1015 values.swap(root, swap);
1016 root = swap;
1017 }
1018 }
1019
1020 let len = values.len();
1021 for start in (0..len / 2).rev() {
1022 sift_down(values, start, len);
1023 }
1024 for end in (1..len).rev() {
1025 values.swap(0, end);
1026 sift_down(values, 0, end);
1027 }
1028}