use feanor_math::algorithms::linsolve::LinSolveRingStore;
use feanor_math::homomorphism::*;
use feanor_math::matrix::OwnedMatrix;
use feanor_math::ring::*;
use feanor_math::rings::zn::*;
use feanor_math::rings::extension::FreeAlgebraStore;
use feanor_math::primitive_int::*;
use feanor_math::seq::VectorFn;
use matmul::MatmulTransform;
use tracing::instrument;
use crate::number_ring::hypercube::structure::HypercubeStructure;
use crate::number_ring::hypercube::isomorphism::*;
use crate::number_ring::*;
use crate::cyclotomic::*;
use crate::lintransform::*;
#[instrument(skip_all)]
fn dwt1d_matrix(H: &HypercubeStructure, slot_ring: &SlotRingOver<Zn>, dim_index: usize, zeta_powertable: &PowerTable<&SlotRingOver<Zn>>) -> OwnedMatrix<El<SlotRingOver<Zn>>> {
assert!(H.is_tensor_product_compatible());
let Gal = H.galois_group();
let ZZ_to_Zn = Gal.underlying_ring().can_hom(&StaticRing::<i64>::RING).unwrap();
OwnedMatrix::from_fn(H.m(dim_index), H.m(dim_index), |i, j| {
let exponent = Gal.underlying_ring().prod([
Gal.to_ring_el(H.map_1d(dim_index, -(i as i64))),
ZZ_to_Zn.map(j as i64),
ZZ_to_Zn.map(H.n() as i64 / H.factor_of_n(dim_index).unwrap())
]);
return slot_ring.clone_el(&*zeta_powertable.get_power(Gal.underlying_ring().smallest_lift(exponent)));
})
}
#[instrument(skip_all)]
fn dwt1d<'a, NumberRing>(H: &DefaultHypercube<NumberRing>, dim_index: usize, zeta_powertable: &PowerTable<&SlotRingOver<Zn>>) -> Vec<MatmulTransform<NumberRing>>
where NumberRing: HECyclotomicNumberRing + Clone
{
if H.hypercube().m(dim_index) == 1{
Vec::new()
} else {
let A = dwt1d_matrix(H.hypercube(), H.slot_ring(), dim_index, zeta_powertable);
vec![MatmulTransform::matmul1d(
H,
dim_index,
|i, j, _idxs| H.slot_ring().clone_el(A.at(i, j))
)]
}
}
#[instrument(skip_all)]
fn dwt1d_inv<'a, NumberRing>(H: &DefaultHypercube<NumberRing>, dim_index: usize, zeta_powertable: &PowerTable<&SlotRingOver<Zn>>) -> Vec<MatmulTransform<NumberRing>>
where NumberRing: HECyclotomicNumberRing + Clone
{
if H.hypercube().m(dim_index) == 1{
Vec::new()
} else {
let mut A = dwt1d_matrix(H.hypercube(), H.slot_ring(), dim_index, zeta_powertable);
let mut rhs = OwnedMatrix::identity(H.hypercube().m(dim_index), H.hypercube().m(dim_index), H.slot_ring());
let mut sol = OwnedMatrix::zero(H.hypercube().m(dim_index), H.hypercube().m(dim_index), H.slot_ring());
<_ as LinSolveRingStore>::solve_right(H.slot_ring(), A.data_mut(), rhs.data_mut(), sol.data_mut()).assert_solved();
vec![MatmulTransform::matmul1d(
H,
dim_index,
|i, j, _idxs| H.slot_ring().clone_el(sol.at(i, j))
)]
}
}
#[instrument(skip_all)]
fn slots_to_powcoeffs_fat_fst_step<NumberRing>(H: &DefaultHypercube<NumberRing>, dim_index: usize, zeta_powertable: &PowerTable<&SlotRingOver<Zn>>) -> OwnedMatrix<El<Zn>>
where NumberRing: HECyclotomicNumberRing + Clone
{
let Gal = H.galois_group();
let ZZ_to_Gal = Gal.underlying_ring().can_hom(&StaticRing::<i64>::RING).unwrap();
OwnedMatrix::from_fn(H.hypercube().m(dim_index) * H.slot_ring().rank(), H.hypercube().m(dim_index) * H.slot_ring().rank(), |row_idx, col_idx| {
let i = row_idx / H.slot_ring().rank();
let k = row_idx % H.slot_ring().rank();
let j = col_idx / H.slot_ring().rank();
let l = col_idx % H.slot_ring().rank();
let exponent = Gal.underlying_ring().prod([
Gal.to_ring_el(H.hypercube().map_1d(0, -(i as i64))),
ZZ_to_Gal.map(H.ring().n() as i64 / H.hypercube().factor_of_n(0).unwrap()),
ZZ_to_Gal.map((j + l * H.hypercube().m(0)) as i64)
]);
return H.slot_ring().wrt_canonical_basis(&*zeta_powertable.get_power(Gal.underlying_ring().smallest_positive_lift(exponent))).at(k);
})
}
#[instrument(skip_all)]
pub fn slots_to_powcoeffs_fat<NumberRing>(H: &DefaultHypercube<NumberRing>) -> Vec<MatmulTransform<NumberRing>>
where NumberRing: HECyclotomicNumberRing + Clone
{
assert!(H.hypercube().is_tensor_product_compatible());
assert!(H.ring().n() % 2 != 0);
assert!(H.hypercube().is_tensor_product_compatible());
let mut result = Vec::new();
let zeta_powertable = PowerTable::new(H.slot_ring(), H.slot_ring().canonical_gen(), H.ring().n() as usize);
let fst_step_matrix = slots_to_powcoeffs_fat_fst_step(H, 0, &zeta_powertable);
result.push(MatmulTransform::blockmatmul1d(
H,
0,
|(i, k), (j, l), _idxs| H.slot_ring().base_ring().clone_el(fst_step_matrix.at(i * H.slot_ring().rank() + k, j * H.slot_ring().rank() + l))
));
for i in 1..H.hypercube().dim_count() {
result.extend(dwt1d(H, i, &zeta_powertable));
}
return result;
}
#[instrument(skip_all)]
pub fn powcoeffs_to_slots_fat<NumberRing>(H: &DefaultHypercube<NumberRing>) -> Vec<MatmulTransform<NumberRing>>
where NumberRing: HECyclotomicNumberRing + Clone
{
assert!(H.ring().n() % 2 != 0);
assert!(H.hypercube().is_tensor_product_compatible());
let mut result = Vec::new();
let zeta_powertable = PowerTable::new(H.slot_ring(), H.slot_ring().canonical_gen(), H.ring().n() as usize);
for i in (1..H.hypercube().dim_count()).rev() {
result.extend(dwt1d_inv(H, i, &zeta_powertable));
}
let mut A = slots_to_powcoeffs_fat_fst_step(H, 0, &zeta_powertable);
let mut rhs = OwnedMatrix::identity(H.hypercube().m(0) * H.slot_ring().rank(), H.hypercube().m(0) * H.slot_ring().rank(), H.slot_ring().base_ring());
let mut sol = OwnedMatrix::zero(H.hypercube().m(0) * H.slot_ring().rank(), H.hypercube().m(0) * H.slot_ring().rank(), H.slot_ring().base_ring());
<_ as LinSolveRingStore>::solve_right(H.slot_ring().base_ring(), A.data_mut(), rhs.data_mut(), sol.data_mut()).assert_solved();
result.push(MatmulTransform::blockmatmul1d(
H,
0,
|(i, k), (j, l), _idxs| H.slot_ring().base_ring().clone_el(sol.at(i * H.slot_ring().rank() + k, j * H.slot_ring().rank() + l))
));
return result;
}
#[instrument(skip_all)]
pub fn slots_to_powcoeffs_thin<NumberRing>(H: &DefaultHypercube<NumberRing>) -> Vec<MatmulTransform<NumberRing>>
where NumberRing: HECyclotomicNumberRing + Clone
{
assert!(H.ring().n() % 2 != 0);
assert!(H.hypercube().is_tensor_product_compatible());
let zeta_powertable = PowerTable::new(H.slot_ring(), H.slot_ring().canonical_gen(), H.ring().n() as usize);
let mut result = Vec::new();
for i in 0..H.hypercube().dim_count() {
result.extend(dwt1d(H, i, &zeta_powertable));
}
return result;
}
#[instrument(skip_all)]
pub fn powcoeffs_to_slots_thin<NumberRing>(H: &DefaultHypercube<NumberRing>) -> Vec<MatmulTransform<NumberRing>>
where NumberRing: HECyclotomicNumberRing + Clone
{
let mut result = powcoeffs_to_slots_fat(H);
let discard_unused = MatmulTransform::blockmatmul0d(
H,
|i, j, _idxs| if j == 0 && i == 0 { H.slot_ring().base_ring().one() } else { H.slot_ring().base_ring().zero() }
);
let last_step = result.last_mut().unwrap();
*last_step = discard_unused.compose(last_step, H);
return result;
}
#[cfg(test)]
use crate::ring_literal;
#[cfg(test)]
use feanor_math::assert_el_eq;
#[cfg(test)]
use crate::number_ring::quotient::*;
#[cfg(test)]
use crate::number_ring::odd_cyclotomic::CompositeCyclotomicNumberRing;
#[test]
fn test_slots_to_powcoeffs_thin() {
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 7), Zn::new(11));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(5 * 7), 11);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
let mut current = ring_literal(&ring, &[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
for transform in slots_to_powcoeffs_thin(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = ring.sum([0, 5, 7, 12, 14, 19, 21, 26].into_iter().map(|k| ring.pow(ring.canonical_gen(), k)));
assert_el_eq!(ring, expected, current);
assert_eq!(7, H.hypercube().factor_of_n(0).unwrap());
assert_eq!(2, H.hypercube().m(0));
assert_eq!(5, H.hypercube().factor_of_n(1).unwrap());
assert_eq!(4, H.hypercube().m(1));
let mut current = H.from_slot_values((1..9).map(|n| H.slot_ring().int_hom().map(n)));
for transform in slots_to_powcoeffs_thin(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let ring_ref = ˚
let expected = ring.sum((0..2).flat_map(|i| (0..4).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), i * 5 + j * 7), ring_ref.int_hom().map((1 + j + i * 4) as i32)))));
assert_el_eq!(ring, expected, current);
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 7), Zn::new(71));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(5 * 7), 71);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
let mut current = H.from_slot_values((1..25).map(|n| H.slot_ring().int_hom().map(n)));
for transform in slots_to_powcoeffs_thin(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let ring_ref = ˚
let expected = ring.sum((0..4).flat_map(|i| (0..6).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), i * 7 + j * 5), ring_ref.int_hom().map((1 + j + i * 6) as i32)))));
assert_el_eq!(ring, expected, current);
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(11, 31), Zn::new(8));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(11 * 31), 2);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
let mut current = H.from_slot_values((1..=30).map(|n| H.slot_ring().int_hom().map(n)));
for transform in slots_to_powcoeffs_thin(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = ring.sum((0..30).map(|j| ring.mul(ring.pow(ring.canonical_gen(), j * 11), ring.int_hom().map((j + 1) as i32))));
assert_el_eq!(ring, expected, current);
}
#[test]
fn test_powcoeffs_to_slots_thin() {
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 7), Zn::new(11));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(5 * 7), 11);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
assert_eq!(7, H.hypercube().factor_of_n(0).unwrap());
assert_eq!(2, H.hypercube().m(0));
assert_eq!(5, H.hypercube().factor_of_n(1).unwrap());
assert_eq!(4, H.hypercube().m(1));
let mut current = ring_literal(&ring, &[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
for transform in powcoeffs_to_slots_thin(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = H.from_slot_values([H.slot_ring().one()].into_iter().chain((2..9).map(|_| H.slot_ring().zero())));
assert_el_eq!(ring, expected, current);
let ring_ref = ˚
let mut current = ring.sum((0..6).flat_map(|i| (0..4).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), i * 5 + j * 7), ring_ref.int_hom().map((1 + j + i * 4) as i32)))));
for transform in powcoeffs_to_slots_thin(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = H.from_slot_values([1, 2, 3, 4, 5, 6, 7, 8].into_iter().map(|n| H.slot_ring().int_hom().map(n)));
assert_el_eq!(ring, expected, current);
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 7), Zn::new(71));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(5 * 7), 71);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
let ring_ref = ˚
let mut current = ring.sum((0..4).flat_map(|i| (0..6).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), i * 7 + j * 5), ring_ref.int_hom().map((1 + j + i * 6) as i32)))));
for transform in powcoeffs_to_slots_thin(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = H.from_slot_values((1..25).map(|n| H.slot_ring().int_hom().map(n)));
assert_el_eq!(ring, expected, current);
}
#[test]
fn test_slots_to_powcoeffs_fat() {
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 7), Zn::new(11));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(5 * 7), 11);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
let mut current = ring_literal(&ring, &[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
for transform in slots_to_powcoeffs_fat(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = ring.sum([0, 5, 7, 12, 14, 19, 21, 26].into_iter().map(|k| ring.pow(ring.canonical_gen(), k)));
assert_el_eq!(ring, expected, current);
assert_eq!(7, H.hypercube().factor_of_n(0).unwrap());
assert_eq!(2, H.hypercube().m(0));
assert_eq!(5, H.hypercube().factor_of_n(1).unwrap());
assert_eq!(4, H.hypercube().m(1));
let mut current = H.from_slot_values((1..9).map(|n| H.slot_ring().int_hom().map(n)));
for transform in slots_to_powcoeffs_fat(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let ring_ref = ˚
let expected = ring.sum((0..2).flat_map(|i| (0..4).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), i * 5 + j * 7), ring_ref.int_hom().map((1 + j + i * 4) as i32)))));
assert_el_eq!(ring, expected, current);
let hom = H.slot_ring().base_ring().int_hom();
let mut current = H.from_slot_values((1..9).map(|n| H.slot_ring().from_canonical_basis([hom.map(n), hom.map(n + 100), hom.map(n + 200)])));
for transform in slots_to_powcoeffs_fat(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let ring_ref = ˚
let expected = ring.sum((0..3).flat_map(|k| (0..2).flat_map(move |i| (0..4).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), (i + k * 2) * 5 + j * 7), ring_ref.int_hom().map((1 + j + i * 4 + k * 100) as i32))))));
assert_el_eq!(ring, expected, current);
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 7), Zn::new(71));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(5 * 7), 71);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
assert_eq!(5, H.hypercube().factor_of_n(0).unwrap());
assert_eq!(4, H.hypercube().m(0));
assert_eq!(7, H.hypercube().factor_of_n(1).unwrap());
assert_eq!(6, H.hypercube().m(1));
let mut current = H.from_slot_values((1..25).map(|n| H.slot_ring().int_hom().map(n)));
for transform in slots_to_powcoeffs_fat(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let ring_ref = ˚
let expected = ring.sum((0..4).flat_map(|i| (0..6).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), i * 7 + j * 5), ring_ref.int_hom().map((1 + j + i * 6) as i32)))));
assert_el_eq!(ring, expected, current);
}
#[test]
fn test_powcoeffs_to_slots_fat() {
let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 7), Zn::new(11));
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(5 * 7), 11);
let H = HypercubeIsomorphism::new::<false>(&ring, hypercube);
assert_eq!(7, H.hypercube().factor_of_n(0).unwrap());
assert_eq!(2, H.hypercube().m(0));
assert_eq!(5, H.hypercube().factor_of_n(1).unwrap());
assert_eq!(4, H.hypercube().m(1));
let ring_ref = ˚
let mut current = ring.sum((0..6).flat_map(|i| (0..4).map(move |j| ring_ref.mul(ring_ref.pow(ring_ref.canonical_gen(), i * 5 + j * 7), ring_ref.int_hom().map((1 + j + i * 4) as i32)))));
for transform in powcoeffs_to_slots_fat(&H) {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = H.from_slot_values([1, 2, 3, 4, 5, 6, 7, 8].into_iter().map(|n| H.slot_ring().from_canonical_basis([n, n + 8, n + 16].into_iter().map(|m| H.slot_ring().base_ring().int_hom().map(m)))));
assert_el_eq!(ring, expected, current);
}
#[test]
#[ignore]
fn test_powcoeffs_to_slots_fat_large() {
let ring = RingValue::from(NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(337, 127), Zn::new(65536)).into());
let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(337 * 127), 2);
let H = HypercubeIsomorphism::new::<true>(&ring, hypercube);
assert_eq!(337, H.hypercube().factor_of_n(0).unwrap());
assert_eq!(16, H.hypercube().m(0));
assert_eq!(127, H.hypercube().factor_of_n(1).unwrap());
assert_eq!(126, H.hypercube().m(1));
let transform = powcoeffs_to_slots_fat(&H);
let ring_ref = ˚
let mut current = ring.pow(ring_ref.canonical_gen(), 7 * 127 + 2 * 337);
for transform in &transform {
current = ring.get_ring().compute_linear_transform(&H, ¤t, &transform);
}
let expected = H.from_slot_values(H.hypercube().hypercube_iter(|idxs| if idxs[0] == 7 && idxs[1] == 2 {
H.slot_ring().one()
} else {
H.slot_ring().zero()
}));
assert_el_eq!(ring, expected, current);
}