Numpy: L'implémentation PCG fournie par Numpy a une auto-corrélation significative et dangereuse

Créé le 20 mai 2020  ·  104Commentaires  ·  Source: numpy/numpy

Le générateur PCG utilisé par Numpy a une autocorrélation significative. C'est-à-dire que pour chaque séquence générée à partir d'une graine, il existe un grand nombre de séquences corrélées sans chevauchement à partir d'autres graines. Par «corrélé», j'entends que l'entrelacement de deux de ces séquences et le test du résultat vous permettent d'obtenir des échecs qui n'apparaissaient pas dans chaque séquence individuellement.

La probabilité que deux générateurs sur un grand nombre de terminaux obtiennent deux de ces séquences est non négligeable. Pourquoi cela se produit d'un point de vue mathématique est bien connu, mais il est expliqué ici en détail: http://prng.di.unimi.it/pcg.pgp (voir "Sous-séquences dans le même générateur").

Pour montrer directement ce problème, j'ai écrit ce simple programme C en réutilisant le code Numpy: http://prng.di.unimi.it/intpcgnumpy.c . Le programme prend deux états de 128 bits de deux générateurs (avec la même constante LCG ou «flux») sous forme de bits haut et bas, entrelace leur sortie et l'écrit sous forme binaire. Une fois que nous l'avons envoyé via PractRand, nous ne devrions voir aucun échec statistique, car les deux flux doivent être indépendants. Mais si vous essayez de partir de deux états avec les mêmes 64 bits inférieurs, vous obtenez:

./intpcgnumpy 0x596d84dfefec2fc7 0x6b79f81ab9f3e37b 0x8d7deae980a64ab0 0x6b79f81ab9f3e37b | stdbuf -oL ~ / svn / c / xorshift / practrand / RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithread
RNG_test avec PractRand version 0.94
RNG = RNG_stdin, seed = inconnu
ensemble de test = étendu, pliable = supplémentaire

rng=RNG_stdin, seed=unknown
length= 128 megabytes (2^27 bytes), time= 2.2 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(0+0,13-2,T)                  R= +27.6  p =  1.0e-13    FAIL
  BCFN(0+1,13-2,T)                  R= +68.0  p =  2.3e-34    FAIL !!!
  BCFN(0+2,13-3,T)                  R= +90.8  p =  8.8e-43    FAIL !!!
  BCFN(0+3,13-3,T)                  R=+120.6  p =  6.9e-57    FAIL !!!!
  DC6-6x2Bytes-1                    R=  +8.9  p =  4.0e-5   mildly suspicious
  DC6-5x4Bytes-1                    R= +15.7  p =  4.3e-9   very suspicious
  [Low1/8]BCFN(0+0,13-4,T)          R= +11.6  p =  4.9e-5   unusual
  ...and 1074 test result(s) without anomalies

Vous pouvez même aller plus bas - vous avez juste besoin des mêmes 58 bits inférieurs:

./intpcgnumpy 0x596d84dfefec2fc7 0x0579f81ab9f3e37b 0x8d7deae980a64ab0 0x6b79f81ab9f3e37b | stdbuf -oL ~/svn/c/xorshift/practrand/RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithreaded

[...]
rng=RNG_stdin, seed=unknown
length= 32 gigabytes (2^35 bytes), time= 453 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/16]FPF-14+6/32:cross        R= +11.6  p =  4.0e-10   VERY SUSPICIOUS
  [Low1/32]FPF-14+6/32:cross        R= +16.5  p =  3.2e-14    FAIL
  [Low1/32]FPF-14+6/16:cross        R= +12.8  p =  3.8e-11   VERY SUSPICIOUS
  [Low1/64]FPF-14+6/64:cross        R=  +6.8  p =  4.8e-6   mildly suspicious
  [Low1/64]FPF-14+6/32:cross        R=  +6.0  p =  1.9e-5   unusual
  [Low1/64]FPF-14+6/16:cross        R=  +5.5  p =  5.8e-5   unusual
  [Low4/32]FPF-14+6/64:all          R=  +5.8  p =  5.9e-5   unusual
  [Low4/32]FPF-14+6/32:(0,14-0)     R=  +7.7  p =  1.0e-6   unusual
  [Low4/32]FPF-14+6/32:(1,14-0)     R=  +7.7  p =  9.1e-7   unusual
  [Low4/32]FPF-14+6/32:all          R=  +6.5  p =  1.3e-5   unusual
  [Low4/64]FPF-14+6/64:all          R=  +5.9  p =  5.1e-5   unusual
  [Low4/64]FPF-14+6/64:cross        R=  +8.2  p =  3.0e-7   suspicious
  [Low4/64]FPF-14+6/32:(0,14-0)     R=  +7.6  p =  1.0e-6   unusual
  [Low8/64]FPF-14+6/64:(0,14-0)     R= +17.0  p =  2.2e-15    FAIL
  [Low8/64]FPF-14+6/64:(1,14-0)     R=  +9.1  p =  5.1e-8   mildly suspicious
  [Low8/64]FPF-14+6/64:all          R= +12.7  p =  2.1e-11   VERY SUSPICIOUS
  [Low8/64]FPF-14+6/32:(0,14-0)     R= +12.8  p =  1.7e-11   VERY SUSPICIOUS
  [Low8/64]FPF-14+6/32:all          R= +11.0  p =  9.3e-10   VERY SUSPICIOUS
  ...and 1696 test result(s) without anomalies

Notez que pour obtenir plus de 50% de probabilité que deux générateurs partent de deux graines corrélées (choisies au hasard), vous avez besoin d'environ un demi-million de générateurs commençant au hasard (paradoxe de l'anniversaire). Et si vous considérez la probabilité qu'ils ne partent pas exactement du même état, mais aient des séquences de corrélation qui se chevauchent significativement, vous avez besoin de beaucoup moins.

Tout générateur sensé de la littérature ne se comportera

Veuillez au moins documenter quelque part que le générateur ne doit pas être utilisé sur plusieurs terminaux ou dans un environnement hautement parallèle.

La même chose peut se produire avec différents "flux", car les séquences générées par un LCG en changeant la constante additive sont toutes les mêmes modulo un changement de signe et une constante additive. Vous pouvez voir une discussion ici: https://github.com/rust-random/rand/issues/907 et une discussion mathématique complète du problème ici: https://arxiv.org/abs/2001.05304 .

numpy.random

Tous les 104 commentaires

@imneme , @bashtage , @rkern seraient les autorités ici, mais je pense que nous avons passé en SeedSequence.spawn à celle jumped one. Par exemple, il y a eu cette discussion lorsque nous avons discuté de l'API. Veuillez consulter les conseils ici https://numpy.org/devdocs/reference/random/parallel.html et suggérer des améliorations si nécessaire.

@mattip Cela n'a rien à voir avec le saut.

Je pense qu'en pratique, il est difficile d'apporter des changements globaux, même si une documentation améliorée est toujours une bonne idée.

Je recommanderais probablement AESCounter à toute personne avec AES-NI ou SPECK128 à toute personne sans paramètres hautement parallèles.

La même chose peut se produire avec différents "flux", car les séquences générées par un LCG en changeant la constante additive sont toutes les mêmes modulo un changement de signe et une constante additive.

Pouvez-vous quantifier cela? Je peux répliquer les échecs en utilisant le même incrément, mais nous semons l'incrément ainsi que l'état, et je n'ai pas encore observé d'échec avec deux incréments aléatoires différents. Si les incréments doivent également être soigneusement construits, cela affecterait la fréquence pratique des collisions d'anniversaire.

https://gist.github.com/rkern/f46552e030e59b5f1ebbd3b3ec045759

❯ ./pcg64_correlations.py --same-increment | stdbuf -oL ./RNG_test stdin64 -tf 2 -te 1 -tlmaxonly -multithreaded
0x56b35656ede2b560587e4251568a8fed
0x93526034ed105e9e587e4251568a8fed
[
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 115244779949650410574112983538102603757,
            "inc": 137507567477557873606783385380908979143
        },
        "has_uint32": 0,
        "uinteger": 0
    },
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 195824235027336627448689568147458133997,
            "inc": 137507567477557873606783385380908979143
        },
        "has_uint32": 0,
        "uinteger": 0
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin64, seed = 0x4bf19f7b
test set = expanded, folding = extra

rng=RNG_stdin64, seed=0x4bf19f7b
length= 128 megabytes (2^27 bytes), time= 3.0 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN_FF(2+0,13-3,T)               R= +59.9  p =  3.8e-28    FAIL !!!       
  BCFN_FF(2+1):freq                 R= +89.0  p~=   6e-18     FAIL !         
  BCFN_FF(2+2):freq                 R= +39.6  p~=   6e-18     FAIL !         
  BCFN_FF(2+3):freq                 R= +14.6  p~=   6e-18     FAIL !         
  BCFN_FF(2+4):freq                 R= +10.3  p~=   5e-11   very suspicious  
  DC6-9x1Bytes-1                    R=  +7.1  p =  5.6e-4   unusual          
  DC6-6x2Bytes-1                    R= +18.9  p =  1.0e-10   VERY SUSPICIOUS 
  DC6-5x4Bytes-1                    R= +11.2  p =  1.4e-6   suspicious       
  [Low4/16]BCFN_FF(2+0):freq        R= +19.5  p~=   6e-18     FAIL !         
  [Low4/16]FPF-14+6/16:all          R=  +5.6  p =  1.0e-4   unusual          
  [Low4/16]FPF-14+6/4:all           R=  +5.9  p =  4.6e-5   unusual          
  [Low4/32]BCFN_FF(2+0):freq        R=  +6.5  p~=   2e-5    unusual          
  [Low8/32]BCFN_FF(2+0):freq        R= +15.1  p~=   6e-18     FAIL !         
  [Low8/32]FPF-14+6/32:all          R=  +8.4  p =  2.5e-7   very suspicious  
  [Low8/32]FPF-14+6/32:all2         R=  +9.0  p =  7.8e-5   unusual          
  [Low8/32]FPF-14+6/16:(0,14-0)     R= +12.4  p =  4.5e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/16:all          R= +15.5  p =  5.2e-14    FAIL           
  [Low8/32]FPF-14+6/16:all2         R= +41.4  p =  2.6e-16    FAIL !         
  [Low8/32]FPF-14+6/4:(0,14-0)      R=  +6.9  p =  5.9e-6   unusual          
  [Low8/32]FPF-14+6/4:all           R=  +7.9  p =  6.6e-7   suspicious       
  ...and 871 test result(s) without anomalies

OK, je vais réessayer.

Il n'y a pas de flux multiples dans un LCG avec un module de puissance de 2. Beaucoup le croyaient au début, et il y a même de vieux papiers prétendant faire des choses intéressantes avec ces "flux", mais on sait depuis des décennies que les orbites que vous obtenez en changeant les constantes sont _tout le même module qu'un additif constante et éventuellement un changement de signe_. Le plus loin que je puisse tracer c'est

Mark J.Durst, Utilisation de générateurs congruentiels linéaires pour la génération parallèle de nombres aléatoires,
1989 Winter Simulation Conference Proceedings, IEEE Press, 1989, pp. 462–466.

J'ai donc écrit un autre programme http://prng.di.unimi.it/corrpcgnumpy.c dans lequel vous pouvez définir:

  • Un état initial pour un PRNG.
  • Un état initial pour un autre PRNG.
  • Une "constante de flux" arbitraire pour le premier PRNG.
  • Une "constante de flux" arbitraire pour le deuxième PRNG (ils devraient être à la fois pairs ou impairs; cette restriction peut être supprimée avec quelques manipulations supplémentaires).
  • Un nombre fixe de bits inférieurs que nous placerons de manière défavorable dans le second PRNG, essentiellement de telle manière qu'il commence par les mêmes bits du premier PRNG. Le reste des bits sera pris de l'état initial pour le deuxième PRNG que vous avez fourni.

C'est donc _exactement_ le réglage du premier programme, mais vous pouvez également choisir les constantes.

./corrpcgnumpy 0x596d84dfefec2fc7 0x6b79f81ab9f3e37b 0xac9c8abfcb89f65f 0xe42e8dff1c46de8b 0x8d7deae9efec2fc7 0x6b79f81ab9f3e37b 0x06e13e5e8c92c843 0xf92e8346feee7a21 56 | stdbuf -oL ~/svn/c/xorshift/practrand/RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithreaded

rng=RNG_stdin, seed=unknown
length= 4 gigabytes (2^32 bytes), time= 113 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]BCFN(0+0,13-1,T)          R= +27.2  p =  4.0e-14    FAIL
  [Low1/8]DC6-6x2Bytes-1            R= +10.9  p =  4.4e-6   suspicious
  [Low1/64]DC6-5x4Bytes-1           R=  -6.4  p =1-1.4e-4   unusual
  [Low8/64]FPF-14+6/64:(0,14-0)     R=  +8.4  p =  2.2e-7   mildly suspicious
  [Low8/64]FPF-14+6/64:all          R=  +8.7  p =  1.2e-7   suspicious
  [Low8/64]FPF-14+6/32:(0,14-0)     R= +10.2  p =  5.1e-9   suspicious
  [Low8/64]FPF-14+6/32:all          R=  +9.4  p =  2.7e-8   very suspicious
  [Low8/64]FPF-14+6/16:all          R=  +5.8  p =  6.4e-5   unusual
  ...and 1439 test result(s) without anomalies

Il y a donc _au moins_ 2 ^ 72 sous-séquences corrélées, peu importe comment vous choisissez les "constantes de flux", exactement comme dans le cas de la même constante.

Et on nous donne une quantité ridicule de mou au générateur: même si au lieu du point de départ exact que je calcule, vous utiliseriez un état un peu avant ou après, la corrélation apparaîtrait de toute façon. Vous pouvez facilement modifier le programme avec un paramètre supplémentaire pour ce faire.

Je le répète, encore une fois: aucun générateur moderne existant de la littérature scientifique n'a ce mauvais comportement (bien sûr, un Power-of-2 LCG a ce comportement, mais, pour l'amour de Dieu, ce n'est pas un générateur moderne).

Les critiques de Sabastiano sur PCG sont abordées dans ce billet de blog de 2018.

La version courte est que si vous êtes autorisé à créer des graines spécifiques, vous pouvez montrer un comportement «mauvais» à partir de pratiquement n'importe quel PRNG. Malgré son affirmation selon laquelle PCG est en quelque sorte unique, en fait PCG est assez conventionnel - les flux de PCG ne sont pas pires que, par exemple, SplitMix, qui est un autre PRNG largement utilisé.

C'est totalement faux. Pour me prouver le contraire, montrez deux séquences corrélées non superposées de MRG32k3a ou xoshiro256 ++.

