← Blog

Tablas de Cayley: el álgebra de grupos, en código Python

Christopher Villamarín

¿Cómo saber si un conjunto con una operación forma grupo? Los axiomas (clausura, asociatividad, identidad e inversos) se pueden comprobar a mano cuando el conjunto es pequeño, pero esa verificación manual es lenta y propensa a errores. La respuesta compacta cabe en una tabla: la tabla de Cayley, la "tabla de multiplicar" del grupo.

Este es el script con el que verifiqué el grupo alternante A₄ — las 12 permutaciones pares de {1, 2, 3, 4} — usando solo diccionarios, tuplas y sets de Python. De paso, cuenta cómo pasé de componer permutaciones de derecha a izquierda (la convención f ∘ g = f(g(x))) a la convención del curso: primero σ, luego τ.

¿Qué es una tabla de Cayley?

Una tabla de Cayley muestra todos los resultados posibles de operar dos elementos del grupo:

  • Las filas y columnas son los elementos del grupo.
  • La celda (i, j) contiene el resultado de fila ∘ columna.
  • Si el conjunto es grupo, ningún elemento se repite por fila ni por columna.

Esa última propiedad tiene nombre propio: es un cuadrado latino. Cada elemento aparece exactamente una vez por fila y una vez por columna, y es exactamente lo que el script verifica al final.

El grupo A₄: 12 permutaciones de 4 elementos

Cada permutación se representa como la tupla de destinos de los números 1, 2, 3 y 4. Por ejemplo, (2 3 4) significa 1→1, 2→3, 3→4, 4→2, así que se escribe (1, 3, 4, 2).

elementos = {
"ι": (1, 2, 3, 4), # identidad (1)(2)(3)(4)
"α": (1, 3, 4, 2), # (2 3 4)
"α²": (1, 4, 2, 3), # (2 4 3)
"β": (2, 1, 4, 3), # (1 2)(3 4)
"γ": (4, 3, 2, 1), # (1 4)(2 3)
"βγ": (3, 4, 1, 2), # (1 3)(2 4)
"αβ": (3, 1, 2, 4), # (1 3 2)
"αγ": (2, 4, 3, 1), # (1 2 4)
"αβγ": (4, 2, 1, 3), # (1 4 3)
"α²β": (4, 1, 3, 2), # (1 4 2)
"α²γ": (3, 2, 4, 1), # (1 3 4)
"α²βγ": (2, 3, 1, 4), # (1 2 3)
}
# Diccionario inverso: buscar el nombre a partir de la tupla resultado
nombre_elemento = {valor: nombre for nombre, valor in elementos.items()}

La composición: notación postfija (primero σ, luego τ)

Aquí está el detalle conceptual del artículo. Originalmente compuse con la convención clásica f ∘ g = f(g(x)): primero actúa el de la derecha. Pero en el curso se usa el orden de aplicación, así que el producto se define como (σ ∘ τ)(x) = τ(σ(x)): se aplica primero σ y después τ.

El nombre formal de esta convención es notación de operadores por la derecha (right operator notation): en lugar de escribir σ(x) se escribe xσ, y el producto se evalúa como x ↦ (xσ)τ. Es la convención de textos clásicos como Wielandt, Finite Permutation Groups; también se conoce como orden diagrámico. La gracia: cambiar de convención tocó una sola línea de código.

# Antes (derecha → izquierda): f(g(x))
def componer(f, g):
return tuple(f[g[i-1] - 1] for i in (1, 2, 3, 4))
# Después (izquierda → derecha): g(f(x))
def componer(f, g):
return tuple(g[f[i-1] - 1] for i in (1, 2, 3, 4))

La lógica de la segunda versión: para cada posición i, primero f[i-1] dice a dónde manda f al númeroi, y ese resultado entra como índice de g.

Ejemplo concreto: α ∘ α = α²

Siguiendo la cadena elemento por elemento con α = (1, 3, 4, 2):

xα(x)α(α(x))
111
234
342
423

El resultado (1, 4, 2, 3) es exactamente α² = (2 4 3): el 2 va a 3 bajo el primer α, y luego el 3 va a 4 bajo el segundo α. ✓

Generando la tabla completa: 12 × 12 = 144 composiciones

Con la convención definida, la tabla son dos bucles anidados. Si alguna composición produjera algo fuera de A₄, el diccionario inverso lanzaríaKeyError: la clausura se verifica sola.

