- assumes [[pairing-index]]
- Adding new variables to a multilinear polynomial
- added variables should have co-efficient 0, meaning they don't affect the evaluation result, they just increase the domain.
- general idea
- adding a new variable means adding a new direction.
- to add a new direction, you duplicate the current space and only changing the bit at the new direction index
- this is exactly equal to the formulation of pairing index.
- this means the pairing index will tell you how to duplicate elements in the new space.
- algorithm
- initialize new space with all zeros
- generate the pair in the direction of the newly inserted variable
- each pair corresponds to each element of the old evaluation array respectively
- each element in the old evaluation array will be copied into both positions indicated by the pairing index
```rust
fn add_var(old_eval, index) {
let mut result = vec![0; new_eval_len];
for (i, (left, right)) in pair_index.enumerate() {
result[left] = old[i];
result[right] = old[i];
}
}
```
- example
- we have $f(a, c) = 2a + 3c$ and we want to add new direction b
- evaluation array for $f(a, c)$ = [0, 3, 2, 5]
- zero initialized result array for $f(a, b, c)$ = [0, 0, 0, 0, 0, 0, 0, 0]
- pairing index for $f(a, b, c)$ in direction of b:
- 0 -> 2
- first element in old should be mapped to 0 and 2 in new
- 1 -> 3
- next element in old should be mapped to 1 and 3 in new
- 4 -> 6
- 5 -> 7
- assuming we want to update (0, 2)
- result[0] = old[0]
- result[2] = old[0]
- we can repeat this for all pairs
- we get the following: [0, 3, 0, 3, 2, 5, 2, 5]
- check our work
- f(a, b, c) = 2a + 0b + 3c
- evaluated over the boolean hypercube we have:
- [0, 3, 0, 3, 2, 5, 2, 5] (exactly matches our computed result)
- adding variables with co-efficients
- if we see adding variables as duplicating faces and putting them in the same direction
- then a zero coefficient variable addition represents duplicating but putting them on top of one another hence no difference in var = 0 or var = 1
- to add the variable with a coefficient, we need to add the coefficient to every node in the new face
- algorithm
- same as adding single variable, but the copy should add the coefficient
```rust
fn add_var(old_eval, index, coefficient) {
let mut result = vec![0; new_eval_len];
for (i, (left, right)) in pair_index.enumerat() {
result[left] = old[i];
result[right] = old[i] + coefficient;
}
}
```
- example
- f(a, b) = 2a + 4b
- evaluation_array = [0, 4, 2, 6]
- we want to add 5c to get $f(a, b, c) = 2a + 4b + 5c$
- expected_evaluation_array = [0, 5, 4, 9, 2, 7, 6, 11]
- pairing index for $f(a, b, c)$ in direction c
- [(0, 1), (2, 3), (4, 5), (6, 7)]
- for (0, 1)
- copy first element from evaluation array in index 0
- copy to index 1 but add coefficient 5
- do the same for all other pairs
- result = [0, 5, 4, 9, 2, 7, 6, 11]
- matches the expected evaluation array
- another approach is to extend $f(a, b)$ to $f(a, b, c)$ but set c to have coefficient 0
- then create another polynomial for the term you want to add e.g. 5b -> [0, 5]
- extend this polynomial to have the same shape as your target $f(a, b, c)$ by adding 0a and 0c
- then do element wise addition to get the target.
- e.g.
- $f(a, b) = 2a + 4b$ = [0, 4, 2, 6]
- extend to c
- $f(a, b, c)$ = [0, 0, 4, 4, 2, 2, 6, 6]
- $f(c)$ = 5c = [0, 5]
- extend to $f(b, c)$ = [0, 5, 0, 5]
- extend to $f(a, b, c)$ = [0, 5, 0, 5, 0, 5, 0, 5]
- result
- [0, 0, 4, 4, 2, 2, 6, 6] + [0, 5, 0, 5, 0, 5, 0, 5] = [0, 5, 4, 9, 2, 7, 6, 11]
- same result
- Addition, Multiplication of evaluation form multilinear polynomials
- to add or multiply two evaluation form multilinear polynomials, figure out the target function signature.
- move both operands to that target signature, then just apply the operation element wise.
- example
- $f_1(a) = 6a$ [0, 6] and $f_2(a, b) = 4a + 5ab$ [0, 0, 4, 9]
- the expected result should be $f_r(a, b) = 10a + 5ab$
- with the following expected evaluation array: [0, 0, 10, 15]
- the target function signature is $f(a, b)$
- so we move $f_1(a)$ to $f_1(a, b)$
- [0, 0, 6, 6]
- $f_2(a, b)$ is already in target signature
- now we can just do element wise addition to get the target result
- [0, 0, 6, 6] + [0, 0, 4, 9] = [0, 0, 10, 15]
- same as the expected.
- for multiplication, to remain multilinear then the operands must not share common variables
- $f(a, b) \cdot f(c, d)$ -> $f(a, b, c, d)$
- $f(a, b) \cdot f(b, c)$ will not result in a multilinear target because b will be a power of 2 i.e $b^2$
- Adding multiple variables at once
- if we see the pairing index as instructions for duplication, adding multiple variables at once means we need to compose instructions.
- [ ] compare the running time of repeated variable insertion vs at once
- we could do this via instruction rewrites I believe
-
- Dealing with space complexity when we add more variables