pub const PVQ_V_N_MAX: u32 = 352;
pub const PVQ_V_K_MAX: u32 = 4096;
pub const PVQ_V_MAX: u64 = (1u64 << 32) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PvqVError {
NOutOfRange {
provided: u32,
max: u32,
},
KOutOfRange {
provided: u32,
max: u32,
},
OverflowsDecUintRange {
n: u32,
k: u32,
},
}
impl core::fmt::Display for PvqVError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
PvqVError::NOutOfRange { provided, max } => write!(
f,
"oxideav-opus: PVQ codebook-size N out of range: provided={provided}, max={max}"
),
PvqVError::KOutOfRange { provided, max } => write!(
f,
"oxideav-opus: PVQ codebook-size K out of range: provided={provided}, max={max}"
),
PvqVError::OverflowsDecUintRange { n, k } => write!(
f,
"oxideav-opus: PVQ codebook size V({n}, {k}) exceeds 2**32 − 1 \
(ec_dec_uint upper bound per RFC 6716 §4.1.5)"
),
}
}
}
impl std::error::Error for PvqVError {}
pub fn pvq_codebook_size(n: u32, k: u32) -> Result<u32, PvqVError> {
if n > PVQ_V_N_MAX {
return Err(PvqVError::NOutOfRange {
provided: n,
max: PVQ_V_N_MAX,
});
}
if k > PVQ_V_K_MAX {
return Err(PvqVError::KOutOfRange {
provided: k,
max: PVQ_V_K_MAX,
});
}
if k == 0 {
return Ok(1);
}
if n == 0 {
return Ok(0);
}
let k_usize = k as usize;
let mut row_prev: Vec<u64> = vec![0u64; k_usize + 1];
row_prev[0] = 1;
let mut row_curr: Vec<u64> = vec![0u64; k_usize + 1];
for _ in 1..=n {
row_curr[0] = 1;
for kk in 1..=k_usize {
let v_nm1_k = row_prev[kk];
let v_n_km1 = row_curr[kk - 1];
let v_nm1_km1 = row_prev[kk - 1];
let v = v_nm1_k + v_n_km1 + v_nm1_km1;
if v > PVQ_V_MAX {
return Err(PvqVError::OverflowsDecUintRange { n, k });
}
row_curr[kk] = v;
}
core::mem::swap(&mut row_prev, &mut row_curr);
}
Ok(row_prev[k_usize] as u32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v_zero_zero_is_one() {
assert_eq!(pvq_codebook_size(0, 0), Ok(1));
}
#[test]
fn v_n_zero_is_one_for_every_n() {
for n in 0..=PVQ_V_N_MAX {
assert_eq!(pvq_codebook_size(n, 0), Ok(1), "V({n}, 0)");
}
}
#[test]
fn v_zero_k_is_zero_for_every_positive_k() {
for k in 1..=PVQ_V_K_MAX {
assert_eq!(pvq_codebook_size(0, k), Ok(0), "V(0, {k})");
}
}
#[test]
fn v_one_k_is_two_for_every_positive_k() {
for k in 1..=200 {
assert_eq!(pvq_codebook_size(1, k), Ok(2), "V(1, {k})");
}
}
#[test]
fn v_n_one_is_two_n_for_every_positive_n() {
for n in 1..=PVQ_V_N_MAX {
let expected = 2 * n;
assert_eq!(pvq_codebook_size(n, 1), Ok(expected), "V({n}, 1)");
}
}
#[test]
fn recurrence_holds_for_small_sweep() {
for n in 1..=12u32 {
for k in 1..=12u32 {
let v_n_k = pvq_codebook_size(n, k).unwrap() as u64;
let v_nm1_k = pvq_codebook_size(n - 1, k).unwrap() as u64;
let v_n_km1 = pvq_codebook_size(n, k - 1).unwrap() as u64;
let v_nm1_km1 = pvq_codebook_size(n - 1, k - 1).unwrap() as u64;
assert_eq!(
v_n_k,
v_nm1_k + v_n_km1 + v_nm1_km1,
"recurrence at (N={n}, K={k})"
);
}
}
}
#[test]
fn v_2_2_equals_eight() {
assert_eq!(pvq_codebook_size(2, 2), Ok(8));
}
#[test]
fn v_3_3_equals_thirty_eight() {
assert_eq!(pvq_codebook_size(3, 3), Ok(38));
}
#[test]
fn v_4_2_equals_twenty_four() {
assert_eq!(pvq_codebook_size(4, 2), Ok(32));
}
#[test]
fn v_symmetric_under_n_k_swap_for_small_cases() {
assert_eq!(pvq_codebook_size(2, 3), Ok(12));
assert_eq!(pvq_codebook_size(3, 2), Ok(18));
assert_ne!(
pvq_codebook_size(2, 3).unwrap(),
pvq_codebook_size(3, 2).unwrap()
);
}
#[test]
fn monotone_non_decreasing_in_n_for_fixed_k() {
for k in 0..=12u32 {
let mut prev = pvq_codebook_size(0, k).unwrap();
for n in 1..=12u32 {
let v = pvq_codebook_size(n, k).unwrap();
assert!(v >= prev, "V({n}, {k}) = {v} < V({}, {k}) = {prev}", n - 1);
prev = v;
}
}
}
#[test]
fn monotone_non_decreasing_in_k_for_fixed_n_ge_two() {
for n in 2..=12u32 {
let mut prev = pvq_codebook_size(n, 0).unwrap();
for k in 1..=12u32 {
let v = pvq_codebook_size(n, k).unwrap();
assert!(v >= prev, "V({n}, {k}) = {v} < V({n}, {}) = {prev}", k - 1);
prev = v;
}
}
}
#[test]
fn overflow_guard_trips_when_recurrence_exceeds_2_pow_32_minus_1() {
let result = pvq_codebook_size(176, 176);
match result {
Err(PvqVError::OverflowsDecUintRange { n: 176, k: 176 }) => {}
other => panic!("expected OverflowsDecUintRange, got {other:?}"),
}
}
#[test]
fn overflow_guard_does_not_trip_on_values_just_under_the_ceiling() {
for k in 0..=100u32 {
let v = pvq_codebook_size(2, k).unwrap();
if k == 0 {
assert_eq!(v, 1);
} else {
assert_eq!(v, 4 * k, "V(2, {k})");
}
}
}
#[test]
fn rejects_n_above_pvq_v_n_max() {
let result = pvq_codebook_size(PVQ_V_N_MAX + 1, 5);
assert_eq!(
result,
Err(PvqVError::NOutOfRange {
provided: PVQ_V_N_MAX + 1,
max: PVQ_V_N_MAX,
})
);
}
#[test]
fn rejects_k_above_pvq_v_k_max() {
let result = pvq_codebook_size(10, PVQ_V_K_MAX + 1);
assert_eq!(
result,
Err(PvqVError::KOutOfRange {
provided: PVQ_V_K_MAX + 1,
max: PVQ_V_K_MAX,
})
);
}
#[test]
fn n_max_boundary_is_accepted() {
assert_eq!(pvq_codebook_size(PVQ_V_N_MAX, 0), Ok(1));
assert_eq!(pvq_codebook_size(PVQ_V_N_MAX, 1), Ok(2 * PVQ_V_N_MAX));
}
#[test]
fn k_max_boundary_is_accepted_for_small_n() {
assert_eq!(pvq_codebook_size(1, PVQ_V_K_MAX), Ok(2));
assert_eq!(pvq_codebook_size(2, PVQ_V_K_MAX), Ok(4 * PVQ_V_K_MAX));
}
#[test]
fn pvq_v_n_max_is_three_hundred_fifty_two() {
assert_eq!(PVQ_V_N_MAX, 352);
}
#[test]
fn pvq_v_k_max_is_four_thousand_ninety_six() {
assert_eq!(PVQ_V_K_MAX, 4096);
}
#[test]
fn pvq_v_max_is_two_pow_thirty_two_minus_one() {
assert_eq!(PVQ_V_MAX, (1u64 << 32) - 1);
assert_eq!(PVQ_V_MAX, 4_294_967_295);
}
#[test]
fn display_messages_mention_the_failing_input() {
let n_err = PvqVError::NOutOfRange {
provided: PVQ_V_N_MAX + 5,
max: PVQ_V_N_MAX,
};
let n_msg = format!("{n_err}");
assert!(n_msg.contains(&format!("{}", PVQ_V_N_MAX + 5)));
assert!(n_msg.contains(&format!("{PVQ_V_N_MAX}")));
let k_err = PvqVError::KOutOfRange {
provided: PVQ_V_K_MAX + 1,
max: PVQ_V_K_MAX,
};
let k_msg = format!("{k_err}");
assert!(k_msg.contains(&format!("{}", PVQ_V_K_MAX + 1)));
let o_err = PvqVError::OverflowsDecUintRange { n: 200, k: 200 };
let o_msg = format!("{o_err}");
assert!(o_msg.contains("V(200, 200)"));
assert!(o_msg.contains("2**32"));
}
#[test]
fn known_v_values_against_hand_computed_table() {
let table: [[u32; 7]; 7] = [
[1, 0, 0, 0, 0, 0, 0],
[1, 2, 2, 2, 2, 2, 2],
[1, 4, 8, 12, 16, 20, 24],
[1, 6, 18, 38, 66, 102, 146],
[1, 8, 32, 88, 192, 360, 608],
[1, 10, 50, 170, 450, 1002, 1970],
[1, 12, 72, 292, 912, 2364, 5336],
];
for (n, row) in table.iter().enumerate() {
for (k, &expected) in row.iter().enumerate() {
assert_eq!(
pvq_codebook_size(n as u32, k as u32),
Ok(expected),
"V({n}, {k})"
);
}
}
}
}