Numpy: La implementación de PCG proporcionada por Numpy tiene una autocorrelación significativa y peligrosa

Creado en 20 may. 2020  ·  104Comentarios  ·  Fuente: numpy/numpy

El generador de PCG utilizado por Numpy tiene una autocorrelación significativa. Es decir, para cada secuencia generada a partir de una semilla hay un gran número de secuencias correlacionadas que no se superponen a partir de otras semillas. Por "correlacionado" me refiero a que entrelazando dos de estas secuencias y probando el resultado se obtienen fallos que no aparecían en cada secuencia individualmente.

La probabilidad de que dos generadores de un gran conjunto de terminales obtengan dos de esas secuencias no es despreciable. Es bien sabido por qué sucede esto desde un punto de vista matemático, pero se explica aquí en detalle: http://prng.di.unimi.it/pcg.pgp (ver "Subsecuencias dentro del mismo generador").

Para mostrar este problema directamente, escribí este sencillo programa en C reutilizando el código Numpy: http://prng.di.unimi.it/intpcgnumpy.c . El programa toma dos estados de 128 bits de dos generadores (con la misma constante LCG o "flujo") en forma de bits altos y bajos, intercala su salida y la escribe en forma binaria. Una vez que lo enviemos a través de PractRand, no deberíamos ver ningún fallo estadístico, ya que los dos flujos deberían ser independientes. Pero si intenta comenzar desde dos estados con los mismos 64 bits inferiores, obtiene:

./intpcgnumpy 0x596d84dfefec2fc7 0x6b79f81ab9f3e37b 0x8d7deae980a64ab0 0x6b79f81ab9f3e37b | stdbuf -oL ~ / svn / c / xorshift / practrand / RNG_test stdin -tf 2 -te 1 -tlmaxonly -multithreaded
RNG_test usando PractRand versión 0.94
RNG = RNG_stdin, semilla = desconocido
conjunto de prueba = expandido, plegable = extra

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

Incluso puede bajar, solo necesita los mismos 58 bits inferiores:

./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

Tenga en cuenta que para obtener más del 50% de probabilidad de que dos generadores comiencen a partir de dos semillas correlacionadas (elegidas al azar), necesita aproximadamente medio millón de generadores que comiencen al azar (paradoja del cumpleaños). Y si considera la probabilidad de que no comiencen exactamente desde el mismo estado, pero tengan secuencias correlativas superpuestas significativas, necesita mucho menos.

Cualquier generador sensato de la literatura no se comportará así. Puede elegir de forma contraria dos estados iniciales cualquiera de MRG32k3a, SFC64, CMWC, xoshiro256 ++, etc., y siempre que genere secuencias que no se superpongan, no verá las fallas anteriores. Este es un inconveniente importante que puede aparecer cuando varios dispositivos utilizan el generador y se supone (como debería ser) que esas secuencias por pares no deberían mostrar correlación. La correlación puede inducir un comportamiento no deseado que es difícil de detectar.

Por favor, documente al menos en algún lugar que el generador no debe usarse en múltiples terminales o en un entorno altamente paralelo.

Lo mismo puede suceder con diferentes "flujos", ya que las secuencias generadas por un LCG al cambiar la constante aditiva son todas iguales módulo un cambio de signo y una constante aditiva. Puede ver una discusión aquí: https://github.com/rust-random/rand/issues/907 y una discusión matemática completa del problema aquí: https://arxiv.org/abs/2001.05304 .

numpy.random

Todos 104 comentarios

@imneme , @bashtage , @rkern serían las autoridades aquí, pero creo que hemos SeedSequence.spawn sobre la de jumped one. Por ejemplo, hubo esta discusión cuando estábamos discutiendo la API. Consulte los consejos aquí https://numpy.org/devdocs/reference/random/parallel.html y sugiera las mejoras necesarias.

@mattip Esto no tiene nada que ver con saltar.

Creo que en la práctica es difícil hacer cambios al por mayor, aunque mejorar la documentación siempre es una buena idea.

Probablemente recomendaría AESCounter para cualquier persona con AES-NI o SPECK128 para cualquiera que no tenga una configuración altamente paralela.

Lo mismo puede suceder con diferentes "flujos", ya que las secuencias generadas por un LCG al cambiar la constante aditiva son todas iguales módulo un cambio de signo y una constante aditiva.

¿Puedes cuantificar esto? Puedo replicar las fallas usando el mismo incremento, pero sembramos tanto el incremento como el estado, y todavía no he observado una falla con dos incrementos aleatorios diferentes. Si los incrementos también tienen que construirse cuidadosamente, eso afectaría la frecuencia práctica de colisión de cumpleaños.

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, lo intentaré de nuevo.

No hay múltiples flujos en un LCG con un módulo de potencia de 2. Muchos lo creían en los primeros días, e incluso hay documentos antiguos que afirman hacer cosas interesantes con esos "flujos", pero se sabe desde hace décadas que las órbitas que se obtienen al cambiar las constantes son _todas el mismo módulo un aditivo constante y posiblemente un cambio de signo_. Lo más lejos que puedo rastrear es

Mark J. Durst, Uso de generadores congruentes lineales para la generación de números aleatorios en paralelo,
1989 Actas de la conferencia de simulación de invierno, IEEE Press, 1989, págs. 462–466.

Entonces, escribí otro programa http://prng.di.unimi.it/corrpcgnumpy.c en el que puedes configurar:

  • Un estado inicial para un PRNG.
  • Un estado inicial para otro PRNG.
  • Una "constante de flujo" arbitraria para el primer PRNG.
  • Una "constante de flujo" arbitraria para el segundo PRNG (ambos deben ser pares o impares; esta restricción se puede eliminar con algunos retoques adicionales).
  • Un número fijo de bits inferiores que configuraremos de manera contradictoria en el segundo PRNG, esencialmente de tal manera que comience con los mismos bits del primer PRNG. El resto de los bits se tomarán del estado inicial para el segundo PRNG que haya proporcionado.

Así que esta es _exactamente_ la configuración del primer programa, pero también puede elegir las 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

Por lo tanto, hay _ al menos_ 2 ^ 72 subsecuencias correlacionadas, sin importar cómo elija las "constantes de flujo", exactamente como en el caso de la misma constante.

Y tenemos una cantidad ridícula de holgura para el generador: incluso si en lugar del punto de partida exacto que estoy calculando, usarías un estado un poco antes o después, la correlación se mostraría de todos modos. Puede modificar fácilmente el programa con un parámetro adicional para hacer eso.

Repito, una vez más: ningún generador moderno existente de la literatura científica tiene este mal comportamiento (por supuesto, un LCG de poder de 2 tiene este comportamiento, pero, por el amor de Dios, eso no es un generador moderno).

Las críticas de Sabastiano a PCG se abordan en esta publicación de blog de 2018.

La versión corta es que si se le permite idear semillas específicas, puede mostrar un comportamiento de "mal aspecto" en prácticamente cualquier PRNG. A pesar de su afirmación de que PCG es de alguna manera único, en realidad PCG es bastante convencional: las transmisiones de PCG no son peores que, digamos, SplitMix, que es otro PRNG ampliamente utilizado.

Eso es completamente falso. Para demostrar que estoy equivocado, muestre dos secuencias correlacionadas que no se superponen de MRG32k3a o xoshiro256 ++.

Nunca dije que no se superpusieran. Muéstrame una prueba disponible actualmente para xoshiro256 ++. que dos semillas eviten la superposición.

En contraste, tengo una prueba para PCG que muestra que las "correlaciones" que mostró son esencialmente una forma de superposición.

No puedo luchar contra FUD como "esencialmente" y "una forma", pero modifiqué http://prng.di.unimi.it/intpcgnumpy.c para que inicialmente repita cada PRNG 10 mil millones de veces y salga con un error. mensaje si la secuencia generada atraviesa el estado inicial del otro PRNG. Esto garantiza que los primeros 160 GB de datos en Practrand provienen de secuencias que no se superponen:

./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

Estos datos de inicialización en particular tienen solo 56 bits fijos más bajos, por lo que se pueden generar 2 ^ 72 secuencias correlacionadas cambiando los bits más altos. Las fallas estadísticas ocurren después de solo 64 GB de datos, lo que muestra que las superposiciones no son responsables de la correlación. Es posible que, con otras opciones específicas, la superposición ocurra antes de 64 GB, por supuesto; este es un ejemplo específico. Pero ahora es fácil comprobar que la superposición no es el problema: el generador tiene una gran cantidad de correlaciones internas no deseadas.

Respete el código de conducta . Trate de mantener sus comentarios en tono con las directrices de ser "empáticos, acogedores, amables y pacientes" y "tener cuidado con las palabras que elegimos. Somos cuidadosos y respetuosos en nuestra comunicación".

Nunca dije que no se superpusieran. Muéstrame una prueba disponible actualmente para xoshiro256 ++. que dos semillas eviten la superposición.

Bueno, es trivial: decida la longitud del flujo, repita y verifique que los dos flujos no crucen el estado inicial. Es el mismo código que utilicé para mostrar que los flujos PCG correlacionados en el programa http://prng.di.unimi.it/intpcgnumpy.c no se superponen.

A pesar de su afirmación de que PCG es de alguna manera único, en realidad PCG es bastante convencional: las transmisiones de PCG no son peores que, digamos, SplitMix, que es otro PRNG ampliamente utilizado.

En mi humilde opinión, la autocorrelación dentro de PCG es mucho peor. No hay ningún resultado para el generador de aditivos subyacente a una instancia de SplitMix análogo a los dramáticos resultados de Durst en 1989 sobre LCG.

Pero se conocen los problemas muy leves de SplitMix, y JEP 356 proporcionará una nueva clase de generadores divisibles, LXM, que tratará de abordar esos problemas. Sería hora de seguir adelante y reemplazar PCG, también, con algo menos defectuoso.

El problema subyacente es conocido por ambos generadores y se debe a la falta de combinación de estados. Si cambia el bit _k_ del estado de uno de esos generadores, el cambio nunca se propagará por debajo del bit _k_. Esto no sucede en LCG con módulo primario, en generadores lineales F₂, generadores CMWC, etc. Todos los demás generadores intentan mezclar su estado lo más rápido y posible.

