-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LogUp-GKR evaluator #18
base: next
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! This is not a full review - I just looked at the math
crate-related changes and left some comments.
Overall, I think we should try to implement this in phases. For example, the first PR in Winterfell could be just for introducing new functions/modules into the math
crate. When we do that, we take the time to implement most things in near-final form (e.g., removing un-needed allocations, potentially parallelizing the most critical things etc.)
/// This means that we can compute the evaluations of q at 1, ..., n - 1 using the evaluations | ||
/// of p and thus reduce by 1 the size of the interpolation problem. | ||
/// Once the coefficient of q are recovered, the c0 coefficient is appended to these and this | ||
/// is precisely the coefficient representation of the original polynomial q. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does q
refer to here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
q
is defined in the two bulletpoints just above, it is just the polynomial resulting from factoring x
.
// construct the vector of interpolation points 1, ..., n | ||
let n_minus_1 = self.partial_evaluations.len(); | ||
let mut points = vec![E::BaseField::ZERO; n_minus_1]; | ||
|
||
// construct their inverses. These will be needed for computing the evaluations | ||
// of the q polynomial as well as for doing the interpolation on q | ||
points | ||
.iter_mut() | ||
.enumerate() | ||
.for_each(|(i, node)| *node = E::BaseField::from(i as u32 + 1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just constructing a vector with values 1, 2, 3, 4 ...
- right? If so, we could do this as something like this to avoid first allocating and zeroing out the vector and then filling it in with values:
let points = (1..n_minus_1).iter().map(E::BaseField::from).collect::<Vec<_>>();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed
/// ```ignore | ||
/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} | ||
/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) | ||
/// * (1 - y_i)) | ||
/// ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These, and other formulas, we should explicitly write as formals (i.e., using $$
in comments) so that they are displayed nicely in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added but not sure if it is the right way to do it. Do you have some documentation to help?
/// Computes the evaluations of the Lagrange basis polynomials over the interpolating | ||
/// set {0 , 1}^ν at (r_0, ..., r_{ν - 1}) i.e., the Lagrange kernel at (r_0, ..., r_{ν - 1}). | ||
fn compute_lagrange_basis_evals_at<E: FieldElement>(query: &[E]) -> Vec<E> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't thought about this too much, but I'm curious if there is a way to parallelize this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought about it a bit but couldn't come up with something clever. This would actually be a very important function to parallelize so we should give it more thought in my opinion. Will think more about it.
/// Constructs a [`MultiLinearPoly`] from its evaluations over the boolean hyper-cube {0 , 1}^ν. | ||
pub fn from_evaluations(evaluations: Vec<E>) -> Result<Self, MultiLinearPolyError> { | ||
if !evaluations.len().is_power_of_two() { | ||
return Err(MultiLinearPolyError::EvaluationsNotPowerOfTwo); | ||
} | ||
Ok(Self { | ||
num_variables: (evaluations.len().ilog2()) as usize, | ||
evaluations, | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is OK to panic here is the number of evaluations is not a power of two. This way, we also won't need the MultiLinearPolyError
enum.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
/// Computes f(r_0, y_1, ..., y_{ν - 1}) using the linear interpolation formula | ||
/// (1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1}) and assigns | ||
/// the resulting multi-linear, defined over a domain of half the size, to `self`. | ||
pub fn bind_least_significant_variable(&mut self, round_challenge: E) { | ||
let mut result = vec![E::ZERO; 1 << (self.num_variables() - 1)]; | ||
for (i, res) in result.iter_mut().enumerate() { | ||
*res = self.evaluations[i << 1] | ||
+ round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); | ||
} | ||
*self = Self::from_evaluations(result) | ||
.expect("should not fail given that it is a multi-linear"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this function a little confusing. I think what we are doing is reducing the number of variables by 1 and as the result, halving the number of evaluations - right?
If so, we should probably just return a new MultiLinearPoly
from here. For example, the signature could be:
pub fn bind_least_significant_variable(self, variable_value: E) -> Self
Another thing, it seems to me that we don't actually need to allocate the result
vector. Instead, we may be able to update the self.evaluations
vector in place and then just truncate its length. Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the two suggestions compatible? I went with the second one and removed the need for result
Incomplete implementation of the proposal here
Notes: