Números Pseudoaleatorios · Tema 12

Generadores congruenciales lineales

La receta x(n+1) = (a·x(n) + c) mod m: qué hace cada parámetro, cómo iterarla a mano y cómo implementarla sin romperla por overflow.

01 · Punto de partida

Una línea que generó décadas de simulación

Después del museo del Tema 11, toca abrir el motor que ganó por simple y rápido: el generador congruencial lineal (GCL) de Lehmer. Una multiplicación, una suma y un resto. Con eso y una semilla produces millones de valores por segundo con teoría conocida.

Pero simple no es inocente: dos GCL con el mismo m y distinto a pueden ser uno excelente y otro desastroso (ya lo viste con 5,3 vs 4,2). Este tema enseña la mecánica; los Temas 13–14 enseñan la elección.

  • ¿Qué significa cada letra de (a·x + c) mod m?
  • ¿Qué diferencia hay entre mixto y multiplicativo?
  • ¿Cómo se itera a mano sin perderse?
  • ¿Cómo se implementa sin overflow ni sesgo?

02 · Definición

Anatomía de la receta

◈

Multiplicador a

Mezcla el estado: a·x lo estira. Mal elegido deja ciclos cortos o estructuras en pares/triplas (RANDU).

⇄

Incremento c

Si c ≠ 0 es mixto; si c = 0 es multiplicativo (Lehmer). El c saca del cero y permite período completo.

▣

Módulo m

El tamaño del anillo: el período nunca supera m. Potencia de 2 = rápido; primo de Mersenne = mejor estadística con más costo.

Mixto frente a multiplicativo.
TipoCondiciónPeríodo máx.Ojo con
Mixtoc ≠ 0mElegir a, c con teorema (Tema 14)
Multiplicativoc = 0m − 1Semilla 0 lo mata; x = 0 prohibido
Park–Millera=16807, m=2³¹−1m − 1Overflow en 32 bits si se multiplica directo

03 · Paso a paso

Iterar a mano sin perderse

Con x₀=7, a=5, c=3, m=16:

Paso 1
x₁ = (5·7+3) mod 16 = 38 mod 16 = 6 → u₁ = 6/16 = 0,375.
Paso 2
x₂ = (5·6+3) mod 16 = 33 mod 16 = 1 → u₂ = 0,0625.
Paso 3
x₃ = (5·1+3) mod 16 = 8 → u₃ = 0,5. Ya ves saltos, no orden.
Regla
Siempre multiplicar → sumar → resto → dividir. El resto es lo que dobla la recta en anillo.

Bien implementado

Enteros exactos

El mod se hace en enteros (Python int, C con 64 bits o Schrage). La división a [0,1) es lo último.

Mal implementado

Flotantes en el medio

Hacer (a*x+c)/m con flotantes y luego resto acumula error y rompe el ciclo teórico.

04 · Ejemplos

Tres GCL para calibrar

(a,c,m,x₀)→Iterar mod→u = x/m
  1. 1
    Didáctico bueno.

    (5,3,16,7): período 16, salta sin orden visible. Para aprender, no para producir.

  2. 2
    Park–Miller mínimo.

    (16807,0,2³¹−1): estándar histórico, período ~2·10⁹. Multiplicativo: semilla nunca 0.

  3. 3
    RANDU (no usar).

    (65539,0,2³¹): rápido y con triplas en 15 planos. El ejemplo de qué no elegir.

05 · Implementación en Python

Generador como iterador

Esta forma con yield separa estado de consumo y te servirá para todos los generadores del curso.

Python en tu navegador. Cambiá a a 4 y c a 2 para ver el colapso con el mismo código.

def gcl(semilla, a=5, c=3, m=16):
    x = semilla
    while True:
        x = (a * x + c) % m
        yield x / m


g = gcl(7)
print([round(next(g), 4) for _ in range(8)])

Ocho uniformes que cubren [0,1) sin orden obvio. Resembrar es crear otro gcl(otra_semilla).

Park–Miller en Python (sin overflow)

def park_miller(semilla, n=5):
    a, m = 16807, 2**31 - 1
    x = semilla
    sal = []
    for _ in range(n):
        x = (a * x) % m
        sal.append(round(x / m, 6))
    return sal


print(park_miller(1))