Je n'ai jamais dit de non-chevauchement. Montrez-moi un test actuellement disponible pour xoshiro256 ++. que deux graines évitent le chevauchement.

En revanche, j'ai un test pour PCG qui montre que les «corrélations» que vous avez montrées sont essentiellement une forme de chevauchement.

Je ne peux pas combattre FUD comme "essentiellement" et "une forme", mais j'ai modifié http://prng.di.unimi.it/intpcgnumpy.c pour qu'au départ, il répète chaque PRNG 10 milliards de fois, et se termine avec une erreur message si la séquence générée traverse l'état initial de l'autre PRNG. Cela garantit que les 160 premiers Go de données dans Practrand proviennent de séquences sans chevauchement:

./intpcgnumpy 0x596d84dfefec2fc7 0x0579f81ab9f3e37b 0x8d7deae980a64ab0 0x6c79f81ab9f3e37b | stdbuf -oL ~/svn/c/xorshift/practrand/RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithreaded
[...]
rng=RNG_stdin, seed=unknown
length= 64 gigabytes (2^36 bytes), time= 926 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]FPF-14+6/64:(0,14-0)      R=  +8.8  p =  8.7e-8   mildly suspicious
  [Low1/8]FPF-14+6/64:all           R=  +6.3  p =  2.1e-5   unusual          
  [Low1/16]FPF-14+6/64:(0,14-0)     R=  +7.6  p =  1.1e-6   unusual          
  [Low1/16]FPF-14+6/64:(1,14-0)     R=  +8.3  p =  2.9e-7   mildly suspicious
  [Low1/16]FPF-14+6/64:all          R=  +8.0  p =  5.8e-7   suspicious       
  [Low1/16]FPF-14+6/32:all          R=  +7.1  p =  3.9e-6   mildly suspicious
  [Low1/64]FPF-14+6/32:cross        R=  +7.1  p =  2.6e-6   mildly suspicious
  [Low4/32]FPF-14+6/64:(0,14-0)     R= +13.5  p =  4.3e-12   VERY SUSPICIOUS 
  [Low4/32]FPF-14+6/64:all          R=  +9.0  p =  5.9e-8   very suspicious  
  [Low4/64]FPF-14+6/64:(0,14-0)     R= +11.4  p =  3.8e-10  very suspicious  
  [Low4/64]FPF-14+6/64:all          R=  +8.0  p =  5.3e-7   suspicious       
  [Low4/64]FPF-14+6/32:(0,14-0)     R= +10.3  p =  3.6e-9   suspicious       
  [Low4/64]FPF-14+6/32:all          R=  +6.1  p =  3.2e-5   unusual          
  [Low8/64]FPF-14+6/64:(0,14-0)     R= +18.6  p =  8.4e-17    FAIL           
  [Low8/64]FPF-14+6/64:(1,14-0)     R= +11.4  p =  3.9e-10  very suspicious  
  [Low8/64]FPF-14+6/64:(2,14-0)     R=  +8.3  p =  2.8e-7   mildly suspicious
  [Low8/64]FPF-14+6/64:all          R= +15.3  p =  6.9e-14    FAIL           
  [Low8/64]FPF-14+6/32:(0,14-0)     R=  +7.8  p =  7.1e-7   unusual          
  [Low8/64]FPF-14+6/32:(1,14-0)     R=  +7.2  p =  2.7e-6   unusual          
  [Low8/64]FPF-14+6/32:all          R=  +5.8  p =  6.9e-5   unusual          
  ...and 1786 test result(s) without anomalies

Ces données d'initialisation particulières n'ont que 56 bits fixes inférieurs, de sorte que l'on peut générer 2 ^ 72 séquences corrélées en retournant les bits supérieurs. Les échecs statistiques se produisent après seulement 64 Go de données, ce qui montre que les chevauchements ne sont pas responsables de la corrélation. Il est possible qu'avec d'autres choix ciblés spécifiques, le chevauchement se produise avant 64 Go, bien sûr - c'est un exemple spécifique. Mais il est maintenant facile de vérifier que le chevauchement n'est pas le problème - le générateur a juste beaucoup de corrélations internes indésirables.

Veuillez respecter le code de conduite . Essayez de garder vos commentaires dans le ton avec les directives d'être «empathique, accueillant, amical et patient» et «soyez prudent dans les mots que nous choisissons. Nous sommes prudents et respectueux dans notre communication».

Je n'ai jamais dit de non-chevauchement. Montrez-moi un test actuellement disponible pour xoshiro256 ++. que deux graines évitent le chevauchement.

Eh bien, c'est trivial: décidez de la longueur du flux, itérez et vérifiez que les deux flux ne traversent pas l'état initial. C'est le même code que j'ai utilisé pour montrer que les flux PCG corrélés dans le programme http://prng.di.unimi.it/intpcgnumpy.c ne se chevauchent pas.

Malgré son affirmation selon laquelle PCG est en quelque sorte unique, en fait PCG est assez conventionnel - les flux de PCG ne sont pas pires que, par exemple, SplitMix, qui est un autre PRNG largement utilisé.

À mon humble avis, l'auto-corrélation au sein de PCG est bien pire. Il n'y a aucun résultat pour le générateur additif sous-jacent à une instance SplitMix analogue aux résultats spectaculaires de Durst en 1989 sur les LCG.

Mais les problèmes très légers de SplitMix sont connus, et JEP 356 fournira une nouvelle classe de générateurs séparables, LXM, essayant de résoudre ces problèmes. Il serait temps de passer à autre chose et de remplacer le PCG également par quelque chose de moins défectueux.

Le problème sous-jacent est connu pour les deux générateurs, et il est dû au manque de mélange d'états. Si vous modifiez le bit _k_ de l'état de l'un de ces générateurs, le changement ne se propagera jamais sous le bit _k_. Cela ne se produit pas dans les LCG à module premier, dans les générateurs linéaires F₂, les générateurs CMWC, etc. Tous les autres générateurs essaient de mélanger leur état aussi rapidement et autant que possible.

Équilibrer les problèmes de PCG et SplitMix est un hareng rouge. SplitMix a un générateur sous-jacent très simple, juste additif, mais en plus il y a une fonction de brouillage qui est très puissante: c'est le finaliseur 64 bits d'Appleby de la fonction de hachage MurmurHash3, qui a été largement utilisée dans un certain nombre de contextes et a été amélioré par Stafford (http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html). Les constantes de la fonction ont été formées pour avoir des propriétés d'avalanche spécifiques et mesurables. Même les changements dans un petit nombre de bits ont tendance à s'étendre sur toute la sortie. En d'autres termes, SplitMix se dresse sur l'épaule des géants.

Au contraire, le LCG sous-jacent aux générateurs PCG a les mêmes problèmes de manque de mélange, mais les fonctions de brouillage ne sont qu'une simple séquence d'opérations arithmétiques et logiques assemblées par l'auteur sans aucune garantie théorique ou statistique. Si elles avaient été conçues en prenant en compte le fait que toutes les séquences du LCG sous-jacent sont les mêmes modulo une constante additive et éventuellement un changement de signe, il aurait été possible de résoudre le problème.

Mais l'auteur n'avait aucune idée que les séquences étaient si facilement dérivables les unes des autres. Cela peut être facilement vu à partir de cette déclaration dans la section 4.2.3 du rapport technique PCG (https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf):

"Chaque choix de _c_ entraîne une séquence de nombres différente qui n'a aucune de ses paires de sorties successives en commun avec une autre séquence."

Ceci est considéré comme la preuve que les séquences sont différentes, c'est-à-dire que le LCG sous-jacent fournit plusieurs flux. Les résultats négatifs de Durst sur ce sujet en 1989 n'apparaissent nulle part dans l'article. Comme je l'ai remarqué plus tôt, d'après ces résultats, toutes ces séquences sont les mêmes, modulo une constante additive et éventuellement un changement de signe (pour les LCG avec un module de puissance de 2 de puissance maximale, comme cela se produit dans PCG).

Je suis sûr que ne pas citer les résultats de Durst est une erreur _bona fide_, mais le problème est qu'une fois que vous êtes convaincu que le LCG sous-jacent que vous utilisez fournit des "flux" qui sont "différents" dans un certain sens, quand ils ne le sont pas, vous vous retrouvez avec un générateur comme PCG dans lequel pour chaque sous-séquence il y a 2 ^ 72 sous-séquences corrélées non superposées, même si vous changez le "flux".

Merci à tous pour votre participation. Pour le moment, je ne suis pas intéressé par les jugements binaires comme "PCG est bon / mauvais". Veuillez utiliser vos propres forums pour de telles discussions. Ce qui est sur le sujet ici, c'est ce que va faire numpy, et ce jugement final appartient aux développeurs numpy. Nous apprécions l'expertise que vous apportez tous à cette discussion, mais je veux me concentrer sur les faits sous-jacents plutôt que sur le jugement final. J'apprécie particulièrement les déclarations quantitatives qui me donnent une idée de la marge de manœuvre dont nous disposons. Si mes jugements précédents étaient erronés, c’était parce que j’ai sauté trop tôt au jugement, alors j’apprécierais votre aide pour éviter cela à nouveau. Je vous remercie.

Notez que pour obtenir plus de 50% de probabilité que deux générateurs partent de deux graines corrélées (choisies au hasard), vous avez besoin d'environ un demi-million de générateurs commençant au hasard (paradoxe de l'anniversaire).

@vigna Pouvez-vous m'expliquer ce calcul? Le calcul de collision d'anniversaire que je connais bien donne 50% de chances d'une collision n bits à 2**(n/2) éléments (donnez ou prenez un facteur de 2). Un demi-million équivaut à 2**19 , donc vous semblez prétendre que les corrélations dangereuses commencent aux alentours d'une collision de 40 bits dans les bits inférieurs, mais je n'ai pas vu de preuves que cela soit pratiquement observable. J'ai testé une paire partageant les 40 bits inférieurs et je suis arrivé à 16 Tio dans PractRand avant d'annuler le test. Si vous avez observé un échec avec une collision de 40 bits, combien de TiB avez-vous dû tester pour le voir?

Je suis convaincu que la modification de l'incrément n'affecte pas la probabilité de collision. Une discussion plus approfondie sur les mérites des «flux PCG» est hors sujet. Utiliser cette discussion comme excuse pour marteler à plusieurs reprises «l'auteur» est particulièrement malvenu et enfreint notre code de conduite . Si nous persistons, nous devrons continuer sans votre avis. Je vous remercie.

@imneme Cela semble être lié aux problèmes de sauts par un multiple d'une grande puissance de 2. Quand je construis une paire d'instances PCG64 avec le même incrément et partageant le plus bas n bits, la distance que je calcule entre les deux est un multiple de 1 << n . Il semble que votre fonction de sortie DXSM plus forte semble également résoudre cette manifestation. J'ai testé une paire d'instances PCG64DXSM qui partagent un incrément et les 64 bits d'état inférieurs à 2 Tio sans problème.

OK, c'est embarrassant: c'était un demi-milliard, pas un demi-million. Une seule lettre peut faire une grande différence. Je m'excuse pour le glissement.

Mais, comme je l'ai dit plus tôt, il s'agit de la probabilité d'atteindre exactement le même état de départ, et non de la probabilité d'un chevauchement significatif de sous-séquences corrélées. Personnellement, je préfère utiliser des PRNG sans sous-séquences corrélées, car il y en a beaucoup, mais, comme vous le dites à juste titre, la décision n'appartient qu'à vous.

Corriger la fonction de brouillage pour qu'elle ait de meilleures propriétés de mixage semble être une solution parfaitement raisonnable.

Mon message était simplement destiné à clarifier les différences structurelles entre PCG et SplitMix, car un article précédent affirmait qu'ils avaient des problèmes similaires, et je ne pense pas que ce soit une déclaration correcte. Vous ne pouvez pas écrire un programme comme http://prng.di.unimi.it/corrpcgnumpy.c pour SplitMix.

@rkern , vous avez demandé:

@imneme Cela semble être lié aux problèmes de sauts par un multiple d'une grande puissance de 2. Quand je construis une paire d'instances PCG64 avec le même incrément et partageant le plus petit n bits, la distance que je calcule entre les deux est un multiple de 1 << n . Il semble que votre fonction de sortie DXSM plus forte semble également résoudre cette manifestation. J'ai testé une paire d'instances PCG64DXSM qui partagent un incrément et les 64 bits d'état inférieurs à 2 Tio sans problème.

Merci d'avoir trouvé et de revenir au fil de discussion de l'année dernière. Oui, comme le note Sebastiano dans sa réponse,

Corriger la fonction de brouillage pour qu'elle ait de meilleures propriétés de mixage semble être une solution parfaitement raisonnable.

XSL-RR est à l'extrémité la plus faible des choses. En revanche, la fonction de sortie RXS-M originale du papier PCG et la nouvelle fonction de sortie DXSM font plus de brouillage, et ne montrent donc pas ce genre de problèmes. DXSM (ajouté à la source PCG dans ce commit l'année dernière) a été spécifiquement conçu pour être plus fort que XSL-RR mais avoir des performances temporelles similaires (cf, RXS-M, qui est plus lent). J'ai testé DXSM assez dur l'année dernière, mais 67 jours après le début de la course, nous avons eu une panne de courant prolongée qui a arrêté le serveur (la batterie de l'onduleur s'est déchargée) et a mis fin au test, mais à ce moment-là, il avait fait ses preuves dans les deux cas. test (128 To de sortie testés) et sauts de 2 ^ 48 (64 To de sortie testés, car cela fonctionne plus lentement).

Si, même sans concevoir DXSM, RXS-M aurait résolu le problème, une question est de savoir pourquoi j'ai déjà utilisé la permutation XSL-RR plus faible à la place - pourquoi ne pas toujours utiliser un brouillage de bits très fort dans la fonction de sortie? La réponse est que cela se résume essentiellement à des cycles. La vitesse compte pour les gens, vous essayez donc d'éviter de faire beaucoup plus de brouillage que nécessaire.

C'est un problème que Sebastiano connaît, car son approche et la mienne ont beaucoup en commun. Nous adoptons chacun une approche établie de longue date qui échouerait aux tests statistiques modernes (LCG dans mon cas, et XorShift LFSR de Marsaglia dans le sien) et ajoutons une fonction de sortie de brouillage pour le racheter. Nous nous efforçons tous les deux de rendre cette fonction de sortie bon marché, et nous avons tous les deux été un peu pris au dépourvu lorsque des lacunes dans le générateur sous-jacent que nous essayons de masquer avec notre fonction de sortie apparaissent néanmoins. Dans son cas, ce sont les problèmes de linéarité qui se sont manifestés .

