#[cfg(feature = "alloc")]
mod frobenius;
mod ladder;
#[cfg(feature = "alloc")]
mod prepared;
pub(crate) mod quadratic;
#[cfg(feature = "alloc")]
pub use frobenius::Frobenius;
#[cfg(feature = "alloc")]
pub use prepared::{PreparedEmbedding, PreparedIsomorphism};
use core::marker::PhantomData;
use super::binary::description::apply;
use super::extension::descriptor::{Descriptor, Node};
use super::{Elem, Field, FieldElem, PrimeIdentity};
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EmbeddingError {
IncompatibleDegree {
source: u32,
target: u32,
},
EqualDegree {
degree: u32,
},
NoCanonicalMap,
InvalidImages,
}
impl core::fmt::Display for EmbeddingError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
EmbeddingError::IncompatibleDegree { source, target } => write!(
formatter,
"source degree {source} does not divide target degree {target}"
),
EmbeddingError::EqualDegree { degree } => write!(
formatter,
"source and target share degree {degree}: use Isomorphism"
),
EmbeddingError::NoCanonicalMap => {
formatter.write_str("no canonical map between the presentations")
}
EmbeddingError::InvalidImages => {
formatter.write_str("generator images fail the transported relations")
}
}
}
}
impl core::error::Error for EmbeddingError {}
#[derive(Clone, Copy, PartialEq, Eq)]
enum LevelKind {
Extension,
Polynomial {
degree: u8,
full: u128,
},
Basis {
poly: u8,
},
}
#[derive(Clone, Copy)]
struct Level {
kind: LevelKind,
node: u8,
base: Option<u8>,
base_degree: u32,
span_offset: usize,
span_len: usize,
}
fn collect_levels(
descriptor: &Descriptor,
root: u8,
offset: usize,
out: &mut [Level; 8],
count: &mut usize,
) {
let push = |level: Level, out: &mut [Level; 8], count: &mut usize| {
assert!(*count < 8, "a presentation holds at most eight levels");
out[*count] = level;
*count += 1;
};
match descriptor.node(root) {
Node::Unused | Node::Prime { .. } => {}
Node::Polynomial { degree, full } => {
if *degree > 1 {
push(
Level {
kind: LevelKind::Polynomial {
degree: *degree,
full: *full,
},
node: root,
base: None,
base_degree: 1,
span_offset: offset,
span_len: usize::from(*degree),
},
out,
count,
);
}
}
Node::Basis { base, .. } => {
let span_len = super::extension::arithmetic::degree_of(descriptor, *base) as usize;
push(
Level {
kind: LevelKind::Basis { poly: *base },
node: root,
base: None,
base_degree: 1,
span_offset: offset,
span_len,
},
out,
count,
);
}
Node::Extension {
base, base_degree, ..
} => {
collect_levels(descriptor, *base, offset, out, count);
let span_len = (*base_degree as usize) * level_relative(descriptor, root);
push(
Level {
kind: LevelKind::Extension,
node: root,
base: Some(*base),
base_degree: *base_degree,
span_offset: offset,
span_len,
},
out,
count,
);
}
}
}
fn levels(descriptor: &Descriptor) -> ([Level; 8], usize) {
let mut out = [Level {
kind: LevelKind::Extension,
node: u8::MAX,
base: None,
base_degree: 0,
span_offset: usize::MAX,
span_len: 0,
}; 8];
let mut count = 0;
collect_levels(descriptor, descriptor.root_index(), 0, &mut out, &mut count);
(out, count)
}
fn level_of(levels: &[Level; 8], count: usize, node: u8) -> Option<usize> {
let mut i = 0;
while i < count {
if levels[i].node == node {
return Some(i);
}
i += 1;
}
None
}
fn one_coordinates(descriptor: &Descriptor, root: u8, out: &mut [u64]) {
match descriptor.node(root) {
Node::Unused | Node::Prime { .. } => {
out[0] = 1;
}
Node::Polynomial { degree, .. } => {
out[0] = 1;
let mut i = 1;
while i < usize::from(*degree) {
out[i] = 0;
i += 1;
}
}
Node::Basis { .. } => {
let mut poly = [0u64; 8];
poly[0] = 1;
let word = pack_bits(&poly);
let Node::Basis { from_base, .. } = descriptor.node(root) else {
panic!("a basis level reads its own node");
};
unpack_bits(apply(*from_base, word), out);
}
Node::Extension {
base, base_degree, ..
} => {
one_coordinates(descriptor, *base, out);
let mut i = *base_degree as usize;
while i < out.len() {
out[i] = 0;
i += 1;
}
}
}
}
fn generator_coordinates(descriptor: &Descriptor, level: &Level, out: &mut [u64]) {
let span = level.span_offset..level.span_offset + level.span_len;
let mut i = span.start;
while i < span.end {
out[i] = 0;
i += 1;
}
match level.kind {
LevelKind::Extension => {
let mut one = [0u64; 128];
one_coordinates(
descriptor,
level.base.expect("an extension level extends a base"),
&mut one[..level.base_degree as usize],
);
let mut i = 0;
while i < level.base_degree as usize {
out[span.start + level.base_degree as usize + i] = one[i];
i += 1;
}
}
LevelKind::Polynomial { .. } => {
out[span.start + 1] = 1;
}
LevelKind::Basis { .. } => {
let mut poly_coords = [0u64; 8];
poly_coords[1] = 1;
let Node::Basis { from_base, .. } = descriptor.node(level.node) else {
panic!("a basis level reads its own node");
};
unpack_bits(
apply(*from_base, pack_bits(&poly_coords)),
&mut out[span.clone()],
);
}
}
}
fn pack_bits(coordinates: &[u64]) -> u64 {
let mut word = 0u64;
let mut i = 0;
while i < coordinates.len() {
word |= (coordinates[i] & 1) << i;
i += 1;
}
word
}
fn unpack_bits(word: u64, out: &mut [u64]) {
let mut i = 0;
while i < out.len() {
out[i] = (word >> i) & 1;
i += 1;
}
}
fn embed_coordinates<T: Field>(
descriptor: &Descriptor,
at: u8,
coords: &[u64],
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
) -> Elem<T> {
match descriptor.node(at) {
Node::Unused | Node::Prime { .. } => {
Elem::<T>::from_residue(coords[0]).expect("a prime coordinate sits below the target")
}
Node::Polynomial { degree, .. } => {
if *degree == 1 {
Elem::<T>::from_residue(coords[0])
.expect("a prime coordinate sits below the target")
} else {
let image =
images[level_of(levels, count, at).expect("a polynomial level owns an image")];
horner(image, coords)
}
}
Node::Basis { .. } => {
let Node::Basis { to_base, .. } = descriptor.node(at) else {
panic!("a basis level reads its own node");
};
let mut poly = [0u64; 8];
unpack_bits(apply(*to_base, pack_bits(coords)), &mut poly);
let image = images[level_of(levels, count, at).expect("a basis level owns an image")];
horner(image, &poly)
}
Node::Extension {
base, base_degree, ..
} => {
let image =
images[level_of(levels, count, at).expect("an extension level owns an image")];
let span = *base_degree as usize;
let relative = coords.len() / span;
let mut acc = Elem::<T>::ZERO;
let mut j = relative;
while j > 0 {
j -= 1;
let component = embed_coordinates(
descriptor,
*base,
&coords[j * span..(j + 1) * span],
levels,
count,
images,
);
acc = acc * image + component;
}
acc
}
}
}
fn horner<T: Field>(image: Elem<T>, coefficients: &[u64]) -> Elem<T> {
let mut acc = Elem::<T>::ZERO;
let mut i = coefficients.len();
while i > 0 {
i -= 1;
let coefficient = Elem::<T>::from_residue(coefficients[i])
.expect("a prime coordinate sits below the target");
acc = acc * image + coefficient;
}
acc
}
fn relations_hold<T: Field>(
descriptor: &Descriptor,
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
) -> bool {
let mut l = 0;
while l < count {
let relative = level_relative(descriptor, levels[l].node);
let mut acc = Elem::<T>::ONE;
let mut j = relative;
while j > 0 {
j -= 1;
acc = acc * images[l]
+ transport_coefficient(descriptor, levels[l].node, levels, count, images, j);
}
if acc != Elem::<T>::ZERO {
return false;
}
l += 1;
}
true
}
fn restrict_coordinates<T: Field>(
descriptor: &Descriptor,
at: u8,
value: Elem<T>,
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
out: &mut [u64],
) -> bool {
if value.pow(order_of(descriptor, at)) != value {
return false;
}
match descriptor.node(at) {
Node::Unused | Node::Prime { .. } => {
let Some(residue) = value.to_residue() else {
return false;
};
out[0] = residue;
true
}
Node::Polynomial { degree, .. } => {
if *degree == 1 {
let Some(residue) = value.to_residue() else {
return false;
};
out[0] = residue;
return true;
}
let mut poly = [0u64; 8];
if out.len() > 8
|| !restrict_flat(
descriptor,
at,
value,
levels,
count,
images,
&mut poly[..out.len()],
)
{
return false;
}
out.copy_from_slice(&poly[..out.len()]);
true
}
Node::Basis { .. } => {
let mut poly = [0u64; 8];
let relative = out.len();
if relative > 8
|| !restrict_flat(
descriptor,
at,
value,
levels,
count,
images,
&mut poly[..relative],
)
{
return false;
}
let Node::Basis { from_base, .. } = descriptor.node(at) else {
panic!("a basis level reads its own node");
};
unpack_bits(apply(*from_base, pack_bits(&poly[..relative])), out);
true
}
Node::Extension {
base, base_degree, ..
} => {
let span = *base_degree as usize;
let relative = out.len() / span;
let mut i = 0;
while i < relative {
let Some(component) =
restrict_component(descriptor, at, *base, value, i, levels, count, images)
else {
return false;
};
let mut chunk = [0u64; 128];
if !restrict_coordinates(
descriptor,
*base,
component,
levels,
count,
images,
&mut chunk[..span],
) {
return false;
}
out[i * span..(i + 1) * span].copy_from_slice(&chunk[..span]);
i += 1;
}
true
}
}
}
#[allow(clippy::too_many_arguments)]
fn restrict_component<T: Field>(
descriptor: &Descriptor,
at: u8,
base: u8,
value: Elem<T>,
index: usize,
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
) -> Option<Elem<T>> {
let sub = order_of(descriptor, base);
let image = images[level_of(levels, count, at).expect("a restricted level owns an image")];
let relative = level_relative(descriptor, at);
let mut trace = Elem::<T>::ZERO;
let mut power = value * dual_element(descriptor, at, image, levels, count, images, index)?;
let mut k = 0;
while k < relative {
trace += power;
power = power.pow(sub);
k += 1;
}
Some(trace)
}
#[allow(clippy::too_many_arguments)]
fn restrict_flat<T: Field>(
descriptor: &Descriptor,
at: u8,
value: Elem<T>,
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
out: &mut [u64],
) -> bool {
let prime = u128::from(super::extension::arithmetic::characteristic_of(
descriptor, at,
));
let image = images[level_of(levels, count, at).expect("a restricted level owns an image")];
let mut i = 0;
while i < out.len() {
let Some(dual) = dual_element(descriptor, at, image, levels, count, images, i) else {
return false;
};
let mut trace = Elem::<T>::ZERO;
let mut power = value * dual;
let mut k = 0;
let relative = out.len();
while k < relative {
trace += power;
power = power.pow(prime);
k += 1;
}
let Some(residue) = trace.to_residue() else {
return false;
};
out[i] = residue;
i += 1;
}
true
}
fn dual_element<T: Field>(
descriptor: &Descriptor,
at: u8,
image: Elem<T>,
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
index: usize,
) -> Option<Elem<T>> {
let relative = level_relative(descriptor, at);
if index >= relative {
return None;
}
let mut acc = Elem::<T>::ONE;
let mut j = relative;
while j > index + 1 {
j -= 1;
acc = acc * image + transport_coefficient(descriptor, at, levels, count, images, j);
}
let derivative = relation_derivative(descriptor, at, levels, count, images, image, relative);
assert!(
derivative != Elem::<T>::ZERO,
"a transported relation is inseparable at its image"
);
Some(acc * derivative.inv())
}
fn relation_derivative<T: Field>(
descriptor: &Descriptor,
at: u8,
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
image: Elem<T>,
relative: usize,
) -> Elem<T> {
let mut acc = Elem::<T>::ZERO;
let mut j = relative;
while j > 1 {
j -= 1;
let coefficient = transport_coefficient(descriptor, at, levels, count, images, j);
let mut scaled = Elem::<T>::ZERO;
let mut k = 0;
while k < j {
scaled += coefficient;
k += 1;
}
acc = acc * image + scaled;
}
let mut power = Elem::<T>::ONE;
let mut k = 1;
while k < relative {
power *= image;
k += 1;
}
let mut leading = Elem::<T>::ZERO;
let mut k = 0;
while k < relative {
leading += power;
k += 1;
}
acc + leading
}
fn transport_coefficient<T: Field>(
descriptor: &Descriptor,
at: u8,
levels: &[Level; 8],
count: usize,
images: &[Elem<T>],
index: usize,
) -> Elem<T> {
match descriptor.node(at) {
Node::Extension {
base,
base_degree,
characteristic,
base_words,
coefficients,
..
} => {
if *characteristic == 2 {
let mut chunk = [0u64; 128];
let word = coefficients[index];
let mut k = 0;
while k < *base_degree as usize {
chunk[k] = (word >> k) & 1;
k += 1;
}
embed_coordinates(
descriptor,
*base,
&chunk[..*base_degree as usize],
levels,
count,
images,
)
} else {
embed_coordinates(
descriptor,
*base,
&coefficients[index * *base_words..(index + 1) * *base_words],
levels,
count,
images,
)
}
}
Node::Polynomial { full, .. } => Elem::<T>::from_residue(((full >> index) & 1) as u64)
.expect("a polynomial bit sits below the target"),
Node::Basis { .. } => {
let poly = basis_poly(descriptor, at);
let Node::Polynomial { full, .. } = descriptor.node(poly) else {
panic!("a basis level reads its polynomial node");
};
Elem::<T>::from_residue(((full >> index) & 1) as u64)
.expect("a polynomial bit sits below the target")
}
Node::Unused | Node::Prime { .. } => {
panic!("a prime node carries no transported relation");
}
}
}
fn level_relative(descriptor: &Descriptor, at: u8) -> usize {
match descriptor.node(at) {
Node::Extension {
relative_degree, ..
} => *relative_degree,
Node::Polynomial { degree, .. } => usize::from(*degree),
Node::Basis { .. } => {
let poly = basis_poly(descriptor, at);
let Node::Polynomial { degree, .. } = descriptor.node(poly) else {
panic!("a basis level reads its polynomial node");
};
usize::from(*degree)
}
Node::Unused | Node::Prime { .. } => {
panic!("a prime node carries no relative degree");
}
}
}
fn basis_poly(descriptor: &Descriptor, at: u8) -> u8 {
let Node::Basis { base, .. } = descriptor.node(at) else {
panic!("a basis level reads its own node");
};
*base
}
fn order_of(descriptor: &Descriptor, at: u8) -> u128 {
super::extension::arithmetic::order_of(descriptor, at)
}
fn structural_images<T: Field>(
source_descriptor: &Descriptor,
source_levels: &[Level; 8],
source_count: usize,
source_degree: usize,
target_descriptor: &Descriptor,
target_degree: usize,
) -> Option<([Elem<T>; 8], usize)> {
let mut uppers = [0u8; 8];
let mut upper_count = 0;
let mut current = *target_descriptor;
loop {
if current.same_structure(source_descriptor) {
break;
}
let root = current.root_index();
match current.node(root) {
Node::Extension { .. } | Node::Basis { .. } => {
assert!(
upper_count < 8,
"a tower descent visits at most eight nodes"
);
uppers[upper_count] = root;
upper_count += 1;
current = current.base_descriptor();
}
_ => return None,
}
}
let mut images = [Elem::<T>::ZERO; 8];
let mut l = 0;
while l < source_count {
let mut coords = [0u64; 128];
generator_coordinates(source_descriptor, &source_levels[l], &mut coords);
let mut len = source_degree;
let mut u = upper_count;
while u > 0 {
u -= 1;
lift_coordinates(target_descriptor, uppers[u], &mut coords, &mut len);
}
assert_eq!(len, target_degree, "a structural lift spans the target");
images[l] = Elem::<T>::from_raw(T::from_prime_coordinates(&coords[..target_degree]));
l += 1;
}
Some((images, source_count))
}
fn is_structural_images<S: Field, T: Field>(
source_descriptor: &Descriptor,
source_levels: &[Level; 8],
source_count: usize,
stored: &[Elem<T>; 8],
) -> bool {
let Some((expected, _)) = structural_images::<T>(
source_descriptor,
source_levels,
source_count,
S::DEGREE as usize,
T::DESCRIPTOR,
T::DEGREE as usize,
) else {
return false;
};
let mut l = 0;
while l < source_count {
if stored[l] != expected[l] {
return false;
}
l += 1;
}
true
}
fn lift_coordinates(descriptor: &Descriptor, at: u8, coords: &mut [u64; 128], len: &mut usize) {
match descriptor.node(at) {
Node::Extension {
base_degree,
relative_degree,
..
} => {
assert_eq!(
*len, *base_degree as usize,
"a structural lift meets its base degree"
);
let mut i = *len;
while i < *relative_degree * *base_degree as usize {
coords[i] = 0;
i += 1;
}
*len = *relative_degree * *base_degree as usize;
}
Node::Basis { from_base, .. } => {
let packed = pack_bits(&coords[..*len]);
let mut converted = [0u64; 128];
unpack_bits(apply(*from_base, packed), &mut converted[..*len]);
coords[..*len].copy_from_slice(&converted[..*len]);
}
Node::Unused | Node::Prime { .. } | Node::Polynomial { .. } => {
panic!("a structural lift climbs a tower level");
}
}
}
pub struct Embedding<S: Field, T: Field<Characteristic = S::Characteristic>> {
images: [Elem<T>; 8],
count: u8,
structural: bool,
phantom: PhantomData<fn() -> (S, T)>,
}
#[allow(clippy::expl_impl_clone_on_copy)]
impl<S: Field, T: Field<Characteristic = S::Characteristic>> Clone for Embedding<S, T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<S: Field, T: Field<Characteristic = S::Characteristic>> Copy for Embedding<S, T> {}
impl<S: Field, T: Field<Characteristic = S::Characteristic>> core::fmt::Debug for Embedding<S, T> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "Embedding<{}, {}>", S::NAME, T::NAME)
}
}
impl<S: Field, T: Field<Characteristic = S::Characteristic>> Embedding<S, T> {
pub fn new() -> Result<Self, EmbeddingError> {
let m = S::DEGREE;
let n = T::DEGREE;
if m == n {
return Err(EmbeddingError::EqualDegree { degree: m });
}
if n % m != 0 {
return Err(EmbeddingError::IncompatibleDegree {
source: m,
target: n,
});
}
let source_descriptor = S::DESCRIPTOR;
let target_descriptor = T::DESCRIPTOR;
let (source_levels, source_count) = levels(source_descriptor);
if m == 1 {
return Ok(Self::checked(
[Elem::<T>::ZERO; 8],
0,
source_descriptor,
&source_levels,
source_count,
));
}
if let Some((images, count)) = structural_images::<T>(
source_descriptor,
&source_levels,
source_count,
m as usize,
target_descriptor,
n as usize,
) {
return Ok(Self::checked(
images,
count,
source_descriptor,
&source_levels,
source_count,
));
}
let characteristic = <S::Characteristic as PrimeIdentity>::CHARACTERISTIC;
if characteristic == 2
&& let (Some(source_binary), Some(target_binary)) =
(S::BINARY_DESCRIPTION, T::BINARY_DESCRIPTION)
{
let mut words = [0u64; 8];
let mut l = 0;
while l < source_count {
let mut coords = [0u64; 128];
generator_coordinates(source_descriptor, &source_levels[l], &mut coords);
words[l] = u64::try_from(super::coordinates_index(&coords[..m as usize], 2))
.expect("a binary generator fits the word");
l += 1;
}
if let Some(image_words) =
ladder::canonical_images(source_binary, target_binary, &words[..source_count])
{
let mut images = [Elem::<T>::ZERO; 8];
let mut l = 0;
while l < source_count {
images[l] = Elem::<T>::from_index(u128::from(image_words[l]))
.expect("a ladder image fits the target");
l += 1;
}
return Ok(Self::checked(
images,
source_count,
source_descriptor,
&source_levels,
source_count,
));
}
}
Err(EmbeddingError::NoCanonicalMap)
}
pub fn from_images(images: &[Elem<T>]) -> Result<Self, EmbeddingError> {
let m = S::DEGREE;
let n = T::DEGREE;
if m == n {
return Err(EmbeddingError::EqualDegree { degree: m });
}
if n % m != 0 {
return Err(EmbeddingError::IncompatibleDegree {
source: m,
target: n,
});
}
let source_descriptor = S::DESCRIPTOR;
let (source_levels, source_count) = levels(source_descriptor);
if images.len() != source_count {
return Err(EmbeddingError::InvalidImages);
}
let mut stored = [Elem::<T>::ZERO; 8];
stored[..source_count].copy_from_slice(images);
if !relations_hold(source_descriptor, &source_levels, source_count, &stored) {
return Err(EmbeddingError::InvalidImages);
}
Ok(Self {
images: stored,
count: u8::try_from(source_count).expect("a level count fits its byte"),
structural: is_structural_images::<S, T>(
source_descriptor,
&source_levels,
source_count,
&stored,
),
phantom: PhantomData,
})
}
fn checked(
images: [Elem<T>; 8],
count: usize,
descriptor: &Descriptor,
levels: &[Level; 8],
level_count: usize,
) -> Self {
assert_eq!(count, level_count, "a constructed map covers every level");
assert!(
relations_hold(descriptor, levels, level_count, &images),
"a constructed image fails its transported relation"
);
let structural = is_structural_images::<S, T>(descriptor, levels, level_count, &images);
Self {
images,
count: u8::try_from(count).expect("a level count fits its byte"),
structural,
phantom: PhantomData,
}
}
#[must_use]
pub fn to_images(&self) -> &[Elem<T>] {
&self.images[..self.count as usize]
}
#[must_use]
pub fn generator_count() -> usize {
levels(S::DESCRIPTOR).1
}
#[must_use]
pub fn generator(level: usize) -> Elem<S> {
let descriptor = S::DESCRIPTOR;
let (source_levels, source_count) = levels(descriptor);
assert!(
level < source_count,
"a generator level sits below the generator count"
);
let mut coords = [0u64; 128];
generator_coordinates(descriptor, &source_levels[level], &mut coords);
Elem::<S>::from_raw(S::from_prime_coordinates(&coords[..S::DEGREE as usize]))
}
#[must_use]
pub fn then<M, U>(&self, next: &M) -> Embedding<S, U>
where
M: Postcompose<T, U>,
U: Field<Characteristic = S::Characteristic>,
{
let mut images = [Elem::<U>::ZERO; 8];
let mut l = 0;
while l < self.count as usize {
images[l] = compose::Postcompose::postcompose(next, self.images[l]);
l += 1;
}
let (source_levels_then, source_count_then) = levels(S::DESCRIPTOR);
let structural = is_structural_images::<S, U>(
S::DESCRIPTOR,
&source_levels_then,
source_count_then,
&images,
);
Embedding {
images,
count: self.count,
structural,
phantom: PhantomData,
}
}
#[must_use]
pub fn relative_degree(&self) -> u32 {
let _ = self.phantom;
T::DEGREE / S::DEGREE
}
#[must_use]
pub fn embed(&self, value: Elem<S>) -> Elem<T> {
let descriptor = S::DESCRIPTOR;
let (source_levels, source_count) = levels(descriptor);
let mut coords = [0u64; 128];
S::prime_coordinates_into(&mut coords[..S::DEGREE as usize], value.to_raw());
embed_coordinates(
descriptor,
descriptor.root_index(),
&coords[..S::DEGREE as usize],
&source_levels,
source_count,
&self.images,
)
}
const QUAD: Option<(u8, quadratic::ConjugationCoefficient)> = {
match quadratic::conjugation_shape::<T>() {
Some(shape) => {
if T::DEGREE == 2 * S::DEGREE && quadratic::routes::<S, T>() {
Some(shape)
} else {
None
}
}
None => None,
}
};
const QUAD_FROBENIUS: bool = {
match Self::QUAD {
Some(_) => !quadratic::is_tiny_gf4::<S, T>(),
None => false,
}
};
const QUAD_CONTAINS: bool = {
match Self::QUAD {
Some(_) => !quadratic::is_tiny_gf4::<S, T>(),
None => false,
}
};
const QUAD_RESTRICT: bool = {
match Self::QUAD {
Some(_) => !quadratic::is_tiny_gf4::<S, T>(),
None => false,
}
};
const QUAD_TRACE: bool = Self::QUAD.is_some();
const QUAD_NORM: bool = Self::QUAD.is_some();
#[inline]
fn is_structural(&self) -> bool {
self.structural
}
#[must_use]
pub fn contains(&self, value: Elem<T>) -> bool {
if Self::QUAD_CONTAINS
&& let Some((base_degree, coefficient)) = Self::QUAD
&& self.is_structural()
{
return quadratic::apply_conjugation::<T>(value, base_degree, coefficient) == value;
}
value.pow(S::ORDER) == value
}
pub fn restrict(&self, value: Elem<T>) -> Option<Elem<S>> {
if Self::QUAD_RESTRICT
&& let Some((base_degree, coefficient)) = Self::QUAD
&& self.is_structural()
{
if quadratic::apply_conjugation::<T>(value, base_degree, coefficient) != value {
return None;
}
return Some(quadratic::restrict_quad::<S, T>(value));
}
let descriptor = S::DESCRIPTOR;
let (source_levels, source_count) = levels(descriptor);
let mut coords = [0u64; 128];
if !restrict_coordinates(
descriptor,
descriptor.root_index(),
value,
&source_levels,
source_count,
&self.images,
&mut coords[..S::DEGREE as usize],
) {
return None;
}
Some(Elem::<S>::from_raw(S::from_prime_coordinates(
&coords[..S::DEGREE as usize],
)))
}
#[must_use]
pub fn frobenius(&self, value: Elem<T>) -> Elem<T> {
if Self::QUAD_FROBENIUS
&& let Some((base_degree, coefficient)) = Self::QUAD
&& self.is_structural()
{
return quadratic::apply_conjugation::<T>(value, base_degree, coefficient);
}
value.pow(S::ORDER)
}
#[must_use]
pub fn trace(&self, value: Elem<T>) -> Elem<S> {
if Self::QUAD_TRACE
&& let Some((base_degree, coefficient)) = Self::QUAD
&& self.is_structural()
{
return quadratic::apply_trace::<S, T>(value, base_degree, coefficient);
}
let mut sum = value;
let mut power = value;
let mut k = 1;
while k < T::DEGREE / S::DEGREE {
power = self.frobenius(power);
sum += power;
k += 1;
}
self.restrict(sum)
.expect("a conjugate sum lies in the embedding image")
}
#[must_use]
pub fn norm(&self, value: Elem<T>) -> Elem<S> {
if Self::QUAD_NORM
&& let Some((base_degree, coefficient)) = Self::QUAD
&& self.is_structural()
{
return quadratic::apply_norm::<S, T>(value, base_degree, coefficient);
}
let mut product = value;
let mut power = value;
let mut k = 1;
while k < T::DEGREE / S::DEGREE {
power = self.frobenius(power);
product *= power;
k += 1;
}
self.restrict(product)
.expect("a conjugate product lies in the embedding image")
}
}
mod compose {
use super::{Elem, Embedding, Field, Isomorphism};
pub trait Postcompose<Mid: Field, Out: Field> {
fn postcompose(&self, image: Elem<Mid>) -> Elem<Out>;
}
impl<Mid: Field, Out: Field<Characteristic = Mid::Characteristic>> Postcompose<Mid, Out>
for Embedding<Mid, Out>
{
#[inline]
fn postcompose(&self, image: Elem<Mid>) -> Elem<Out> {
self.embed(image)
}
}
impl<Mid: Field, Out: Field<Characteristic = Mid::Characteristic>> Postcompose<Mid, Out>
for Isomorphism<Mid, Out>
{
#[inline]
fn postcompose(&self, image: Elem<Mid>) -> Elem<Out> {
self.apply(image)
}
}
}
#[allow(private_bounds)]
pub trait Postcompose<Mid: Field, Out: Field>: compose::Postcompose<Mid, Out> {}
impl<Mid: Field, Out: Field, M: compose::Postcompose<Mid, Out>> Postcompose<Mid, Out> for M {}
pub struct Isomorphism<S: Field, T: Field<Characteristic = S::Characteristic>> {
forward: [Elem<T>; 8],
reverse: [Elem<S>; 8],
forward_count: u8,
reverse_count: u8,
phantom: PhantomData<fn() -> (S, T)>,
}
#[allow(clippy::expl_impl_clone_on_copy)]
impl<S: Field, T: Field<Characteristic = S::Characteristic>> Clone for Isomorphism<S, T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<S: Field, T: Field<Characteristic = S::Characteristic>> Copy for Isomorphism<S, T> {}
impl<S: Field, T: Field<Characteristic = S::Characteristic>> core::fmt::Debug
for Isomorphism<S, T>
{
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "Isomorphism<{}, {}>", S::NAME, T::NAME)
}
}
fn iso_images<S: Field, T: Field<Characteristic = S::Characteristic>>(
forward: &[Elem<T>; 8],
forward_count: usize,
) -> ([Elem<S>; 8], usize) {
let source_descriptor = S::DESCRIPTOR;
let (source_levels, source_count) = levels(source_descriptor);
assert_eq!(
forward_count, source_count,
"an isomorphism covers every source level"
);
assert!(
relations_hold(source_descriptor, &source_levels, source_count, forward),
"a constructed image fails its transported relation"
);
let target_descriptor = T::DESCRIPTOR;
let (target_levels, target_count) = levels(target_descriptor);
let mut reverse = [Elem::<S>::ZERO; 8];
let mut l = 0;
while l < target_count {
let mut coords = [0u64; 128];
generator_coordinates(target_descriptor, &target_levels[l], &mut coords);
let generator =
Elem::<T>::from_raw(T::from_prime_coordinates(&coords[..T::DEGREE as usize]));
let mut preimage = [0u64; 128];
assert!(
restrict_coordinates(
source_descriptor,
source_descriptor.root_index(),
generator,
&source_levels,
source_count,
forward,
&mut preimage[..S::DEGREE as usize],
),
"a target generator lies in the isomorphism image"
);
reverse[l] =
Elem::<S>::from_raw(S::from_prime_coordinates(&preimage[..S::DEGREE as usize]));
l += 1;
}
(reverse, target_count)
}
fn canonical_iso_images<S: Field, T: Field<Characteristic = S::Characteristic>>()
-> Option<([Elem<T>; 8], usize)> {
let source_descriptor = S::DESCRIPTOR;
let target_descriptor = T::DESCRIPTOR;
let (source_levels, source_count) = levels(source_descriptor);
let m = S::DEGREE as usize;
if let Some((images, count)) = structural_images::<T>(
source_descriptor,
&source_levels,
source_count,
m,
target_descriptor,
T::DEGREE as usize,
) {
return Some((images, count));
}
if <S::Characteristic as PrimeIdentity>::CHARACTERISTIC != 2 {
return None;
}
let (Some(source_binary), Some(target_binary)) = (S::BINARY_DESCRIPTION, T::BINARY_DESCRIPTION)
else {
return None;
};
let mut words = [0u64; 8];
let mut l = 0;
while l < source_count {
let mut coords = [0u64; 128];
generator_coordinates(source_descriptor, &source_levels[l], &mut coords);
words[l] = u64::try_from(super::coordinates_index(&coords[..m], 2))
.expect("a binary generator fits the word");
l += 1;
}
let image_words =
ladder::canonical_images(source_binary, target_binary, &words[..source_count])?;
let mut images = [Elem::<T>::ZERO; 8];
let mut l = 0;
while l < source_count {
images[l] = Elem::<T>::from_index(u128::from(image_words[l]))
.expect("a ladder image fits the target");
l += 1;
}
Some((images, source_count))
}
impl<S: Field, T: Field<Characteristic = S::Characteristic>> Isomorphism<S, T> {
pub fn new() -> Result<Self, EmbeddingError> {
if S::DEGREE != T::DEGREE {
return Err(EmbeddingError::IncompatibleDegree {
source: S::DEGREE,
target: T::DEGREE,
});
}
let Some((forward, count)) = canonical_iso_images::<S, T>() else {
if S::DEGREE == 1 {
return Ok(Self {
forward: [Elem::<T>::ZERO; 8],
reverse: [Elem::<S>::ZERO; 8],
forward_count: 0,
reverse_count: 0,
phantom: PhantomData,
});
}
return Err(EmbeddingError::NoCanonicalMap);
};
let (reverse, reverse_count) = iso_images::<S, T>(&forward, count);
Ok(Self {
forward,
reverse,
forward_count: u8::try_from(count).expect("a level count fits its byte"),
reverse_count: u8::try_from(reverse_count).expect("a level count fits its byte"),
phantom: PhantomData,
})
}
pub fn from_images(images: &[Elem<T>]) -> Result<Self, EmbeddingError> {
if S::DEGREE != T::DEGREE {
return Err(EmbeddingError::IncompatibleDegree {
source: S::DEGREE,
target: T::DEGREE,
});
}
let source_descriptor = S::DESCRIPTOR;
let (source_levels, source_count) = levels(source_descriptor);
if images.len() != source_count {
return Err(EmbeddingError::InvalidImages);
}
let mut forward = [Elem::<T>::ZERO; 8];
forward[..source_count].copy_from_slice(images);
if !relations_hold(source_descriptor, &source_levels, source_count, &forward) {
return Err(EmbeddingError::InvalidImages);
}
let (reverse, reverse_count) = iso_images::<S, T>(&forward, source_count);
Ok(Self {
forward,
reverse,
forward_count: u8::try_from(source_count).expect("a level count fits its byte"),
reverse_count: u8::try_from(reverse_count).expect("a level count fits its byte"),
phantom: PhantomData,
})
}
#[must_use]
pub fn to_images(&self) -> &[Elem<T>] {
&self.forward[..self.forward_count as usize]
}
#[must_use]
pub fn then<U: Field<Characteristic = S::Characteristic>>(
&self,
next: &Isomorphism<T, U>,
) -> Isomorphism<S, U> {
let mut forward = [Elem::<U>::ZERO; 8];
let mut l = 0;
while l < self.forward_count as usize {
forward[l] = next.apply(self.forward[l]);
l += 1;
}
let mut reverse = [Elem::<S>::ZERO; 8];
let mut l = 0;
while l < next.reverse_count as usize {
reverse[l] = self.unapply(next.reverse[l]);
l += 1;
}
Isomorphism {
forward,
reverse,
forward_count: self.forward_count,
reverse_count: next.reverse_count,
phantom: PhantomData,
}
}
#[must_use]
pub fn then_embedding<U: Field<Characteristic = S::Characteristic>>(
&self,
next: &Embedding<T, U>,
) -> Embedding<S, U> {
let mut images = [Elem::<U>::ZERO; 8];
let mut l = 0;
while l < self.forward_count as usize {
images[l] = next.embed(self.forward[l]);
l += 1;
}
let (source_levels_then, source_count_then) = levels(S::DESCRIPTOR);
let structural = is_structural_images::<S, U>(
S::DESCRIPTOR,
&source_levels_then,
source_count_then,
&images,
);
Embedding {
images,
count: self.forward_count,
structural,
phantom: PhantomData,
}
}
#[must_use]
pub fn inverse(&self) -> Isomorphism<T, S> {
Isomorphism {
forward: self.reverse,
reverse: self.forward,
forward_count: self.reverse_count,
reverse_count: self.forward_count,
phantom: PhantomData,
}
}
#[must_use]
pub fn apply(&self, value: Elem<S>) -> Elem<T> {
let descriptor = S::DESCRIPTOR;
let (source_levels, source_count) = levels(descriptor);
let mut coords = [0u64; 128];
S::prime_coordinates_into(&mut coords[..S::DEGREE as usize], value.to_raw());
embed_coordinates(
descriptor,
descriptor.root_index(),
&coords[..S::DEGREE as usize],
&source_levels,
source_count,
&self.forward,
)
}
#[must_use]
pub fn unapply(&self, value: Elem<T>) -> Elem<S> {
let descriptor = T::DESCRIPTOR;
let (target_levels, target_count) = levels(descriptor);
let mut coords = [0u64; 128];
T::prime_coordinates_into(&mut coords[..T::DEGREE as usize], value.to_raw());
embed_coordinates(
descriptor,
descriptor.root_index(),
&coords[..T::DEGREE as usize],
&target_levels,
target_count,
&self.reverse,
)
}
}