06 · Exploración

Laboratorio: la receta bajo la lupa

Mové a, c, semilla y N con m = 16 fijo para ver la mecánica. El panel lista los primeros 6 x, el período y la media. La línea vertical marca el fin del primer ciclo.

EXPERIMENTO 12

Iterar el mod

x(n+1) = (a·x(n) + c) mod 16

Los resultados numéricos aparecen debajo.
Primeros x—
Período—
Media u—
Tipo—

Con (7,5,3) el período es 16.

Cada punto es u(n)=x(n)/16. Pasada la marca, el dibujo se repite: el mod cerró el anillo.

Preguntas para explorar

  1. Con (7,5,3), anotá los 6 primeros x a mano y compará con el panel. ¿Coinciden?
  2. Poné c = 0 con semilla 0: ¿qué pasa? ¿Y con semilla 7?
  3. Poné (7,4,2): ¿período? ¿Media «buena» igual? ¿Qué dice eso de juzgar por media?
Ver respuestas sugeridas
  1. Sí: 6, 1, 8, 11, 10, 5… El cálculo a mano es la mejor forma de fijar el orden multiplicar→sumar→resto.
  2. Con 0 queda en 0 para siempre; con 7 entra en un ciclo sin el 0. El multiplicativo expulsa al cero del anillo útil.
  3. Período ≤ 4 con media que puede dar 0,5 igual: la media no detecta el desastre.

07 · Comprensión

Confusiones frecuentes

«x y u son lo mismo»

x es entero en 0..m−1 (aritmética del ciclo); u = x/m es el uniforme que usa tu simulación. Mezclarlos rompe tests y transformaciones.

«Hago el mod con flotantes, da igual»

No: el ciclo teórico vive en enteros exactos. Flotantes intermedios meten redondeo y cambian la secuencia (Temas 39–40).

«Cualquier a impar sirve»

El período y la estructura 2D/3D dependen finamente de a. RANDU era impar y es el contraejemplo del curso.

«En Python no hay overflow, ignoro el tema»

En Python no, pero el GCL que portes a C/Java/simulador sí. Si elegís a·(m−1) mayor que 2³², tu port explota.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: iterar a mano y verificar

Calculá x₁, x₂, x₃ desde 7 con (5,3,16) a mano y verificá con código.

x = 7
for i in range(3):
    x = (5 * x + 3) % 16
    print(i + 1, x, round(x / 16, 4))
Ver solución razonada

6 → 0,375; 1 → 0,0625; 8 → 0,5. Si te dio otra cosa, revisá el orden: primero 5·x+3, después mod 16.

Ejercicio 2: mixto vs. multiplicativo desde 0

Compará ambos desde semilla 0 con el mismo a. ¿Cuál sobrevive?

def seq(s, a, c, m, n=6):
    x = s
    sal = []
    for _ in range(n):
        x = (a * x + c) % m
        sal.append(x)
    return sal


print(seq(0, 5, 3, 16))
print(seq(0, 5, 0, 16))
Ver solución

Mixto varía; multiplicativo queda en ceros. Por eso el multiplicativo exige semilla ≠ 0 y período ≤ m−1.

Ejercicio 3: bits bajos débiles

Con m potencia de 2, el bit menos significativo alterna con período ≤ 2. Observá la paridad de la secuencia.

Ver una posible respuesta
x = 7
par = []
for _ in range(10):
    x = (5 * x + 3) % 16
    par.append(x % 2)
print(par)

Verás alternancia o constancia: los bits bajos son los más débiles con m = 2ᵏ. Moraleja: nunca uses x mod 2 para decidir; usá los bits altos o la división completa (Tema 22).

09 · Síntesis

Ideas para recordar

  • GCL: x(n+1) = (a·x(n)+c) mod m, salida u = x/m.
  • Mixto (c≠0, p≤m) vs. multiplicativo (c=0, p≤m−1, semilla≠0).
  • Orden: multiplicar → sumar → resto en enteros → dividir.
  • Cuidado con overflow al portar y con bits bajos si m es potencia de 2.
  • La mecánica es fácil; la elección de (a,c,m) es el arte (Temas 13–14).

En el próximo tema diseccionaremos cada parámetro de un generador congruencial: qué rompe cada uno cuando se elige mal.