use super::Expression;
use ff::Field;
#[derive(Debug, Clone)]
pub struct SelectorDescription {
pub selector: usize,
pub activations: Vec<bool>,
pub max_degree: usize,
}
#[derive(Debug, Clone)]
pub struct SelectorAssignment<F> {
pub selector: usize,
pub combination_index: usize,
pub expression: Expression<F>,
}
pub fn process<F: Field, E>(
mut selectors: Vec<SelectorDescription>,
max_degree: usize,
mut allocate_fixed_column: E,
) -> (Vec<Vec<F>>, Vec<SelectorAssignment<F>>)
where
E: FnMut() -> Expression<F>,
{
if selectors.is_empty() {
return (vec![], vec![]);
}
let n = selectors[0].activations.len();
assert!(selectors.iter().all(|a| a.activations.len() == n));
let mut combination_assignments = vec![];
let mut selector_assignments = vec![];
selectors = selectors
.into_iter()
.filter(|selector| {
if selector.max_degree == 0 {
let expression = allocate_fixed_column();
let combination_assignment = selector
.activations
.iter()
.map(|b| if *b { F::one() } else { F::zero() })
.collect::<Vec<_>>();
let combination_index = combination_assignments.len();
combination_assignments.push(combination_assignment);
selector_assignments.push(SelectorAssignment {
selector: selector.selector,
combination_index,
expression,
});
false
} else {
true
}
})
.collect();
let mut exclusion_matrix = (0..selectors.len())
.map(|i| vec![false; i])
.collect::<Vec<_>>();
for (i, rows) in selectors
.iter()
.map(|selector| &selector.activations)
.enumerate()
{
for (j, other_selector) in selectors.iter().enumerate().take(i) {
if rows
.iter()
.zip(other_selector.activations.iter())
.any(|(l, r)| l & r)
{
exclusion_matrix[i][j] = true;
}
}
}
let mut added = vec![false; selectors.len()];
for (i, selector) in selectors.iter().enumerate() {
if added[i] {
continue;
}
added[i] = true;
assert!(selector.max_degree <= max_degree);
let mut d = selector.max_degree - 1;
let mut combination = vec![selector];
let mut combination_added = vec![i];
'try_selectors: for (j, selector) in selectors.iter().enumerate().skip(i + 1) {
if d + combination.len() == max_degree {
break 'try_selectors;
}
if added[j] {
continue 'try_selectors;
}
for &i in combination_added.iter() {
if exclusion_matrix[j][i] {
continue 'try_selectors;
}
}
let new_d = std::cmp::max(d, selector.max_degree - 1);
if new_d + combination.len() + 1 > max_degree {
continue 'try_selectors;
}
d = new_d;
combination.push(selector);
combination_added.push(j);
added[j] = true;
}
let mut combination_assignment = vec![F::zero(); n];
let combination_len = combination.len();
let combination_index = combination_assignments.len();
let query = allocate_fixed_column();
let mut assigned_root = F::one();
selector_assignments.extend(combination.into_iter().map(|selector| {
let mut expression = query.clone();
let mut root = F::one();
for _ in 0..combination_len {
if root != assigned_root {
expression = expression * (Expression::Constant(root) - query.clone());
}
root += F::one();
}
for (combination, selector) in combination_assignment
.iter_mut()
.zip(selector.activations.iter())
{
if *selector {
*combination = assigned_root;
}
}
assigned_root += F::one();
SelectorAssignment {
selector: selector.selector,
combination_index,
expression,
}
}));
combination_assignments.push(combination_assignment);
}
(combination_assignments, selector_assignments)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::poly::Rotation;
use pasta_curves::Fp;
use proptest::collection::{vec, SizeRange};
use proptest::prelude::*;
prop_compose! {
fn arb_selector(assignment_size: usize, max_degree: usize)
(degree in 0..max_degree,
assignment in vec(any::<bool>(), assignment_size))
-> (usize, Vec<bool>) {
(degree, assignment)
}
}
prop_compose! {
fn arb_selector_list(assignment_size: usize, max_degree: usize, num_selectors: impl Into<SizeRange>)
(list in vec(arb_selector(assignment_size, max_degree), num_selectors))
-> Vec<SelectorDescription>
{
list.into_iter().enumerate().map(|(i, (max_degree, activations))| {
SelectorDescription {
selector: i,
activations,
max_degree,
}
}).collect()
}
}
prop_compose! {
fn arb_instance(max_assignment_size: usize,
max_degree: usize,
max_selectors: usize)
(assignment_size in 1..max_assignment_size,
degree in 1..max_degree,
num_selectors in 1..max_selectors)
(list in arb_selector_list(assignment_size, degree, num_selectors),
degree in Just(degree))
-> (Vec<SelectorDescription>, usize)
{
(list, degree)
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]
#[test]
fn test_selector_combination((selectors, max_degree) in arb_instance(10, 10, 15)) {
let mut query = 0;
let (combination_assignments, selector_assignments) =
process::<Fp, _>(selectors.clone(), max_degree, || {
let tmp = Expression::Fixed {
query_index: query,
column_index: query,
rotation: Rotation::cur(),
};
query += 1;
tmp
});
{
let mut selectors_seen = vec![];
assert_eq!(selectors.len(), selector_assignments.len());
for selector in &selector_assignments {
assert!(selector.combination_index < combination_assignments.len());
assert!(!selectors_seen.contains(&selector.selector));
selectors_seen.push(selector.selector);
}
}
for selector in selector_assignments {
assert_eq!(
selectors[selector.selector].activations.len(),
combination_assignments[selector.combination_index].len()
);
for (&activation, &assignment) in selectors[selector.selector]
.activations
.iter()
.zip(combination_assignments[selector.combination_index].iter())
{
let eval = selector.expression.evaluate(
&|c| c,
&|_| panic!("should not occur in returned expressions"),
&|query_index, _, _| {
assert_eq!(selector.combination_index, query_index);
assignment
},
&|_, _, _| panic!("should not occur in returned expressions"),
&|_, _, _| panic!("should not occur in returned expressions"),
&|a| -a,
&|a, b| a + b,
&|a, b| a * b,
&|a, f| a * f,
);
if activation {
assert!(!eval.is_zero_vartime());
} else {
assert!(eval.is_zero_vartime());
}
}
let expr_degree = selector.expression.degree();
assert!(expr_degree <= max_degree);
if selectors[selector.selector].max_degree > 0 {
assert!(
(selectors[selector.selector].max_degree - 1) + expr_degree <= max_degree
);
}
}
}
}
}