Zulip Chat Archive

Stream: general

Topic: Fast modexp


Bolton Bailey (Mar 02 2022 at 05:13):

For the Miller-Rabin project with Sean, it would be nice if there were an efficient way to compute whether a number is an efficient Miller-Rabin witness to a given base. Currently, it seems the bottleneck is that modular exponentiation is naively evaluated:

import data.zmod.basic
#eval (1234565432124 : zmod 10000000000009) * (12327261237214456) -- fast
#eval (1234324 : zmod 1000000009) ^ (1239382) -- about 1 sec
#eval (1234324 : zmod 1000000009) ^ (12393825) -- about 10 sec
#eval (1234324 : zmod 1000000009) ^ (123938256) -- excessive memory consumption detected at 'vm'

What do you all think will be the best approach for getting #eval to do a fast modexp?

Kevin Buzzard (Mar 02 2022 at 05:23):

There's a Miller-Rabin project?? You know #eval can't be used in proofs, right?

Bolton Bailey (Mar 02 2022 at 05:26):

Yes, I know eval can't be used in proofs. It's a research apprenticeship program, and Sean is my mentee. For the sake of the poster session at the end of term, it would be nice to make a graph showing the relative performance of the Miller-Rabin test we're implementing and norm_num

Bolton Bailey (Mar 02 2022 at 05:27):

The PR is here #12254

Mario Carneiro (Mar 02 2022 at 05:55):

def binpow {M} [has_one M] [has_mul M] (m : M) :   M :=
nat.binary_rec 1 (λ b _ ih, let ih2 := ih * ih in cond b (m * ih2) ih2)

#eval (1234565432124 : zmod 10000000000009) * (12327261237214456) -- fast
#eval binpow (1234324 : zmod 1000000009) (1239382) -- fast
#eval binpow (1234324 : zmod 1000000009) (12393825) -- fast
#eval binpow (1234324 : zmod 1000000009) (123938256) -- fast

Bolton Bailey (Mar 02 2022 at 05:56):

Thanks!

Simon Hudon (Mar 02 2022 at 05:56):

The simplest solution is to write a modexp function with the right performance characteristics. If you're working in Lean 4 (or plan to), I believe (I haven't used it yet) writing csimp lemmas will let you specify rewrite rules for the compiler to transform your ^ expression into a call to your optimized function

Mario Carneiro (Mar 02 2022 at 05:56):

the issue is that the default implementation of monoid.pow is npow_rec which works by unary recursion. The main downside of making binpow the default is that it has different defeqs

Mario Carneiro (Mar 02 2022 at 05:59):

By the way, norm_num always uses binary recursion for implementing ^, so it might even be able to beat the #eval implementation for large enough exponent

Mario Carneiro (Mar 02 2022 at 06:01):

@Bolton Bailey, I updated the code snippet to fix a bug

Gabriel Ebner (Mar 02 2022 at 09:41):

We should probably give fin and zmod (and nat and int) the fast npow implementation then.

Mario Carneiro (Mar 02 2022 at 10:24):

should we make it the default for all monoids?

Gabriel Ebner (Mar 02 2022 at 10:29):

That seems like a good idea.

Eric Wieser (Jun 01 2022 at 06:31):

Is docs#fin.comm_ring using the default power? (Edit: yes)

Eric Wieser (Jun 01 2022 at 06:44):

The implementation I expected was the one that makes docs#fin.coe_pow defeq, but that lemma doesn't even exist!


Last updated: Dec 20 2023 at 11:08 UTC