Equiparar los problemas de PCG y SplitMix es una pista falsa. SplitMix tiene un generador subyacente muy simple, simplemente aditivo, pero además de eso hay una función de codificación que es muy poderosa: es el finalizador de 64 bits de Appleby de la función hash MurmurHash3, que se ha utilizado ampliamente en varios contextos y ha sido mejorado por Stafford (http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html). Las constantes de la función se han entrenado para tener propiedades de avalancha específicas y medibles. Incluso los cambios en una pequeña cantidad de bits tienden a extenderse por toda la salida. En otras palabras, SplitMix está sobre el hombro de gigantes.

Por el contrario, los generadores PCG subyacentes de LCG tienen los mismos problemas de falta de mezcla, pero las funciones de codificación son solo una secuencia simple de operación aritmética y lógica ensamblada por el autor sin ninguna garantía teórica o estadística. Si se hubieran ideado teniendo en cuenta el hecho de que todas las secuencias de la LCG subyacente son el mismo módulo, una constante aditiva y posiblemente un cambio de signo, habría sido posible abordar el problema.

Pero el autor no tenía idea de que las secuencias eran tan fáciles de derivar unas de otras. Esto se puede ver fácilmente en esta declaración en la Sección 4.2.3 del informe técnico del PCG (https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf):

"Cada elección de _c_ da como resultado una secuencia de números diferente que no tiene ninguno de sus pares de salidas sucesivas en común con otra secuencia".

Esto se toma como prueba de que las secuencias son diferentes, es decir, que el LCG subyacente proporciona múltiples flujos. Los resultados negativos de Durst en 1989 sobre este tema no aparecen en ninguna parte del documento. Como señalé anteriormente, según esos resultados, todas esas secuencias son iguales, módulo una constante aditiva y posiblemente un cambio de signo (para LCG con módulo de potencia de 2 de potencia máxima, como sucede en PCG).

Estoy seguro de que no citar los resultados de Durst es un error _bona fide_, pero el problema es que una vez que estás convencido de que el LCG subyacente que estás usando proporciona "flujos" que son "diferentes" en algún sentido, cuando no lo son, terminas con un generador como PCG en el que para cada subsecuencia hay 2 ^ 72 subsecuencias correlacionadas y no superpuestas, incluso si cambia el "flujo".

Gracias por su aportación a todos ustedes. Por el momento, no estoy interesado en juicios binarios como "PCG es bueno / malo". Utilice sus propios foros para estas discusiones. Lo que está en el tema aquí es lo que hará numpy, y ese juicio final pertenece a los desarrolladores de numpy. Apreciamos la experiencia que todos aportan a esta discusión, pero quiero centrarla en los hechos subyacentes en lugar del juicio final. Aprecio especialmente las declaraciones cuantitativas que me dan una idea de la cantidad de espacio para la cabeza que tenemos. Si mis juicios anteriores estaban equivocados, fue porque salté al juicio demasiado pronto, por lo que agradecería su ayuda para evitarlo nuevamente. Gracias.

Tenga en cuenta que para obtener más del 50% de probabilidad de que dos generadores comiencen a partir de dos semillas correlacionadas (elegidas al azar), necesita aproximadamente medio millón de generadores que comiencen al azar (paradoja del cumpleaños).

@vigna ¿Puedes n -bit en artículos 2**(n/2) (más o menos un factor de 2). Medio millón es 2**19 , por lo que parece estar afirmando que las correlaciones peligrosas comienzan alrededor de una colisión de 40 bits en los bits inferiores, pero no he visto evidencia de que esto sea prácticamente observable. He probado un par que comparte los 40 bits inferiores y llegué a 16 TiB en PractRand antes de cancelar la prueba. Si ha observado una falla con una colisión de 40 bits, ¿cuántos TiB tuvo que probar para verla?

Estoy convencido de que cambiar el incremento no afecta la probabilidad de colisión. La discusión adicional sobre los méritos de las "corrientes PCG" está fuera de tema. Usar esa discusión como excusa para golpear repetidamente al "autor" es especialmente desagradable y pisotea nuestro código de conducta . Persistir significará que tendremos que continuar sin su participación. Gracias.

@imneme Esto parece estar relacionado con los problemas de saltar por un múltiplo de una gran potencia de 2. Cuando construyo un par de instancias PCG64 con el mismo incremento y compartiendo el menor n bits, la distancia que calculo entre los dos es un múltiplo de 1 << n . Parece que su función de salida DXSM más fuerte parece resolver esta manifestación también. Probé un par de instancias PCG64DXSM que comparten un incremento y el estado inferior de 64 bits a 2 TiB sin problemas.

Bien, esto es vergonzoso: fue medio billón, no medio millón. Una sola letra puede marcar una gran diferencia. Pido disculpas por el desliz.

Pero, como dije antes, esta es la probabilidad de alcanzar exactamente el mismo estado inicial, no la probabilidad de una superposición significativa de subsecuencias correlacionadas. Personalmente prefiero usar PRNG sin subsecuencias correlacionadas, ya que hay muchas, pero, como bien dice, la decisión es solo suya.

Arreglar la función de codificación para que tenga mejores propiedades de mezcla parece una solución perfectamente razonable.

Mi publicación estaba destinada a ser una aclaración de las diferencias estructurales entre PCG y SplitMix, ya que una publicación anterior afirmaba que tenían problemas similares, y no creo que sea una afirmación correcta. No puede escribir un programa como http://prng.di.unimi.it/corrpcgnumpy.c para SplitMix.

@rkern , preguntaste:

@imneme Esto parece estar relacionado con los problemas de saltar por un múltiplo de una gran potencia de 2. Cuando construyo un par de instancias PCG64 con el mismo incremento y compartiendo el n bits, la distancia que calculo entre los dos es un múltiplo de 1 << n . Parece que su función de salida DXSM más fuerte parece resolver también esta manifestación. Probé un par de instancias PCG64DXSM que comparten un incremento y el estado inferior de 64 bits a 2 TiB sin problemas.

Gracias por encontrar y vincular al hilo de discusión del año pasado. Sí, como señala Sebastiano en su respuesta,

Arreglar la función de codificación para que tenga mejores propiedades de mezcla parece una solución perfectamente razonable.

XSL-RR está en el extremo más débil de las cosas. Por el contrario, tanto la función de salida RXS-M original del papel PCG como la nueva función de salida DXSM hacen más en la forma de codificación, por lo que no muestran este tipo de problemas. DXSM (agregado a la fuente PCG en este compromiso el año pasado) fue diseñado específicamente para ser más fuerte que XSL-RR pero tiene un rendimiento de tiempo similar (cf, RXS-M, que es más lento). Probé DXSM bastante duro el año pasado, pero 67 días después de la ejecución tuvimos un corte de energía prolongado que apagó el servidor (la batería del UPS se agotó) y terminó la ejecución de prueba, pero en ese momento había demostrado ser bastante bueno en ambos pruebas (128 TB de salida probada) y saltos de 2 ^ 48 (64 TB de salida probada, ya que se ejecuta más lento).

Si, incluso sin diseñar DXSM, RXS-M se hubiera ocupado del problema, una pregunta es por qué alguna vez usé la permutación XSL-RR más débil en su lugar, ¿por qué no usar siempre una codificación de bits muy fuerte en la función de salida? La respuesta es que básicamente se trata de ciclos. La velocidad es importante para las personas, por lo que intenta evitar hacer más revueltas de las necesarias.

Este es un tema con el que Sebastiano está familiarizado, porque su enfoque y el mío tienen mucho en común. Cada uno de nosotros adopta un enfoque establecido desde hace mucho tiempo que fallaría las pruebas estadísticas modernas (LCG en mi caso, y XorShift LFSR de Marsaglia en el suyo) y agrega una función de salida de codificación para canjearlo. Ambos nos esforzamos por hacer que esa función de salida sea barata, y ambos hemos sido atrapados un poco donde las deficiencias en el generador subyacente que estamos tratando de enmascarar con nuestra función de salida, sin embargo, se muestran. En su caso, son las cuestiones de linealidad las que se han manifestado .

Pero en un trabajo algo reciente que me gustó mucho, también mostró cuántos diseños basados ​​en LFSR tienen problemas de peso de martillo (lo que refleja una preocupación desde hace mucho tiempo) que están enmascarados de manera inadecuada por sus funciones de salida. Sus propios generadores pasan su prueba, así que eso es algo, pero en 2018, cuando estaba mirando su nuevo xoshiro PRNG, me pareció que los problemas de peso de martillo del generador subordinado lograron superar su función de salida. Desde entonces, ha revisado xoshiro con una nueva función de salida, y espero que eso funcione (también hizo algunos otros cambios, así que quizás uno también solucione el problema de las repeticiones, resaltado por este programa de prueba ).

En cuanto a sus programas de correlación, en 2018, cuando publicó una crítica de PCG en su sitio web que incluía programas que había escrito con varios problemas (como siembra artificial, etc.), escribí una respuesta que contenía un montón de programas similares. para otros PRNG establecidos desde hace mucho tiempo, incluido corrsplitmix2.c que crea flujos correlacionados en SplitMix. No estoy muy seguro de lo que Sebastian quiere decir cuando dice que no se puede hacer, pero admito que no he tenido la oportunidad de mirar de cerca su programa de prueba para ver si el nuevo es sustancialmente diferente de los que él. escribió hace un par de años.

Por favor, perdone mi falta de comprensión, pero ¿podría pedirle a alguien una conclusión resumida en esta etapa? ¿Existen circunstancias realistas en las que el PCG predeterminado no es seguro? ¿Cuáles son los argumentos para cambiar el valor predeterminado?

El camino fácil es agregar algo de documentación sobre aplicaciones de alta (¿ultra alta?) Dimensión y la probabilidad de secuencias correlacionadas.

Un camino más difícil sería reemplazar la función de salida que interrumpiría la transmisión. No estoy seguro de cuán fuerte es una promesa default_rng . Los documentos no parecen advertir que esto puede cambiar, por lo que probablemente necesitaría un ciclo de obsolescencia para cambiar. Esto requeriría agregar la nueva función de salida como un generador de bits independiente (o configurable desde PCG64, lo que sería más sensato) y luego advertir a los usuarios que cambiará después del año XXXX / lanzamiento Y.ZZ.

El camino más difícil sería encontrar un nuevo rng predeterminado. No fue fácil la primera vez y no creo que nada haya cambiado para mover la aguja en ninguna dirección en particular durante los últimos 18 meses.

