1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
use super::Expression;
use halo2_middleware::ff::Field;
/// This describes a selector and where it is activated.
#[derive(Debug, Clone)]
pub(crate) struct SelectorDescription {
/// The selector that this description references, by index.
pub selector: usize,
/// The vector of booleans defining which rows are active for this selector.
pub activations: Vec<bool>,
/// The maximum degree of a gate involving this selector, including the
/// virtual selector itself. This means this will be at least 1 for any
/// expression containing a simple selector, even if that selector is not
/// multiplied by anything.
pub max_degree: usize,
}
/// This describes the assigned combination of a particular selector as well as
/// the expression it should be substituted with.
#[derive(Debug, Clone)]
pub(crate) struct SelectorAssignment<F> {
/// The selector that this structure references, by index.
pub selector: usize,
/// The combination this selector was assigned to
pub combination_index: usize,
/// The expression we wish to substitute with
pub expression: Expression<F>,
}
/// This function takes a vector that defines each selector as well as a closure
/// used to allocate new fixed columns, and returns the assignment of each
/// combination as well as details about each selector assignment.
///
/// This function takes
/// * `selectors`, a vector of `SelectorDescription`s that describe each
/// selector
/// * `max_degree`, the maximum allowed degree of any gate
/// * `allocate_fixed_columns`, a closure that constructs a new fixed column and
/// queries it at Rotation::cur(), returning the expression
///
/// and returns `Vec<Vec<F>>` containing the assignment of each new fixed column
/// (which each correspond to a combination) as well as a vector of
/// `SelectorAssignment` that the caller can use to perform the necessary
/// substitutions to the constraint system.
///
/// This function is completely deterministic.
pub(crate) 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() {
// There is nothing to optimize.
return (vec![], vec![]);
}
// The length of all provided selectors must be the same.
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![];
// All provided selectors of degree 0 are assumed to be either concrete
// selectors or do not appear in a gate. Let's address these first.
selectors.retain(|selector| {
if selector.max_degree == 0 {
// This is a complex selector, or a selector that does not appear in any
// gate constraint.
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
}
});
// All of the remaining `selectors` are simple. Let's try to combine them.
// First, we compute the exclusion matrix that has (j, k) = true if selector
// j and selector k conflict -- that is, they are both enabled on the same
// row. This matrix is symmetric and the diagonal entries are false, so we
// only need to store the lower triangular entries.
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()
{
// Loop over the selectors previous to this one
for (j, other_selector) in selectors.iter().enumerate().take(i) {
// Look at what selectors are active at the same row
if rows
.iter()
.zip(other_selector.activations.iter())
.any(|(l, r)| l & r)
{
// Mark them as incompatible
exclusion_matrix[i][j] = true;
}
}
}
// Simple selectors that we've added to combinations already.
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);
// This is used to keep track of the largest degree gate involved in the
// combination so far. We subtract by one to omit the virtual selector
// which will be substituted by the caller with the expression we give
// them.
let mut d = selector.max_degree - 1;
let mut combination = vec![selector];
let mut combination_added = vec![i];
// Try to find other selectors that can join this one.
'try_selectors: for (j, selector) in selectors.iter().enumerate().skip(i + 1) {
if d + combination.len() == max_degree {
// Short circuit; nothing can be added to this
// combination.
break 'try_selectors;
}
// Skip selectors that have been added to previous combinations
if added[j] {
continue 'try_selectors;
}
// Is this selector excluded from co-existing in the same
// combination with any of the other selectors so far?
for &i in combination_added.iter() {
if exclusion_matrix[j][i] {
continue 'try_selectors;
}
}
// Can the new selector join the combination? Reminder: we use
// selector.max_degree - 1 to omit the influence of the virtual
// selector on the degree, as it will be substituted.
let new_d = std::cmp::max(d, selector.max_degree - 1);
if new_d + combination.len() + 1 > max_degree {
// Guess not.
continue 'try_selectors;
}
d = new_d;
combination.push(selector);
combination_added.push(j);
added[j] = true;
}
// Now, compute the selector and combination assignments.
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| {
// Compute the expression for substitution. This produces an expression of the
// form
// q * Prod[i = 1..=combination_len, i != assigned_root](i - q)
//
// which is non-zero only on rows where `combination_assignment` is set to
// `assigned_root`. In particular, rows set to 0 correspond to all selectors
// being disabled.
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;
}
// Update the combination assignment
for (combination, selector) in combination_assignment
.iter_mut()
.zip(selector.activations.iter())
{
// This will not overwrite another selector's activations because
// we have ensured that selectors are disjoint.
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::plonk::FixedQuery;
use halo2_middleware::poly::Rotation;
use halo2curves::pasta::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(FixedQuery {
index: Some(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 {
// Every selector should be assigned to a combination
assert!(selector.combination_index < combination_assignments.len());
assert!(!selectors_seen.contains(&selector.selector));
selectors_seen.push(selector.selector);
}
}
// Test that, for each selector, the provided expression
// 1. evaluates to zero on rows where the selector's activation is off
// 2. evaluates to nonzero on rows where the selector's activation is on
// 3. is of degree d such that d + (selector.max_degree - 1) <= max_degree
// OR selector.max_degree is zero
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| {
// Should be the correct combination in the expression
assert_eq!(selector.combination_index, query.index.unwrap());
assignment
},
&|_| panic!("should not occur in returned expressions"),
&|_| 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
);
}
}
}
}
}