nombres_ordenados = list(elementos.keys())
tabla_pitagorica = []
for fila_nombre in nombres_ordenados:
fila_resultados = []
f = elementos[fila_nombre]
for col_nombre in nombres_ordenados:
g = elementos[col_nombre]
resultado_tupla = componer(f, g)
# Propiedad clausurativa: si el resultado no está en los 12
# elementos, esta línea lanza KeyError.
resultado_nombre = nombre_elemento[resultado_tupla]
fila_resultados.append(resultado_nombre)
tabla_pitagorica.append(fila_resultados)

Verificación automática: ¿es realmente un grupo?

El cuadrado latino se comprueba convirtiendo cada fila y cada columna en un set: si tiene 12 elementos, no hay repetidos.

errores = 0
# Verificar filas
for i, fila in enumerate(tabla_pitagorica):
if len(set(fila)) != 12:
print(f"ERROR en la fila {nombres_ordenados[i]}: repetidos.")
errores += 1
# Verificar columnas
for j in range(12):
columna = [tabla_pitagorica[i][j] for i in range(12)]
if len(set(columna)) != 12:
print(f"ERROR en la columna {nombres_ordenados[j]}: repetidos.")
errores += 1
if errores == 0:
print("✅ CUADRADO LATINO PERFECTO: clausura y sin repetidos.")

Trazabilidad: imprimir cada composición en ciclos

Para entregar el trabajo hace falta mostrar cómo se compuso cada celda, no solo el resultado. Esta función convierte cualquier tupla en su notación de ciclos, omitiendo puntos fijos: (1, 3, 4, 2)(2 3 4).

def perm_a_ciclos(perm):
visitado = [False] * 4
ciclos = []
for i in range(4):
if not visitado[i] and perm[i] != i + 1:
ciclo = []
j = i
while not visitado[j]:
visitado[j] = True
ciclo.append(j + 1)
j = perm[j] - 1
ciclos.append(" ".join(str(x) for x in ciclo))
return "(" + ")(".join(ciclos) + ")" if ciclos else "(1)"
def imprimir_composicion(f_nombre, g_nombre):
resultado = componer(elementos[f_nombre], elementos[g_nombre])
return (
f"{f_nombre}{g_nombre}: "
f"{perm_a_ciclos(elementos[f_nombre])} "
f"{perm_a_ciclos(elementos[g_nombre])} = "
f"{perm_a_ciclos(resultado)} => "
f"{nombre_elemento[resultado]}"
)
# α ∘ α: (2 3 4) (2 3 4) = (2 4 3) => α²

Exportar a Excel: pandas + openpyxl

El entregable final fue un Excel con dos hojas: la tabla 12×12 y las 144 composiciones detalladas y numeradas.

import pandas as pd
df_tabla = pd.DataFrame(
tabla_pitagorica,
index=nombres_ordenados,
columns=nombres_ordenados,
)
df_composiciones = pd.DataFrame([
{"N°": n, "Composición": imprimir_composicion(f, c)}
for n, (f, c) in enumerate(
((fn, cn) for fn in nombres_ordenados for cn in nombres_ordenados),
start=1,
)
])
with pd.ExcelWriter("tabla_cayley_A4.xlsx", engine="openpyxl") as writer:
df_tabla.to_excel(writer, sheet_name="Tabla_Cayley")
df_composiciones.to_excel(writer, sheet_name="Composiciones", index=False)

Resultado final: A₄ verificado como grupo

  • Clausura: toda composición da un elemento de A₄ (sin KeyError).
  • Cuadrado latino: sin repetidos por fila ni columna, en ambas convenciones de composición.
  • Identidad: ι actúa como neutro en toda la tabla.
  • Inversos: cada elemento aparece emparejado con ι en su fila.

Curiosidad que salió de jugar con la tabla: la fila de ι y las filas de β, γ y βγ forman por sí solas el grupo de Klein V₄ dentro de A₄ — el subgrupo normal famoso que hace que A₄ no sea simple.

Stack usado: minimalista, sin magia

  • dict + tuple: representar permutaciones.
  • Función componer: right operator notation.
  • set(): verificar el cuadrado latino.
  • pandas + openpyxl: exportar a Excel.

Sin librerías de álgebra: solo Python puro y la stdlib.

Lo que me llevo de este ejercicio: una permutación es solo una tupla de destinos; componer es encadenar índices en una línea; verificar un grupo es comprobar que set(fila) tiene 12 elementos; y la notación postfija hace que el orden de escritura coincida con el orden de aplicación. El código no solo calcula: también verifica y explica cada paso.