Abrí gh-16493 sobre la modificación default_rng() , no creo que esté relacionado con este problema, ni siquiera estoy seguro de que tengamos que discutirlo, probablemente ya habíamos establecido las reglas hace mucho tiempo y simplemente no ' no recuerdo.


No pretendo entender completamente esta discusión, pero parece que hay dos cosas que resolver:

  1. Teniendo la certeza de que tenemos suficientes avances, tengo que confiar en Robert en esto y ahora mismo me parece que debería estar bien según nuestro conocimiento. (Es decir, ¿la probabilidad de una colisión real es probablemente vergonzosamente baja incluso en entornos de magnitudes mayores que cualquier otro en el que se pueda usar NumPy? Si puede justificar o no cambiar el valor predeterminado en el futuro es un tema diferente).
  2. Declaramos:

    y admite [...] además de: matemáticas: 2^{127} transmisiones

    lo cual, sin saber exactamente de dónde viene el número, parece que puede ser una ligera exageración de nuestra parte y podríamos considerarlo ajustado ligeramente para que sea perfectamente correcto. ¿O un enlace a algún recurso externo con detalles adicionales?

Lo más fácil de hacer ahora sería agregar una PCG64DXSM BitGenerator , la variante de PCG64 con el "multiplicador barato" y una función de salida DXSM más fuerte. Creo que todos están de acuerdo en que eso es un paso adelante de la función de salida XSL-RR que tenemos ahora en nuestra implementación PCG64 , con un mejor rendimiento estadístico sin dañar el rendimiento en tiempo de ejecución. Es una actualización sencilla en el nicho que PCG64 sirve para los BitGenerator s que ofrecemos. Creo que deberíamos agregarlo junto con PCG64 .

Por cierto, prefiero que sea un BitGenerator nombre separado en lugar de una opción para el constructor PCG64 . Estas opciones son excelentes en randomgen , cuyo propósito es proporcionar una gran cantidad de algoritmos y variantes diferentes, pero para numpy , creo que queremos que nuestras selecciones sean "listas para usar" como tanto como podamos.

No creo que realmente hayamos establecido la política para realizar cambios en lo que proporciona default_rng() . Cuando lo propuse, mencioné la noción de que una de las razones por las que prefería poner su funcionalidad en el constructor Generator() era que podíamos desaprobar y movernos a una función con un nombre diferente si lo necesitáramos. Sin embargo, en ese momento estábamos considerando que default_rng() podría necesitar exponer muchos detalles del BitGenerator subyacente, lo que posteriormente evitamos. Debido a que PCG64DXSM expone la misma API ( .jumped() en particular) que PCG64 , la única consideración que tendríamos es que usar como nuevo valor predeterminado cambiaría el flujo de bits. Creo que sería razonable que siguiéramos la misma línea de tiempo que cualquier otra modificación de la transmisión proveniente de los métodos Generator según NEP 19 (es decir, en las versiones de funciones de X.Y.0 ). Podemos optar por ser un poco más cautelosos, si queremos, y primero exponer PCG64DXSM como BitGenerator en 1.20.0 y documento (pero no warn() , demasiado ruidoso para ningún efecto) que default_rng() cambiará para usarlo en 1.21.0 .

Como parte de la adición del nuevo BG, sería bueno actualizar las notas a PCG64 para comenzar a servir como guía y proporcionar una justificación para preferir la variante más nueva.

  1. Teniendo la certeza de que tenemos suficientes avances, tengo que confiar en Robert en esto y ahora mismo me parece que debería estar bien según nuestro conocimiento. (Es decir, ¿la probabilidad de una colisión real es probablemente vergonzosamente baja incluso en entornos de magnitudes mayores que cualquier otro en el que se pueda usar NumPy? Si puede justificar o no cambiar el valor predeterminado en el futuro es un tema diferente).

Probablemente sea un poco demasiado simplista. Depende de cuántas transmisiones, cuántos datos extraiga de cada transmisión y cuál es su tolerancia al riesgo de una colisión de cumpleaños. Todavía no he logrado convertir esas matemáticas en un párrafo comprensible para hacer recomendaciones fáciles de seguir, razón por la cual no he revisado este problema de Github en un tiempo. Sin embargo, no creo que sea un problema de pelo en llamas que deba arreglarse ahora mismo.

Escribiré algo más largo más tarde, pero como lo veo en este hilo, hemos estado recauchutando el terreno que repasamos el año pasado. Nada ha cambiado más que Sebastiano descubrió que NumPy había enviado PCG. El análisis del equipo de NumPy el año pasado fue más profundo y consideró escenarios más plausibles.

Mi preferencia sería actualizar el valor predeterminado lo más rápido posible, solo para reducir la confusión. Quiero decir, no esperar un ciclo de depreciación.

@imneme - muchas gracias - Encontraría algo más largo muy útil.

Probablemente la mejor explosión de la publicación

Tenía en la cabeza lo que quería decir, pero al mirar las publicaciones de hace un año, ya lo he dicho todo, tanto aquí como en otros lugares ( mi blog (2017), reddit , mi blog nuevamente (2018) , y en discusiones de NumPy, etc.)

Sobre las transmisiones y su auto-similitud (para lo cual @rkern escribió un probador de dependencia de la transmisión ), escribí el año pasado:

Como se señaló en la publicación de mi blog que se mencionó anteriormente, las transmisiones de PCG tienen mucho en común con las de SplitMix.

Con respecto al gráfico de @mdickinson, para _ cada_ PRNG que le permite sembrar su estado completo, incluidos los criptográficos basados ​​en contador, podemos idear semillas en las que tendríamos PRNG cuyas salidas se correlacionaron de alguna manera (la forma más fácil de hacerlo es hacer estados PRNG que estén a una corta distancia, pero a menudo podemos hacer otras cosas basándonos en una comprensión de cómo funcionan). Y aunque los PRNG que no permiten la siembra de estado completo pueden evitar este problema, hacerlo solo introduce uno nuevo, solo brinda acceso práctico a una pequeña fracción de sus posibles estados.

La forma correcta de pensar en las corrientes es simplemente un estado más aleatorio que debe sembrarse. El uso de valores pequeños como 1,2,3 es generalmente una mala idea para cualquier propósito de siembra de _cualquier_ PRNG (porque si todos favorecen estas semillas, sus secuencias iniciales correspondientes estarán sobrerrepresentadas).

Podemos elegir no llamarlo flujo en absoluto y simplemente llamarlo estado. Eso es lo que hizo Marsaglia en XorWow . Si observa el código, la secuencia de Weyl counter no interactúa en absoluto con el resto del estado y, como las LCG, y las variaciones en el valor inicial en realidad solo equivalen a una constante agregada.

Las transmisiones de SplitMix, PCG y XorWow son lo que podríamos llamar transmisiones "estúpidas". Constituyen una trivial reparametrización del generador. Sin embargo, esto tiene valor. Supongamos que sin corrientes, nuestro PRNG tendría una interesante repetición cercana de 42, donde 42 aparece varias veces en rápida sucesión y solo hace esto para 42 y ningún otro número. Con secuencias estúpidas de “solo un incremento” o “solo un xor”, en realidad evitaremos cablear la extraña repetición a 42; todos los números tienen una secuencia en la que se repiten de forma extraña. (Por esta razón, la solución que aplicaría para reparar los problemas de repetición cercana en Xoshiro 256 es mezclar en una secuencia de Weyl).

(Luego profundicé más en este comentario y en este ).

Yo diría que la conclusión clave es que para casi todos los PRNG, con un poco de tiempo y energía se pueden idear semillas patológicas. PCG está en una posición un tanto inusual que tiene a alguien que disfruta trabajar semillas de apariencia plausible para PCG específicamente que tienen una patología hecha a mano (es decir, Sebastiano). Como resultado de ese trabajo, me di la vuelta e hice lo mismo tanto para sus PRNG como para otros de larga data.

En general, desea inicializar el estado PRNG con algo que parezca "algo aleatorio". Eso es bastante universal, incluso si la gente quiere hacer lo contrario . Por ejemplo, para cualquier LFSR PRNG (estilo XorShift, Mersenne Twister, etc.), no debe inicializarlo con un estado de todos ceros porque simplemente permanecerá atascado allí. Pero incluso los estados que son en su mayoría cero a menudo son problemáticos para los LFSR (un fenómeno conocido como zeroland, y por qué la gente de C ++ ganó seed_seq .). Si quieres jugar al juego de "hagamos una siembra artificial", no es difícil crear una colección de inicializaciones para 100 LFSR y hacer que todas estén a 1K de llegar a tierra cero. Todas las inicializaciones artificiales parecerán bastante inocentes, pero todas golpearán esta extraña caída en el peso del martillo al mismo tiempo.

Si está utilizando un generador de PCG que se inicializó con una entropía razonable, está bien. Si desea inicializarlo con basura como 1, 2, 3, eso es realmente problemático con cualquier PRNG . Y con PCG, el 99,9% de las veces, incluso usar algo un poco basura estaría bien. No tiene nada parecido al tipo de problemas que tienen los LFSR.

Pero DXSM es ciertamente más fuerte. Creo que es mejor o no lo habría logrado, pero vale la pena tener un poco de perspectiva y darse cuenta de que, en la práctica, los usuarios no van a tener problemas con la implementación clásica de PCG64.

Me gustaría separar la crítica / defensa de las transmisiones de PCG64 (a través del incremento de LCG) de la presente discusión. Si bien hay una cierta dualidad involucrada debido a las matemáticas de la LCG, no es el tema central que se trató originalmente aquí. Además, hay más detalles aquí para ser considerados de los que estaban presentes en la crítica original de Sebastiano, su respuesta o el viejo mega hilo. Quizás la conexión sea más obvia para los expertos que han dedicado más tiempo a las matemáticas, pero las consecuencias prácticas ahora son más claras para mí, al menos.

Yo diría que la conclusión clave es que para casi todos los PRNG, con un poco de tiempo y energía se pueden idear semillas patológicas.

