01 · Punto de partida
Del cuaderno suelto al módulo que se importa
Tenés 40 temas de recetas en bloques sueltos. Este proyecto las convierte en biblioteca: un módulo azar con una función por distribución, parámetros con nombre, semilla explícita y una batería de tests que corre antes de cada entrega.
El contrato es simple: misma semilla ⇒ misma muestra, bit a bit; y cada generador pasa chi, KS y QQ antes de mergearse.
- ¿Qué API cubre todo el curso sin crecer sin control?
- ¿Cómo se aíslan las semillas por experimento?
- ¿Qué tests corren en segundos y cuáles en la noche?
- ¿Cómo se documenta cada decisión del Tema 40?
02 · Definición
Contrato de la librería
Cada generador recibe rng primero y parámetros con nombre después; devuelve un flotante o entero, nunca imprime. muestrear(f, n, rng) produce vectores. bateria() corre chi/KS/QQ sobre el catálogo y reporta pasa/falla por función.
Funciones puras
Sin estado global: el azar entra por rng inyectado. Dos experimentos no se roban la secuencia.
Semilla por experimento
Un entero por corrida, guardado junto a (método, N, versión). Reproducir = reinyectar el trío.
Batería que bloquea
Chi, KS y rachas del Tema 38 sobre cada generador: un rojo frena el release, como un test unitario.
| Función | Método (Tema) | Test que la cuida |
|---|---|---|
bernoulli/exponencial | Umbral / inversa (16, 22) | Chi + KS |
binomial/poisson/gamma | Suma, Knuth, MT (19, 20, 26) | Chi + media/var |
normal/fisher_yates | Polar, FY (25, 33) | QQ + matriz chi |
03 · Anatomía
Módulos, versiones y costo
- Un módulo, cuatro zonas
base(RNG + utilidades),discretas,continuas,muestreo(con/sin reemplazo, permutaciones) yvalidación. Cada zona, un archivo; cada función, su test.- Versionar el azar
- Si cambia un algoritmo, cambia la versión mayor: la misma semilla puede dar otra muestra y los experimentos viejos deben advertirlo.
- Batería en dos velocidades
- Rápida (N = 2000, chi+KS) en cada commit; completa (N = 10⁵, QQ, rachas, eficiencia) de noche. Lo lento no frena, lo rápido no perdona.
- Performance con techo
- Medir ms/1000 por generador (Tema 39) y fijar presupuestos: si polar supera su techo, el CI avisa antes que el usuario.
Biblioteca con contrato
Se importa y se confía
API estable, semillas explícitas y tests en verde: otro equipo la usa sin leer el código.
Carpeta de scripts
Se copia y se reza
Semilla global, parámetros mágicos y “a mí me anduvo”: cada reuse es un experimento nuevo sin control.
04 · El mapa de los 41 temas
De U(0,1) a la librería validada
- 1Fundamentos (1–7).
U → F → F⁻¹ analítica y numérica: la base que todo lo genera.
- 2Métodos (8–15).
Rechazo, composición, alias y urnas para lo que no se invierte.
- 3Catálogo (16–27).
Doce distribuciones con receta, costo y trampa documentadas.
- 4Calidad (28–41).
Correlación, muestreo, validación, eficiencia, selección y esta biblioteca.
05 · Representación en Python
El núcleo en 40 líneas
Seis generadores puros con rng inyectado más mini-batería de medias: el esqueleto sobre el que crece todo.
Python en tu navegador. Cambiá la semilla: cambian los valores, no las medias.
import random
import math
def bernoulli(rng, p):
return 1 if rng.random() < p else 0
def binomial(rng, n, p):
return sum(1 for _ in range(n) if rng.random() < p)
def geometrica(rng, p):
return int(math.log(1 - rng.random()) / math.log(1 - p))
def poisson(rng, lam):
L, k, q = math.exp(-lam), 0, 1.0
while True:
q *= rng.random()
if q <= L:
return k
k += 1
def exponencial(rng, lam):
return -math.log(1 - rng.random()) / lam
def normal(rng):
u1 = max(rng.random(), 1e-12)
u2 = rng.random()
return math.sqrt(-2*math.log(u1)) * math.cos(2*math.pi*u2)
rng = random.Random(410)
N = 6000
print("bern:", round(sum(bernoulli(rng, 0.3) for _ in range(N))/N, 3))
print("geom:", round(sum(geometrica(rng, 0.3) for _ in range(N))/N, 3), "vs 2.333")
print("pois:", round(sum(poisson(rng, 4.0) for _ in range(N))/N, 3))
print("exp:", round(sum(exponencial(rng, 0.5) for _ in range(N))/N, 3), "vs 2.0")
0,3 · 2,33 · 4,0 · 2,0: el núcleo respira. Cada línea nueva entra con su test.
06 · Exploración
Laboratorio: tablero de aceptación
Seis generadores de la biblioteca contra su F teórica: cada barra es D/D_crít (bajo 1, verde). Movete N y semilla: con N grande hasta el sesgo chico enciende alguna.
Batería del catálogo
D/D_crít < 1 en los 6
Batería en verde: 6 de 6.
Bernoulli, binomial, geométrica, Poisson, exponencial y normal. Probá las 5 semillas y N = 12000.
Preguntas para explorar
- Con N = 3000, ¿los 6 en verde en las 5 semillas?
- Subí a N = 12000. ¿Alguna barra roza el 1? ¿Falla o avisa?
- ¿Qué test agregarías para rachas si la batería corriera de noche?
Ver respuestas sugeridas
- Sí: implementaciones exactas con D ≈ 0,3–0,7 del umbral.
- Rozar no es fallar: con α implícito, 1 de cada 20 corridas toca el borde por azar.
- Rachas sobre la mediana y chi de la matriz de posiciones (Temas 38 y 32).
07 · Comprensión
Confusiones frecuentes
«La batería pasa una vez y listo»
Corre en cada commit: un cambio inocente (orden de uniformes) altera secuencias y puede romper un test. Verde una vez ≠ verde siempre.
«Semilla fija global alcanza»
Un solo stream global entrelaza experimentos: agregar una corrida cambia todas las demás. Un rng por corrida.
«Documento después»
Método sin (supuestos, costo, tests) es código muerto al mes: documentar es parte del merge, no del después.
«41 temas = 41 funciones»
Los temas de criterio (36–40) no generan nada: viven como batería y guía, no como código. La API cubre el catálogo, no el índice.
08 · Práctica guiada
Ejercicios con Python
Ejercicio 1: sumar la uniforme discreta
Agregá uniforme_discreta(rng, a, b) con piso y validala: media ≈ (a+b)/2 con N = 8000.
import random
def uniforme_discreta(rng, a, b):
return a + int((b - a + 1) * rng.random())
rng = random.Random(415)
N = 8000
m = [uniforme_discreta(rng, 1, 6) for _ in range(N)]
print(round(sum(m)/N, 3), "vs 3.5")
Ver solución razonada
≈ 3,5. El test de verdad agrega chi contra 1/6 por cara (Tema 38).
Ejercicio 2: gamma por suma entera
Agregá gamma_erlang(rng, k, lam) y validala con α = 4, λ = 2: media ≈ 2, var ≈ 1.
import random
import math
def gamma_erlang(rng, k, lam):
return -sum(math.log(1 - rng.random()) for _ in range(k)) / lam
rng = random.Random(412)
N = 6000
m = [gamma_erlang(rng, 4, 2.0) for _ in range(N)]
media = sum(m)/N
print(round(media, 3), round(sum((x-media)**2 for x in m)/N, 3))
Ver solución
≈ (2,0; 1,0). La general con Marsaglia-Tsang queda como extensión con su propio test.
Ejercicio 3: harness que condena al ingenuo
Escribí el test de matriz chi que el shuffle ingenuo no pasa y Fisher-Yates sí (n = 6).
Ver una posible respuesta
import random
def chi_matriz(mezcla, n, B, seed):
rng = random.Random(seed)
mat = [[0]*n for _ in range(n)]
for _ in range(B):
for j, v in enumerate(mezcla(rng, list(range(n)))):
mat[v][j] += 1
e = B/n
return sum((c-e)**2/e for f in mat for c in f)
def fy(rng, xs):
for i in range(len(xs)-1, 0, -1):
j = int((i+1)*rng.random())
xs[i], xs[j] = xs[j], xs[i]
return xs
print("FY chi:", round(chi_matriz(fy, 6, 12000, 413), 1), "vs gl 35")
≈ 35. El harness queda en la batería: toda mezcla futura debe pasarlo.
09 · Síntesis
Ideas para recordar
- API pura con rng primero; semilla por experimento.
- Batería rápida siempre, completa de noche.
- Versionar el azar: algoritmo nuevo, versión nueva.
- Decisiones del Tema 40 escritas junto al código.
- 41 temas, una librería: fin del curso, inicio del uso.
Volvé al índice, recorré los 41 y que tus simulaciones sean reproducibles.