use alloc::vec::Vec;
use p3_binary_field::TowerLevel;
use p3_matrix::Matrix;
use p3_matrix::dense::RowMajorMatrix;
use p3_maybe_rayon::prelude::*;
use p3_util::log2_strict_usize;
use crate::butterfly::ButterflyField;
use crate::encoder::padded_message_len;
use crate::lch::transform_stages;
const fn closed_layers(log_bits: usize) -> usize {
1 << log_bits
}
const NARROW_MESSAGE_BYTES: usize = 128 * 1024;
const PARALLEL_WIDEN_BYTES: usize = 4 * 1024 * 1024;
fn widen<S: TowerLevel, F: TowerLevel + From<S>>(values: &[S]) -> Vec<F> {
if size_of::<F>() * values.len() < PARALLEL_WIDEN_BYTES {
return values.iter().map(|&value| F::from(value)).collect();
}
values.par_iter().map(|&value| F::from(value)).collect()
}
fn split_ntt_batch<S, F>(mut mat: RowMajorMatrix<S>, head: usize) -> RowMajorMatrix<F>
where
S: ButterflyField,
F: ButterflyField + From<S>,
{
let width = mat.width();
let log_n = log2_strict_usize(mat.height());
assert!(log_n <= 1 << F::LOG_BITS, "domain exceeds field dimension");
assert!(
head <= closed_layers(S::LOG_BITS).min(log_n),
"the subfield is not closed under that many layers"
);
let low = log_n - head;
transform_stages::<S, false>(&mut mat.values, width << low, head, S::ZERO);
let mut values = widen::<S, F>(&mat.values);
transform_stages::<F, false>(&mut values, width, low, F::ZERO);
RowMajorMatrix::new(values, width)
}
#[must_use]
pub fn subfield_ntt_batch<S, F>(mat: RowMajorMatrix<S>) -> RowMajorMatrix<F>
where
S: ButterflyField,
F: ButterflyField + From<S>,
{
let log_n = log2_strict_usize(mat.height());
let head = if size_of::<S>() * mat.values.len() >= NARROW_MESSAGE_BYTES {
closed_layers(S::LOG_BITS).min(log_n)
} else {
0
};
split_ntt_batch(mat, head)
}
#[must_use]
pub fn subfield_encode_batch<S, F>(
mut message: RowMajorMatrix<S>,
log_inv_rate: usize,
) -> RowMajorMatrix<F>
where
S: ButterflyField,
F: ButterflyField + From<S>,
{
let padded_len = padded_message_len(message.values.len(), log_inv_rate);
let _ = log2_strict_usize(message.height());
message.values.resize(padded_len, S::ZERO);
subfield_ntt_batch(message)
}
#[cfg(test)]
mod tests {
use alloc::format;
use p3_binary_field::{
BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, Gf2, TowerLevel,
};
use p3_field::PrimeCharacteristicRing;
use p3_matrix::dense::RowMajorMatrix;
use proptest::prelude::*;
use super::{closed_layers, split_ntt_batch, subfield_encode_batch, subfield_ntt_batch, widen};
use crate::butterfly::ButterflyField;
use crate::domain::{domain_point, subspace_polynomial};
use crate::lch::LchNtt;
use crate::naive::NaiveAdditiveNtt;
use crate::traits::AdditiveNtt;
const WIDTHS: [usize; 4] = [1, 3, 4, 16];
fn matrix<S: TowerLevel>(log_n: usize, width: usize, seed: u64) -> RowMajorMatrix<S> {
RowMajorMatrix::new(
(0..(width << log_n))
.map(|i| {
let bits = seed
.wrapping_mul(0x9e37_79b9_7f4a_7c15)
.wrapping_add(i as u64 + 1)
.wrapping_mul(0xbf58_476d_1ce4_e5b9);
S::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
})
.collect(),
width,
)
}
fn check_against_the_wide_transform<S, F>(log_n: usize, width: usize, seed: u64)
where
S: ButterflyField,
F: ButterflyField + From<S>,
{
let message = matrix::<S>(log_n, width, seed);
let wide = RowMajorMatrix::new(widen::<S, F>(&message.values), width);
let expected = LchNtt::<F>::default().ntt_batch(wide);
for head in 0..=closed_layers(S::LOG_BITS).min(log_n) {
let label = format!("log_n={log_n} width={width} seed={seed} head={head}");
assert_eq!(
split_ntt_batch::<S, F>(message.clone(), head),
expected,
"{label}"
);
}
assert_eq!(subfield_ntt_batch::<S, F>(message), expected);
}
#[test]
fn a_byte_message_transforms_like_the_widened_one() {
for width in WIDTHS {
for log_n in 0..=10 {
check_against_the_wide_transform::<BinaryField8, BinaryField128>(log_n, width, 7);
check_against_the_wide_transform::<BinaryField8, BinaryField64>(log_n, width, 11);
check_against_the_wide_transform::<BinaryField8, BinaryField32>(log_n, width, 13);
}
}
}
#[test]
fn a_bit_message_transforms_like_the_widened_one() {
for width in WIDTHS {
for log_n in 0..=8 {
check_against_the_wide_transform::<Gf2, BinaryField128>(log_n, width, 17);
}
}
}
#[test]
fn a_two_and_four_byte_message_transforms_like_the_widened_one() {
for width in WIDTHS {
for log_n in 0..=10 {
check_against_the_wide_transform::<BinaryField16, BinaryField128>(log_n, width, 19);
check_against_the_wide_transform::<BinaryField32, BinaryField128>(log_n, width, 23);
}
}
}
#[test]
fn the_widest_subfield_is_the_alphabet_itself() {
for log_n in 0..=6 {
check_against_the_wide_transform::<BinaryField64, BinaryField64>(log_n, 3, 29);
}
}
#[test]
#[should_panic = "the subfield is not closed under that many layers"]
fn a_split_deeper_than_the_subfield_allows_is_refused() {
let message = matrix::<BinaryField8>(10, 1, 0);
let _ = split_ntt_batch::<BinaryField8, BinaryField128>(message, 9);
}
#[test]
fn a_message_below_the_narrow_threshold_runs_no_narrow_layer() {
let message = matrix::<BinaryField8>(6, 1, 41);
assert_eq!(
subfield_ntt_batch::<BinaryField8, BinaryField128>(message.clone()),
split_ntt_batch::<BinaryField8, BinaryField128>(message, 0)
);
}
fn check_against_the_oracle(log_n: usize, width: usize) {
let message = matrix::<BinaryField8>(log_n, width, 31);
let wide = RowMajorMatrix::new(widen::<_, BinaryField128>(&message.values), width);
let expected = NaiveAdditiveNtt::default().ntt_batch(wide);
let head = closed_layers(BinaryField8::LOG_BITS).min(log_n);
assert_eq!(
split_ntt_batch::<_, BinaryField128>(message, head),
expected,
"log_n={log_n} width={width}"
);
}
#[test]
fn a_byte_message_matches_the_reference_oracle() {
for width in [1usize, 3] {
for log_n in 0..=6 {
check_against_the_oracle(log_n, width);
}
}
}
#[test]
fn a_byte_message_past_the_closed_layers_matches_the_reference_oracle() {
for log_n in [9usize, 10] {
check_against_the_oracle(log_n, 1);
}
}
#[test]
fn the_closed_layer_count_is_the_subfield_bit_width() {
assert_eq!(closed_layers(Gf2::LOG_BITS), 1);
assert_eq!(closed_layers(BinaryField8::LOG_BITS), 8);
assert_eq!(closed_layers(BinaryField128::LOG_BITS), 128);
}
#[test]
fn the_closed_layers_really_keep_their_twiddles_in_the_subfield() {
const LOG_N: usize = 12;
let head = closed_layers(BinaryField8::LOG_BITS);
for layer in LOG_N - head..LOG_N {
for block in 0..1usize << (LOG_N - 1 - layer) {
let t = subspace_polynomial::<BinaryField128>(layer, BinaryField128::ZERO)
+ domain_point::<BinaryField128>(block << 1);
assert!(
t.to_repr() <= u128::from(u8::MAX),
"layer={layer} block={block}"
);
}
}
let t = domain_point::<BinaryField128>(1 << (head - 1) << 1);
assert!(
t.to_repr() > u128::from(u8::MAX),
"the head could be one layer deeper"
);
}
#[test]
fn encoding_pads_the_coefficients_at_the_narrow_width() {
for width in WIDTHS {
for log_inv_rate in 0..=3 {
let message = matrix::<BinaryField8>(4, width, 37);
let mut padded = widen::<_, BinaryField128>(&message.values);
padded.resize(padded.len() << log_inv_rate, BinaryField128::ZERO);
let expected = LchNtt::<BinaryField128>::default()
.ntt_batch(RowMajorMatrix::new(padded, width));
let actual = subfield_encode_batch::<_, BinaryField128>(message, log_inv_rate);
assert_eq!(actual, expected, "width={width} rate={log_inv_rate}");
}
}
}
#[test]
#[should_panic = "codeword length overflows usize"]
fn encoding_rejects_a_codeword_length_past_the_address_space() {
let message = matrix::<BinaryField8>(1, 1, 0);
let _ = subfield_encode_batch::<_, BinaryField128>(message, usize::BITS as usize - 1);
}
#[test]
#[should_panic = "domain exceeds field dimension"]
fn a_domain_past_the_alphabet_dimension_is_refused() {
let message = matrix::<BinaryField8>(9, 1, 0);
let _ = subfield_ntt_batch::<BinaryField8, BinaryField8>(message);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn random_subfield_messages_transform_like_the_widened_ones(
log_n in 0usize..=9,
width in 1usize..=5,
seed in any::<u64>(),
) {
check_against_the_wide_transform::<Gf2, BinaryField128>(log_n, width, seed);
check_against_the_wide_transform::<BinaryField8, BinaryField128>(log_n, width, seed);
check_against_the_wide_transform::<BinaryField16, BinaryField64>(log_n, width, seed);
}
}
}