De acuerdo, pero no es el binario can / can't lo que impulsa la decisión frente a mí. Si saco demasiados números de un PRNG finito, PractRand eventualmente lo detectará. Ese hecho binario no invalida ese algoritmo PRNG. Alejarme de ese binario y establecer el concepto de headroom fue una de las cosas que realmente aprecié del artículo original de PCG. Dada una patología generada por el adversario, podemos echar un vistazo a la frecuencia con la que esa patología podría surgir al azar de una buena siembra de entropía. Quiero cuantificar eso y convertirlo en consejos prácticos para los usuarios.

Dados dos estados que comparten los 58 bits inferiores y el mismo incremento (pondremos un pin en eso), el entrelazado de instancias PCG64 XSL-RR de esos estados demuestra fallas prácticamente observables en PractRand en alrededor de 32 GiB. Creo que es razonable querer evitar eso. Así que tomemos eso como nuestro punto de referencia y veamos con qué frecuencia surge con una buena siembra de entropía. Afortunadamente, este esquema contradictorio es susceptible de análisis probabilístico (no todos son tan amigables). Para las instancias n , la probabilidad de que 2 compartan los mismos 58 bits inferiores es n**2 / 2**58 , más o menos un factor de 2 para el conteo doble. Por lo tanto, en 500 millones de instancias, las probabilidades de que exista una combinación de este tipo fallarían en PractRand si se intercalaran. ¡500 millones es mucho! En mi opinión, probablemente nunca veremos un programa numpy que intente crear tantas instancias PCG64 . numpy probablemente sería la herramienta incorrecta, entonces.

Creo que también es razonable querer evitar estados iniciales cuyos dibujos posteriores _cruzarán_ cualquiera de los estados de colisión de 58 bits inferiores de los otros estados iniciales. Todavía estoy tratando de pensar en la lógica de eso, pero creo que la longitud afecta la probabilidad de forma lineal en lugar de cuadrática. Si estoy en lo cierto y quiero extraer 16 GiB de cada instancia ( 2**28 sorteos), que es la cantidad que extrajimos de cada uno de los pares que mostraron fallas de PractRand, entonces solo puedo trabajar con aproximadamente 2**15 instancias, o alrededor de 32k, antes de que sea bastante probable que observe un cruce. ¡Siguen siendo bastantes casos para Python! Y la cantidad total de datos generados es aproximadamente medio petabyte, ¡que es mucho! Pero está en el horizonte de la practicidad, y si quiero mantener la probabilidad baja, no solo por debajo de la mitad, tengo que bajar en una de esas. No estoy particularmente preocupado por estos números; No creo que ningún programa numpy real tenga problemas al usar PCG64 con la función de salida XSL-RR. Pero algunas aplicaciones pueden empezar a acercarse (por ejemplo, grandes carreras de aprendizaje por refuerzo distribuido ).

Vamos a sacar ese pin de incremento y abordarlo. Creo que es justo decir que con la función de salida XSL-RR, la siembra de entropía del incremento además del estado no cambia este análisis en particular. Parece que para cualquier par dado de incrementos sembrados de entropía, existe el mismo número de estados que prácticamente chocan. El procedimiento concreto para construir deliberadamente esos estados parece más complicado que golpear en los mismos 58 bits inferiores, pero parece que el número de estados en colisión es el mismo, por lo que los cálculos de probabilidad siguen siendo los mismos. Esto no es intrínseco al esquema PCG en general. La función de salida DXSM parece ser lo suficientemente fuerte como para que cambiar el incremento (incluso con un simple +2 ) parezca ser suficiente para resistir incluso el peor estado de caso para el LCG subyacente (cuando la métrica de distancia da 0 ), al menos hasta donde me he molestado en probar con PractRand.

Quiero terminar reiterando en lo que todos parecemos estar en perfecto acuerdo: PCG64DXSM es una buena idea. Por lo menos, sus propiedades estadísticas mejoradas simplifican los modelos mentales que me siento obligado a documentar, y cualquier cosa que signifique que tengo que escribir menos documentación es buena en mi libro.

Las transmisiones siguen siendo algo relevantes porque el problema solo aparece si tenemos los generadores en la misma transmisión.

Pero, ¿en qué circunstancias tendrían los mismos 58 bits inferiores y estarían en la misma secuencia? ¿Existe un caso de uso en el que esto suceda?

El único caso algo realista que conozco es del que hablamos el año pasado (cuando hablamos de jumped ), y del que hablé en esta publicación a la que vinculé anteriormente.

Las transmisiones siguen siendo algo relevantes porque el problema solo aparece si tenemos los generadores en la misma transmisión.

Desafortunadamente, ese no es el caso de XSL-RR. Consideremos dos instancias PCG64 XSL-RR. Entropía-sembramos los incrementos arbitrariamente y entropía-sembramos uno de los estados. Podemos construir 2**70 estados incorrectos para la otra instancia PCG64 que falla en PractRand de la misma manera que la falla del mismo incremento del mismo estado inferior de 58 bits. Es más complicado de hacer que en el caso del mismo incremento. En lugar de compartir los 58 bits inferiores como primer estado con el incremento diferente, comparte los 58 bits inferiores del estado que está a la distancia 0 (según su medida de distancia LCG) desde la primera instancia, teniendo en cuenta los incrementos. Tengo una prueba constructiva (código Python), pero tengo que irme a la cama ahora y limpiarla mañana.

@rkern , buen punto. Lo admito, no he probado ese escenario para ver cómo le va.

Yo diría que la conclusión clave es que para casi todos los PRNG, con un poco de tiempo y energía se pueden idear semillas patológicas. PCG está en una posición un tanto inusual que tiene a alguien que disfruta trabajar semillas de apariencia plausible para PCG específicamente que tienen una patología hecha a mano (es decir, Sebastiano). Como resultado de ese trabajo, me di la vuelta e hice lo mismo tanto para sus PRNG como para otros de larga data.

Como ya he señalado, esto es falso. No conozco ningún ejemplo de un par de secuencias correlacionadas que no se superpongan, digamos, de xoshiro256 ++ como se puede encontrar fácilmente en PCG.

Los PRNG que mezclan rápidamente todo su estado no tienen este problema. Si puede proporcionar un programa que genere dos secuencias no superpuestas de xoshiro256 ++ que estén correlacionadas, como los ejemplos que publiqué aquí, hágalo.

En cuanto a sus programas de correlación, en 2018, cuando publicó una crítica de PCG en su sitio web que incluía programas que había escrito con varios problemas (como siembra artificial, etc.), escribí una respuesta que contenía un montón de programas similares. para otros PRNG establecidos desde hace mucho tiempo, incluido corrsplitmix2.c que crea flujos correlacionados en SplitMix. No estoy muy seguro de lo que Sebastian quiere decir cuando dice que no se puede hacer, pero admito que no he tenido la oportunidad de mirar de cerca su programa de prueba para ver si el nuevo es sustancialmente diferente de los que él. escribió hace un par de años.

El programa citado anteriormente _elige las corrientes_. Evidentemente, es fácil de escribir.

Pero eso no tiene nada que ver con los problemas de PCG. El programa que proporcioné permite que _user_ elija las secuencias y luego muestra la correlación.

Invito, nuevamente, a @inmeme a proporcionar un programa para SplitMix en el que el usuario puede seleccionar dos flujos diferentes arbitrariamente, un estado inicial arbitrario del primer generador, y _entonces_ el programa encuentra una secuencia correlacionada en el otro generador, como http: / /prng.di.unimi.it/corrpcgnumpy.c lo hace.

Dejar que el usuario elija la secuencia arbitrariamente muestra una forma de correlación mucho más fuerte.

Como ya he señalado, esto es falso. No conozco ningún ejemplo de un par de secuencias correlacionadas que no se superpongan, digamos, de xoshiro256 ++ como se puede encontrar fácilmente en PCG.

Parece que estamos hablando entre nosotros aquí. No dije que pudiera encontrar secuencias correlacionadas que no se superpongan para ningún PRNG, dije que podría generar semillas patológicas en general, como se muestra con los diversos programas de correlación que había escrito anteriormente, y otros como como el programa de demostración de repeticiones malas para Xoshiro ** .

Además, los PRNG que no mezclan todo su estado tienen una larga historia, incluidos XorWow, los generadores en recetas numéricas, etc. El argumento de Sebastiano representa el punto de vista, pero su argumento diría que de alguna manera Marsaglia hizo que XorShift _peor_ en XorWow agregando un Weyl secuencia, ya que crea una gran cantidad de generadores similares.

Parece que estamos hablando entre nosotros aquí. No dije que pudiera encontrar secuencias correlacionadas que no se superpongan para ningún PRNG, dije que podría generar semillas patológicas en general, como se muestra con los diversos programas de correlación que había escrito anteriormente, y otros como como el programa de demostración de repeticiones malas para Xoshiro ** .

Intente mantener la discusión a nivel técnico. "Patológico" no tiene ningún significado matemático.

La forma técnicamente correcta de verificar la autocorrelación es encontrar dos semillas que produzcan dos secuencias que no se superpongan (que no se superpongan durante la prueba; se superpondrán inevitablemente si se va lo suficientemente lejos), intercalarlas y pasarlas a una batería de pruebas.

Si considera dos secuencias que se superponen, se correlacionarán para cada generador, incluso uno criptográfico, simplemente porque las mismas salidas ocurrirán dos veces después de que las secuencias se superpongan, y cualquier prueba razonable lo detectará.

La "siembra patológica" utilizando secuencias superpuestas es una tarea trivial para todo generador (cualquiera que sea el significado de "patológico").

Una vez más, dado que afirma haber encontrado una correlación similar a PCG (en la que las secuencias no se superponen, como muestra la prueba) en otros generadores, ¿puede proporcionar un par de secuencias correlacionadas y no superpuestas, por ejemplo, de xoshiro256 ++ o SFC64? ?

La forma técnicamente correcta de verificar la autocorrelación es encontrar dos semillas que produzcan dos secuencias que no se superpongan (que no se superpongan durante la prueba; se superpondrán inevitablemente si se va lo suficientemente lejos), intercalarlas y pasarlas a una batería de pruebas.

¿Puede señalar la literatura para esta definición de correlación para que yo pueda asegurarme de ser "técnicamente correcto" sobre eso también?

Sebastiano, sigues queriendo que responda a un desafío establecido en tus términos. Lo que está señalando se relaciona con las propiedades intrínsecas de los LCG, donde hay auto-similitud. No encontrará el mismo problema en un PRNG caótico o uno basado en LFSR.