Mais dans un travail un peu récent qui m'a beaucoup plu, il a également montré combien de conceptions basées sur LFSR ont des problèmes de poids (reflétant une préoccupation de longue date) qui sont insuffisamment masqués par leurs fonctions de sortie. Ses propres générateurs ont réussi son test, c'est donc quelque chose, mais en 2018, lorsque je regardais son nouveau xoshiro PRNG, il m'a semblé que les problèmes de poids de marteau du générateur sous-jacent passaient par sa fonction de sortie. Il a depuis révisé xoshiro avec une nouvelle fonction de sortie, et j'espère que cela fera l'affaire (il a également fait d'autres changements, alors peut-être que l'un corrige également le problème des répétitions, mis en évidence par ce programme de test ?).

En ce qui concerne ses programmes de corrélation, en 2018, quand il a mis une critique de PCG sur son site Web qui comprenait des programmes qu'il avait écrits avec divers problèmes (comme l'ensemencement artificiel, etc.), j'ai écrit une réponse qui contenait un tas de programmes similaires pour d'autres PRNG établis de longue date, y compris corrsplitmix2.c qui crée des flux corrélés dans SplitMix. Je ne suis pas tout à fait sûr de ce que Sebastian veut dire quand il dit que cela ne peut pas être fait, mais j'admets que je n'ai pas eu l'occasion d'examiner de près son programme de test pour voir si son nouveau programme est substantiellement différent de ceux qu'il écrit il y a quelques années.

Veuillez pardonner mon manque de compréhension - mais pourrais-je demander à quelqu'un une conclusion sommaire à ce stade? Existe-t-il des circonstances réalistes dans lesquelles le PCG par défaut n'est pas sûr? Quels sont les arguments pour changer la valeur par défaut?

La solution la plus simple consiste à ajouter de la documentation sur les applications à haute (ultra-haute?) Dimension et la probabilité de séquences corrélées.

Un chemin plus difficile serait de remplacer la fonction de sortie qui interromprait le flux. Je ne suis pas sûr de la force d'une promesse default_rng . Les documents ne semblent pas avertir que cela peut changer, il faudrait donc probablement un cycle de dépréciation pour changer. Cela nécessiterait d'ajouter la nouvelle fonction de sortie en tant que générateur de bits autonome (ou configurable à partir de PCG64, ce qui serait plus judicieux), puis d'avertir les utilisateurs qu'elle changera après l'année XXXX / version Y.ZZ.

Le chemin le plus difficile serait de trouver un nouveau rng par défaut. Ce n'était pas facile la première fois et je ne pense pas que rien ait changé pour déplacer l'aiguille dans une direction particulière au cours des 18 derniers mois.

J'ai ouvert gh-16493 à propos de la modification de default_rng() , je ne pense pas que cela soit lié à ce problème, pas même sûr que nous devions en discuter, nous avions probablement déjà établi les règles il y a longtemps et je ne le fais pas. t souviens-toi.


Je ne prétends pas comprendre pleinement cette discussion, mais il semble qu'il y ait deux choses à comprendre:

  1. Etant certain que nous avons suffisamment de progrès, je dois faire confiance à Robert sur ce point et pour le moment, il me semble que cela devrait aller au mieux de nos connaissances? (C'est-à-dire que la probabilité d'une collision réelle est probablement extrêmement faible même dans des environnements dont la magnitude est plus grande que tout ce dans quoi NumPy peut être utilisé? Que cela justifie ou non de changer la valeur par défaut à l'avenir est un autre problème.)
  2. Nous déclarons:

    et prend en charge [...] ainsi que: math: 2^{127} streams

    qui, ne sachant pas exactement d'où vient le nombre, semble être une légère exagération de notre part et nous pourrions considérer l'ajustement légèrement ajusté pour être parfaitement correct? Ou un lien vers une ressource externe donnant des détails supplémentaires?

La chose la plus simple à faire maintenant serait d'ajouter un PCG64DXSM BitGenerator , la variante de PCG64 avec le "multiplicateur bon marché" et une fonction de sortie DXSM plus forte. Je pense que tout le monde convient que c'est un pas en avant par rapport à la fonction de sortie XSL-RR que nous avons maintenant dans notre implémentation PCG64 , fonctionnant mieux statistiquement sans nuire aux performances d'exécution. C'est une mise à niveau simple dans le créneau que PCG64 sert pour les BitGenerator que nous fournissons. Je pense que nous devrions l'ajouter à côté de PCG64 .

Incidemment, je préfère que ce soit un nom distinct BitGenerator plutôt qu'une option du constructeur PCG64 . Ces options sont excellentes dans randomgen , dont le but est de fournir un grand nombre d'algorithmes et de variantes différents, mais pour numpy , je pense que nous voulons que nos sélections soient "à emporter" comme autant que nous pouvons.

Je ne pense pas que nous ayons vraiment réglé la politique de modification de ce que fournit default_rng() . Lorsque je l'ai proposé, j'ai évoqué l'idée que l'une des raisons pour lesquelles je préférais simplement mettre ses fonctionnalités dans le constructeur Generator() était que nous pouvions abandonner et passer à une fonction nommée différemment si nous en avions besoin. Cependant, à ce moment-là, nous pensions que default_rng() pourrait avoir besoin d'exposer beaucoup de détails sur le BitGenerator sous-jacent, ce que nous avons évité par la suite. Parce que PCG64DXSM expose la même API ( .jumped() en particulier) que PCG64 , la seule considération que nous aurions est que l'utilisation comme nouvelle valeur par défaut changerait le bitstream. Je pense qu'il serait raisonnable pour nous de suivre le même calendrier que toute autre modification du flux provenant des méthodes Generator par NEP 19 (c'est-à-dire sur les versions de fonctionnalités X.Y.0 ). Nous pouvons choisir d'être un peu plus prudents, si nous le voulons, et d'abord exposer PCG64DXSM comme un BitGenerator dans 1.20.0 et un document (mais pas warn() , trop bruyant sans effet) que default_rng() changera pour l'utiliser dans 1.21.0 .

Dans le cadre de l'ajout du nouveau BG, il serait bon de mettre à jour les notes de PCG64 pour commencer à servir de guide et de fournir une justification pour préférer la nouvelle variante.

  1. Etant certain que nous avons suffisamment de progrès, je dois faire confiance à Robert sur ce point et pour le moment, il me semble que cela devrait aller au mieux de nos connaissances? (C'est-à-dire que la probabilité d'une collision réelle est probablement extrêmement faible même dans des environnements dont la magnitude est plus grande que tout ce dans quoi NumPy peut être utilisé? Que cela justifie ou non de changer la valeur par défaut à l'avenir est un autre problème.)

C'est probablement un peu trop désinvolte. Cela dépend du nombre de flux, de la quantité de données que vous extrayez de chaque flux et de votre tolérance au risque pour une collision d'anniversaire. Je n'ai pas encore réussi à transformer ces calculs en un paragraphe compréhensible pour faire des recommandations faciles à suivre, c'est pourquoi je n'ai pas revu ce problème Github depuis un moment. Je ne pense pas que ce soit un problème de cheveux en feu qui doive être corrigé _ maintenant_, cependant.

J'écrirai quelque chose plus tard, mais comme je le vois dans ce fil, nous avons rechapé le terrain que nous avons parcouru l'année dernière. Rien n'a changé à part que Sebastiano a découvert que NumPy avait expédié PCG. L'an dernier, l'analyse de l'équipe NumPy était plus approfondie et envisageait des scénarios plus plausibles.

Ma préférence serait de mettre à jour la valeur par défaut aussi rapidement que raisonnablement possible - juste pour réduire la confusion. Je veux dire, n'attendez pas un cycle de dépréciation.

@imneme - merci beaucoup - je trouverais une chose plus longue très utile.

Probablement la meilleure explosion du post

J'avais en tête ce que je voulais dire, mais je trouve qu'en regardant les articles d'il y a un an, j'ai déjà tout dit, ici et ailleurs ( mon blog (2017), reddit , mon blog encore (2018)) , et dans les discussions NumPy, etc.)

À propos des flux et de leur auto-similitude (pour lesquels @rkern a écrit un testeur de dépendance au flux ), j'ai écrit l'année dernière:

Comme indiqué dans mon article de blog mentionné précédemment, les flux de PCG ont beaucoup en commun avec ceux de SplitMix.

En ce qui

La bonne façon de penser aux flux est juste un état plus aléatoire qui doit être amorcé. L'utilisation de petites valeurs telles que 1,2,3 est généralement une mauvaise idée pour tous les besoins d'amorçage de _any_ PRNG (car si tout le monde favorise ces graines, leurs séquences initiales correspondantes seront surreprésentées).

Nous pouvons choisir de ne pas l'appeler du tout un flux et de l'appeler simplement state. C'est ce que Marsaglia a fait dans XorWow . Si vous regardez le code, la séquence de Weyl counter n'interagit pas du tout avec le reste de l'état et, comme les LCG, et les variations de la valeur initiale ne représentent en réalité qu'une constante ajoutée.

Les flux de SplitMix, PCG et XorWow sont ce que nous pourrions appeler des flux «stupides». Ils constituent un reparamétrage trivial du générateur. Il y a cependant de la valeur à cela. Supposons que sans flux, notre PRNG aurait une répétition proche intéressante de 42, où 42 apparaît plusieurs fois en succession rapide et ne le fait que pour 42 et aucun autre nombre. Avec des flux stupides «juste un incrément» ou «juste un xor», nous éviterons en fait de câbler l'étrange répétition à 42; tous les nombres ont un flux dans lequel ils se répètent bizarrement. (Pour cette raison, le correctif que j'appliquerais pour réparer les problèmes de répétition rapprochée dans Xoshiro 256 est de mélanger dans une séquence de Weyl.)

(Je suis ensuite allé plus en profondeur dans ce commentaire et dans celui-ci .)

