#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ANALIZADOR DE REJILLAS v2 — Even ha-Shoham (Yosef ibn Tzayach, 1538) ==================================================================== Analiza cuadrados (riboa), rectángulos y ruedas (gilgal) del manuscrito. USO 1) Pega tus transcripciones en el diccionario REJILLAS de abajo. Puedes poner tantas como quieras; se analizan todas de una vez. 2) python3 analizador-rejillas.py FORMATO - Una fila por línea, valores separados por espacios. - Letras hebrewas (צה) o números árabes (95). Ambos valen. - '?' o '0' para celda ilegible: el programa intenta reconstruirla. - tipo: "cuadrado" | "rectangulo" | "rueda" En "rueda", cada línea es un anillo (pueden tener longitudes distintas). - rtl: True si transcribiste en el orden visual hebreo (derecha a izquierda). QUÉ DETECTA · orden, contenido, valores ausentes o repetidos · las 8 simetrías del cuadrado, con y sin complemento · magia: filas, columnas, diagonales, pandiagonales, cuadrantes, bloques 2x2 · marcos concéntricos (la conjetura anillos = marcos) · vectores de construcción y ruptura (método siamés generalizado) · patrón respecto a la rejilla natural · progresiones aritméticas y patrones modulares · hipuj: inversión entre mitades · gematría de las constantes · reconstrucción de celdas dañadas """ # ===================================================================== # BLOQUE DE TRANSCRIPCIÓN — edita aquí # ===================================================================== REJILLAS = { "ejemplo_rejilla_natural_10x10": { "tipo": "cuadrado", "rtl": False, "datos": """ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 """ }, # "riboa_keter": { # "tipo": "cuadrado", "rtl": True, # "datos": """ # א ב ... (pega aquí las diez filas) # """ # }, # "gilgal_keter": { # "tipo": "rueda", "rtl": True, # "datos": """ # ... anillo exterior ... # ... anillo 2 ... # """ # }, } # ===================================================================== VAL = {'א':1,'ב':2,'ג':3,'ד':4,'ה':5,'ו':6,'ז':7,'ח':8,'ט':9, 'י':10,'כ':20,'ך':20,'ל':30,'מ':40,'ם':40,'נ':50,'ן':50, 'ס':60,'ע':70,'פ':80,'ף':80,'צ':90,'ץ':90, 'ק':100,'ר':200,'ש':300,'ת':400} INV = [(400,'ת'),(300,'ש'),(200,'ר'),(100,'ק'),(90,'צ'),(80,'פ'),(70,'ע'), (60,'ס'),(50,'נ'),(40,'מ'),(30,'ל'),(20,'כ'),(10,'י'),(9,'ט'),(8,'ח'), (7,'ז'),(6,'ו'),(5,'ה'),(4,'ד'),(3,'ג'),(2,'ב'),(1,'א')] def a_hebreo(n): """Escribe un número en letras hebreas (con las excepciones 15 y 16).""" if n <= 0: return "?" if n % 100 == 15: base, n = "טו", n-15 elif n % 100 == 16: base, n = "טז", n-16 else: base = "" out = "" for v, l in INV: while n >= v: out += l; n -= v return out + base def gem(tok): t = tok.replace('"','').replace("'",'').replace('״','').replace('׳','').strip() if not t or t == '?': return None if t.lstrip('-').isdigit(): v = int(t); return None if v == 0 else v s = 0 for c in t: if c not in VAL: return None s += VAL[c] return s or None def parsea(txt, rtl=False): filas = [] for ln in txt.strip().splitlines(): tk = ln.split() if not tk: continue v = [gem(t) for t in tk] if rtl: v.reverse() filas.append(v) return filas def S(xs): """Suma ignorando huecos.""" return sum(x for x in xs if x is not None) def completa(g): return all(c is not None for r in g for c in r) # --------------------------------------------------------------------- def sec_contenido(g, n, N): print("\n--- CONTENIDO ---") vals = [v for r in g for v in r if v is not None] huecos = sum(1 for r in g for v in r if v is None) print(f" celdas {sum(len(r) for r in g)} | legibles {len(vals)} | ilegibles {huecos}") if not vals: return print(f" min {min(vals)} max {max(vals)} distintos {len(set(vals))}") esp = set(range(1, N+1)) fuera = sorted(set(vals) - esp) falta = sorted(esp - set(vals)) rep = sorted({v for v in vals if vals.count(v) > 1}) perm = not fuera and not falta and not rep and len(vals) == N print(f" ¿permutación exacta de 1..{N}? {'SÍ' if perm else 'NO'}") if fuera: print(f" fuera de rango: {fuera[:14]}") if falta: print(f" ausentes: {falta[:14]}{'…' if len(falta)>14 else ''}") if rep: print(f" REPETIDOS (probable error de lectura): {rep[:14]}") def sec_simetrias(g, n, comp): print("\n--- SIMETRÍAS ---") def cmpf(f, nombre): ig = com = mal = 0 for i in range(n): for j in range(n): a, b = g[i][j], f(i, j) if a is None or b is None: continue if a + b == comp: com += 1 elif a == b: ig += 1 else: mal += 1 tot = ig + com + mal if not tot: return if ig == tot: print(f" {nombre:22s} IDÉNTICA (invariante)") elif com == tot: print(f" {nombre:22s} COMPLEMENTARIA (suman {comp}) ***") elif mal == 0: print(f" {nombre:22s} mezcla idéntica/complementaria") else: print(f" {nombre:22s} — (coincide {ig}, complementa {com}, falla {mal})") cmpf(lambda i,j: g[n-1-i][n-1-j], "rotación 180°") cmpf(lambda i,j: g[i][n-1-j], "espejo vertical") cmpf(lambda i,j: g[n-1-i][j], "espejo horizontal") cmpf(lambda i,j: g[j][i], "transposición") cmpf(lambda i,j: g[n-1-j][n-1-i], "antitransposición") cmpf(lambda i,j: g[n-1-j][i], "rotación 90°") print(" (*** = la propiedad que declara la letanía del manuscrito)") def sec_magia(g, n, N, comp, M): print("\n--- SUMAS Y MAGIA ---") filas = [S(r) for r in g] cols = [S([g[i][j] for i in range(n)]) for j in range(n)] d1 = S([g[i][i] for i in range(n)]) d2 = S([g[i][n-1-i] for i in range(n)]) print(f" objetivo mágico M = {M}") print(f" filas {filas}") print(f" columnas {cols}") print(f" diagonales {d1}, {d2}") fo = all(x == M for x in filas); co = all(x == M for x in cols) do = (d1 == M and d2 == M) print(f" filas={fo} columnas={co} diagonales={do}") if fo and co and do: v = "CUADRADO MÁGICO PLENO" elif fo and co: v = "SEMIMÁGICO (filas y columnas)" else: des = [abs(x-M) for x in filas+cols] v = f"NO mágico (desviación media {sum(des)/len(des):.1f})" print(f" >>> {v}") if completa(g) and fo and co and do: pan = all(S([g[i][(i+k) % n] for i in range(n)]) == M and S([g[i][(n-1-i+k) % n] for i in range(n)]) == M for k in range(n)) print(f" pandiagonal (diagonales rotas): {pan}") if n % 2 == 0 and completa(g): h = n//2 cuad = [S([g[i][j] for i in range(a,a+h) for j in range(b,b+h)]) for a in (0,h) for b in (0,h)] print(f" cuadrantes {cuad} {'(iguales)' if len(set(cuad))==1 else ''}") b2 = [S([g[i][j], g[i][j+1], g[i+1][j], g[i+1][j+1]]) for i in range(0,n-1,2) for j in range(0,n-1,2)] if len(set(b2)) == 1: print(f" todos los bloques 2x2 suman {b2[0]} ***") def sec_marcos(g, n, comp, M): """Marcos concéntricos: la conjetura anillos-del-gilgal = marcos-del-riboa.""" print("\n--- MARCOS CONCÉNTRICOS (conjetura gilgal = riboa girando) ---") for k in range(n//2): cel = [] for j in range(k, n-k): cel += [(k,j), (n-1-k,j)] for i in range(k+1, n-1-k): cel += [(i,k), (i,n-1-k)] cel = list(dict.fromkeys(cel)) vs = [g[i][j] for i,j in cel if g[i][j] is not None] if not vs: continue print(f" marco {k+1}: {len(cel)} celdas | suma {sum(vs)} | " f"media {sum(vs)/len(vs):.1f} | min {min(vs)} max {max(vs)}") print(f" (un cuadrado de orden {n} tiene {n//2} marcos)") def sec_vectores(g, n, N): """Sigue 1→2→3… y deduce los vectores de construcción y ruptura.""" print("\n--- VECTORES DE CONSTRUCCIÓN Y RUPTURA (método siamés) ---") pos = {} for i in range(n): for j in range(n): if g[i][j] is not None: pos[g[i][j]] = (i, j) pasos = [] for k in range(1, N): if k in pos and k+1 in pos: (i1,j1),(i2,j2) = pos[k], pos[k+1] pasos.append((((i2-i1) % n), ((j2-j1) % n))) if not pasos: print(" no hay suficientes números consecutivos legibles"); return from collections import Counter c = Counter(pasos) tot = len(pasos) print(f" {tot} pasos consecutivos analizados. Vectores más frecuentes:") for v, k in c.most_common(4): print(f" (Δfila {v[0]:2d}, Δcol {v[1]:2d}) ×{k} ({100*k/tot:.0f}%)") dom = c.most_common(1)[0] if dom[1]/tot > 0.5: print(f" >>> VECTOR DE CONSTRUCCIÓN: {dom[0]} (domina el {100*dom[1]/tot:.0f}% de los pasos)") rup = [v for v in pasos if v != dom[0]] if rup: cr = Counter(rup).most_common(1)[0] print(f" >>> VECTOR DE RUPTURA: {cr[0]} (×{cr[1]})") print(f" rupturas: {len(rup)} (un siamés puro de orden {n} tiene {n-1})") print(" El cuadrado es COMPATIBLE con una construcción siamesa generalizada.") else: print(" >>> sin vector dominante: NO parece construido por recorrido siamés.") def sec_natural(g, n, comp): print("\n--- PATRÓN RESPECTO A LA REJILLA NATURAL ---") ig = co = ot = 0 filas = [] for i in range(n): ln = "" for j in range(n): v, nat = g[i][j], n*i+j+1 if v is None: ln += "? " elif v == nat: ln += ". "; ig += 1 elif v == comp-nat: ln += "X "; co += 1 else: ln += "o "; ot += 1 filas.append(ln) print(f" . natural ({ig}) X complementado ({co}) o otro ({ot})") for f in filas: print(" ", f) if ot == 0 and co == 0: print(" >>> ES exactamente la rejilla natural, sin modificar.") elif ot == 0: print(f" >>> ES la rejilla natural con {co} celdas complementadas.") print(" (esa familia NUNCA alcanza la constante mágica — demostrado)") def sec_progresiones(g, n): print("\n--- PROGRESIONES Y PATRONES MODULARES ---") def dif(xs): if any(x is None for x in xs) or len(xs) < 3: return None d = {xs[i+1]-xs[i] for i in range(len(xs)-1)} return d.pop() if len(d) == 1 else None hall = False for i, r in enumerate(g): d = dif(r) if d is not None: print(f" fila {i+1}: progresión aritmética de razón {d}"); hall = True for j in range(n): d = dif([g[i][j] for i in range(n)]) if d is not None: print(f" columna {j+1}: progresión aritmética de razón {d}"); hall = True d = dif([g[i][i] for i in range(n)]) if d is not None: print(f" diagonal principal: razón {d}"); hall = True if completa(g): res = [[(g[i][j]-1) % n for j in range(n)] for i in range(n)] fl = all(len(set(r)) == n for r in res) cl = all(len({res[i][j] for i in range(n)}) == n for j in range(n)) if fl and cl: print(f" cuadrado latino módulo {n}: cada resto aparece una vez por fila y columna ***") elif fl: print(f" restos módulo {n}: completos por filas") if not hall: print(" sin progresiones aritméticas completas en filas ni columnas") def sec_hipuj(g, n, comp): """La inversión que menciona el texto de Maljut.""" print("\n--- HIPUJ (inversión entre mitades) ---") h = n//2 ar = [g[i] for i in range(h)] ab = [g[n-1-i] for i in range(h)] esp = pal = 0 for i in range(h): for j in range(n): a, b = ar[i][j], ab[i][n-1-j] if a is None or b is None: continue if a == b: esp += 1 elif a + b == comp: pal += 1 print(f" mitad superior vs inferior invertida: {esp} coincidencias, {pal} complementos") pl = 0 for i, r in enumerate(g): if None in r: continue if r == r[::-1]: print(f" fila {i+1} es palíndromo"); pl += 1 if not pl: print(" ninguna fila es palíndromo") def sec_reconstruccion(g, n, comp, M): """Rellena celdas ilegibles: por asociatividad y por sumas de línea. La constante de línea se DEDUCE de las filas completas, no se supone.""" hue = [(i,j) for i in range(n) for j in range(len(g[i])) if g[i][j] is None] if not hue: return print("\n--- RECONSTRUCCIÓN DE CELDAS ILEGIBLES ---") comp_rows = [S(r) for r in g if None not in r] comp_cols = [S([g[i][j] for i in range(n)]) for j in range(n) if all(g[i][j] is not None for i in range(n))] obs = comp_rows + comp_cols K = obs[0] if obs and len(set(obs)) == 1 else None if K: print(f" constante de línea deducida de las líneas completas: {K}" f"{' (= la constante mágica)' if K == M else ''}") else: print(" las líneas completas no comparten una constante: solo uso asociatividad") rec, sigue = 0, True while sigue: sigue = False for i in range(n): for j in range(n): if g[i][j] is not None: continue op = g[n-1-i][n-1-j] if op is not None: g[i][j] = comp - op; rec += 1; sigue = True print(f" ({i+1},{j+1}) = {comp}-{op} = {g[i][j]} [{a_hebreo(g[i][j])}] · asociatividad") if K: for i in range(n): f = [j for j in range(n) if g[i][j] is None] if len(f) == 1: j = f[0]; g[i][j] = K - S(g[i]); rec += 1; sigue = True print(f" ({i+1},{j+1}) = {g[i][j]} [{a_hebreo(g[i][j])}] · suma de fila") for j in range(n): f = [i for i in range(n) if g[i][j] is None] if len(f) == 1: i = f[0]; g[i][j] = K - S([g[k][j] for k in range(n)]); rec += 1; sigue = True print(f" ({i+1},{j+1}) = {g[i][j]} [{a_hebreo(g[i][j])}] · suma de columna") print(f" recuperadas {rec} de {len(hue)}") if rec < len(hue): print(" ATENCIÓN: quedan huecos; las sumas de abajo son provisionales.") def sec_rueda(anillos, nombre): print(f"\n{'='*68}\nRUEDA (gilgal): {nombre}\n{'='*68}") print(f" {len(anillos)} anillos") tot = 0 for k, an in enumerate(anillos): vs = [v for v in an if v is not None] if not vs: continue tot += sum(vs) difs = {vs[i+1]-vs[i] for i in range(len(vs)-1)} if len(vs) > 2 else set() prog = f" | progresión de razón {difs.pop()}" if len(difs) == 1 else "" print(f" anillo {k+1}: {len(an)} celdas | suma {sum(vs)} | " f"min {min(vs)} max {max(vs)}{prog}") print(f" suma total {tot}") sumas = [S(a) for a in anillos] if len(set(sumas)) == 1: print(f" *** todos los anillos suman lo mismo ({sumas[0]})") N = max((v for a in anillos for v in a if v is not None), default=0) for n in range(3, 30): if n*n >= N and len(anillos) == n//2: print(f" >>> {len(anillos)} anillos = los {n//2} marcos de un cuadrado {n}×{n}") print(" (conjetura: la rueda es el cuadrado desenrollado)") break # --------------------------------------------------------------------- def analiza(nombre, cfg): tipo = cfg.get("tipo", "cuadrado") g = parsea(cfg["datos"], cfg.get("rtl", False)) if tipo == "rueda": return sec_rueda(g, nombre) filas = len(g); anchos = {len(r) for r in g} print(f"\n{'='*68}\n{nombre.upper()}\n{'='*68}") if len(anchos) > 1: print(f" AVISO: filas de longitud desigual {[len(r) for r in g]}") ancho = max(anchos) if tipo == "rectangulo" or filas != ancho: N = filas*ancho print(f"REJILLA {filas}×{ancho} | {N} celdas") fs = [S(r) for r in g] cs = [S([g[i][j] for i in range(filas) if j < len(g[i])]) for j in range(ancho)] print(f" suma total {S([v for r in g for v in r])}") print(f" filas {fs}") print(f" columnas {cs}") if len(set(fs)) == 1: print(f" *** todas las filas suman {fs[0]}") if len(set(cs)) == 1: print(f" *** todas las columnas suman {cs[0]}") sec_contenido(g, ancho, N) return n = filas; N = n*n; comp = N+1; M = n*(N+1)//2 print(f"CUADRADO de orden {n} | {N} celdas") print(f" constante de pares n²+1 = {comp} [{a_hebreo(comp)}]") print(f" constante mágica n(n²+1)/2 = {M} [{a_hebreo(M)}]") if n % 4 == 2: print(f" ORDEN SIMPLEMENTE PAR: magia y asociatividad son INCOMPATIBLES (demostrado)") elif n % 2 == 0: print(f" orden doblemente par: admite mágico y asociativo a la vez") else: print(f" orden impar: el método siamés da mágico y asociativo") sec_contenido(g, n, N) sec_reconstruccion(g, n, comp, M) sec_simetrias(g, n, comp) sec_magia(g, n, N, comp, M) sec_marcos(g, n, comp, M) sec_vectores(g, n, N) sec_natural(g, n, comp) sec_progresiones(g, n) sec_hipuj(g, n, comp) if __name__ == "__main__": print("ANALIZADOR DE REJILLAS v2 — Even ha-Shoham") for nombre, cfg in REJILLAS.items(): try: analiza(nombre, cfg) except Exception as e: print(f"\n[{nombre}] ERROR: {e}") print("\nFin.")