Pero para otros PRNG habrá otros puntos débiles.

Los LFSR tienen cero tierras, malos estados, problemas de peso de martillo, linealidad y, como aprendimos con sus intentos con xoshiro, a veces otras rarezas como problemas extraños con repeticiones.

Los PRNG caóticos tienen el riesgo de ciclos cortos (aunque los que tienen un contador evitan eso, ¡secuencias de Weyl FTW!) Y su sesgo intrínseco.

Si las secuencias se superponen, como escribí, la prueba _siempre_ fallará. No necesita literatura para comprender que una prueba que _siempre falla_ no es una prueba.

Una vez más, dado que afirma haber encontrado una correlación similar a PCG (en la que las secuencias no se superponen, como muestra la prueba) en otros generadores, ¿puede proporcionar un par de secuencias correlacionadas y no superpuestas, por ejemplo, de xoshiro256 ++ o SFC64? ?

Realmente parece estar esquivando la pregunta. Sería muy fácil para usted, siguiendo sus afirmaciones, proporcionar dicha evidencia, si tuviera alguna.

Lo más fácil de hacer ahora sería agregar una PCG64DXSM BitGenerator , la variante de PCG64 con el "multiplicador barato" y una función de salida DXSM más fuerte. Creo que todos están de acuerdo en que eso es un paso adelante de la función de salida XSL-RR que tenemos ahora en nuestra implementación PCG64 , con un mejor rendimiento estadístico sin dañar el rendimiento en tiempo de ejecución. Es una actualización sencilla en el nicho que PCG64 sirve para los BitGenerator s que ofrecemos. Creo que deberíamos agregarlo junto con PCG64 .

Tenga en cuenta que los "multiplicadores baratos" de 64 bits tienen defectos demostrables. Esto se conoce desde hace mucho tiempo:

W. Hörmann y G. Derflinger, un generador portátil de números aleatorios muy adecuado para
método de rechazo, ACM Trans. Matemáticas. Softw. 19 (1993), núm. 4, 489–495.

En general, los multiplicadores más pequeños que la raíz cuadrada del módulo tienen límites inherentes a su puntuación espectral f₂.

El límite se puede superar fácilmente mediante el uso de un multiplicador de 65 bits, que el compilador transformará simplemente en una operación adicional de "agregar", probablemente sin cambiar la velocidad del generador.

Guy Steele y yo trabajamos un poco en el tema y publicamos tablas de puntuaciones espectrales para multiplicadores baratos de varios tamaños: https://arxiv.org/pdf/2001.05304.pdf . Cuanto más grande, mejor, pero hay una brecha demostrable de 64 a 65 bits (para un LCG con 128 bits de estado).

Por ejemplo, de la Tabla 7 del documento obtiene 0x1d605bbb58c8abbfd, que tiene una puntuación f₂ de 0,9919. Ningún multiplicador de 64 bits puede ir más allá de 0.9306 (Teorema 4.1 en el artículo).

Después de la mezcla y todo, la mejora en la puntuación f₂ puede pasar completamente desapercibida desde un punto de vista estadístico. Pero teniendo en cuenta la gran mejora que obtiene para la dimensión más relevante con solo una operación de adición adicional, creo (bueno, creemos, o no hubiéramos escrito el artículo) que vale la pena el esfuerzo.

Sebastiano, sigues queriendo que responda a un desafío establecido en tus términos. Lo que está señalando se relaciona con las propiedades intrínsecas de los LCG, donde hay auto-similitud. No encontrará el mismo problema en un PRNG caótico o uno basado en LFSR.

¡Vaya, me tomó un tiempo llegar allí!

Los LFSR tienen cero tierras, malos estados, problemas de peso de martillo, linealidad y, como aprendimos con sus intentos con xoshiro, a veces otras rarezas como problemas extraños con repeticiones.

Estoy totalmente de acuerdo, por eso tienes que revolverlos. Tenga en cuenta que los LFSR y los generadores lineales F₂ son cosas diferentes; relacionados, pero diferentes.

"Problemas extraños con las repeticiones" es, como de costumbre, un término no técnico que no puedo comentar.

Los PRNG caóticos tienen el riesgo de ciclos cortos (aunque los que tienen un contador evitan eso, ¡secuencias de Weyl FTW!) Y su sesgo intrínseco.

[Actualización: me perdí la contraobservación entre paréntesis, así que estoy actualizando mi comentario].

Sí, SFC64 no tiene tales problemas (usa un contador), por lo que no generalizaría a toda la categoría. Hay generadores caóticos cuidadosamente diseñados que tienen una larga duración de ciclo más corta y demostrable.

"Problemas extraños con las repeticiones" es, como de costumbre, un término no técnico que no puedo comentar.

Parece extraño no poder comentar porque no utilicé la jerga correcta: ejecute este programa y luego siéntase libre de explicarme la mejor manera de describir el problema en la jerga adecuada y luego proporcionar cualquier comentario que parezca apropiado. Me hubiera imaginado que tú y David Blackman habrían discutido el tema cuando salió a la luz por primera vez porque mantuve correspondencia con él al respecto, pero nunca te he visto comentarlo.

La discusión de los PRNG que no están en numpy está fuera de tema. Utilice sus propios foros para continuar esa discusión. Gracias.

@rkern : eso parece un poco estricto como criterio. Si hay una deficiencia en la implementación de Numpy que no es compartida por otras implementaciones, parece razonable discutirlo.

Puedo confirmar que el intercambio al que me refiero no me está ayudando a tomar las decisiones que tenemos frente a nosotros en este tema. Hasta que avancemos en eso, necesito que la conversación se mantenga enfocada.

Creo que es útil comprender el contexto más amplio. Sebastiano tiene algo sobre PCG y lo ha criticado durante años. Creo que en estos días algunas personas pueden mirarnos a los dos y poner los ojos en blanco y decir "los dos son tan malos el uno como el otro" porque también he criticado sus PRNG, pero en realidad solo lo hice después de que él estuvo haciendo afirmaciones de que Estaba tratando de esconder algo sin hablar nunca de sus cosas (cuando en realidad, simplemente no tenía tiempo / ganas, de hecho, asumí que estaban bien).

Sin embargo, sus críticas son útiles y estoy encantado de que haya elegido pasar gran parte de su vida pensando en mi trabajo, pero es importante darse cuenta de que sus pruebas son de naturaleza contradictoria. Él usa el conocimiento de la estructura de PCG para idear arreglos que se puedan alimentar a los probadores de RNG y fallar las pruebas.

Dado lo aterrador que puede parecer, parece razonable demostrar que un enfoque adversario similar también haría tropezar a muchos otros generadores y que muchas de las preocupaciones que plantea sobre PCG también se aplicarían a otros esquemas de generación, como he observado sobre los esquemas de generación. como XorWow, y suelo usar SplitMix como ejemplo, como haré a continuación. (Ninguno de nosotros está particularmente involucrado en SplitMix de una forma u otra, me imagino).

Podemos ser muy atemorizantes con SplitMix, por ejemplo, y mostrar que con la constante de flujo predeterminada, si miramos cada salida 35185, falla en un conjunto de pruebas PRNG. ¡Oh no! Esto se debe a que internamente está incrementando un contador (¡secuencia de Weyl!) En 0x9e3779b97f4a7c15 (basado en φ, la proporción áurea), pero 35185 * 0x9e3779b97f4a7c15 = 0x86a100000c480245 , que solo tiene Conjunto de 14 bits y una gran franja de nada en el medio. O si miramos cada salida 360998717-th, llegamos a ser equivalentes a una adición al estado interno de 0x48620000800401 , que son solo 8 bits agregados y nuevamente algo difícil para que su función de salida enmascare completamente .

Podríamos seguir alardeando sobre SplitMix y decir mira, ¿qué pasa si tengo dos transmisiones, una con la constante aditiva 0x9e3779b97f4a7c15 y otra con 0xdaa66d2c7ddf743f , veríamos fallas si introdujimos esto en una suite de pruebas PRNG !!! Pero eso se debe a que el segundo está diseñado para ser solo 3 veces el otro.

Y finalmente, si alguien dice "Voy a darte ambas transmisiones, ¡haz algo aterrador con eso!", Y digamos que las suyas se basaron en π ( 0x243f6a8885a308d3 ) y _e_ ( 0xb7e151628aed2a6b ), podemos decir, claro, tengamos un poco más de alarmismo y tomemos cada elemento 6561221343-th del flujo Pi y mezclemos con cada elemento 6663276199-th del flujo E y bajo y belhold, producen dos elementos idénticos secuencias. Y, peor aún, continúo mostrando que por cada salto en la secuencia a, hay un salto coincidente en la secuencia b para dar el mismo resultado, ¡así que en realidad hay 2 ^ 64 formas en las que se correlacionan! (Y podemos hacer esto para dos transmisiones, no había nada especial en π y _e_).

Volviendo a PCG, la prueba de Sebastiano se basa en que los dos generadores PCG64 XSH RR estén alineados con precisión para que las salidas coincidentes se intercalen. Si solo avanzamos una de las PRNG en una pequeña cantidad, rompiendo la alineación perfecta solo un poco, se vuelve mucho más difícil detectar algo sospechoso.

Una prueba de confrontación similar en la otra dirección (poniendo una carga sobre Sebastiano) sería proporcionar a las salidas de PCG64 XSH RR que cumplan con su afirmación de que están correlacionadas, pero no le decimos exactamente cómo están alineadas (simplemente en el barrio general derecho). Su trabajo sería encontrar la alineación para mostrar que están correlacionados.

En general, no creo que sea un problema en la práctica con los incendios urgentes que deben apagarse, pero por otro lado, la versión DXSM es mejor, ya que se escribió el año pasado para mitigar precisamente este tipo de problemas, y estaría encantado de que se cambie a él.

PD: puedes crear constantes aditivas mágicas de Weyl a partir de tu número real favorito usando este código:

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

Eso es Mathematica, dejaré la versión de Python como ejercicio.

Así es como construyo las colisiones de bits inferiores para diferentes incrementos.

Resultados con PCG64 XSL-RR y una colisión de 58 bits inferior

❯ ./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

Resultados con PCG64 DXSM y una colisión de 64 bits inferior (para provocar problemas más rápido, aunque no veo ninguno)

