Skip to main content

dsi_bitstream/codes/
zeta.rs

1/*
2 * SPDX-FileCopyrightText: 2023 Tommaso Fontana
3 * SPDX-FileCopyrightText: 2023 Inria
4 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
5 *
6 * SPDX-License-Identifier: Apache-2.0 OR MIT
7 */
8
9//! Boldi–Vigna ζ codes.
10//!
11//! The ζ code with parameter *k* ≥ 1 of a natural number *n* is the
12//! concatenation of the unary code of *h* = ⌊⌊log₂(*n* + 1)⌋ / *k*⌋ and of the
13//! [minimal binary code] of *n* + 1 − 2*ʰᵏ* with 2⁽*ʰ* ⁺ ¹⁾*ᵏ* − 2*ʰᵏ* as upper
14//! bound.
15//!
16//! The implied distribution of a ζ code with parameter *k* is ≈ 1/*x*<sup>1 +
17//! 1/*k*</sup>.
18//!
19//! Note that ζ₁ = [π₀] = [γ] and ζ₂ = [π₁]. However, due to [subtle problems
20//! with endianness], in the little-endian case ζ₂ and π₁ have the same codeword
21//! lengths but slightly permuted bits.
22//!
23//! This module provides a generic implementation of ζ codes, and a specialized
24//! implementation for ζ₃ that may use tables.
25//!
26//! The supported range is [0 . . 2⁶⁴ – 1) for *k* in [1 . . 64).
27//!
28//! # References
29//!
30//! Paolo Boldi and Sebastiano Vigna. “[Codes for the World–Wide Web]”. Internet
31//! Math., 2(4):405–427, 2005.
32//!
33//! [minimal binary code]: crate::codes::minimal_binary
34//! [π₀]: crate::codes::pi
35//! [γ]: crate::codes::gamma
36//! [π₁]: crate::codes::pi
37//! [subtle problems with endianness]: crate::codes
38//! [Codes for the World–Wide Web]: https://doi.org/10.1080/15427951.2005.10129113
39
40use super::{MinimalBinaryRead, MinimalBinaryWrite, len_minimal_binary, zeta_tables};
41use crate::traits::*;
42
43/// Returns the length of the ζ code with parameter `k` for `n`.
44#[must_use]
45#[inline(always)]
46#[allow(clippy::collapsible_if)]
47pub const fn len_zeta_param<const USE_TABLE: bool>(mut n: u64, k: usize) -> usize {
48    debug_assert!(k >= 1);
49    if USE_TABLE {
50        if k == zeta_tables::K {
51            // We cannot use .get() here because n is a u64
52            if n < zeta_tables::LEN.len() as u64 {
53                return zeta_tables::LEN[n as usize] as usize;
54            }
55        }
56    }
57    debug_assert!(n < u64::MAX);
58    n += 1;
59    let h = n.ilog2() as usize / k;
60    let l = 1 << (h * k);
61    h + 1 + len_minimal_binary(n - l, (l << k).wrapping_sub(l))
62}
63
64/// Returns the length of the ζ code with parameter `k` for `n` using
65/// a default value for `USE_TABLE`.
66#[must_use]
67#[inline(always)]
68pub const fn len_zeta(n: u64, k: usize) -> usize {
69    len_zeta_param::<true>(n, k)
70}
71
72/// Trait for reading ζ codes.
73///
74/// This is the trait you should usually pull into scope to read ζ codes.
75pub trait ZetaRead<E: Endianness>: BitRead<E> {
76    fn read_zeta(&mut self, k: usize) -> Result<u64, Self::Error>;
77    fn read_zeta3(&mut self) -> Result<u64, Self::Error>;
78}
79
80/// Parametric trait for reading ζ codes.
81///
82/// This trait is more general than [`ZetaRead`], as it makes it possible
83/// to specify how to use tables using const parameters.
84///
85/// We provide an implementation of this trait for [`BitRead`]. An implementation
86/// of [`ZetaRead`] using default values is usually provided exploiting the
87/// [`crate::codes::params::ReadParams`] mechanism.
88pub trait ZetaReadParam<E: Endianness>: BitRead<E> {
89    fn read_zeta_param(&mut self, k: usize) -> Result<u64, Self::Error>;
90    fn read_zeta3_param<const USE_TABLE: bool>(&mut self) -> Result<u64, Self::Error>;
91}
92
93impl<B: BitRead<BE>> ZetaReadParam<BE> for B {
94    #[inline(always)]
95    fn read_zeta_param(&mut self, k: usize) -> Result<u64, B::Error> {
96        default_read_zeta(self, k)
97    }
98
99    #[inline(always)]
100    fn read_zeta3_param<const USE_TABLE: bool>(&mut self) -> Result<u64, B::Error> {
101        const {
102            if USE_TABLE {
103                zeta_tables::check_read_table(B::PEEK_BITS)
104            }
105        }
106        if USE_TABLE {
107            if let Some((res, _)) = zeta_tables::read_table_be(self) {
108                return Ok(res);
109            }
110        }
111        default_read_zeta(self, 3)
112    }
113}
114
115impl<B: BitRead<LE>> ZetaReadParam<LE> for B {
116    #[inline(always)]
117    fn read_zeta_param(&mut self, k: usize) -> Result<u64, B::Error> {
118        default_read_zeta(self, k)
119    }
120
121    #[inline(always)]
122    fn read_zeta3_param<const USE_TABLE: bool>(&mut self) -> Result<u64, B::Error> {
123        const {
124            if USE_TABLE {
125                zeta_tables::check_read_table(B::PEEK_BITS)
126            }
127        }
128        if USE_TABLE {
129            if let Some((res, _)) = zeta_tables::read_table_le(self) {
130                return Ok(res);
131            }
132        }
133        default_read_zeta(self, 3)
134    }
135}
136
137/// Default, internal non-table based implementation that works
138/// for any endianness.
139#[inline(always)]
140fn default_read_zeta<BO: Endianness, B: BitRead<BO>>(
141    backend: &mut B,
142    k: usize,
143) -> Result<u64, B::Error> {
144    debug_assert!(k >= 1);
145    let h = backend.read_unary()? as usize;
146    debug_assert!(h * k < 64);
147    let l = 1_u64 << (h * k);
148    let res = backend.read_minimal_binary((l << k).wrapping_sub(l))?;
149    Ok(l + res - 1)
150}
151
152/// Trait for writing ζ codes.
153///
154/// This is the trait you should usually pull into scope to write ζ codes.
155pub trait ZetaWrite<E: Endianness>: BitWrite<E> {
156    fn write_zeta(&mut self, n: u64, k: usize) -> Result<usize, Self::Error>;
157    fn write_zeta3(&mut self, n: u64) -> Result<usize, Self::Error>;
158}
159
160/// Parametric trait for writing ζ codes.
161///
162/// This trait is more general than [`ZetaWrite`], as it makes it possible
163/// to specify how to use tables using const parameters.
164///
165/// We provide an implementation of this trait for [`BitWrite`]. An implementation
166/// of [`ZetaWrite`] using default values is usually provided exploiting the
167/// [`crate::codes::params::WriteParams`] mechanism.
168pub trait ZetaWriteParam<E: Endianness>: BitWrite<E> {
169    fn write_zeta_param(&mut self, n: u64, k: usize) -> Result<usize, Self::Error>;
170    fn write_zeta3_param<const USE_TABLE: bool>(&mut self, n: u64) -> Result<usize, Self::Error>;
171}
172
173impl<B: BitWrite<BE>> ZetaWriteParam<BE> for B {
174    #[inline(always)]
175    fn write_zeta_param(&mut self, n: u64, k: usize) -> Result<usize, Self::Error> {
176        default_write_zeta(self, n, k)
177    }
178
179    #[inline(always)]
180    #[allow(clippy::collapsible_if)]
181    fn write_zeta3_param<const USE_TABLE: bool>(&mut self, n: u64) -> Result<usize, Self::Error> {
182        if USE_TABLE {
183            if let Some(len) = zeta_tables::write_table_be(self, n)? {
184                return Ok(len);
185            }
186        }
187        default_write_zeta(self, n, 3)
188    }
189}
190
191impl<B: BitWrite<LE>> ZetaWriteParam<LE> for B {
192    #[inline(always)]
193    fn write_zeta_param(&mut self, n: u64, k: usize) -> Result<usize, Self::Error> {
194        default_write_zeta(self, n, k)
195    }
196
197    #[inline(always)]
198    #[allow(clippy::collapsible_if)]
199    fn write_zeta3_param<const USE_TABLE: bool>(&mut self, n: u64) -> Result<usize, Self::Error> {
200        if USE_TABLE {
201            if let Some(len) = zeta_tables::write_table_le(self, n)? {
202                return Ok(len);
203            }
204        }
205        default_write_zeta(self, n, 3)
206    }
207}
208
209/// Default, internal non-table based implementation that works
210/// for any endianness.
211#[inline(always)]
212fn default_write_zeta<E: Endianness, B: BitWrite<E>>(
213    backend: &mut B,
214    mut n: u64,
215    k: usize,
216) -> Result<usize, B::Error> {
217    debug_assert!(k >= 1);
218    debug_assert!(n < u64::MAX);
219    n += 1;
220    let h = n.ilog2() as usize / k;
221    let l = 1 << (h * k);
222
223    debug_assert!(l <= n, "{} <= {}", l, n);
224
225    // Write the code
226    Ok(backend.write_unary(h as u64)?
227        + backend.write_minimal_binary(n - l, (l << k).wrapping_sub(l))?)
228}