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)k4k≥2nx2k2k−122k+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)=nlg⁡n+nF(2n)=Θ(nlg⁡nlg⁡lg⁡n) , acabo com um algoritmo F(n)=nlg⁡n+nF(4n)=Θ(nlg2⁡n) .

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=(p−1)2ss≤p≤2s

  • 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 2 ≥ 2 n + m + 1 é satisfeito, portanto a entrada se encaixa com espaço para transporte.s2≤p2≤2s22m2s2p2≥2n+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(p2−1)2s2s

F(s)(≤)(p-1 1)2sm+2mF(⌈s/2⌉+1 1)(≤)2s2s(⌊s/2⌋+1 1)+2⌊s/2⌋+1 1F(⌈s/2⌉+1 1)(≤)s22s+2⋅2s/2F(s/2+1 1)(≤)s22s+4(s/2)22s+16(s/4)22s+...(≤)2ss2lg⁡(s)(≤)nlg⁡n(lg⁡nlg⁡n)2lg⁡lg⁡nlg⁡n(≤)nlg⁡n(lg2⁡n)lg⁡lg⁡n(≤)n(lg⁡n)lg⁡lg⁡n

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.s2sssregistro⁡nps

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.