❯ ./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 , gracias por compartir tu código. Sería instructivo agregar una opción para agregar un sesgo para alimentar la salida intercalada en el probador que no está perfectamente alineada. Eso es algo que exploré un poco.

Sí, lo he hecho de manera informal insertando bg0.advance(N) para varios N antes del return final. Usé la colisión inferior de 64 bits para asegurarme de ver algo. Pequeños cambios como 16 no cambian mucho la falla, pero incluso cambios modestos como 128 prolongan la falla a 32 GiB.

Parece que deberíamos agregar PCG64 DXSM como un generador de bits opcional y eventualmente convertirlo en el predeterminado. ¿Tenemos una implementación?

Creo que es útil comprender el contexto más amplio. Sebastiano tiene algo sobre PCG y lo ha criticado durante años. Creo que en estos días algunas personas pueden mirarnos a los dos y poner los ojos en blanco y decir "los dos son tan malos el uno como el otro" porque también he criticado sus PRNG, pero en realidad solo lo hice después de que él estuvo haciendo afirmaciones de que Estaba tratando de esconder algo sin hablar nunca de sus cosas (cuando en realidad, simplemente no tenía tiempo / ganas, de hecho, asumí que estaban bien).

Creo que estas consideraciones son totalmente inapropiadas.

Insistir en atacar el trabajo de otras personas (por ejemplo, SplitMix) sin ningún mérito y sin ninguna evidencia no va a mejorar el desastre de PCG, o el generador de Numpy, mejor. En su lugar, un mejor multiplicador o un codificador mejor diseñado podrían ayudar.

Todavía estoy esperando una prueba que muestre la correlación en SplitMix cuando el usuario pueda elegir las transmisiones. Para que quede claro, para el generador de Numpy probé una declaración de la forma

∀c ∀d ∀x ∃y correlacionado

donde c, d son incrementos ("flujos"), y x, y son estados iniciales. De hecho, hay 2 ^ 72 años. Es decir, no importa cómo elija c, d y x, hay 2 ^ 72 y que muestran correlación.

El supuesto código correspondiente que proporcionó para SplitMix muestra que

∃c ∃d ∃x ∃y correlacionado

Es decir, eligiendo en forma contraria c, d, xey puede mostrar correlación.

La diferencia de fuerza en las dos declaraciones es bastante asombrosa. Intentar combinar las dos afirmaciones es incorrecto.

@vigna ya le han advertido dos veces sobre nuestro código de conducta, por @mattip y @rkern. No está bien usar un lenguaje como "criticar el trabajo de otras personas" y "tratar de combinar las dos declaraciones es puro FUD". Considere esta su última advertencia. Por favor, cambie su tono o lo prohibiremos. Los argumentos técnicos aún son bienvenidos, cualquier otra cosa no lo es en este momento.

Modifiqué el mensaje reemplazando esas expresiones por unas neutrales. Sigo pensando que atacar personalmente a otro participante en la discusión ("Sebastiano tiene algo con PCG y lo ha criticado durante años") es completamente inapropiado. Estoy muy sorprendido de que no sea para ti.

Por tercera y última vez, la discusión sobre SplitMix, en cualquier dirección, no me está ayudando en lo más mínimo. Puedo entender por qué cree que proporciona el contexto necesario, o que se siente obligado a responder al otro, pero confíe en que le estoy diciendo la verdad, que no me proporciona ninguna información que me ayude a tomar una decisión aquí. Ambos tienen sus propios sitios web. Usalos, usalos a ellos.

Modifiqué el mensaje reemplazando esas expresiones por unas neutrales.

Gracias.

Sigo pensando que atacar personalmente a otro participante en la discusión ("Sebastiano tiene algo con PCG y lo ha criticado durante años") es completamente inapropiado. Estoy muy sorprendido de que no sea para ti.

Preferiría no ver eso tampoco. Sin embargo, el tono de ese mensaje no es tan malo.

Apreciaría mucho que ambos pudieran ceñirse a declaraciones fácticas constructivas @vigna y @imneme.

OKAY. Empecemos desde cero: desea un generador con algún tipo de flujo basado en LCG con módulo de potencia de 2 para mayor comodidad y velocidad. La literatura sugiere que basar los flujos en las constantes aditivas de LCG puede generar problemas (como sucede ahora), pero supongamos que eso es lo que desea.

¿Por qué no tomar un LCG con 128 bits de estado y un buen multiplicador (al menos 65 bits) y perturbar los bits superiores usando la función de mezcla de SplitMix, que ha sido ampliamente probada en diferentes aplicaciones (hash, PRNG, etc.)? dando excelentes resultados?

Estoy bastante seguro de que la diferencia de velocidad será marginal. Y tiene alguna garantía (estadística) de que el resultado dependerá de todos los bits, que es el problema aquí.

Esto me parece más un enfoque de "apoyarse en el hombro de gigantes" que la creación manual de funciones de mezcla en un generador que tiene problemas de autocorrelación.

@imneme Lo que podría usar es una publicación de blog sobre DXSM que sea más fácil de vincular que este comentario de anuncio en el antiguo mega problema. No tiene que ser mucho más de lo que está en ese comentario, pero sería bueno incluir el estado actual de las pruebas que mencionaste aquí. Si quisiera resumir parte de la discusión del mega tema que condujo a ese desarrollo, sería útil, sin duda, pero no del todo necesario.

@vigna

¿Por qué no tomar un LCG con 128 bits de estado y un buen multiplicador (al menos 65 bits) y perturbar los bits superiores usando la función de mezcla de SplitMix, que ha sido ampliamente probada en diferentes aplicaciones (hash, PRNG, etc.)? dando excelentes resultados?

Me disculpo si esto suena sarcástico (aunque ciertamente será puntual), pero también es sincero: espero ver la implementación, el análisis, los puntos de referencia y los resultados de PractRand en su sitio web o en arXiv. Somos profesionales (razonablemente informados) aquí, no investigadores de PRNG, y no estamos particularmente bien equipados para llevar a cabo esta sugerencia. Puedo ver el sentido de esto, pero dadas las otras limitaciones de mi tiempo personal, no tengo ganas de gastar el esfuerzo de llevar esto de la sugerencia a una implementación y análisis. Si está dirigiendo esta sugerencia a Numpy, necesitamos investigadores de PRNG para hacer ese trabajo. Si realmente está dirigiendo esta sugerencia a otra persona, use su sitio web.

La arquitectura aleatoria Generator -> BitGenerator -> SeedSequence en NumPy está destinada a ser enchufable. Creo que hemos llegado al punto de la discusión en el que necesitamos que alguien abra un PR para un BitGenerator, para que podamos comparar sus atributos prácticos con los que se encuentran actualmente en NumPy. Una vez que se convierte en parte del proyecto, podemos continuar probándolo y podemos decidir convertirlo en el predeterminado. Esa decisión, espero, se basará en

  • falta de sesgo (y otros criterios? cedo a los expertos)
  • actuación
  • probabilidad de varios tipos de colisión de flujo a través de las interfaces normativas que promovemos: usando BitGenerator.spawn y SeedSequence .

Personalmente, esta discusión me perdió cuando evitó discutir los méritos de BitGenerators través del código que usa la interfaz spawn . Hay una razón por la que lo promocionamos como la mejor práctica, y espero que la discusión del futuro PR se centre en las mejores prácticas para los usuarios de NumPy .

Quizás una de las conclusiones aquí podría ser que solo deberíamos permitir spawn como método, ya que usar jumped o advance puede ser contrario a las buenas prácticas. Un nuevo número o NEP centrado en esto también podría ser productivo.

@mattip La colisión de cumpleaños de bits inferiores que observó @vigna también afecta nuestra interfaz SeedSequence.spawn() . Tenga la seguridad de que cualquier parte de la discusión en la que participé es relevante para el uso adecuado de nuestras API.

Solo requiere agregar alrededor de 8 líneas a pcg64.c con algunos bloques #ifdef para usar el enfoque preferido de @rkern de generadores completamente separados. El pyx / pxd sería idéntico a la clase PCG64 solo se construyó con las definiciones correctas (PCG_DXSM = 1) y una cadena de documentación actualizada.

Probablemente sería más explícito al respecto, especialmente para las matemáticas emuladas de 128 bits para aquellas plataformas que lo necesitan.

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

Me pareció más fácil que eso, ya que utiliza un multiplicador de 64 bits "barato". Puede simplemente agregar un nuevo mezclador de salida (que es invariante) y luego ifdef alrededor de la línea final del generador aleatorio que toma la salida del LCG y luego aplica el mezclador.

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

Si uno quisiera, incluso podría agregar el Murmur Hash 3 en este punto, si así lo deseara.

¿Se van a compilar las declaraciones if ? No creo que queramos múltiples de ellos dentro de circuitos calientes.

Nuevamente, esto se reduce a la diferencia de propósito entre randomgen y numpy . Tiene sentido en randomgen hacer familias parametrizadas, pero en numpy , no creo que sea una buena idea entrelazar las implementaciones de un BitGenerator heredado del BitGenerator . Si tenemos que hacer algún mantenimiento o refactorización para el rendimiento de uno u otro, solo hará que ese esfuerzo sea peor en lugar de mejorarlo.

De acuerdo con Robert aquí. No tengo reparos en poner un nuevo generador de bits en la versión 1.19.0, no cambiaría ningún comportamiento actual.

@bashtage Además, tenga en cuenta que pcg_cm_random_r() usa el estado pre-iterado para generar en lugar del estado post-iterado , por lo que no será tan simple mantener la misma ruta de código con #ifdef o if cambia.

¿Se van a compilar las declaraciones if ? No creo que queramos múltiples de ellos dentro de circuitos calientes.

No, en NumPy el if else debería convertirse en algo como

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

Estos deben definirse por separado en la versión uint128 y en la versión alternativa para manejar el cambio manual del uint128 a alto y bajo.

@bashtage Además, tenga en cuenta que pcg_cm_random_r() usa el estado pre-iterado para generar en lugar del estado post-iterado , por lo que no será tan simple mantener la misma ruta de código con #ifdef o if cambia.

Hmm, probé contra la implementación de referencia de @imneme y obtuve una coincidencia del 100% en 1000 valores usando 2 semillas distintas:

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

AFAICT (y puedo estar equivocado)

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

y

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

significa que la ruta uint_128 siempre usa un multiplicador barato.

