-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add Field::shifted_powers
and some iterator niceties
#1599
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.
It's possible this is the most commonly expected case and nth should just be removed altogether
Why do you think this is the most commonly expected case? In general, I'm not sure where nth
could be useful. Do you have use cases in mind?
The reason for the PR as a whole was to hopefully make it easier to replace as many instances of // n is the (hypothetical) number of bits to shift by.
// f_n is a flag indicating whether this is really the n to shift by.
// All bits in the output with index < n are 0.
// m in {n..32} is the LE index of a possibly nonzero bit in the output.
// We want to line up the nth bit of the output with the 0th bit of the input,
// so we take `output_bits[m] = input_bits[m - n]`.
let shift_left: P = lv
.shift_by_indices
.into_iter()
.enumerate()
.flat_map(|(n, f_n)| {
(n..32)
.zip(P::Scalar::TWO.powers().skip(n))
.map(move |(m, base)| f_n * lv.input_bits[m - n] * base)
})
.sum(); But, this is more efficiently done with |
Thinking about this more, I gave a misleading example as using let shift_left: P = lv
.shift_by_indices
.into_iter()
.enumerate()
.flat_map(|(n, f_n)| {
(n..32)
.zip(P::Scalar::TWO.powers_shifted(P::Scalar::from_canonical_u32(1 << n)))
.map(move |(m, base)| f_n * lv.input_bits[m - n] * base)
})
.sum(); It's a small thing, but this still requires verifying that at no point in the loop does
|
Nice, I miss |
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.
Looks good to me!
But, this is more efficiently done with Field::shifted_powers anyway. nth is used under the hood by iterator adapters like skip and step_by, so if those seem useful in the context of powers() than maybe it's worth keeping. Otherwise, I'm happy to remove it.
For step_by
we could actually do better by returning Powers {base: self.base.exp_u64(step as u64), current: self.current}
, but I don't think it's possible to override this method and panicking seems a bit extreme.
I think we can keep your implementation of nth
though. It's much faster for large n
and shouldn't be called that often for n=1
.
I merged upstream (#1601) so the tests would run, but let me know if I should squash. Otherwise, this should be ready to go. Thanks for the feedback. |
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.
LGTM too!
I merged upstream (#1601) so the tests would run, but let me know if I should squash.
No problem about this, PRs are squashed into main
anyway!
* Add Field::shifted_powers and iterator niceties * Remove comment
Adds the
Field::shifted_powers
method from Plonky3 along with a few additions to thePowers
iterator implementation.Please pick and choose what's useful.
<Powers<F> as Iterator>::nth
in particular might require some tweaking:n
overflowsu64
. This seems at least as good as the current behavior of hanging trying to computeu64::MAX
field multiplications. But, maybe it's worth the performance hit to useField::exp_biguint
.n = 1
(e.g.generator.powers().skip(1)
). It's possible this is the most commonly expected case andnth
should just be removed altogether.