Je dirais que la clé à retenir est que pour presque n'importe quel PRNG, avec un peu de temps et d'énergie, vous pouvez créer des semis pathologiques. PCG est dans une position quelque peu inhabituelle, car il a quelqu'un qui aime travailler des semences d'apparence plausible pour PCG spécifiquement qui ont une pathologie artisanale (c'est-à-dire, Sebastiano). À la suite de ce travail, je me suis retourné et j'ai fait la même chose pour ses PRNG et pour d'autres de longue date.

En général, vous voulez initialiser l'état PRNG avec quelque chose qui semble «plutôt aléatoire». C'est assez universel, même si les gens veulent faire autrement . Par exemple, pour tout LFSR PRNG (style XorShift, Mersenne Twister, etc.), vous ne devez pas l'initialiser avec un état entièrement zéros car il restera simplement bloqué là-bas. Mais même les états qui sont pour la plupart à zéro sont souvent problématiques pour les LFSR (un phénomène connu sous le nom de zeroland, et pourquoi les gens du C ++ ont fait seed_seq .). Si vous voulez jouer au jeu «faisons un semis artificiel», il n'est pas difficile de créer une collection d'initialisations pour 100 LFSR et de les avoir tous à 1K de toucher zéro. Les initialisations artificielles auront toutes l'air assez innocentes, mais elles toucheront toutes cette étrange baisse de poids en même temps.

Si vous utilisez un générateur PCG qui a été initialisé avec une entropie raisonnable, tout va bien. Si vous voulez l'initialiser avec des fichiers indésirables comme 1, 2, 3, c'est en fait problématique avec n'importe quel PRNG . Et avec PCG, 99,9% du temps, même utiliser quelque chose d'un peu junky serait bien. Cela n'a rien du genre de problèmes que rencontrent les LFSR.

Mais DXSM est certainement plus fort. Je pense que c'est mieux ou je ne l'aurais pas fait, mais cela vaut la peine d'avoir un peu de recul et de se rendre compte qu'en pratique, les utilisateurs ne rencontreront pas de problèmes avec l'implémentation PCG64 classique.

Je voudrais séparer la critique / défense des flux de PCG64 (via l'incrément LCG) de la discussion actuelle. Bien qu'il y ait une certaine dualité en cause en raison des mathématiques du LCG, ce n'est pas le problème central qui a été soulevé à l'origine ici. De plus, il y a plus de détails ici à considérer que ce qui était présent dans la critique originale de Sebastiano, votre réponse ou le vieux méga-fil. Le lien est peut-être plus évident pour les experts qui ont passé plus de temps sur les mathématiques, mais les conséquences pratiques sont maintenant plus claires pour moi, du moins.

Je dirais que la clé à retenir est que pour presque n'importe quel PRNG, avec un peu de temps et d'énergie, vous pouvez créer des semis pathologiques.

Certes, mais ce n'est pas le peut / ne peut pas binaire qui motive la décision devant moi. Si je tire trop de nombres à partir d'un PRNG fini, PractRand finira par le comprendre. Ce fait binaire n'invalide pas cet algorithme PRNG. S'éloigner de ce binaire et établir le concept de marge de sécurité était l'une des choses que j'ai vraiment appréciées à propos du papier PCG original. Étant donné une pathologie générée de manière indésirable, nous pouvons examiner la fréquence à laquelle cette pathologie pourrait survenir au hasard à partir d'un bon ensemencement par entropie. Je veux quantifier cela et en faire des conseils pratiques pour les utilisateurs.

Étant donné deux états qui partagent les 58 bits inférieurs et le même incrément (nous y placerons une épingle), l'entrelacement d'instances PCG64 XSL-RR de ces états démontre des échecs pratiquement observables dans PractRand à environ 32 Gio. Je pense qu'il est raisonnable de vouloir éviter cela. Prenons donc cela comme notre référence et regardons à quelle fréquence cela se produit avec un bon ensemencement par entropie. Heureusement, ce schéma contradictoire se prête à une analyse probabiliste (tous ne sont pas si amicaux). Pour les instances n , la probabilité que 2 quelconques partagent les mêmes 58 bits inférieurs est n**2 / 2**58 , donnez ou prenez un facteur de 2 pour un double comptage. Donc, à un demi-milliard d'instances, il y a de bonnes chances qu'il y ait un tel couplage qui échouerait PractRand s'il était entrelacé. Un demi-milliard, c'est beaucoup! À mon avis, nous ne verrons probablement jamais un programme numpy qui essaie de créer autant d'instances PCG64 . numpy serait probablement le mauvais outil, alors.

Je pense qu'il est également raisonnable de vouloir éviter les états initiaux dont les tirages ultérieurs croiseront n'importe lequel des états de collision inférieurs à 58 bits des autres états initiaux. J'essaie toujours de réfléchir à la logique à ce sujet, mais je pense que la longueur affecte la probabilité de manière linéaire plutôt que quadratique. Si j'ai raison et que je veux tirer 16 Gio de chaque instance ( 2**28 tirages), ce qui correspond à la quantité que nous avons tirée de chacune des paires qui ont montré des échecs de PractRand, alors je ne peux travailler qu'avec environ 2**15 instances, soit environ 32k, avant qu'il ne devienne tout à fait susceptible d'observer un croisement. C'est encore beaucoup d'instances pour Python! Et la quantité totale de données générées est d'environ un demi-pétaoctet, ce qui est beaucoup! Mais c'est à l'horizon de l'aspect pratique, et si je veux garder la probabilité faible, pas seulement en dessous de la moitié, je dois aller plus bas sur l'un d'entre eux. Je ne suis pas particulièrement préoccupé par ces chiffres; Je ne pense pas que de vrais programmes numpy soient susceptibles de rencontrer des problèmes en utilisant PCG64 avec la fonction de sortie XSL-RR. Mais certaines applications peuvent commencer à se rapprocher (grandes séries d' apprentissage par renforcement distribué , par exemple).

Supprimons cette broche d'incrément et résolvons-la. Je pense qu'il est juste de dire qu'avec la fonction de sortie XSL-RR, l'ensemencement par entropie de l'incrément en plus de l'état ne change pas cette analyse particulière. Il semble que pour toute paire donnée d'incréments entropiques, il y a le même nombre d'états pratiquement en collision. La procédure concrète pour construire délibérément ces états semble plus compliquée que de dénigrer les mêmes 58 bits inférieurs, mais il semble que le nombre d'états en collision soit le même, de sorte que les calculs de probabilité restent les mêmes. Ce n'est pas intrinsèque au schéma PCG en général. La fonction de sortie DXSM semble être suffisamment forte pour que changer l'incrément (même avec un simple +2 ) semble être suffisant pour résister même au pire état du LCG sous-jacent (lorsque la métrique de distance donne 0 ), du moins dans la mesure où j'ai pris la peine de tester avec PractRand.

Je veux terminer en réitérant ce sur quoi nous semblons tous être en parfait accord: PCG64DXSM est une bonne idée. Si rien d'autre, ses propriétés statistiques améliorées simplifient les modèles mentaux que je me sens obligé de documenter, et tout ce qui signifie que je dois écrire moins de documentation est bon dans mon livre.

Les flux sont encore quelque peu pertinents car le problème n'apparaît que si nous avons les générateurs sur le même flux.

Mais dans quelles circonstances auraient-ils les mêmes 58 bits inférieurs et seraient sur le même flux? Y a-t-il un cas d'utilisation où cela se produirait?

Le cas quelque peu réaliste que je connaisse est celui dont nous avons parlé l'année dernière (lorsque nous avons parlé de jumped ), et dont j'ai parlé dans cet article auquel j'ai lié plus tôt.

Les flux sont encore quelque peu pertinents car le problème n'apparaît que si nous avons les générateurs sur le même flux.

Malheureusement, ce n'est pas le cas pour XSL-RR. Considérons deux instances PCG64 XSL-RR. Nous ensemencons les incréments par entropie arbitrairement et nous ensemencons en entropie l'un des états. Nous pouvons construire 2**70 mauvais états pour l'autre instance PCG64 qui échouent PractRand de la même manière que l'échec du même incrément de même état inférieur à 58 bits. C'est juste plus compliqué à faire que dans le cas du même incrément. Au lieu de partager les 58 bits inférieurs comme premier état avec l'incrément différent, il partage les 58 bits inférieurs de l'état qui est à 0 distance (selon votre mesure de distance LCG) de la première instance, en tenant compte des incréments. J'ai une preuve constructive (code Python), mais je dois aller me coucher maintenant et la nettoyer demain.

@rkern , bon point. J'avoue que je n'ai pas testé ce scénario pour voir comment il se déroule.

Je dirais que la clé à retenir est que pour presque n'importe quel PRNG, avec un peu de temps et d'énergie, vous pouvez créer des semis pathologiques. PCG est dans une position quelque peu inhabituelle, car il a quelqu'un qui aime travailler des semences d'apparence plausible pour PCG spécifiquement qui ont une pathologie artisanale (c'est-à-dire, Sebastiano). À la suite de ce travail, je me suis retourné et j'ai fait la même chose pour ses PRNG et pour d'autres de longue date.

Comme je l'ai déjà fait remarquer, c'est faux. Je ne connais aucun exemple d'une paire de séquences corrélées, non chevauchantes, par exemple, de xoshiro256 ++ comme vous pouvez le trouver facilement dans PCG.

Les PRNG qui mélangent rapidement tout leur état n'ont pas ce problème. Si vous pouvez fournir un programme générant deux séquences non superposées à partir de xoshiro256 ++ qui sont corrélées, comme les exemples que j'ai postés ici, veuillez le faire.

En ce qui concerne ses programmes de corrélation, en 2018, lorsqu'il a mis une critique de PCG sur son site Web qui comprenait des programmes qu'il avait écrits avec divers problèmes (comme l'ensemencement artificiel, etc.), j'ai écrit une réponse contenant un tas de programmes similaires pour d'autres PRNG établis depuis longtemps, y compris corrsplitmix2.c qui crée des flux corrélés dans SplitMix. Je ne suis pas tout à fait sûr de ce que Sebastian veut dire quand il dit que cela ne peut pas être fait, mais j'admets que je n'ai pas eu l'occasion d'examiner de près son programme de test pour voir si son nouveau programme est substantiellement différent de ceux qu'il écrit il y a quelques années.

Le programme cité ci-dessus _choisit les flux_. Il est évidemment facile de l'écrire.

Mais cela n'a rien à voir avec les problèmes de PCG. Le programme que j'ai fourni laisse _user_ choisir les flux, puis montre la corrélation.

J'invite, à nouveau, @inmeme à fournir un programme pour SplitMix dans lequel l'utilisateur peut sélectionner arbitrairement deux flux différents, un état initial arbitraire du premier générateur, et _puis_ le programme trouve une séquence corrélée dans l'autre générateur, comme http: / /prng.di.unimi.it/corrpcgnumpy.c le fait.

Laisser l'utilisateur choisir le flux de manière arbitraire montre une forme de corrélation beaucoup plus forte.

Comme je l'ai déjà fait remarquer, c'est faux. Je ne connais aucun exemple d'une paire de séquences corrélées, non chevauchantes, par exemple, de xoshiro256 ++ comme vous pouvez le trouver facilement dans PCG.

On dirait que nous nous parlons ici. Je n'ai pas dit que je pouvais trouver des séquences corrélées et non chevauchantes pour n'importe quel PRNG, j'ai dit que je pouvais trouver des germes pathologiques en général, comme le montrent les divers programmes de corrélation que j'avais écrits précédemment, et d'autres tels comme le programme de démonstration de mauvaises répétitions pour Xoshiro ** .

De plus, les PRNG qui ne mélangent pas tout leur état ont une longue histoire, y compris XorWow, les générateurs dans les recettes numériques, etc. L'argument de Sebastiano représente le point de vue, mais son argument dirait que d'une manière ou d'une autre, Marsaglia a créé XorShift _worse_ dans XorWow en ajoutant un Weyl séquence, car il crée un grand nombre de générateurs similaires.

On dirait que nous nous parlons ici. Je n'ai pas dit que je pouvais trouver des séquences corrélées et non chevauchantes pour n'importe quel PRNG, j'ai dit que je pouvais trouver des germes pathologiques en général, comme le montrent les divers programmes de corrélation que j'avais écrits précédemment, et d'autres tels comme le programme de démonstration de mauvaises répétitions pour Xoshiro ** .

Veuillez essayer de maintenir la discussion à un niveau technique. «Pathologique» n'a pas de signification mathématique.

La manière techniquement correcte de vérifier l'auto-corrélation est de trouver deux graines produisant deux séquences qui ne se chevauchent pas (ne se chevauchant pas pendant la durée du test - elles se chevaucheront inévitablement si vous allez assez loin), les entrelacent et les transmettent à une batterie des tests.

Si vous considérez deux séquences qui se chevauchent, elles seront corrélées pour chaque générateur, même crypto, simplement parce que les mêmes sorties se produiront deux fois après le chevauchement des séquences, et tout test raisonnable le captera.

"L'ensemencement pathologique" utilisant des séquences qui se chevauchent est une tâche triviale pour chaque générateur (quel que soit le sens "pathologique").

Une fois de plus, puisque vous prétendez avoir trouvé une corrélation similaire à PCG (dans laquelle les séquences ne se chevauchent pas, comme le montre le test) dans d'autres générateurs, pouvez-vous fournir une paire de séquences corrélées, sans chevauchement, par exemple, de xoshiro256 ++ ou SFC64 ?

La manière techniquement correcte de vérifier l'auto-corrélation est de trouver deux graines produisant deux séquences qui ne se chevauchent pas (ne se chevauchant pas pendant la durée du test - elles se chevaucheront inévitablement si vous allez assez loin), les entrelacent et les transmettent à une batterie des tests.

Pouvez-vous citer la littérature pour cette définition de la corrélation afin que je puisse m'assurer que je reste «techniquement correct» à ce sujet également?

Sebastiano, tu veux toujours que je réponde à un défi selon tes conditions. Ce que vous faites remarquer concerne les propriétés intrinsèques des LCG, là où il y a une autosimilitude. Vous ne trouverez pas le même problème sur un PRNG chaotique ou basé sur LFSR.

Mais pour les autres PRNG, il y aura d'autres points faibles.

Les LFSR ont zéro, de mauvais états, des problèmes de poids martelé, de linéarité et, comme nous l'avons appris avec vos tentatives avec xoshiro, parfois d'autres bizarreries comme d'étranges problèmes de répétitions.

Les PRNG chaotiques ont le risque de cycles courts (bien que ceux avec un compteur évitent cela - Weyl séquences FTW!) Et leur biais intrinsèque.

Si les séquences se chevauchent, comme je l'ai écrit, le test échouera _toujours_. Vous n'avez pas besoin de documentation pour comprendre qu'un test qui échoue toujours n'est pas un test.

Une fois de plus, puisque vous prétendez avoir trouvé une corrélation similaire à PCG (dans laquelle les séquences ne se chevauchent pas, comme le montre le test) dans d'autres générateurs, pouvez-vous fournir une paire de séquences corrélées, sans chevauchement, par exemple, de xoshiro256 ++ ou SFC64 ?

Vous semblez vraiment esquiver la question. Il vous serait très facile, à la suite de vos allégations, de fournir de telles preuves, si vous en aviez.

La chose la plus simple à faire maintenant serait d'ajouter un PCG64DXSM BitGenerator , la variante de PCG64 avec le "multiplicateur bon marché" et une fonction de sortie DXSM plus forte. Je pense que tout le monde convient que c'est un pas en avant par rapport à la fonction de sortie XSL-RR que nous avons maintenant dans notre implémentation PCG64 , fonctionnant mieux statistiquement sans nuire aux performances d'exécution. C'est une mise à niveau simple dans le créneau que PCG64 sert pour les BitGenerator que nous fournissons. Je pense que nous devrions l'ajouter à côté de PCG64 .

Notez que les "multiplicateurs bon marché" 64 bits ont des défauts prouvables. Ceci est connu depuis longtemps:

W. Hörmann et G. Derflinger, Un générateur de nombres aléatoires portable bien adapté à la
méthode de rejet, ACM Trans. Math. Softw. 19 (1993), no. 4, 489–495.

En général, les multiplicateurs plus petits que la racine carrée du module ont des limites inhérentes à leur score spectral f₂.

La limite peut être facilement surmontée en utilisant un multiplicateur de 65 bits, que le compilateur transformera simplement en une opération "d'ajout" supplémentaire, sans probablement même changer la vitesse du générateur.

Guy Steele et moi avons travaillé un peu sur la question et publié des tableaux de scores spectraux pour des multiplicateurs bon marché de différentes tailles: https://arxiv.org/pdf/2001.05304.pdf . Le plus grand est le mieux, mais il y a un écart prouvable de 64 à 65 bits (pour un LCG avec 128 bits d'état).

Par exemple, à partir du tableau 7 de l'article, vous obtenez 0x1d605bbb58c8abbfd, qui a un score f₂ de 0,9919. Aucun multiplicateur de 64 bits ne peut aller au-delà de 0,9306 (théorème 4.1 dans l'article).

Après le mixage et tout, l'amélioration du score f₂ pourrait passer totalement inaperçue d'un point de vue statistique. Mais compte tenu de la grande amélioration que vous obtenez pour la dimension la plus pertinente avec juste une opération d'ajout supplémentaire, je pense (enfin, nous pensons, sinon nous n'aurions pas écrit l'article) cela en vaut la peine.

Sebastiano, tu veux toujours que je réponde à un défi selon tes conditions. Ce que vous faites remarquer concerne les propriétés intrinsèques des LCG, là où il y a une autosimilitude. Vous ne trouverez pas le même problème sur un PRNG chaotique ou basé sur LFSR.

Wow, il a fallu du temps pour y arriver!

Les LFSR ont zéro, de mauvais états, des problèmes de poids martelé, de linéarité et, comme nous l'avons appris avec vos tentatives avec xoshiro, parfois d'autres bizarreries comme d'étranges problèmes de répétitions.

Je suis tout à fait d'accord - c'est pourquoi vous devez les brouiller. Notez que les LFSR et les générateurs F₂-linéaires sont des choses différentes; liés, mais différents.

"Problèmes étranges avec les répétitions" est, comme d'habitude, un terme non technique sur lequel je ne peux pas faire de commentaire.

Les PRNG chaotiques ont le risque de cycles courts (bien que ceux avec un compteur évitent cela - Weyl séquences FTW!) Et leur biais intrinsèque.

[Mise à jour: j'ai manqué l'observation du compteur entre parenthèses, je mets donc à jour mon commentaire.]

Oui, SFC64 n'a pas de tels problèmes (il utilise un compteur), donc je ne généraliserais pas à toute la catégorie. Il existe des générateurs chaotiques soigneusement conçus qui ont la plus grande longueur de cycle prouvable.

"Problèmes étranges avec les répétitions" est, comme d'habitude, un terme non technique sur lequel je ne peux pas faire de commentaire.

Il semble étrange de ne pas pouvoir commenter parce que je n'ai pas utilisé le bon jargon - exécutez ce programme et n'hésitez pas à m'éclairer avec la meilleure façon de décrire le problème dans le jargon approprié, puis de fournir tout commentaire qui vous semble approprié. J'aurais imaginé que vous et David Blackman auriez discuté de la question quand elle a été révélée pour la première fois parce que j'ai correspondu avec lui à ce sujet, mais je ne vous ai jamais vu commenter.

La discussion sur les PRNG ne figurant pas dans numpy est hors sujet. Veuillez utiliser vos propres forums pour poursuivre cette discussion. Je vous remercie.

@rkern - cela semble un peu strict comme critère. S'il y a un défaut dans l'implentation Numpy qui n'est pas partagé par d'autres implémentations, cela semble raisonnable à discuter.

Je peux confirmer que l'échange dont je parle ne m'aide pas à prendre les décisions qui nous attendent dans ce numéro. Jusqu'à ce que nous progressions là-dessus, j'ai besoin que la conversation reste concentrée.

Je pense qu'il est utile de comprendre le contexte plus large. Sebastiano a un petit quelque chose à propos du PCG et s'y oppose depuis des années. Je pense que ces jours-ci, certaines personnes pourraient nous regarder tous les deux et rouler des yeux et dire "vous êtes tous les deux aussi mauvais les uns que les autres" parce que j'ai également critiqué ses PRNG, mais en fait, je ne l'ai fait qu'après avoir fait des déclarations selon lesquelles J'essayais de cacher quelque chose en ne parlant jamais de ses affaires (alors qu'en réalité, je n'avais tout simplement pas le temps / l'envie - en fait, j'avais juste supposé qu'elles allaient bien).

Ses critiques sont cependant utiles, et je suis ravi qu'il ait choisi de passer une si grande partie de sa vie à penser à mon travail, mais il est important de réaliser que ses tests sont de nature contradictoire. Il utilise la connaissance de la structure du PCG pour concevoir des arrangements qui peuvent être introduits dans les testeurs RNG et échouer les tests.

Étant donné à quel point cela peut paraître effrayant, il semble raisonnable de montrer qu'une approche contradictoire similaire ferait également trébucher de nombreux autres générateurs et que bon nombre des préoccupations qu'il soulève à propos du PCG s'appliqueraient également à d'autres schémas de production, comme je l'ai observé à propos des schémas de génération. comme XorWow, et j'utilise souvent SplitMix comme exemple, comme je le ferai ci-dessous. (Aucun de nous n'est particulièrement investi dans SplitMix d'une manière ou d'une autre, j'imagine.)

Nous pouvons être très effrayants à propos de SplitMix, par exemple, et montrer qu'avec la constante de flux par défaut, si nous regardons toutes les 35185 èmes sorties, cela échoue dans une suite de tests PRNG. Oh non! En effet, en interne, il incrémente un compteur (séquence de Weyl!) De 0x9e3779b97f4a7c15 (basé sur φ, le nombre d'or), mais 35185 * 0x9e3779b97f4a7c15 = 0x86a100000c480245 , qui n'a que Ensemble de 14 bits et une grande bande de rien au milieu. Ou si nous regardons chaque sortie 360998717-ème, nous en arrivons à être équivalent à un ajout à l'état interne de 0x48620000800401 , qui ne représente que 8 bits ajoutés et encore quelque chose de difficile pour que sa fonction de sortie masque complètement .

Nous pourrions continuer à faire peur à propos de SplitMix et dire regardez, et si j'avais deux flux, l'un avec la constante additive 0x9e3779b97f4a7c15 et l'autre avec 0xdaa66d2c7ddf743f , nous verrions des défauts si nous introduisions cela dans une suite de tests PRNG !!! Mais c'est parce que le second est conçu pour être juste 3x l'autre.

Et enfin, si quelqu'un a dit «Je vais vous donner les deux flux, faites quelque chose d'effrayant avec ça!», Et disons que le leur était basé sur π ( 0x243f6a8885a308d3 ) et _e_ ( 0xb7e151628aed2a6b ), nous pouvons dire, bien sûr, ayons un peu plus de peur et prenons chaque élément 6561221343-ème du flux Pi et mélangez-le avec chaque élément 6663276199-ème du flux E et bas-et-belhold, ils produisent deux identiques séquences. Et _ pire_, je vais ensuite montrer que pour chaque saut sur le flux a, il y a un saut correspondant sur le flux b pour donner le même résultat, donc il y a en fait 2 ^ 64 façons dont ils sont corrélés !!! (Et nous pouvons le faire pour deux flux quelconques, il n'y avait rien de spécial à propos de π et _e_.)

Pour revenir à PCG, le test de Sebastiano repose sur l'alignement précis des deux générateurs PCG64 XSH RR afin que les sorties correspondantes soient entrelacées. Si nous avançons simplement l'un des PRNG d'une petite quantité, brisant un peu l'alignement parfait, il devient beaucoup plus difficile de détecter quoi que ce soit de suspect.

Un test contradictoire similaire dans l'autre sens (mettre une charge sur Sebastiano) serait de fournir des sorties de PCG64 XSH RR qui répondent à son affirmation selon laquelle elles sont corrélées mais nous ne lui disons pas exactement comment elles sont alignées (elles sont juste dans le bon quartier général). Son travail serait de trouver l'alignement pour montrer qu'ils sont corrélés.

Dans l'ensemble, je ne pense pas que ce soit un problème dans la pratique avec des incendies urgents à éteindre, mais d'un autre côté, la version DXSM est meilleure car elle a été écrite l'année dernière pour atténuer précisément ce genre de problèmes, et je le serais ravi de vous y avoir changé.

PS Vous pouvez créer des constantes additives magiques Weyl à partir de votre nombre réel préféré en utilisant ce code:

WeylConst[r_,bits_] = BitOr[Floor[(r-Floor[r])*2^bits],1]

C'est Mathematica, je laisse la version Python comme exercice.

Voici comment je construis les collisions de bits inférieurs pour différents incréments.

Résultats avec PCG64 XSL-RR et une collision inférieure à 58 bits

❯ ./pcg64_correlations.py -m 58 | stdbuf -oL ./RNG_test stdin64 -tf 2 -te 1 -tlmaxonly -multithreaded
s0 = 0b01110010100110011101000110010010101111111001100011001011001011111001001110101010011101111101001101011000011100001111111111100001
s1 = 0b10110001011001100111100010000110101110011010101010011011010100011001011111001100010001101001001011010010110101001011101111111100
dist = 0x2eb6ec432b0ea0f4fc00000000000000
[
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 152330663589051481538402839025803132897,
            "inc": 228410650821285501905570422998802152525
        },
        "has_uint32": 0,
        "uinteger": 0
    },
    {
        "bit_generator": "PCG64",
        "state": {
            "state": 235805414096687854712168706130903874556,
            "inc": 70910205337619270663569052684874994465
        },
        "has_uint32": 0,
        "uinteger": 0
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin64, seed = 0x12d551b8
test set = expanded, folding = extra

rng=RNG_stdin64, seed=0x12d551b8
length= 128 megabytes (2^27 bytes), time= 2.8 seconds
  no anomalies in 891 test result(s)

rng=RNG_stdin64, seed=0x12d551b8
length= 256 megabytes (2^28 bytes), time= 9.4 seconds
  no anomalies in 938 test result(s)

rng=RNG_stdin64, seed=0x12d551b8
length= 512 megabytes (2^29 bytes), time= 18.1 seconds
  no anomalies in 985 test result(s)

rng=RNG_stdin64, seed=0x12d551b8
length= 1 gigabyte (2^30 bytes), time= 31.2 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]FPF-14+6/16:cross         R=  +4.9  p =  1.7e-4   unusual          
  [Low4/16]FPF-14+6/16:all          R=  +8.4  p =  2.3e-7   very suspicious  
  [Low4/16]FPF-14+6/16:all2         R=  +8.3  p =  8.1e-5   unusual          
  [Low8/32]FPF-14+6/32:all          R=  +6.3  p =  2.1e-5   mildly suspicious
  [Low8/32]FPF-14+6/16:all          R=  +5.7  p =  8.0e-5   unusual          
  ...and 1034 test result(s) without anomalies

rng=RNG_stdin64, seed=0x12d551b8
length= 2 gigabytes (2^31 bytes), time= 52.7 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low4/16]FPF-14+6/32:all          R=  +7.4  p =  2.0e-6   suspicious       
  [Low4/16]FPF-14+6/16:(0,14-0)     R=  +7.7  p =  9.4e-7   unusual          
  [Low4/16]FPF-14+6/16:all          R=  +8.0  p =  5.9e-7   suspicious       
  [Low4/16]FPF-14+6/16:all2         R= +12.2  p =  2.1e-6   mildly suspicious
  [Low4/16]FPF-14+6/4:(0,14-0)      R=  +7.9  p =  6.3e-7   mildly suspicious
  [Low4/16]FPF-14+6/4:all           R=  +5.8  p =  6.7e-5   unusual          
  [Low4/16]FPF-14+6/4:all2          R= +11.5  p =  3.1e-6   mildly suspicious
  [Low8/32]FPF-14+6/32:(0,14-0)     R=  +7.8  p =  8.4e-7   unusual          
  [Low8/32]FPF-14+6/32:all          R=  +7.3  p =  2.3e-6   suspicious       
  [Low8/32]FPF-14+6/32:all2         R= +14.3  p =  3.8e-7   suspicious       
  [Low8/32]FPF-14+6/16:(0,14-0)     R=  +7.7  p =  8.8e-7   unusual          
  [Low8/32]FPF-14+6/16:(1,14-0)     R=  +7.7  p =  9.3e-7   unusual          
  [Low8/32]FPF-14+6/16:all          R=  +6.9  p =  5.3e-6   mildly suspicious
  [Low8/32]FPF-14+6/16:all2         R= +18.3  p =  8.0e-9   very suspicious  
  ...and 1078 test result(s) without anomalies

rng=RNG_stdin64, seed=0x12d551b8
length= 4 gigabytes (2^32 bytes), time= 90.2 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/8]BCFN_FF(2+0):freq         R= +14.8  p~=   6e-18     FAIL !         
  [Low1/8]BCFN_FF(2+1):freq         R=  +7.4  p~=   1e-6    mildly suspicious
  [Low1/8]FPF-14+6/16:cross         R=  +8.4  p =  2.1e-7   very suspicious  
  [Low4/16]FPF-14+6/32:(0,14-0)     R=  +8.9  p =  8.1e-8   mildly suspicious
  [Low4/16]FPF-14+6/32:(1,14-0)     R=  +8.5  p =  1.9e-7   mildly suspicious
  [Low4/16]FPF-14+6/32:all          R=  +9.4  p =  2.4e-8   very suspicious  
  [Low4/16]FPF-14+6/32:all2         R= +23.9  p =  5.2e-11   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/16:(0,14-0)     R= +13.8  p =  2.2e-12   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/16:(1,14-0)     R= +10.0  p =  7.3e-9   suspicious       
  [Low4/16]FPF-14+6/16:all          R= +12.1  p =  8.0e-11   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/16:all2         R= +52.5  p =  1.3e-22    FAIL !!        
  [Low4/16]FPF-14+6/4:(0,14-0)      R= +12.2  p =  7.0e-11   VERY SUSPICIOUS 
  [Low4/16]FPF-14+6/4:all           R=  +7.1  p =  3.7e-6   mildly suspicious
  [Low4/16]FPF-14+6/4:all2          R= +29.8  p =  7.1e-14    FAIL           
  [Low4/16]FPF-14+6/4:cross         R=  +5.3  p =  7.8e-5   unusual          
  [Low4/32]FPF-14+6/32:(0,14-0)     R=  +7.6  p =  1.3e-6   unusual          
  [Low4/32]FPF-14+6/32:all          R=  +6.0  p =  4.4e-5   unusual          
  [Low4/32]FPF-14+6/32:all2         R=  +9.4  p =  2.9e-5   unusual          
  [Low4/32]FPF-14+6/16:(0,14-0)     R=  +7.3  p =  2.5e-6   unusual          
  [Low4/32]FPF-14+6/16:all          R=  +6.5  p =  1.4e-5   mildly suspicious
  [Low4/32]FPF-14+6/16:all2         R=  +8.2  p =  8.0e-5   unusual          
  [Low8/32]FPF-14+6/32:(0,14-0)     R= +17.2  p =  1.7e-15    FAIL           
  [Low8/32]FPF-14+6/32:(1,14-0)     R= +12.7  p =  2.3e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/32:all          R= +15.3  p =  7.9e-14    FAIL           
  [Low8/32]FPF-14+6/32:all2         R= +86.1  p =  1.2e-35    FAIL !!!       
  [Low8/32]FPF-14+6/16:(0,14-0)     R= +16.8  p =  3.5e-15    FAIL           
  [Low8/32]FPF-14+6/16:(1,14-0)     R= +12.2  p =  6.6e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/16:all          R= +13.1  p =  8.9e-12   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/16:all2         R= +82.1  p =  1.7e-34    FAIL !!!       
  [Low8/32]FPF-14+6/4:(0,14-0)      R= +12.8  p =  2.0e-11   VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/4:(1,14-0)      R=  +9.4  p =  2.5e-8   suspicious       
  [Low8/32]FPF-14+6/4:all           R= +10.5  p =  2.2e-9    VERY SUSPICIOUS 
  [Low8/32]FPF-14+6/4:all2          R= +42.0  p =  5.8e-19    FAIL !         
  ...and 1118 test result(s) without anomalies