No estoy seguro de lo que intentas decir allí.

No está claro qué es el DXSM de PCG64 canónico. En cualquier caso, la función de salida utiliza solo operaciones de 64 bits. La versión que tiene utiliza un multiplicador de 64 bits en otra ubicación para ser aún más rápido y devuelve pre, en lugar de post. setseq_dxsm_128_64 parece la extensión natural del PCG64 existente y solo cambia la función de salida.

Oh ya veo. No, usaste un generador de C ++ diferente al que implementé en C. Estoy implementado el equivalente de cm_setseq_dxsm_128_64 que usa el "multiplicador barato" en la iteración LCG, no setseq_dxsm_128_64 que todavía usa el gran multiplicador en la iteración LCG. El "multiplicador barato" se reutiliza dentro de la función de salida DXSM, pero ese es un eje de diseño ortogonal.

¿Por qué no preferir setseq_dxsm_128_64?

@imneme dijo que eventualmente cambiaría el pcg64 oficial en la versión C ++ para que apunte a cm_setseq_dxsm_128_64 , no a setseq_dxsm_128_64 . El multiplicador barato compensa parte del costo adicional de DXSM en comparación con XSL-RR. Y creo que es la variante que pasó un par de meses probando.

La salida del estado iterado previamente también es parte del aumento de rendimiento .

Aquí hay algunos horarios:

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)

Todo en Ubuntu-20.04, compilador predeterminado.

6% más lento. Me parece una pequeña diferencia. Todos los tiempos en NumPy / randomgen están bastante lejos de lo que puede obtener en un bucle cerrado real de código nativo.

Comparar

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)

a lo que se ha publicado desde código C (¿150% más lento?).

Por supuesto, en el contexto numpy , no importa mucho. Importa más en el contexto de C ++, que impulsó la asignación de meses de clúster a las pruebas y la nominación como el futuro predeterminado pcg64 en el código oficial de C ++. Esas son las motivaciones próximas para numpy , en mi opinión.

La diferencia entre stock PCG64 y mi PCG64DXSM en mi rama ("multiplicador barato", función de salida DXSM, salida de estado pre-iterado, ruta de código separada):

[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

Sigo diciendo que son solo unos pocos (más de los que tenía) #ifdefs entre los dos, incluso con una implementación especializada para MSVC. Además, los usuarios de RSN MS podrán utilizar clang # 13816 👍.

¿Estamos discutiendo sobre la duplicación de código? Preferiría tener implementaciones inconexas que preocuparme por unas pocas líneas de código ofuscadas con #ifdefs :)

En su mayor parte fue solo una broma, aunque resaltó la necesidad de una declaración absolutamente clara que defina "PCG 2.0" (preferiblemente en algún lugar que no sea los problemas de GitHub de NumPy).

Gracias, @rkern et al.

@imneme Lo que podría usar es una publicación de blog sobre DXSM que sea más fácil de vincular que este comentario de anuncio en el antiguo mega problema. No tiene que ser mucho más de lo que está en ese comentario, pero sería bueno incluir el estado actual de las pruebas que mencionaste aquí. Si quisiera resumir parte de la discusión del mega tema que condujo a ese desarrollo, sería útil, sin duda, pero no del todo necesario.

¿Tiene un marco de tiempo en mente? He tenido la intención de hacer esto durante algún tiempo y otras cosas lo han empujado, por lo que tener una fecha límite propuesta sería un motivador útil para mí. ¿Quizás una semana? ¿Dos?

@rkern también citó a @vigna , quien escribió:

¿Por qué no tomar un LCG con 128 bits de estado y un buen multiplicador (al menos 65 bits) y perturbar los bits superiores usando la función de mezcla de SplitMix, que ha sido ampliamente probada en diferentes aplicaciones (hash, PRNG, etc.)? dando excelentes resultados?

FWIW, este enfoque se discutió en el documento original de PCG, usando _FastHash_ como la función hash estándar, que es una función hash multiply-xorshift muy similar. En mis pruebas, no fue tan rápido como otras permutaciones, pero fue de alta calidad. Sebastiano también mencionó esta idea en su crítica de 2018 a PCG y la analizo en esta sección de mi respuesta a esa crítica.

En la versión original de su crítica de PCG, termina escribiendo su propia variante de PCG, que citaré a continuación:

        #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);
        }

Desde entonces, actualizó el código en su crítica a una versión aún más rápida que usa un multiplicador más barato y reduce la constante aditiva a solo 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);
        }

Mi problema con estas dos variantes es que la permutación es invertible porque el estado truncado (la mitad superior) está permutado / codificado; puede ejecutarlo al revés y descifrar la codificación dejándolo con un LCG simple truncado con todos los defectos inherentes allí. Mi preferencia es permutar / escalar todo el estado y luego generar un truncamiento de eso. (Por supuesto, permutar / codificar menos bits será la forma más rápida; como de costumbre, hay compensaciones. Las personas razonables pueden no estar de acuerdo con lo que es importante).

Pero su trabajo al hacer su propia variante de PCG proporcionó una inspiración muy útil para la permutación DXSM cuando escribí eso el año pasado.

@charris ¿Cuál es su apetito por hacer que la implementación PCG64DXSM esté disponible (pero aún no predeterminada) en 1.19.0? ¿Qué es esa línea de tiempo? Veo que ya hemos lanzado 1.19.0rc2, que no es _ genial_ para introducir una nueva característica. Una vez más, no estoy entusiasmado con este tema. Me inclinaría por lanzar 1.19.0 simplemente documentando nuestra política sobre cambios en default_rng() e introduciendo cosas nuevas en 1.20.0.

@rkern El rc final debe estar disponible durante> dos semanas, por lo que estamos buscando un lanzamiento en la segunda mitad de junio. Estoy a favor de poner el PCG64DXSM como una opción opcional si facilita las pruebas, no lo considero una característica nueva, más bien un accesorio nuevo. Y a veces ayuda a mover las cosas para tener un código de trabajo real disponible. NumPy es un creador de tendencias :)

EDITAR: Suponiendo, por supuesto, que no hay grandes problemas con el nuevo código, y no parece que los haya. Tampoco estoy demasiado preocupado por los problemas con PCG64, parece poco probable que alguien tenga problemas al usar nuestros procedimientos recomendados.

@imneme Una semana sería genial. Dos semanas estaría bien. ¡Gracias!

Hay una pregunta que me he estado haciendo a mí mismo que está algo fuera de tema. Queremos que nuestros generadores de bits produzcan bits aleatorios, pero AFAICT, la mayoría de las pruebas implican números enteros. ¿Qué tan bien funcionan las pruebas existentes en la prueba real de los bits? Si alguien más familiarizado con el área pudiera responder a esa pregunta, estaría muy agradecido.

Lo que se prueba es el flujo de bits. Le decimos al software de prueba el tamaño de palabra natural del PRNG que estamos generando, pero solo para que pueda hacer los mejores plegados de los bits para provocar errores de manera más eficiente que tienden a surgir en los bits bajos o altos del palabra en mal PRNG. El software que todos tendemos a usar en estos días es PractRand, y sus pruebas están ligeramente documentadas aquí . El mejor artículo para leer es probablemente el de TestU01 , el conjunto de pruebas estándar de oro anterior. Su Guía del usuario tiene más detalles sobre las pruebas.

Me disculpo si esto suena sarcástico (aunque ciertamente será puntual), pero también es sincero: espero ver la implementación, el análisis, los puntos de referencia y los resultados de PractRand en su sitio web o en arXiv. Somos profesionales (razonablemente informados) aquí, no investigadores de PRNG, y no estamos particularmente bien equipados para llevar a cabo esta sugerencia. Puedo ver el sentido de esto, pero dadas las otras limitaciones de mi tiempo personal, no tengo ganas de gastar el esfuerzo de llevar esto de la sugerencia a una implementación y análisis. Si está dirigiendo esta sugerencia a Numpy, necesitamos investigadores de PRNG para hacer ese trabajo. Si realmente está dirigiendo esta sugerencia a otra persona, use su sitio web.

Puedo entender perfectamente tu punto de vista. El código y el benchmark se encuentran en la parte inferior de la página comentando los problemas de PCG (http://prng.di.unimi.it/pcg.php) desde hace un par de años con el nombre LCG128Mix. Toma 2.16ns en mi hardware, una CPU Intel (R) Core (TM) i7-7700 @ 3.60GHz, con gcc 9.2.1 y -fno-move-loop-invariants -fno-unroll-loops.

El código es muy simple: combina un LCG estándar con una función de mezcla estándar (el finalizador MurmurHash3 mejorado de Stafford). Lo modifiqué ligeramente para tener una constante programable:

    #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);
    }

Como expliqué antes, la constante de 65 bits es mucho mejor que cualquier constante de 64 bits como multiplicador, debido a problemas teóricos de multiplicadores más pequeños que la raíz cuadrada del módulo.

Si está interesado en un diseño más basado en principios, ejecutaré las pruebas PractRand. Sin embargo, debe tener en cuenta que esta función de mezcla produjo un generador excelente, SplitMix, incluso con un generador subyacente mucho más débil (solo era aditivo) y con un estado más pequeño (64 bits). Así que va a ser simplemente "mejor" que SplitMix, que supera PractRand en 32TB.

Y el generador subyacente es un LCG, por lo que tiene todas las campanas y silbidos habituales de los 60: saltos, distancias, etc. Pero también tiene una garantía estadística de que cada bit del resultado depende de cada bit de los 64 bits más altos de estado.

Si tiene en mente evaluaciones comparativas con otros generadores o si usa otros compiladores, hágamelo saber.

Pero, por favor, de la misma manera sincera: solo si está realmente interesado en considerar un diseño de "apoyarse en los hombros de gigantes" utilizando únicamente componentes estándar. También existen limitaciones personales en mi tiempo, y estoy feliz de contribuir, pero me gustaría evitar gastar tiempo en un generador que no tiene ninguna posibilidad de ser considerado.

Por cierto, para dar una medida más tangible de cómo mejorar la calidad de los multiplicadores involucrados, calculé las puntuaciones espectrales, de f₂ a f₈, del multiplicador de 64 bits actual utilizado por PCG DXS y alguna alternativa.

