use core::iter::Chain;
use itertools::Itertools;
use std_shims::Vec;
use crate::core::circle::{
CirclePoint, CirclePointIndex, Coset, CosetIterator, M31_CIRCLE_LOG_ORDER,
};
use crate::core::fields::m31::BaseField;
pub const MAX_CIRCLE_DOMAIN_LOG_SIZE: u32 = M31_CIRCLE_LOG_ORDER - 1;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CircleDomain {
pub half_coset: Coset,
}
impl CircleDomain {
pub const fn new(half_coset: Coset) -> Self {
Self { half_coset }
}
pub fn iter(&self) -> CircleDomainIterator {
self.half_coset
.iter()
.chain(self.half_coset.conjugate().iter())
}
pub fn iter_indices(&self) -> CircleDomainIndexIterator {
self.half_coset
.iter_indices()
.chain(self.half_coset.conjugate().iter_indices())
}
pub const fn size(&self) -> usize {
1 << self.log_size()
}
pub const fn log_size(&self) -> u32 {
self.half_coset.log_size + 1
}
pub fn at(&self, i: usize) -> CirclePoint<BaseField> {
self.index_at(i).to_point()
}
pub fn index_at(&self, i: usize) -> CirclePointIndex {
if i < self.half_coset.size() {
self.half_coset.index_at(i)
} else {
-self.half_coset.index_at(i - self.half_coset.size())
}
}
pub fn is_canonic(&self) -> bool {
self.half_coset.initial_index * 4 == self.half_coset.step_size
}
pub fn split(&self, log_parts: u32) -> (CircleDomain, Vec<CirclePointIndex>) {
assert!(log_parts <= self.half_coset.log_size);
let subdomain = CircleDomain::new(Coset::new(
self.half_coset.initial_index,
self.half_coset.log_size - log_parts,
));
let shifts = (0..1 << log_parts)
.map(|i| self.half_coset.step_size * i)
.collect_vec();
(subdomain, shifts)
}
pub fn shift(&self, shift: CirclePointIndex) -> CircleDomain {
CircleDomain::new(self.half_coset.shift(shift))
}
}
impl IntoIterator for CircleDomain {
type Item = CirclePoint<BaseField>;
type IntoIter = CircleDomainIterator;
fn into_iter(self) -> CircleDomainIterator {
self.iter()
}
}
pub type CircleDomainIterator =
Chain<CosetIterator<CirclePoint<BaseField>>, CosetIterator<CirclePoint<BaseField>>>;
type CircleDomainIndexIterator =
Chain<CosetIterator<CirclePointIndex>, CosetIterator<CirclePointIndex>>;
#[cfg(test)]
mod tests {
use itertools::Itertools;
use std_shims::Vec;
use super::CircleDomain;
use crate::core::circle::{CirclePointIndex, Coset};
use crate::core::poly::circle::CanonicCoset;
#[test]
fn test_circle_domain_iterator() {
let domain = CircleDomain::new(Coset::new(CirclePointIndex::generator(), 2));
for (i, point) in domain.iter().enumerate() {
if i < 4 {
assert_eq!(
point,
(CirclePointIndex::generator() + CirclePointIndex::subgroup_gen(2) * i)
.to_point()
);
} else {
assert_eq!(
point,
(-(CirclePointIndex::generator() + CirclePointIndex::subgroup_gen(2) * i))
.to_point()
);
}
}
}
#[test]
fn is_canonic_invalid_domain() {
let half_coset = Coset::new(CirclePointIndex::generator(), 4);
let not_canonic_domain = CircleDomain::new(half_coset);
assert!(!not_canonic_domain.is_canonic());
}
#[test]
fn test_at_circle_domain() {
let domain = CanonicCoset::new(7).circle_domain();
let half_domain_size = domain.size() / 2;
for i in 0..half_domain_size {
assert_eq!(domain.index_at(i), -domain.index_at(i + half_domain_size));
assert_eq!(domain.at(i), domain.at(i + half_domain_size).conjugate());
}
}
#[test]
fn test_domain_split() {
let domain = CanonicCoset::new(5).circle_domain();
let (subdomain, shifts) = domain.split(2);
let domain_points = domain.iter().collect::<Vec<_>>();
let points_for_each_domain = shifts
.iter()
.map(|&shift| (subdomain.shift(shift)).iter().collect_vec())
.collect::<Vec<_>>();
let extended_points = (0..(1 << 3))
.flat_map(|point_ind| {
(0..(1 << 2))
.map(|shift_ind| points_for_each_domain[shift_ind][point_ind])
.collect_vec()
})
.collect_vec();
assert_eq!(domain_points, extended_points);
}
}