Résultats avec PCG64 DXSM et une collision inférieure à 64 bits (pour provoquer des problèmes plus rapidement, même si je n'en vois aucun)

❯ ./pcg64_correlations.py -m 64 --dxsm | stdbuf -oL ./RNG_test stdin64 -tf 2 -te 1 -tlmaxonly -multithreaded
s0 = 0b10001000010110111101010101010101111100100011011111011111011111001011110101111100101101101100110101110001101101111111010101111111
s1 = 0b11000101110100011001011000001110100001001111001001100101010000101100011001010111011001100000010010011100101110001110101000011100
dist = 0x3a26b19c91e6da1d0000000000000000
[
    {
        "bit_generator": "PCG64DXSM",
        "state": {
            "state": 181251833403477538233003277050491434367,
            "inc": 46073632738916603716779705377640239269
        },
        "has_uint32": 0,
        "uinteger": 0
    },
    {
        "bit_generator": "PCG64DXSM",
        "state": {
            "state": 262946148724842088422233355148768897564,
            "inc": 125105549038853892415237434774494719583
        },
        "has_uint32": 0,
        "uinteger": 0
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin64, seed = 0x85cea9
test set = expanded, folding = extra

rng=RNG_stdin64, seed=0x85cea9
length= 128 megabytes (2^27 bytes), time= 2.6 seconds
  no anomalies in 891 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 256 megabytes (2^28 bytes), time= 9.4 seconds
  no anomalies in 938 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 512 megabytes (2^29 bytes), time= 18.5 seconds
  no anomalies in 985 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 1 gigabyte (2^30 bytes), time= 32.3 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low4/32]BCFN_FF(2+3,13-3,T)      R=  -8.3  p =1-9.5e-5   unusual          
  ...and 1035 test result(s) without anomalies

rng=RNG_stdin64, seed=0x85cea9
length= 2 gigabytes (2^31 bytes), time= 55.8 seconds
  no anomalies in 1092 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 4 gigabytes (2^32 bytes), time= 93.1 seconds
  no anomalies in 1154 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 8 gigabytes (2^33 bytes), time= 175 seconds
  no anomalies in 1222 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 16 gigabytes (2^34 bytes), time= 326 seconds
  no anomalies in 1302 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 32 gigabytes (2^35 bytes), time= 594 seconds
  no anomalies in 1359 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 64 gigabytes (2^36 bytes), time= 1194 seconds
  no anomalies in 1434 test result(s)

rng=RNG_stdin64, seed=0x85cea9
length= 128 gigabytes (2^37 bytes), time= 2334 seconds
  no anomalies in 1506 test result(s)
...

@rkern , merci d'avoir partagé votre code. Il serait instructif d'ajouter une option pour ajouter un biais pour alimenter la sortie entrelacée dans le testeur qui n'est pas parfaitement alignée. C'est quelque chose que j'ai un peu exploré.

Oui, je l'ai fait de manière informelle en insérant bg0.advance(N) pour divers N avant le dernier return . J'ai utilisé la collision inférieure à 64 bits pour être sûr de voir quelque chose. De minuscules changements comme 16 ne changent pas beaucoup l'échec, mais même des changements modestes comme 128 prolongent l'échec à 32 Gio.

Il semble que nous devrions ajouter PCG64 DXSM en tant que générateur de bits optionnel et éventuellement en faire la valeur par défaut. Pour nous avoir une mise en œuvre?

Je pense qu'il est utile de comprendre le contexte plus large. Sebastiano a un petit quelque chose à propos du PCG et s'y oppose depuis des années. Je pense que ces jours-ci, certaines personnes pourraient nous regarder tous les deux et rouler des yeux et dire "vous êtes tous les deux aussi mauvais les uns que les autres" parce que j'ai également critiqué ses PRNG, mais en fait, je ne l'ai fait qu'après avoir fait des déclarations selon lesquelles J'essayais de cacher quelque chose en ne parlant jamais de ses affaires (alors qu'en réalité, je n'avais tout simplement pas le temps / l'envie - en fait, j'avais juste supposé qu'elles allaient bien).

Je pense que ces considérations sont tout à fait inappropriées.

Insister pour attaquer le travail des autres (par exemple, SplitMix) sans aucun mérite et sans aucune preuve ne va pas améliorer le désordre de PCG, ou le générateur de Numpy. Un meilleur multiplicateur ou un brouilleur mieux conçu pourrait plutôt aider.

J'attends toujours un test montrant la corrélation dans SplitMix lorsque l'utilisateur peut choisir les flux. Juste pour être clair, pour le générateur de Numpy, j'ai prouvé une déclaration de la forme

∀c ∀d ∀x ∃y corrélé

où c, d sont des incréments ("flux") et x, y sont des états initiaux. En fait, il y a 2 ^ 72 ans. Autrement dit, peu importe comment vous choisissez c, d et x, il y a 2 ^ 72 y montrant une corrélation.

Le prétendu code correspondant que vous avez fourni pour SplitMix montre que

∃c ∃d ∃x ∃y corrélé

Autrement dit, en choisissant de manière négative c, d, x et y, vous pouvez montrer la corrélation.

La différence de force entre les deux déclarations est assez stupéfiante. Essayer de confondre les deux déclarations est incorrect.

@vigna vous avez été averti deux fois de notre code de conduite, par @mattip et @rkern. Utiliser un langage comme «battre le travail des autres» et «essayer de confondre les deux affirmations est purement FUD» n'est pas acceptable. Considérez ceci comme votre dernier avertissement. Veuillez changer votre ton ou nous vous interdirons. Les arguments techniques sont toujours les bienvenus, rien d'autre n'est à ce stade.

J'ai modifié le message en remplaçant ces expressions par des expressions neutres. Je pense toujours qu'attaquer personnellement un autre participant à la discussion ("Sebastiano a un petit quelque chose à propos du PCG et se moque de lui depuis des années") est totalement inapproprié. Je suis très surpris que ce ne soit pas pour vous.

Pour la troisième et dernière fois, la discussion sur SplitMix, dans les deux sens, ne m'aide pas du tout. Je peux comprendre pourquoi vous pensez que cela fournit le contexte nécessaire, ou que vous vous sentez obligé de répondre à l'autre, mais ayez confiance que je vous dis la vérité que cela ne me fournit aucune information qui m'aide à prendre une décision ici. Vous avez tous les deux vos propres sites Web. Utilise les.

J'ai modifié le message en remplaçant ces expressions par des expressions neutres.

Je vous remercie.

Je pense toujours qu'attaquer personnellement un autre participant à la discussion ("Sebastiano a un petit quelque chose à propos du PCG et se moque de lui depuis des années") est totalement inapproprié. Je suis très surpris que ce ne soit pas pour vous.

Je préfère ne pas voir cela non plus. Cependant, le ton de ce message n'est pas aussi mauvais.

J'apprécierais beaucoup si vous pouviez tous les deux vous en tenir à des déclarations factuelles constructives @vigna et @imneme.

D'ACCORD. Commençons à partir de zéro: vous voulez un générateur avec une sorte de flux basé sur LCG avec un module de puissance de 2 pour plus de commodité et de vitesse. La littérature suggère que baser les flux sur des constantes additives LCG pourrait vous conduire à des problèmes (comme cela se produit maintenant), mais supposons que c'est ce que vous voulez.

Pourquoi ne pas prendre un LCG avec 128 bits d'état et un bon multiplicateur (au moins 65 bits) et perturber les bits supérieurs en utilisant la fonction mix de SplitMix, qui a été fortement testée dans différentes applications (hachage, PRNG, etc.), donnant d'excellents résultats?

Je suis presque sûr que la différence de vitesse sera marginale. Et vous avez une certaine garantie (statistique) que le résultat dépendra de tous les bits, ce qui est le problème ici.

Cela me semble plus une approche «debout sur l'épaule de géants» que la fabrication artisanale de fonctions de mixage sur un générateur qui a des problèmes d'auto-corrélation.

@imneme Ce que je pourrais utiliser, c'est un article de blog sur DXSM auquel il est plus facile de créer un lien que ce commentaire d'annonce dans l'ancien méga-numéro. Il n'est pas nécessaire que ce soit beaucoup plus que ce que contient ce commentaire, mais inclure l'état actuel des tests que vous avez mentionnés ici serait bien. Si vous vouliez résumer une partie du débat sur la méga-question qui a conduit à cette évolution, ce serait utile, bien sûr, mais pas entièrement nécessaire.

@vigna

Pourquoi ne pas prendre un LCG avec 128 bits d'état et un bon multiplicateur (au moins 65 bits) et perturber les bits supérieurs en utilisant la fonction mix de SplitMix, qui a été fortement testée dans différentes applications (hachage, PRNG, etc.), donnant d'excellents résultats?

Je m'excuse si cela semble sournois (même si cela sera certainement souligné), mais c'est aussi sincère: j'ai hâte de voir l'implémentation, l'analyse, les benchmarks et les résultats de PractRand sur votre site Web ou sur arXiv. Nous sommes des praticiens (raisonnablement informés) ici, pas des chercheurs de PRNG, et ne sommes pas particulièrement bien équipés pour mettre en œuvre cette suggestion. Je peux en voir le sens, mais étant donné les autres contraintes sur mon temps personnel, je n'ai pas envie de dépenser l'effort de passer de la suggestion à une mise en œuvre et une analyse. Si vous adressez cette suggestion à numpy, nous avons besoin de chercheurs PRNG pour faire ce travail. Si vous adressez vraiment cette suggestion à quelqu'un d'autre, utilisez votre site Web.

L'architecture aléatoire Generator -> BitGenerator -> SeedSequence dans NumPy est censée être enfichable. Je pense que nous en sommes au point dans la discussion où nous avons besoin de quelqu'un pour ouvrir un PR pour un BitGenerator, afin que nous puissions comparer ses attributs pratiques avec ceux actuellement dans NumPy. Une fois qu'il fait partie du projet, nous pouvons continuer à le tester et décider de le définir par défaut. Cette décision serait, je l'espère, basée sur

  • absence de parti pris (et autres critères? je cède aux experts)
  • performance
  • probabilité de divers types de collision de flux via les interfaces normatives que nous promouvons: en utilisant BitGenerator.spawn et SeedSequence .

Personnellement, cette discussion m'a perdu lorsqu'elle a évité de discuter des mérites de BitGenerators via un code qui utilise l'interface spawn . Il y a une raison pour laquelle nous en faisons la promotion comme meilleure pratique, et j'espère que la discussion du futur PR se concentrera sur les meilleures pratiques pour les utilisateurs de NumPy .

Peut-être qu'une des conclusions ici pourrait être que nous ne devrions autoriser que spawn comme méthode, car l'utilisation de jumped ou advance peut aller à l'encontre des bonnes pratiques. Un nouveau problème ou un NEP axé sur cela pourrait également être productif.

@mattip La collision d'anniversaire des bits inférieurs que @vigna a noté affecte également notre interface SeedSequence.spawn() . Soyez assuré que toute partie de la discussion dans laquelle je me suis engagée est pertinente pour une utilisation correcte de nos API.

Il suffit d'ajouter environ 8 lignes à pcg64.c avec quelques blocs #ifdef pour utiliser l'approche préférée de @rkern de générateurs complètement séparés. Le pyx / pxd serait par ailleurs identique à la classe PCG64 uniquement en cours de construction avec les définitions correctes (PCG_DXSM = 1) et une docstring mise à jour.

Je serais probablement plus explicite à ce sujet, en particulier pour les mathématiques émulées à 128 bits pour les plates-formes qui en ont besoin.

https://github.com/rkern/numpy/compare/v1.17.4...rkern%3Awip/pcg64-dxsm

Cela m'a semblé plus facile que cela car il utilise un multiplicateur 64 bits "bon marché". Vous pouvez simplement ajouter un nouveau mélangeur de sortie (qui est invariant) puis ifdef autour de la dernière ligne du générateur aléatoire qui prend la sortie du LCG et applique ensuite le mélangeur.

https://github.com/bashtage/randomgen/commit/63e50a63f386b5fd725025f2199ff89454321f4c#diff -879bd64ee1e2b88fec97b5315cf77be1R115

Si vous le vouliez, vous pourriez même ajouter le Murmur Hash 3 à ce stade, étaient-ils si enclins.

Les instructions if vont-elles être compilées? Je ne pense pas que nous en voulons des multiples dans des boucles chaudes.

Encore une fois, cela revient à la différence de but entre randomgen et numpy . Il est logique dans randomgen de créer des familles paramétrées, mais dans numpy , je ne pense pas que ce soit une bonne idée d'enchevêtrer les implémentations d'un héritage BitGenerator de la valeur par défaut active BitGenerator . Si nous devons faire de la maintenance ou des refactorisations pour des performances sur l'un ou l'autre, cela ne fera que rendre cet effort pire plutôt que meilleur.

D'accord avec Robert ici. Je n'ai aucun scrupule à mettre un nouveau générateur de bits dans la version 1.19.0, cela ne changerait aucun comportement actuel.

@bashtage Notez également que pcg_cm_random_r() utilise l'état pré-itéré pour la sortie plutôt que l'état post-itéré , il ne sera donc pas aussi simple de maintenir le même chemin de code avec #ifdef ou if commutateurs.

Les instructions if vont-elles être compilées? Je ne pense pas que nous en voulons des multiples dans des boucles chaudes.

Non, dans NumPy, le if else devrait devenir quelque chose comme

#if defined(PCG_DXSM)
    pcg_output_dxsm(state.high, state.low)
#else 
   <old way>
#endif

Celles-ci doivent être définies séparément dans la version uint128 et dans la version de repli pour gérer le déplacement manuel de l'uint128 vers haut et bas.

@bashtage Notez également que pcg_cm_random_r() utilise l'état pré-itéré pour la sortie plutôt que l'état post-itéré , il ne sera donc pas aussi simple de maintenir le même chemin de code avec #ifdef ou if commutateurs.

Hmm, j'ai testé l' implémentation de référence

https://github.com/bashtage/randomgen/blob/master/randomgen/src/pcg64/pcg_dxsm-test-data-gen.cpp

AFAICT (et je me trompe peut-être)

https://github.com/imneme/pcg-cpp/blob/master/include/pcg_random.hpp#L174

et

https://github.com/imneme/pcg-cpp/blob/master/include/pcg_random.hpp#L1045

signifie que le chemin uint_128 utilise toujours un multiplicateur bon marché.

Je ne suis pas sûr de ce que vous essayez de dire.

On ne sait pas ce qu'est le PCG64 DXSM canonique. Dans les deux cas, la fonction de sortie utilise uniquement des opérations 64 bits. La version que vous avez utilise un multiplicateur 64 bits dans un autre emplacement pour être encore plus rapide et renvoie avant, plutôt que après. setseq_dxsm_128_64 semble être l'extension naturelle du PCG64 existant et ne change que la fonction de sortie.

Oh je vois. Non, vous avez utilisé un générateur C ++ différent de celui que j'ai implémenté en C. J'ai implémenté l'équivalent de cm_setseq_dxsm_128_64 qui utilise le "multiplicateur bon marché" dans l'itération LCG, et non setseq_dxsm_128_64 qui est toujours utilise le grand multiplicateur dans l'itération LCG. Le "multiplicateur bon marché" est réutilisé à l'intérieur de la fonction de sortie DXSM, mais c'est un axe de conception orthogonal.

Pourquoi ne pas préférer setseq_dxsm_128_64?

@imneme a dit qu'elle allait éventuellement changer le pcg64 officiel dans la version C ++ pour qu'il pointe vers cm_setseq_dxsm_128_64 , pas setseq_dxsm_128_64 . Le multiplicateur bon marché compense une partie du coût supplémentaire de DXSM par rapport à XSL-RR. Et je pense que c'est la variante qu'elle a passé quelques mois à tester.

La sortie de l'état pré-itéré fait également partie de l'augmentation des performances .

Voici quelques horaires:

In [4]: %timeit p.random_raw(1000000)
3.24 ms ± 4.61 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [5]: p = rg.PCG64(mode="sequence",use_dxsm=False)

In [6]: %timeit p.random_raw(1000000)
3.04 ms ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [7]: import numpy as np

In [8]: p = np.random.PCG64()

In [9]: %timeit p.random_raw(1000000)
3.03 ms ± 2.54 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Le tout sur Ubuntu-20.04, compilateur par défaut.

6% plus lent. Cela me semble une petite différence. Tous les timings dans NumPy / randomgen sont assez éloignés de ce que vous pouvez obtenir dans une boucle serrée de code natif.

Comparer

In [10]: x = rg.Xoroshiro128(mode="sequence")

In [11]: %timeit x.random_raw(1000000)
2.59 ms ± 35.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

à ce qui a été publié à partir du code C (150% plus lent ??).

Certes, dans le contexte numpy , cela n'a pas beaucoup d'importance. Cela a plus d'importance dans le contexte C ++, qui a conduit à l'allocation de cluster-mois aux tests et à la nomination comme future valeur par défaut pcg64 dans le code C ++ officiel. Telles sont les motivations immédiates pour numpy , IMO.

La différence entre stock PCG64 et mon PCG64DXSM sur ma branche ("multiplicateur bon marché", fonction de sortie DXSM, sortie de l'état pré-itéré, chemin de code séparé):

[practrand]
|1> s = np.random.SeedSequence()

[practrand]
|2> pcg64 = np.random.PCG64(s)

[practrand]
|3> pcg64dxsm = np.random.PCG64DXSM(s)

[practrand]
|4> %timeit pcg64.random_raw(1000000)
100 loops, best of 3: 3.46 ms per loop

[practrand]
|5> %timeit pcg64dxsm.random_raw(1000000)
100 loops, best of 3: 2.9 ms per loop

Je dis toujours que c'est seulement quelques (plus que ce que j'avais) #ifdefs entre les deux, même avec une implémentation spécialisée pour MSVC. De plus, les utilisateurs de RSN MS pourront utiliser clang # 13816 👍.

Sommes-nous en train de discuter de la duplication de code? Je préfère de loin avoir des implémentations disjointes plutôt que de m'inquiéter de quelques lignes de code obscurcies avec #ifdefs :)

C'était surtout juste une blague, bien que cela ait mis en évidence le besoin d'une déclaration absolument claire définissant «PCG 2.0» (de préférence quelque part qui ne soit pas les problèmes de GitHub de NumPy).

Merci, @rkern et al.

@imneme Ce que je pourrais utiliser, c'est un article de blog sur DXSM auquel il est plus facile de créer un lien que ce commentaire d'annonce dans l'ancien méga-numéro. Il n'est pas nécessaire que ce soit beaucoup plus que ce que contient ce commentaire, mais inclure l'état actuel des tests que vous avez mentionnés ici serait bien. Si vous vouliez résumer une partie du débat sur la méga-question qui a conduit à cette évolution, ce serait utile, bien sûr, mais pas entièrement nécessaire.

Avez-vous un calendrier en tête? Je voulais faire cela depuis un certain temps et l'avoir poussé par d'autres choses, donc avoir une date limite proposée serait une motivation utile pour moi. Une semaine peut-être? Deux?

@rkern a également cité @vigna , qui a écrit:

Pourquoi ne pas prendre un LCG avec 128 bits d'état et un bon multiplicateur (au moins 65 bits) et perturber les bits supérieurs en utilisant la fonction mix de SplitMix, qui a été fortement testée dans différentes applications (hachage, PRNG, etc.), donnant d'excellents résultats?

FWIW, cette approche a été discutée dans le document original de PCG, en utilisant _FastHash_ comme fonction de hachage standard, qui est une fonction de hachage multiply-xorshift très similaire. Dans mes tests, ce n'était pas aussi rapide que les autres permutations, mais était de haute qualité. Sebastiano a également mentionné cette idée dans sa critique de 2018 du PCG et j'en discute dans cette section de ma réponse à cette critique.

Dans la version originale de sa critique PCG, il termine par écrire sa propre variante PCG, que je citerai ci-dessous:

        #include <stdint.h>

        __uint128_t x;

        uint64_t inline next(void) {
            // Put in z the top bits of state
            uint64_t z = x >> 64;
            // Update state
            x = x * ((__uint128_t)0x2360ed051fc65da4 << 64 ^ 0x4385df649fccf645)
                  + ((__uint128_t)0x5851f42d4c957f2d << 64 ^ 0x14057b7ef767814f);
            // Compute mix
            z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
            z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
            return z ^ (z >> 31);
        }

Il a depuis mis à jour le code de sa critique vers une version encore plus rapide qui utilise un multiplicateur moins cher et réduit la constante additive à seulement 64 bits,

        #include <stdint.h>

        __uint128_t x;

        uint64_t inline next(void) {
            // Put in z the top bits of state
            uint64_t z = x >> 64;
            // Update state (multiplier from https://arxiv.org/abs/2001.05304)
            x = x * ((__uint128_t)1 << 64 ^ 0xd605bbb58c8abbfd) + 0x14057b7ef767814f;
            // Compute mix
            z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
            z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
            return z ^ (z >> 31);
        }

Mon problème avec ces deux variantes est que la permutation est inversible car l'état tronqué (la moitié supérieure) est permuté / brouillé - vous pouvez l'exécuter à l'envers et déchiffrer le brouillage, vous laissant avec un simple LCG tronqué avec tous les défauts inhérents. Ma préférence est de permuter / scamble l'état entier, puis de produire une troncature de cela. (Bien sûr, permuter / brouiller moins de bits sera le moyen le plus rapide - comme d'habitude, il y a des compromis. Les gens raisonnables peuvent être en désaccord sur ce qui est important.)

Mais son travail de création de sa propre variante PCG a fourni une inspiration très utile pour la permutation DXSM lorsque j'ai écrit cela l'année dernière.

@charris Quel est votre appétit pour rendre l'implémentation PCG64DXSM disponible (mais pas encore par défaut) en 1.19.0? Quelle est cette chronologie? Je vois que nous avons déjà publié la version 1.19.0rc2, ce qui n'est pas génial pour introduire une nouvelle fonctionnalité. Encore une fois, je ne suis pas effrayé par ce problème. Je pencherais vers la publication de la 1.19.0 en documentant simplement notre politique sur les changements de default_rng() et en introduisant de nouvelles choses dans la 1.20.0.

@rkern Le rc final doit être disponible pendant plus de deux semaines, nous envisageons donc une sortie dans la deuxième quinzaine de juin. Je suis en faveur de mettre le PCG64DXSM en option si cela facilite les tests, je ne le considère pas vraiment comme une nouvelle fonctionnalité, mais plutôt comme un nouvel accessoire. Et parfois, cela aide à faire avancer les choses pour avoir un code de travail réel. NumPy est un créateur de tendances :)

EDIT: En supposant, bien sûr, qu'il n'y a pas de gros problèmes avec le nouveau code, et il ne semble pas y en avoir. Je ne suis pas non plus trop inquiet des problèmes avec PCG64, il semble peu probable que quiconque ait des problèmes en utilisant nos procédures recommandées.

@imneme Une semaine serait super. Deux semaines, ce serait bien. Merci!

Il y a une question que je me pose qui est quelque peu hors sujet. Nous voulons que nos générateurs de bits produisent des bits aléatoires, mais AFAICT, la plupart des tests impliquent des entiers. Dans quelle mesure les tests existants réussissent-ils à tester réellement les bits? Si quelqu'un plus familier avec la région pouvait répondre à cette question, je vous serais très reconnaissant.

Ce qui est testé, c'est le flux de bits. Nous indiquons au logiciel de test la taille naturelle des mots du PRNG que nous produisons, mais uniquement pour qu'il puisse faire les meilleurs replis des bits pour provoquer le plus efficacement possible des erreurs qui ont tendance à apparaître dans les bits bas ou hauts du mot dans les mauvais PRNG. Le logiciel que nous avons tous tendance à utiliser de nos jours est PractRand, et ses tests sont légèrement documentés ici . Le meilleur article à lire est probablement celui de TestU01 , la précédente suite de tests standard. Son guide de l'utilisateur contient plus de détails sur les tests.

Je m'excuse si cela semble sournois (même si cela sera certainement souligné), mais c'est aussi sincère: j'ai hâte de voir l'implémentation, l'analyse, les benchmarks et les résultats de PractRand sur votre site Web ou sur arXiv. Nous sommes des praticiens (raisonnablement informés) ici, pas des chercheurs de PRNG, et ne sommes pas particulièrement bien équipés pour mettre en œuvre cette suggestion. Je peux en voir le sens, mais étant donné les autres contraintes sur mon temps personnel, je n'ai pas envie de dépenser l'effort de passer de la suggestion à une mise en œuvre et une analyse. Si vous adressez cette suggestion à numpy, nous avons besoin de chercheurs PRNG pour faire ce travail. Si vous adressez vraiment cette suggestion à quelqu'un d'autre, utilisez votre site Web.

Je peux parfaitement comprendre votre point de vue. Le code et le benchmark sont en bas de page commentant les problèmes de PCG (http://prng.di.unimi.it/pcg.php) depuis quelques années sous le nom LCG128Mix. Il faut 2,16ns sur mon matériel, un processeur Intel (R) Core (TM) i7-7700 à 3,60 GHz, avec gcc 9.2.1 et -fno-move-loop-invariants -fno-unroll-loops.

Le code est très simple - il combine un LCG standard avec une fonction de mixage standard (le finaliseur amélioré de MurmurHash3 de Stafford). Je l'ai légèrement modifié pour avoir une constante programmable:

    #include <stdint.h>
    __uint128_t x; // state
    __uint64_t c;  // stream constant (odd)

    uint64_t inline next(void) {
        // Put in z the top bits of state
        uint64_t z = x >> 64;
        // Update LCG state
        x = x * ((__uint128_t)1 << 64 ^ 0xd605bbb58c8abbfd) + c;
        // Compute mix
        z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
        z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
        return z ^ (z >> 31);
    }

Comme je l'ai expliqué précédemment, la constante de 65 bits est bien meilleure que toute constante de 64 bits en tant que multiplicateur, en raison de problèmes théoriques de multiplicateurs plus petits que la racine carrée du module.

Si vous êtes intéressé par une conception plus fondée sur des principes, je vais exécuter des tests PractRand. Vous devez cependant prendre en considération que cette fonction de mixage a donné un excellent générateur, SplitMix, même avec un générateur sous-jacent beaucoup plus faible (c'était juste additif) et avec un état plus petit (64 bits). Donc ça va être juste "mieux" que SplitMix, qui passe PractRand à 32 To.

Et le générateur sous-jacent est un LCG, vous avez donc toutes les cloches et sifflets habituels des années 60: sauts, distances, etc. Mais vous avez également une garantie statistique que chaque bit du résultat dépend de chaque bit des 64 bits supérieurs de Etat.

Si vous avez à l'esprit des benchmarks par rapport à d'autres générateurs, ou en utilisant d'autres compilateurs, faites-le moi savoir.

Mais, s'il vous plaît, de la même manière sincère: seulement si vous êtes vraiment intéressé à envisager une conception «debout sur les épaules de géants» utilisant uniquement des composants standard. Il y a aussi des contraintes personnelles sur mon temps, et je suis heureux de contribuer, mais j'aimerais éviter de passer du temps sur un générateur qui n'a aucune chance d'être considéré.

BTW, pour donner une mesure plus tangible de la façon d'améliorer la qualité des multiplicateurs impliqués, j'ai calculé les scores spectraux, de f₂ à f₈, du multiplicateur 64 bits actuel utilisé par PCG DXS et une alternative.

Les scores spectraux sont le moyen standard de juger de la bonté d'un multiplicateur. 0 est mauvais, 1 est excellent. Chaque partition décrit dans quelle mesure les paires, triplets, 4-tuples, etc. sont bien répartis dans la sortie.

Ces sept nombres peuvent être repris dans la mesure classique, le minimum, ou une mesure pondérée (le premier score, plus le second divisé par deux, etc., normalisés), pour rendre les premiers scores plus importants, comme le suggère Knuth dans TAoCP , et ce sont la mesure minimale et pondérée pour le multiplicateur actuel:

0xda942042e4dd58b  0.633  0.778

Il existe de bien meilleures constantes 64 bits que cela:

0xff37f1f758180525  0.761  0.875

Si vous passez à 65 bits, essentiellement à la même vitesse (au moins, pour LCG128Mix, c'est la même vitesse), vous obtenez une meilleure mesure pondérée:

0x1d605bbb58c8abbfd  0.761  0.899

La raison en est que les multiplicateurs 64 bits ont une limite intrinsèque à leur score f₂ (≤0,93), ce qui est comme noté par Knuth est le plus pertinent:

0xda942042e4dd58b5  0.795
0xff37f1f758180525  0.928
0x1d605bbb58c8abbfd  0.992

Le premier multiplicateur a donc un score f₂ médiocre. Le deuxième multiplicateur est très proche de l'optimum pour un multiplicateur 64 bits. Le multiplicateur 65 bits n'a pas ces limitations et a un score très proche de 1, le meilleur possible en général.

Par souci d'exhaustivité, voici toutes les partitions:

 0xda942042e4dd58b5  0.794572 0.809219 0.911528 0.730396 0.678620 0.632688 0.639625
 0xff37f1f758180525  0.927764 0.913983 0.828210 0.864840 0.775314 0.761406 0.763689 
0x1d605bbb58c8abbfd  0.991889 0.907938 0.830964 0.837980 0.780378 0.797464 0.761493

Vous pouvez recalculer ces scores ou chercher votre propre multiplicateur avec le code que Guy Steele et moi avons distribué: https://github.com/vigna/CPRNG . Les meilleurs multiplicateurs sont tirés du papier associé.

PCG sera probablement un bon prng par défaut pour numpy, mais je ne pense pas qu'il résistera à l'épreuve du temps, car il existe des moyens plus prometteurs, mais moins testés, de le faire. J'en propose un dans ce qui suit.

Le SFC64 semi-chaotique est l'un des générateurs de son statistiquement les plus rapides avec une période minimum raisonnable. SFC64 n'a pas de fonctions de saut, mais peut _sans surcharge de vitesse être étendu pour prendre en charge 2 ^ 63 flux uniques garantis_. Ajoutez simplement une séquence de Weyl avec une constante additive k choisie par l'utilisateur (doit être impaire), au lieu de simplement incrémenter le compteur de un. Chaque k impair produit une période complète unique. Il faut 64 bits supplémentaires d'état pour maintenir la constante de Weyl:

typedef struct {uint64_t a, b, c, w, k;} sfcw64_t; // k = stream

static inline uint64_t sfcw64_next(sfcw64_t* s) {
    enum {LROT = 24, RSHIFT = 11, LSHIFT = 3};
    const uint64_t out = s->a + s->b + (s->w += s->k);
    s->a = s->b ^ (s->b >> RSHIFT);
    s->b = s->c + (s->c << LSHIFT);
    s->c = ((s->c << LROT) | (s->c >> (64 - LROT))) + out;
    return out;
}

Un état de 320 bits est parfois indésirable, j'ai donc essayé de le réduire à nouveau en utilisant 256 bits. Notez également la fonction de sortie modifiée, qui utilise mieux la séquence de Weyl pour le mélange de bits. Il utilise un état chaotique / structuré de 128/128 bits, ce qui semble trouver un bon équilibre:
/ EDIT: suppression de rotl64 () de la sortie func + cleanup, 6 août:

typedef struct {uint64_t a, b, w, k;} tylo64_t;

static inline uint64_t tylo64_next(tylo64_t* s) {
    enum {LROT = 24, RSHIFT = 11, LSHIFT = 3};
    const uint64_t b = s->b, out = s->a ^ (s->w += s->k);
    s->a = (b + (b << LSHIFT)) ^ (b >> RSHIFT);
    s->b = ((b << LROT) | (b >> (64 - LROT))) + out;
    return out;
}

Cela a actuellement passé 4 To dans les tests PractRand sans anomalies, et j'ai brièvement exécuté le test de poids de Hamming de Vigna sans problèmes jusqu'à présent (bien que la réussite de ces tests ne soit pas une garantie pour une sortie aléatoire presque vraie, plutôt un test si le prng est défectueux ou non ).

Remarque: il est censé être statistiquement avantageux d'utiliser une constante de Weyl aléatoire (unique) avec environ 50% de l'ensemble de bits, mais seuls des tests ou analyses supplémentaires révéleront à quel point cela est significatif.

/ Modifications: nettoyages.

@ tylo-work SFC64 est déjà dans NumPy, avec Philox, il s'agit du générateur par défaut.

Ok, je ne savais pas exactement lesquels ont été mis en œuvre, il ne s'agit donc que de sélectionner le plus adapté dans l'ensemble? Très bien, et merci d'avoir clarifié.

J'essaierai de tester de manière approfondie mon générateur proposé pour voir comment il se compare aux autres, et jusqu'à présent, il semble très bon en termes de vitesse, de qualité de sortie, de simplicité / taille / portabilité et pour une utilisation parallèle massive. Mais je serais heureux si d'autres le testaient également.

Je ne pense pas que nous rouvrons la discussion sur le PRNG par défaut à partir de zéro. Nous avons un problème très spécifique avec notre PRNG actuel et nous recherchons des variantes disponibles et étroitement liées qui abordent ce problème spécifique. L'une de nos préoccupations est que le PRNG par défaut actuel expose certaines fonctionnalités du PRNG, comme la sautabilité, que la variante qui le remplace doit encore exposer. Le SFC64 (le nôtre ou le vôtre) n'a pas cette fonctionnalité.

Il est possible que @bashtage soit prêt à accepter un PR pour randomgen pour ajouter vos variantes de flux Weyl de SFC64.

@ tylo-work Si vous êtes intéressé par l'exécution parallèle, vous voudrez peut-être regarder l'implémentation SeedSequence de NumPy.

Je ne pense pas que nous rouvrons la discussion sur le PRNG par défaut à partir de zéro. Nous avons un problème très spécifique avec notre PRNG actuel et nous recherchons des variantes disponibles et étroitement liées qui abordent ce problème spécifique.

En supposant que vous vouliez quelque chose de semblable à PCG-DXS, il y a d'autres améliorations que vous pouvez faire avec de meilleures constantes (et un ralentissement très marginal). Par exemple, PCG-DXS échouera bientôt deux types distincts de tests sur deux sous-séquences entrelacées et corrélées avec les mêmes 112 bits d'état inférieurs:

rng=PCGDXS_int112, seed=0x4d198651
length= 128 gigabytes (2^37 bytes), time= 5700 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/64]TMFn(0+2):wl             R= +57.3  p~=   2e-27     FAIL !!
  [Low8/64]FPF-14+6/64:(1,14-0)     R= +17.5  p =  8.0e-16    FAIL
  [other failures in the same tests]
  ...and 1893 test result(s) without anomalies

Notez que nous ne parlons que de ≈65536 séquences corrélées - rien à craindre.

Mais vous pouvez améliorer le générateur en choisissant un meilleur multiplicateur, tel que 0x1d605bbb58c8abbfd, et un meilleur mélangeur, tel que 0x9e3779b97f4a7c15. Le premier nombre est un multiplicateur de 65 bits qui a de bien meilleurs scores spectraux. Le deuxième nombre est juste le nombre d'or dans une représentation de point fixe de 64 bits, et ceci est connu pour avoir de belles propriétés de mélange (voir Knuth TAoCP sur le hachage multiplicatif); par exemple, il est utilisé par la bibliothèque Eclipse Collections pour mélanger les codes de hachage.

En conséquence, vous échouez uniquement FPF pour la même quantité de données:

rng=PCG65-DXSϕ_int112, seed=0x4d198651
length= 128 gigabytes (2^37 bytes), time= 5014 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low8/64]FPF-14+6/64:(0,14-0)     R= +16.1  p =  1.5e-14    FAIL
  [other failures in the same test]
  ...and 1892 test result(s) without anomalies

En fait, si nous allons plus loin à 2 To, PCG-DXS échoue à trois types de tests pour les mêmes sous-séquences entrelacées et corrélées:

rng=PCGDXS_int112, seed=0x4d198651
length= 2 terabytes (2^41 bytes), time= 53962 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/32]TMFn(0+0):wl             R= +50.2  p~=   4e-23     FAIL !!
  [Low8/64]FPF-14+6/64:(1,14-0)     R=+291.1  p =  4.7e-269   FAIL !!!!!!
  [Low8/64]Gap-16:B                 R= +19.5  p =  1.4e-16    FAIL !
  [other failures in the same tests]
  ...and 2153 test result(s) without anomalies

alors que PCG65-DXSϕ échoue toujours juste FPF:

rng=PCGDXS65ϕ_int112, seed=0x4d198651
length= 2 terabytes (2^41 bytes), time= 55280 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low8/64]FPF-14+6/64:(0,14-0)     R=+232.1  p =  2.0e-214   FAIL !!!!!!
  [other failures in the same test]
  ...and 2153 test result(s) without anomalies

Tôt ou tard, bien sûr, PCG65-DXSϕ échouera également Gap et TMFn. Mais vous devez voir beaucoup plus de sortie qu'avec PCG-DXS.

Ceci est le code complet pour PCG65-DXSϕ, qui est juste PCG-DXS avec de meilleures constantes:

#include <stdint.h>

__uint128_t x; // State
uint64_t c; // Additive constant

static inline uint64_t output(__uint128_t internal) {
    uint64_t hi = internal >> 64;
    uint64_t lo = internal;

    lo |= 1;
    hi ^= hi >> 32;
    hi *= 0x9e3779b97f4a7c15;
    hi ^= hi >> 48;
    hi *= lo;
    return hi;
}

static uint64_t inline next(void) {
    __uint128_t old_x = x;
    x = x *  ((__uint128_t)1 << 64 ^ 0xd605bbb58c8abbfd) + c;
    return output(old_x);
}

Le ralentissement marginal est dû à une instruction d'ajout (causée par le multiplicateur de 65 bits) et à deux constantes de 64 bits à charger.

Je n'approuve pas les générateurs de ce type en général, mais PCG65-DXSϕ est nettement meilleur que PCG-DXS pour masquer la corrélation.

@Vigna , FYI, j'ai également fait des tests d'entrelacement, et j'ai remarqué que xoshiro256 ** échouait assez rapidement lors de la création de 128 flux entrelacés ou plus. Avec 256, il a échoué rapidement. Le but du test est de vérifier le comportement des PRNG lorsque chaque flux a été initialisé avec des dépendances linéaires. Essentiellement, l'état est initialisé à s[0]=s[1]=s[2]=s[3] = k1 + stream*k2 . Ensuite, 12 sorties sont ignorées, ce qui correspond essentiellement à l'initialisation de sfc64.

Je me rends compte que ce n'est pas l'initialisation recommandée pour xoshiro, mais il est toujours intéressant - et un peu inquiétant - que les tests semblent bons pour xoshiro avec peu de flux entrelacés, mais ont échoué avec beaucoup.

seed: 1591888413
RNG_test using PractRand version 0.95
RNG = RNG_stdin64, seed = unknown
test set = core, folding = standard (64 bit)
...
rng=RNG_stdin64, seed=unknown
length= 2 gigabytes (2^31 bytes), time= 29.6 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/64]FPF-14+6/16:(1,14-1)     R=  +7.2  p =  3.7e-6   unusual
  [Low1/64]FPF-14+6/16:all          R=  +9.6  p =  1.8e-8   very suspicious
  ...and 261 test result(s) without anomalies

rng=RNG_stdin64, seed=unknown
length= 4 gigabytes (2^32 bytes), time= 55.5 seconds
  Test Name                         Raw       Processed     Evaluation
  [Low1/64]FPF-14+6/16:(0,14-0)     R= +13.4  p =  4.7e-12   VERY SUSPICIOUS
  [Low1/64]FPF-14+6/16:(1,14-0)     R=  +9.4  p =  2.6e-8   suspicious
  [Low1/64]FPF-14+6/16:(2,14-1)     R=  +7.7  p =  1.3e-6   unusual
  [Low1/64]FPF-14+6/16:all          R= +17.4  p =  8.8e-16    FAIL !
  ...and 275 test result(s) without anomalies

J'ai également essayé d'affaiblir l'initialisation pour SFC64 et TYLO64 pour ne sauter que 2 sorties, mais elles semblaient toujours OK.
Concernant les performances: xoshiro256 ** tourne 33% plus lentement sur ma machine que les deux autres. TYLO64 ne met à jour que 196 bits de variables d'état. Voici le programme de test:

int main()
{
    //FILE* f = freopen(NULL, "wb", stdout);  // Only necessary on Windows, but harmless.
    enum {THREADS = 256};
    uint64_t seed = 1591888413; // <- e.g. this fails. // (uint64_t) time(NULL); 
    fprintf(stderr, "seed: %lu\n", seed);

    static tylo64_t tyl[THREADS];
    static sfc64_t sfc[THREADS];
    static uint64_t xo[THREADS][4];

    for (size_t i = 0; i < THREADS; ++i) {
    tyl[i] = tylo64_seed(seed + (12839732 * i), 19287319823 * i);
    sfc[i] = sfc64_seed(seed + (12839732 * i));
    xo[i][0] = xo[i][1] = xo[i][2] = xo[i][3] = seed + (12839732 * i);
    for (int j=0; j<12; ++j) xoshiro256starstar_rand(xo[i]);
    }
    static uint64_t buffer[THREADS];
    size_t n = 1024 * 1024 * 256 / THREADS;

    while (1/*n--*/) {
        for (int i=0; i<THREADS; ++i) {
        //buffer[i] = tylo64_rand(&tyl[i]);
        //buffer[i] = sfc64_rand(&sfc[i]);
            buffer[i] = xoshiro256starstar_rand(xo[i]);
        }
        fwrite((void*) buffer, sizeof(buffer[0]), THREADS, stdout);
    }
    return 0;
}

Je vais inclure un code d'en-tête pertinent:

typedef struct {uint64_t a, b, w, k;} tylo64_t; // k = stream

static inline uint64_t tylo64_rand(tylo64_t* s) {
    enum {LROT = 24, RSHIFT = 11, LSHIFT = 3};
    const uint64_t b = s->b, w = s->w, out = (s->a + w) ^ (s->w += s->k);
    s->a = (b + (b << LSHIFT)) ^ (b >> RSHIFT);
    s->b = ((b << LROT) | (b >> (64 - LROT))) + out;
    return out;
}

/* stream in range [0, 2^63) */
static inline tylo64_t tylo64_seed(const uint64_t seed, const uint64_t stream) {
    tylo64_t state = {seed, seed, seed, (stream << 1) | 1};
    for (int i = 0; i < 12; ++i) tylo64_rand(&state);
    return state;
}

static inline uint64_t rotl(const uint64_t x, int k) {
    return (x << k) | (x >> (64 - k));
}
static inline uint64_t xoshiro256starstar_rand(uint64_t* s) {
    const uint64_t result = rotl(s[1] * 5, 7) * 9;
    const uint64_t t = s[1] << 17;
    s[2] ^= s[0];
    s[3] ^= s[1];
    s[1] ^= s[2];
    s[0] ^= s[3];
    s[2] ^= t;
    s[3] = rotl(s[3], 45);
    return result;
}

@ tylo-work J'apprécie l'analyse, mais j'ai vraiment besoin de cette question pour rester concentrée. Si vous souhaitez poursuivre cette discussion, je vous encourage à publier votre travail dans votre propre dépôt Github et à publier un autre message ici pour inviter les gens à y participer. Tout le monde, veuillez y répondre. Merci de votre collaboration.

@imneme @rkern Le temps

@rkern On dirait que PCG64DXSM ne fera pas partie de la version 1.19.0, je sortirai ce week-end. Si vous pouviez écrire la note sur notre politique de changement / les changements à venir que vous avez mentionnés ci-dessus, je vous en serais reconnaissant.

Désolé, j'ai traité d'autres questions sans rapport. Sur la base de notre discussion, je ne pense pas qu'un petit retard soit un gros problème, car PCG64DXSM a été prévu comme une option alternative, pas comme une nouvelle valeur par défaut (pour l'instant, du moins).