Las puntuaciones espectrales son la forma estándar de juzgar la bondad de un multiplicador. 0 es malo, 1 es excelente. Cada puntuación describe qué tan bien están distribuidos los pares, triples, 4-tuplas, etc. en la salida.

Estos siete números se pueden resumir en la medida clásica, la mínima, o una medida ponderada (la primera puntuación, más la segunda dividida por dos, etc., normalizada), para hacer que las primeras puntuaciones sean más importantes, como sugiere Knuth en TAoCP. , y estos son el mínimo y la medida ponderada para el multiplicador actual:

0xda942042e4dd58b  0.633  0.778

Hay constantes de 64 bits mucho mejores que eso:

0xff37f1f758180525  0.761  0.875

Si pasa a 65 bits, esencialmente a la misma velocidad (al menos, para LCG128Mix es la misma velocidad) obtendrá una medida mejor ponderada:

0x1d605bbb58c8abbfd  0.761  0.899

La razón es que los multiplicadores de 64 bits tienen un límite intrínseco a su puntuación f₂ (≤0,93), que, como señaló Knuth, es la más relevante:

0xda942042e4dd58b5  0.795
0xff37f1f758180525  0.928
0x1d605bbb58c8abbfd  0.992

Por tanto, el primer multiplicador tiene una puntuación f₂ mediocre. El segundo multiplicador se acerca mucho al óptimo para un multiplicador de 64 bits. El multiplicador de 65 bits no tiene estas limitaciones y tiene una puntuación muy cercana a 1, la mejor posible en general.

Para completar, aquí están todas las puntuaciones:

 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

Puede volver a calcular estos puntajes o buscar su propio multiplicador con el código que Guy Steele y yo distribuimos: https://github.com/vigna/CPRNG . Los mejores multiplicadores se toman del documento asociado.

PCG probablemente será un buen prng predeterminado para numpy, pero no creo que resistirá la prueba del tiempo, ya que hay formas más prometedoras, pero menos probadas de hacer esto. Propongo uno de los siguientes.

El medio caótico SFC64 es uno de los generadores estadísticamente sólidos más rápidos con un período mínimo razonablemente grande. La SFC64 no tiene funciones de salto, pero puede _sin sobrecarga de velocidad ampliarse para admitir 2 ^ 63 flujos únicos garantizados_. Simplemente agregue una secuencia de Weyl con una constante aditiva k elegida por el usuario (debe ser impar), en lugar de simplemente incrementar el contador en uno. Cada k impar produce un período completo único. Requiere 64 bits de estado adicionales para mantener 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 estado de 320 bits a veces es indeseable, así que he intentado reducirlo para usar 256 bits nuevamente. Tenga en cuenta también la función de salida modificada, que utiliza mejor la secuencia de Weyl para la mezcla de bits. Utiliza un estado caótico / estructurado de 128/128 bits, que parece lograr un buen equilibrio:
/ EDITAR: se eliminó rotl64 () de la función de salida + limpieza, 6 de agosto:

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;
}

Esto ha superado actualmente los 4 TB en las pruebas de PractRand sin anomalías, y hasta ahora ejecuté brevemente la prueba de peso Hamming de Vigna sin problemas (aunque, pasar estas pruebas no es garantía de una salida aleatoria casi verdadera, sino una prueba de si el prng es defectuoso o no). ).

Nota: se supone que es estadísticamente una ventaja usar una constante de Weyl aleatoria (única) con aproximadamente el 50% de los bits establecidos, pero solo más pruebas o análisis revelarán cuán significativo es esto.

/ Ediciones: limpiezas.

@ tylo-work SFC64 ya está en NumPy, junto con Philox, se trata del generador predeterminado.

Ok, no sabía exactamente cuáles se implementaron, así que esto solo se trata de seleccionar el más adecuado en general de entre ellos Muy bien, y gracias por aclararlo.

Intentaré probar mi generador propuesto ampliamente para ver cómo se compara con otros, y hasta ahora se ve muy bien en cuanto a velocidad, calidad de salida, simplicidad / tamaño / portabilidad y uso paralelo masivo. Pero estaría feliz si otros también lo probaran.

No creo que estemos reabriendo la discusión sobre el PRNG predeterminado desde cero. Tenemos un problema muy específico con nuestro PRNG actual y estamos buscando variantes disponibles, estrechamente relacionadas que abordan ese problema específico. Una de nuestras preocupaciones es que el PRNG predeterminado actual expone ciertas características del PRNG, como la capacidad de salto, que la variante que lo reemplaza aún debe exponer. SFC64 (ya sea nuestro o tuyo) no tiene esa característica.

Es posible que @bashtage esté dispuesto a aceptar un PR para randomgen para agregar sus variantes de Weyl-stream de SFC64.

@ tylo-work Si está interesado en la ejecución en paralelo, es posible que desee ver la implementación de SeedSequence de NumPy.

No creo que estemos reabriendo la discusión sobre el PRNG predeterminado desde cero. Tenemos un problema muy específico con nuestro PRNG actual y estamos buscando variantes disponibles, estrechamente relacionadas que abordan ese problema específico.

Suponiendo que desee algo similar a PCG-DXS, hay más mejoras que puede hacer con mejores constantes (y una desaceleración muy marginal). Por ejemplo, PCG-DXS pronto fallará dos tipos distintos de pruebas en dos subsecuencias correlacionadas intercaladas con los mismos 112 bits inferiores de estado:

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

Tenga en cuenta que estamos hablando de secuencias correlacionadas ≈65536, nada que temer.

Pero puede mejorar el generador eligiendo un mejor multiplicador, como 0x1d605bbb58c8abbfd, y un mejor mezclador, como 0x9e3779b97f4a7c15. El primer número es un multiplicador de 65 bits que tiene puntuaciones espectrales mucho mejores. El segundo número es solo la proporción áurea en una representación de punto fijo de 64 bits, y se sabe que tiene buenas propiedades de mezcla (consulte Knuth TAoCP sobre hash multiplicativo); por ejemplo, la biblioteca Eclipse Collections lo utiliza para mezclar códigos hash.

Como resultado, solo falla FPF para la misma cantidad de datos:

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

De hecho, si vamos más allá a 2TB, PCG-DXS falla _tres_ tipos de pruebas para las mismas subsecuencias intercaladas y correlacionadas:

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

mientras que PCG65-DXSϕ todavía falla solo 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

Tarde o temprano, por supuesto, también PCG65-DXSϕ fallará en Gap y TMFn. Pero necesita ver mucha más salida que con PCG-DXS.

Este es el código completo para PCG65-DXSϕ, que es solo PCG-DXS con mejores 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);
}

La desaceleración marginal se debe a una instrucción de suma (causada por el multiplicador de 65 bits) y a tener dos constantes de 64 bits para cargar.

No estoy respaldando generadores de este tipo en general, pero PCG65-DXSϕ es considerablemente mejor que PCG-DXS para ocultar la correlación.

@Vigna , FYI, también hice algunas pruebas de intercalado y noté que xoshiro256 ** falló bastante rápido al crear 128 flujos intercalados o más. Con 256 falló rápidamente. El objetivo de la prueba es comprobar qué tan bien se comportan los PRNG cuando se inicializó cada flujo con algunas dependencias lineales. Básicamente, el estado se inicializa en s[0]=s[1]=s[2]=s[3] = k1 + stream*k2 . Luego se omiten 12 salidas, que es básicamente cómo se inicializa sfc64.

Me doy cuenta de que esta no es la inicialización recomendada para xoshiro, pero sigue siendo interesante, y un poco preocupante, que las pruebas parecían estar bien para xoshiro con pocas secuencias entrelazadas, pero fallaron con muchas.

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

También intenté debilitar la inicialización de SFC64 y TYLO64 para omitir solo 2 salidas, sin embargo, todavía parecían estar bien.
En rendimiento: xoshiro256 ** funciona un 33% más lento en mi máquina que los otros dos. TYLO64 solo actualiza 196 bits de variables de estado. Aquí está el programa de prueba:

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;
}

Incluiré un código de encabezado relevante:

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 Aprecio el análisis, pero realmente necesito este tema para mantenerme enfocado. Si desea continuar con esa línea de discusión, le animo a que publique su trabajo en su propio repositorio de Github y haga una publicación más aquí invitando a la gente a hacerlo. Todos los demás, respondan allí. Gracias por su cooperación.

@imneme @rkern El tiempo se agota para la versión 1.19.

@rkern Parece que PCG64DXSM no llegará a 1.19.0, lo lanzaré este fin de semana. Si pudiera escribir la nota sobre nuestra política de cambios / los próximos cambios que mencionó anteriormente, se lo agradecería.

Lo siento, he estado lidiando con otros asuntos no relacionados. Según nuestra discusión, no creo que una pequeña demora sea un gran problema, ya que PCG64DXSM se planeó como una opción alternativa, no como un nuevo valor predeterminado (al menos por ahora).

Ahora que se está iniciando 1.20, ¿es hora de revisar esto y pasar a DXSM?

Todavía tendríamos algo de tiempo para hacer el movimiento antes de ramificarnos, pero puede ser bueno comenzar dentro de la próxima semana. @bashtage Supongo que tienes el PCG64DXSM listo para usar y esto principalmente necesita la decisión de activar el interruptor en la transmisión predeterminada.

Por lo que ve, parecía que deberíamos hacer esto por 1,20 si lo tenemos disponible.

IIRC, hemos estado esperando una referencia que pudiera vincularse. Pero si el número aleatorio la gente está contenta con el cambio, deberíamos usarlo. ¿Necesitamos algún código especial para Windows?

Es solo una constante diferente y una función de codificación diferente. Nada más novedoso que lo que @rkern escribió para la implementación original de PCG64 en Windows. Creo que la decisión fue tener un PCG64DXSM completamente independiente en lugar de compartir algún código (por rendimiento).

Probablemente tendría sentido comenzar desde rkern .

Dije que escribiría una publicación de blog al respecto, lo cual creo que @rkern quería, pero he estado atendiendo otros asuntos y aún no ha sucedido (lo siento). Mientras tanto, la permutación DXSM se ha ido desgastando bajo prueba y sigue pareciendo una mejora con respecto al original. De los comentarios anteriores en el hilo, creo que a @rkern podría haberle gustado una permutación de salida aún más fuerte, pero hacerlo le cuesta velocidad o (si

¿Fue útil esta página
0 / 5 - 0 calificaciones