Como o anel interno é escolhido no algoritmo de Schönhage – Strassen?


9

Eu tenho tentado implementar o algoritmo de multiplicação de números inteiros Schönhage-Strassen, mas atingi um obstáculo na etapa recursiva.

Eu tenho um valor com bits e quero calcular x ^ 2 \ pmod {2 ^ n + 1} . Eu originalmente pensei que a idéia era escolher um k tal que 4 ^ k \ geq 2n , dividir x em 2 ^ k pedaços cada um com 2 ^ {k-1} bits, aplicar a convolução da SSA enquanto trabalhava no módulo 2 ^ {2 ^ k} +1 , um anel com 2 ^ k bits de capacidade por valor e, em seguida, reinstale as peças. No entanto, a saída da convolução tem um pouco mais de 2n bits (ou seja, > 2 ^ kn x 2xnkx2(mod2n+1)k4k2nx2k2k122k+12k2n>2kbits por valor de saída, que é mais do que a capacidade do anel, devido a cada valor de saída ser uma soma de vários produtos) para que isso não funcione. Eu tive que adicionar um fator extra de 2 de preenchimento.

Esse fator extra de 2 no preenchimento arruina a complexidade. Isso torna meu passo recursivo muito caro. Em vez de um F(n)=nlgn+nF(2n)=Θ(nlgnlglgn) , acabo com um algoritmo F(n)=nlgn+nF(4n)=Θ(nlg2n) .

Li algumas referências vinculadas da wikipedia, mas todas parecem encobrir os detalhes de como esse problema foi resolvido. Por exemplo, eu poderia evitar a sobrecarga do preenchimento extra trabalhando no módulo 2p2k+1 para um p que não é uma potência de 2 ... mas depois as coisas quebram mais tarde, quando eu tenho apenas de-2 fatores restantes e não pode aplicar o Cooley-Tukey sem dobrar o número de peças. Além disso, p pode não ter um módulo inverso multiplicativo 2p+1 . Portanto, ainda há fatores forçados de 2 sendo introduzidos.

Como escolho o anel para usar durante a etapa recursiva, sem soprar a complexidade assintótica?

Ou, na forma de pseudo-código:

multiply_in_ring(a, b, n):
  ...
  // vvv                          vvv //
  // vvv HOW DOES THIS PART WORK? vvv //
  // vvv                          vvv //
  let inner_ring = convolution_ring_for_values_of_size(n);
  // ^^^                          ^^^ //
  // ^^^ HOW DOES THIS PART WORK? ^^^ //
  // ^^^                          ^^^ //

  let input_bits_per_piece = ceil(n / inner_ring.order);
  let piecesA = a.splitIntoNPiecesOfSize(inner_ring.order, input_bits_per_piece);
  let piecesB = b.splitIntoNPiecesOfSize(inner_ring.order, input_bits_per_piece);

  let piecesC = inner_ring.negacyclic_convolution(piecesA, piecesB);
  ...

Por favor, não postar a mesma pergunta em vários sites . Cada comunidade deve ter uma chance honesta de responder sem perder tempo com ninguém. Sugiro que você exclua uma das duas cópias.
DW

@DW Done. Fiz uma postagem cruzada depois que o cs não deu nenhuma resposta por uma semana, pensando que era muito difícil para esse site. Iria vincular de volta todas as respostas, obviamente.
22416 Craig Gidney

Compreendo. Se surgir no futuro, você sempre poderá sinalizar sua postagem para obter atenção do moderador e solicitar a migração, e podemos transferi-la para o CSTheory. Obrigado pela sua compreensão!
19416 DW

3
Existe uma versão do algoritmo que funciona com números de módulo da forma : A. Schönhage. Algoritmos assintoticamente rápidos para a multiplicação e divisão numérica de polinômios com coeficientes complexos. Em EUROCAM '82: European Computer Algebra Conference, Lect. Notas Comp. Sci. 144, 3-15. iai.uni-bonn.de/~schoe/publi39.dvi2ν2n
Markus Bläser

IIRC, você teve uma resposta parcial na questão CS agora excluída. Parece uma pena perder isso. Você pode incluí-lo aqui (na pergunta, para que a pergunta não esteja marcada como já respondida)?
Peter Taylor

Respostas:


4

Esta resposta é retirada do artigo "Algoritmos assintoticamente rápidos para a multiplicação e divisão numérica de polinômios com coeficientes complexos" que Markus vinculou nos comentários.


Você deseja quadrado um número de bits, módulo . Aqui está o que você faz:2 n + 1n2n+1

  • Encontrar e que satisfazem e .s n = ( p - 1 ) 2 s s p 2 spsn=(p1)2ssp2s

  • Escolha o número de peças para dividir os bits e os parâmetros correspondentes para os tamanhos das peças: n2mn

    m=s/2+1s2=s/2+1p2=p/2+1

    Observe que es2p2 continuam satisfazendo os invariantes . Observe também que 2 m 2 s 2 p 22 n + m + 1 é satisfeito, portanto a entrada se encaixa com espaço para transporte.s2p22s22m2s2p22n+m+1

  • Realize a convolução negacíclica baseada na FFT nas peças e no restante, como de costume.

Então essa é a ideia geral: um fator de preenchimento logarítmico . Agora, para a análise de complexidade. A FFT levará n m trabalho a fazer, e estamos recorrendo em peças de 2 m de tamanho ( p 2 - 1 ) 2 s 2 , para que agora possamos fazer contas extremamente grosseiras com a relação de recorrência wrt s :pnm2m(p21)2s2s

F(s)()(p-1 1)2sm+2mF(s/2+1 1)()2s2s(s/2+1 1)+2s/2+1 1F(s/2+1 1)()s22s+22s/2F(s/2+1 1)()s22s+4(s/2)22s+16(s/4)22s+...()2ss2lg(s)()nlgn(lgnlgn)2lglgnlgn()nlgn(lg2n)lglgn()n(lgn)lglgn

O que parece certo, embora eu tenha trapaceado bastante nessas etapas.

O "truque" parece ser o fato de acabarmos com vez de s no custo base. Ainda existem duas multiplicações por duas por nível recursivo, como eu estava reclamando na pergunta, mas agora a metade de s está pagando dividendos duplos, para que tudo dê certo. Então, no final, cancelamos o fator extra de s (que na verdade é um fator de log n ), graças a tornar p logaritmicamente grande em relação a s inicialmente.s2sssregistronps

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.