Maintenant que la 1.20 démarre, est-il temps de revoir cela et de passer à DXSM?

Nous aurions encore un peu de temps pour faire le déménagement avant de créer une branche, mais il serait peut-être bon de commencer dans la semaine prochaine. @bashtage Je suppose que vous avez le PCG64DXSM prêt à l'emploi et cela nécessite principalement la décision de basculer le commutateur sur le flux par défaut?

D'après ce qu'il a vu, il semblait que nous devrions simplement faire cela pour 1,20 si nous l'avons facilement disponible.

IIRC, nous attendions une référence qui pourrait être liée. Mais si les gens au nombre aléatoire sont satisfaits du changement, nous devrions l'utiliser. Avons-nous besoin d'un code spécial pour Windows?

Il s'agit simplement d'une constante différente et d'une fonction de brouillage différente. Rien de plus nouveau que ce que @rkern a écrit pour l'implémentation PCG64 originale sous Windows. Je pense que la décision a été d'avoir un PCG64DXSM entièrement autonome plutôt que de partager du code (pour les performances).

Il serait probablement logique de partir de la branche WIP de

J'ai dit que j'écrirais un article de blog à ce sujet, ce que je pense que @rkern voulait, mais je me suis occupé d'autres questions et cela ne s'est pas encore produit (désolé). Dans l'intervalle, la permutation DXSM a été testée et continue de sembler être une amélioration par rapport à l'original. D'après les remarques plus tôt dans le fil, je pense que @rkern aurait peut-être aimé une permutation de sortie encore plus forte, mais cela vous coûte soit de la vitesse, soit (si vous coupez les coins pour gagner en vitesse) ajoute une prévisibilité triviale.

Cette page vous a été utile?
0 / 5 - 0 notes