Arquitectura y evidencia
El que propone, el que verifica, y la capa de en medio
Escribes una pregunta de matemáticas en castellano o en inglés. Metamatemático la convierte en un enunciado de Lean 4 y deja que el kernel decida. Lo que devuelve no es una respuesta: es un veredicto —de ocho estados, no de dos— junto al código exacto que Lean aceptó o rechazó. Cuando no puede verificar, dice qué falló.
La forma del sistema es un lazo, no una tubería. El modelo de lenguaje propone un paso formal; Lean lo evalúa; y lo que Lean contesta condiciona el paso siguiente. Los dos extremos ya existían y no se hablaban: un modelo escribe con la misma seguridad un lema que existe y uno que ha inventado, y un verificador sólo sabe decir sí o no a un texto que ya le llega escrito. Lo que este proyecto construye es la capa de en medio: prepara lo que el modelo ve antes de escribir, y lee lo que Lean dice después de compilar, para que el lazo se cierre sobre evidencia y no sobre confianza.
Esa capa tiene dos mitades que no se parecen, y la distinción no se postuló: se descubrió midiendo. Antes de que exista un enunciado formal se trabaja con palabras humanas —ahí el grafo de conceptos gana, y por mucho—. Después, el objeto ya es un tipo, y quien busca bien sobre tipos es Lean. Ésa es la frontera de formalización, y de ella salen las cinco capas: tres antes (lengua, concepto, territorio), dos después (evidencia, emergencia).
Sirve a quien estudia —un veredicto sobre lo que uno mismo escribió, no sobre lo que dice el libro—, a quien enseña —comprobar que un enunciado dice lo que uno cree que dice— y a quien investiga —vocabulario de Mathlib verificado en un área que no domina—. Esta página describe cada pieza y la evidencia que la sostiene, incluida la que no la sostiene.
Leonardo Jiménez Martínez · BIOMAT, Centro de Biomatemáticas · Licencia MIT
El planteamiento
Qué es. Un traductor con verificador: convierte una pregunta en lenguaje natural en un enunciado de Lean 4 y somete ese enunciado al kernel. Lo que devuelve no es una respuesta sino un veredicto, y el código que lo sostiene.
Por qué hace falta. Un modelo de lenguaje que responde matemáticas produce texto plausible. La plausibilidad no es una demostración, y el modelo no distingue entre las dos: escribe con la misma seguridad un lema que existe y uno que ha inventado. De 28 identificadores que propuso de memoria, 21 no existían —y los 21 eran lemas, con la convención de nombres de Mathlib seguida al dedillo—. Por eso la decisión sobre qué es cierto se saca del modelo y se entrega al kernel de Lean 4, que sólo acepta lo que puede comprobar.
Eso reparte el trabajo en tres papeles que no se solapan:
El modelo de lenguaje
Traduce del castellano a Lean 4 y de vuelta. Escribe el enunciado formal y la explicación. No emite ningún juicio de verdad que llegue al alumno sin pasar por Lean.
nucleo/llm/El núcleo, en cinco capas
Antes de la frontera lee la pregunta y ofrece
vocabulario de Mathlib comprobado con #check.
Después ordena las tácticas por la forma del objetivo
y explica el resultado. No decide qué es cierto en ningún
punto.
Lean y Mathlib
Comprueba. Su veredicto es inapelable y va delante del texto de la respuesta, para que el alumno lea primero qué se ha comprobado y sólo después la prosa.
lake build · MathlibNinguna afirmación llega al alumno con sello de verificada si Lean no la ha comprobado, y el sello dice exactamente qué se comprobó. Compilar sin errores no es lo mismo que demostrar lo que se preguntó: por eso el veredicto tiene ocho estados y no dos, y tres de ellos existen justamente para nombrar los casos en que Lean acepta un archivo que no demuestra lo pedido.
Las tres vueltas del lazo, y dónde se cierra cada una
«El modelo propone y Lean evalúa» es la forma; lo que la hace funcionar es qué vuelve, y hasta dónde. El sistema cierra tres lazos de radio distinto, y ninguno de los tres es el modelo hablando consigo mismo:
| lazo | qué vuelve al que propone | cuántas vueltas | evidencia |
|---|---|---|---|
| el encabezadofalta un módulo | el import que Lean echa en falta, resuelto contra el índice de nombres reales | 1 | rescata 4, rompe 0 |
| el errorerror semántico | el error estructurado de Lean —su campo kind, no una subcadena— con el código que lo produjo | 2 como máximo | 4 de 15 · p = 0,125 |
la tácticaqueda un sorry | no vuelve al modelo: entran las 12 tácticas en un solo compilado, ordenadas por la forma del objetivo | 1 compilado | 1,57 vs 2,44 posiciones |
Es la condición que separa un lazo de una deriva. Sin ella, un reintento que rompe lo que ya compilaba sería indistinguible de uno que arregla, y el sistema iría hacia atrás sin que nadie lo notara. Por eso el número de vueltas es pequeño y está declarado: lo que se devuelve al modelo es el veredicto de Lean, no una opinión sobre él.
Las tres vueltas operan sobre un objeto que el grafo de conceptos no
contiene: el estado de prueba. Ese objeto tiene su propia
categoría, construida y medida
(§8) — objetos los estados, morfismos las tácticas,
no goals el objeto terminal. Y el sistema enseña las tres
vueltas al alumno en vez de resumírselas: el panel de
explicabilidad dice qué se entendió, qué se activó, qué
capacidades corrieron y cuáles no —con su motivo—, y qué contestó Lean en
cada ronda.
Y una segunda regla, sobre la evidencia
Cada componente del núcleo lleva una medición con su modelo nulo al lado. Un porcentaje solo no dice nada: un clasificador que acierta el 61 % parece bueno hasta que se comprueba que responder siempre lo mismo acierta el 89 %. Comparar dos versiones de la propia idea tampoco es medir — es preferir.
La consecuencia es que este documento contiene componentes que no baten a su nulo, y lo dice. Tres de ellos están apagados en producción por esa razón, mediante una regla explícita (§12), no por criterio de nadie.
El flujo, de la entrada a la salida
Todo pasa por Nucleo.process(texto). Son seis pasos entre
dos fronteras de idioma. El núcleo interviene en tres de los seis; en
ninguno decide si algo es cierto.
| paso | quién | qué hace | evidencia |
|---|---|---|---|
| 1 | grafo | inyecta nombres de Mathlib verificados en el prompt_find_relevant_context | aporta · 16,5× |
| 2 | modelo | escribe Lean 4 — no juzga si es correctoLEAN_SYSTEM_PROMPT | — |
| 3 | grafo | da el módulo de cada nombre que el paso 1 ofreció_modulos_de_los_nombres · va siempre | imprescindible · 282 de 284 |
| 4 | Lean | verifica · veredicto inapelablelean/client.py | — |
| 5 | L3 | ordena las 12 tácticas ante un sorry y las prueba sobre la sesión vivasolver_cascade.py::TacticRanker · cascada_sesion.py | aporta · 3,7× · 276 s → 40,4 s |
| 6 | modelo | traduce el código que Lean aceptóEXPLICACION_SYSTEM_PROMPT | — |
La última columna es el resultado de medir cada punto por separado. No hay un veredicto único sobre «el núcleo»: aporta en el paso 1 y en el paso 5 —y por razones distintas, a un lado y a otro de la frontera de formalización (§5)—, y en el 3 lo que va siempre es el módulo del nombre que se ofreció. Resumirlo en una sola cifra sería mentir por agregación.
Esta tabla tenía dos filas más. Proponer módulos vecinos además
de los del nombre ofrecido empataba con un conjunto fijo de tres —18 de 20
enunciados elaboran, los mismos— y la regla por área que ordenaba
la cascada perdía contra «probar simp primero» —1,262 contra
1,091—. Las dos estaban apagadas por el decisor y el 21 de septiembre de
2026 se quitaron del código; su cifra queda en
§16. Lo que ordena la cascada hoy es un
clasificador sobre la forma del objetivo, que es otra cosa
y se mide aparte.
La frontera del idioma
El alumno pregunta en castellano y todo el aparato del sistema es inglés: las palabras clave del grafo, los 183 433 hechos de Mathlib, los ejemplos few-shot de miniF2F y el propio Lean. Quien escribe «¿Es 17 un número primo?» no toca ninguna de esas palabras.
La consulta se traduce una sola vez, al entrar, con un
modelo local de 74 M de parámetros
(Helsinki-NLP/opus-mt-es-en): sin API y sin coste por consulta.
El inglés pasa directo.
Un traductor general no distingue notación de prosa. Medido sobre el modelo desnudo:
$x^2 - 5x + 6$ → $x^2 - 5x + $6
\mathbb{R}$ → \mathbb{R$
\int_0^\infty ... dx$ → int_0=infty ... dx$ (destruido)
\sin x → \without x
El último lo explica todo: \sin es el seno, y «sin» en
castellano es una preposición. Así que la notación se extrae, se sustituye
por marcas que sobreviven al tokenizador y se restituye en su sitio.
Medido sobre 200 enunciados con notación: 15 014 caracteres
de notación protegidos y 653 expuestos, con hueco en 21 de los
200.
Traducir no basta, porque el emparejador comparaba dos alfabetos. La
puntuación —primo? no es primo—
y los acentos, porque las palabras clave se escribieron
sin acentuar y el alumno escribe con ellos. Normalizar sólo la consulta
empeoraría las cosas: una palabra clave acentuada dejaría de casar.
La ñ se conserva: es una letra del alfabeto, no una
n con tilde — año → ano cambia la
palabra. Sobre 3 000 consultas etiquetadas, el grafo guarda silencio
total en el 7,7 % de los casos y activa
7,00 conceptos por consulta de media.
Y la frontera se cruza de vuelta. Si preguntó en castellano, la respuesta sale en castellano, y eso hay que fijarlo explícitamente en el prompt: el enunciado que el modelo tiene delante ya está en inglés, así que una instrucción como «responde en el idioma del usuario» le haría contestar en inglés. Lo que se le enseña como «pregunta original» es la del alumno, no la traducción, y el historial guarda lo que él escribió.
La sintaxis de la consulta
Una expresión matemática es un árbol, no una cadena. El
módulo nucleo/sintaxis/ la analiza por precedencia
—lexer, parser tipo Pratt, extractor de rasgos y revisor— y produce dos
cosas que conviene no mezclar: rasgos estructurales que
alimentan al emparejador, y un aviso para el alumno que
sólo se emite cuando merece la pena.
Los rasgos predicen qué hechos harán falta
La relación entre sintaxis y semántica se puede medir aquí sin metáforas, porque están las dos mitades: el enunciado en Lean es un objeto puramente simbólico, y los lemas que su prueba usó son lo que hizo falta en el universo matemático. Sobre 22 117 pares y los 100 lemas con suficientes ejemplos:
| representación | rasgos | cobertura | acierta alguno |
|---|---|---|---|
| modelo nulo los 6 lemas más citados | — | 56,6 % | 80,3 % |
| n-gramas de caracteres | 40 000 | 76,8 % | 94,7 % |
| estructura sintáctica | 68 | 76,3 % | 94,9 % |
| las dos juntas | 40 068 | 80,9 % | 96,8 % |
Sesenta y ocho rasgos igualan a cuarenta mil, y juntos suman cuatro puntos: no son la misma información. Y lo que aprende la estructura se puede leer, que es lo que una bolsa de n-gramas no da nunca:
| lema | el rasgo que lo predice | qué significa |
|---|---|---|
| sq_nonneg | relación principal ≤ (+2,92)en contra: = (−2,54) | los cuadrados sirven para desigualdades, no para igualdades |
| mul_pos | < o > en las hipótesis (+2,90) | hace falta cuando el signo viene supuesto |
| Real.sqrt_nonneg | √ en la conclusión (+1,67)en contra: tipo ℤ (−1,92) | no hay raíces sobre los enteros |
| mul_comm | tipo ℂ y relación =en contra: ¬ y % | no es lo que se usa en aritmética modular |
El corpus es lean_workbook y está dominado por
desigualdades: sq_nonneg aparece en el 72 % de las
pruebas, y por eso el modelo nulo ya llega al 56,6 %. Mide
recuperar premisas, no cerrar pruebas, y son 100 lemas
de los 809 que aparecen — los que tienen al menos 30 ejemplos. Los rasgos
corren sobre enunciados de Lean, que sólo existen después del paso 2.
La arquitectura: cinco capas y una frontera
El sistema y Lean hacen búsquedas distintas, y durante mucho tiempo compitieron sin saberlo. La diferencia no es de calidad: es de tipo de entrada. Eso parte el trabajo en dos mitades con dueños distintos, y la frontera es el momento en que existe un enunciado formal.
| quién busca | indexa por | necesita |
|---|---|---|
| el grafo | palabras humanas, ES/EN | prosa |
| Lean exact? · apply? · aesop | estructura del tipo, sobre ~200 000 declaraciones | un objetivo formal |
La evidencia del reparto
Antes de formalizar, con la consulta todavía en prosa, buscar sobre el índice completo no supera al azar; el vocabulario curado lo bate trece veces:
| antes de la frontera | precisión | veredicto |
|---|---|---|
| grafo curado 353 nodos | 23,9 % | 16,5× · aporta |
| índice completo de Mathlib 217 419 nombres | 1,53 % | empata con el azar |
| modelo nulo | 1,45 % | — |
Después de formalizar, con un objetivo formal delante, la búsqueda del grafo pierde por dos órdenes de magnitud:
| después de la frontera | cobertura | veredicto |
|---|---|---|
| recuperación de lemas, método léxico | 0,64 % | muy por debajo |
| recuperación de lemas, método semántico | 0,37 % | muy por debajo |
| modelo nulo los lemas más citados | 77,05 % | — |
El decisor (§12) había separado estas capacidades una a una, cada cual contra su propio nulo, antes de que nadie nombrara la frontera. Al ponerlas en fila apareció la regla: toda capacidad del grafo que actúa antes de formalizar gana, y toda la que actúa después pierde o empata. No es una tesis previa con datos buscados a posteriori; es lo que quedó cuando se apagó lo que no batía a su nulo.
Las cinco capas
consulta (ES/EN)
│
L0 LENGUA palabras clave ES/EN → concepto ANTES
L1 CONCEPTO 189 curados · vocabulario #check DE
L2 TERRITORIO 147 generados · dónde vive FORMALIZAR
│
ENUNCIADO FORMAL ←── la frontera
│
LEAN VERIFICA
│
L3 EVIDENCIA forma del objetivo → qué cierra DESPUÉS
L4 EMERGENCIA colímites sobre teoremas aceptados
│
LA RESPUESTA veredicto delante del texto
Hasta el 10 de septiembre de 2026 L3 y L4 estaban descritas y
medidas, y no estaban enchufadas. El rankeador estaba entrenado,
guardado en disco y con su cifra publicada, y
set_tactic_ranker no lo llamaba nadie: el
getattr de la cascada devolvía None en todas
las consultas y se ordenaba solo con la heurística. L4 igual —
cargar_coocurrencia_verificada existía en CR_org y en la
fachada, y el paisaje se seguía sembrando con lo que el emparejador
léxico adivinaba.
Ninguna de las dos cosas daba error. Los tests pasaban, las mediciones
eran ciertas, y el sistema que corría no era el que este documento
describía. Es la misma familia de fallo que la §16 persigue: afirmar con
más autoridad de la que se tiene. Ahora las dos corren, y hay un
guardián —tests/test_capas_cableadas.py— que falla si
alguna vuelve a quedarse desconectada.
Qué está cableado hoy
| capa | en el camino | quién la gobierna | evidencia |
|---|---|---|---|
| L0 lengua | sí | clasificacion_por_palabras_clave | 62,1 % vs 33,3 % |
| L1 concepto | sí | nombres_de_mathlib_en_el_prompt | 23,9 % vs 1,45 % |
| L2 territorio | alcance, sin nombres | — por diseño | no transfiere |
| Lean | sí | verificacion_con_leanla excepción declarada: no pasa por la regla | el veredicto |
| L3 evidencia | sí · desde hoy | modelo_de_orden_de_cascada | 0,621 vs 0,318 1,57 vs 2,44 posiciones |
| L4 emergencia | sí · desde hoy | siembra al arrancar28 pares con exceso ≥ 1,5 | exceso hasta +6,74 |
| bloque estructuralla explicabilidad en el prompt | no | contexto_estructural_en_el_prompt | sin evidencia — el decisor lo deja fuera |
| sesión de Leanla cascada, sobre la sesión viva | sí · desde el 21-09 | cascada_por_estado | mismos 9 cierres fallos: 276 s → 40,4 s |
| lazo por pasosmediador, proponentes, φ | no | lazo_por_pasos | construido; su puerta con modelo no se ha corrido (§18) |
El bloque estructural —prerrequisitos, tácticas sugeridas, competencia emergente y skills que suelen acompañar, que es lo que el grafo sabe por sus aristas— está escrito y apagado. Su efecto sólo se ve en lo que el modelo escribe, y eso cuesta una llamada. Este repositorio ya ha medido dos veces que añadir contenido correcto al prompt puede empeorar el resultado, así que encenderlo sin medirlo sería justo el error contra el que existe el decisor.
L0 — Lengua
Por qué. Es lo único que Lean no puede hacer: leer una
frase. Un índice de 217 419 nombres formales no contiene la correspondencia
«grupo → Group, Subgroup, MonoidHom»; ésa la escribió una
persona.
Qué muestra. 62,1 % de acierto de área equilibrado, frente
al 33,3 % de responder siempre la clase mayoritaria.
L1 — Concepto
Por qué. El modelo inventa 21 de cada 28
identificadores cuando tira de memoria.
Qué muestra. 180 de 186 nombres existen; los seis restantes
son espacios de nombres y están filtrados antes del prompt, así que la
etiqueta «verificado» es cierta para todo lo que se ofrece.
Lo que NO muestra: que eso haga verificar más a Lean. Se
midió — rescata 3, rompe 2, p = 1,0. Es puntería, no
resultado.
L2 — Territorio
Por qué. El curado nombra 189 conceptos; la taxonomía de
Mathlib tiene 1 139. Los generados dan alcance para reconocer temas.
Qué muestra. Que su vocabulario no transfiere, por
tres vías medidas: sin criba 17,45 %, criba por uso 20,77 % y cuota separada
20,18 %, frente al 21,64 % de la base. El módulo de un objeto generado es un
rincón de su área, no su centro.
L3 — Evidencia
Por qué. Pasada la frontera la pregunta ya no es «de qué
trata» sino «qué cierra esto», y eso lo decide la forma del objetivo. Cada
intento fallido cuesta una compilación de 12 a 30 segundos.
Qué hace. Un clasificador ordena la cascada combinando
n-gramas —que ven los símbolos— con 74 rasgos estructurales, que ven la
forma. No son redundantes: 60,53 % y 61,14 % por separado,
67,11 % juntos, sobre un nulo de 23,77 % —responder
siempre nlinarith—.
Qué muestra. Medido con el modelo de producción contra el
orden fijo real, sobre 1 530 casos que no vio:
| orden | posición media | 1er intento | en los 3 |
|---|---|---|---|
fijo SOLVER_CASCADE | 5,79 | 0,0 % | 36,0 % |
| modelo nulo por frecuencia | 2,44 | 42,5 % | 78,5 % |
| el rankeador | 1,57 | 71,0 % | 93,5 % |
3,7 veces menos invocaciones de Lean que el orden fijo, y bate al nulo fuerte por 0,87 posiciones. Es la pieza que más mejora la búsqueda de todo el sistema.
L4 — Emergencia
Por qué. El paisaje de CR_org, de donde salen los
patrones que se ligan en colímites, se alimentaba de lo que el emparejador
léxico adivinaba: el grafo llevaba la cuenta de sus propias
conjeturas.
Qué hace. Dos conceptos coocurren si un teorema que Lean
aceptó habla de los dos — 40 025 teoremas de Mathlib, corregido por
frecuencia.
Qué muestra. Que los pares que emergen son relaciones
genuinas, medidas por el exceso sobre lo esperado:
| exceso | par de conceptos | juntas | esperadas |
|---|---|---|---|
| +6,74 | derived-category + homological-algebra | 6 | 0,1 |
| +6,23 | measure-theory + random-variables | 9 | 0,1 |
| +5,03 | hilbert-spaces + inner-product-spaces | 22 | 0,7 |
| +4,95 | ideals-quotient-rings + ring-theory | 49 | 1,6 |
| +4,61 | commutative-algebra + ideals-quotient-rings | 49 | 2,0 |
En crudo, el cuarto par más frecuente es cic + linear-algebra
con 134 coocurrencias, y su exceso es +0,11: azar puro,
porque cic declara Type y eso sale en todos los
enunciados. Contar coocurrencias sin corregir mide qué palabras son
comunes, no qué conceptos se relacionan.
Qué parte busca y qué parte explica
Las capas no hacen todas lo mismo, y confundirlo lleva a pedirle a una lo que sólo puede dar otra.
| pieza | busca | explica | evidencia |
|---|---|---|---|
| L0 lengua | sí | poco | 62,1 % vs 33,3 % |
| L1 vocabulario | sí, y no llega al final | mucho | 23,9 % vs 1,45 %p = 1,0 en verificación |
| L2 territorio | reconoce temas | poco | no transfiere |
| L3 rankeador | la más efectiva | poco | 3,7× menos compilaciones |
| L4 coocurrencia | no | mucho | exceso hasta +6,74 |
| la reparación | sí | no | rescata 4, rompe 0 |
| 387 teoremas Lean | no | el fundamento | 62 de 63 operaciones |
La pieza que más mejora la búsqueda no es el grafo de
conceptos: es L3, el rankeador del estado de prueba, que trabaja
del lado formal de la frontera. L1 busca bien y no llega al
resultado. Y L4 no busca nada y es de lo más valioso para
explicar: no entra en el prompt ni ordena tácticas; lo que aporta es
poder decirle a un alumno que group-theory y
subgroups-cosets aparecen juntos en 206 teoremas que
Lean aceptó, veinte veces más de lo que cabría esperar por azar
(exceso +4,32).
Las dos capas de conocimiento
Hay dos almacenes con trabajos distintos, y se miden distinto. Uno dice de qué habla algo; el otro dice qué es cierto.
Por qué hacen falta las dos
De los 169 nombres que el grafo inyecta hoy, 3 son teoremas o
lemas y 166 son tipos, estructuras y clases: el grafo aporta los
sustantivos. Y ahí no es donde el modelo falla. De 28 nombres que el modelo
propuso de memoria, los 21 inexistentes eran todos lemas —
tsum_geometric_two, Subgroup.isCyclic,
isOpen_union: siguen la convención de nombres al dedillo y no
existen.
El modelo acierta razonablemente los sustantivos e inventa los hechos. La lista existe para cubrir esa mitad, y el índice de nombres (§9) para desmentir la otra.
El grafo: anatomía
Una categoría finita: 353 objetos y 1 586 morfismos, con identidad
en cada objeto y composición por transitividad. Cada nodo lleva un
sort que dice qué clase de objeto es, y ese tipado lo
hace cumplir el constructor: una dependencia mal tipada se rechaza.
Un dibujo estático no puede mostrar 353 nodos con sus nombres ni 1 586 flechas a la vez. El explorador interactivo sí: lleva el grafo completo dentro, con filtro por sort y por área, búsqueda por identificador, nombre o palabra clave, y la ficha de cada nodo — qué vocabulario lo alcanza desde una consulta, qué identificadores de Mathlib aporta al prompt, de qué depende y qué le apunta.
Se regenera desde el grafo del runtime con
scripts/exportar_grafo_web.py y
scripts/incrustar_grafo_web.py, así que no puede quedarse
desfasado en silencio.
CONCEPTO pueden aportar nombres de Mathlib al prompt —y
147 lo hacen—: los
MODULO y los AREA aportan estructura, y por eso
la política de ranking los sitúa detrás — un acierto de área es más grueso
que uno de concepto.Un solo grafo, cosido por el anidamiento de módulos
Los nodos generados (125) y los curados (173) están cosidos por la jerarquía
que Mathlib ya tiene escrita en el anidamiento de sus
módulos, que va de lo general a lo especial — la misma dirección
que el grafo curado sigue con group-theory → ring-theory →
field-theory. Un cuerpo es un anillo con más axiomas: la teoría
general se inyecta en la especializada.
aristas que cruzan curado ↔ generado 125 generados con ancestro curado común 109 de 125 nodos que alcanza la lógica 278 de 320
fol-deduction → zfc-axioms. ZFC es una teoría de
primer orden: sus axiomas son fórmulas de primer orden con
igualdad, el ejemplo canónico. Sin esa arista la lógica alcanzaba 158 de
353 nodos, o sea media matemática del grafo sin la lógica detrás.
Es curada, no derivada: Mathlib no construye ZFC sobre
Logic.Basic, así que el DAG de imports no la contiene. Sale
de un juicio matemático y va marcada como tal.
Lo que el esqueleto de módulos no da: los generados y
los curados son hermanos bajo un área, no descendientes, porque la
profundidad del módulo no ordena la generalidad —
LinearAlgebra.Basis está a profundidad 2 y
LinearAlgebra.Matrix.Defs a 3, y sin embargo el álgebra lineal
es más general que la noción de base. Eso requiere un juicio que Mathlib no
contiene.
Los ciclos son de la agregación, no de la matemática
Hay 134 ciclos, todos entre nodos generados; entre los 173 curados hay cero. El DAG oficial de imports es acíclico —Lean prohíbe imports circulares— pero al colapsar 7 747 módulos en conceptos de dos niveles aparecen:
Topology.Instances → Analysis.Asymptotics → Analysis.Complex → Topology.Instances
Son tres desarrollos distintos: los espacios vectoriales topológicos
necesitan la topología de ℝ≥0∞, el análisis asintótico complejo
necesita la notación Θ, y la topología de ℂ necesita su estructura normada.
Nadie dio la vuelta. El ciclo marca dónde la división en ramas deja
de funcionar, no un recorrido.
Se intentó definir las áreas por componentes fuertemente conexas, para que el corte lo dictara la estructura y no una elección de profundidad. Da una mega-área con 918 de los 1 358 conceptos — Algebra, Data, Order, Topology y Analysis en el mismo saco— más dos residuos.
A nivel de fichero Mathlib es un DAG limpio; en cuanto se agrupa por ramas, casi todo queda entrelazado. El orden en que se construye la matemática formalizada no respeta la frontera entre álgebra, topología y análisis. Las ramas son una capa humana, útil y no derivable.
La base y las fibras
El grafo tiene dos preguntas distintas sobre cada nodo, y separarlas es
lo que le da estructura: area dice de qué rama habla
—y es la base— y sort dice qué clase de
objeto es —y determina la fibra—.
sorry
(CategoryFoundations/Fibracion.lean); lo que no se cumple es
la hipótesis sobre el grafo real. No falta formalización: faltan
morfismos que crucen de área.ColimitVerifier.lean y JoinColimit.lean
exigen que la alcanzabilidad sea un preorden. Con un solo conjunto plano
eso hay que pedirlo sobre los 353 nodos. En una
estructura fibrada la delgadez se pide por fibra, y la base es un
poset por construcción: la obligación de prueba se hace más pequeña y más
honesta.
Y interpretado=False deja de ser un booleano que hay que
acordarse de respetar: como sort, una arista de dependencia
CONCEPTO → MODULO es sencillamente mal tipada, y el
constructor la rechaza.
El claim que el sistema sostiene, y con esa precisión: I es el
colímite de P en la subcategoría delgada finita Gn — no
en una categoría arbitraria. La propiedad universal, la unicidad del
mediador y el puente con CategoryTheory.Limits.IsColimit de
Mathlib están demostrados, y el Python que los implementa está auditado
contra ellos (§13).
Dos cosas distintas que Lean comprueba aquí
Los archivos de CategoryFoundations/ demuestran los
teoremas generales: que ser colímite equivale a ser join
en un preorden delgado, que el mediador es único, que la construcción
encaja con IsColimit de Mathlib. Son enunciados sobre
cualquier grafo, y se prueban una vez.
Lo que no dicen es si este nodo concreto es
colímite de este patrón en el grafo de hoy. Eso es una
afirmación sobre una instancia finita, y es decidible:
hay 353 objetos, así que el ∀ del enunciado se recorre entero.
build_colimit(…, con_lean=True) genera ese enunciado en Lean
—sin import Mathlib, para que compile en segundos— y lo
cierra con decide, que lo comprueba el kernel
y no el compilador. El veredicto queda en Colimit.lean_verified.
Tres valores, no dos. True es «el kernel
lo comprobó»; False es «el kernel lo refutó, hay un
co-cono sin mediador»; y None es «no se sabe» — no había
Lean, o no pudo terminar. Distinguir el tercero del segundo es lo único
que impide que un fallo del verificador se lea como un resultado sobre
el grafo, y no es hipotético: a escala real el kernel agotaba la
profundidad de recursión y eso salía indistinguible de una refutación.
Va apagado por defecto, porque cuesta un compilado. Y cuesta asimétrico:
refutar es barato —decide se para en el primer
contraejemplo— mientras que confirmar obliga a reducir la
conjunción entera. Medido sobre tres colímites reales del grafo: 2,6 s,
3,4 s y 15,3 s.
Al enunciado sólo viajan los morfismos que consulta —los que salen del diagrama y los que salen del ápice—, que no es una poda sino el conjunto exacto que la propiedad mira; el universo del cuantificador sigue siendo los 353 nodos. Sin ese recorte, los casos verdaderos agotaban el presupuesto del kernel a los 25 segundos.
La lista y los índices
Tres estructuras derivadas del fuente de Mathlib, con trabajos distintos: una dice qué es cierto, otra qué nombres existen, y la tercera qué herramienta puede usar cada lema.
La lista de hechos
Nombre, enunciado, módulo y concepto por cada teorema, lema e instancia. Alimenta el índice de premisas.
183 433 entradas · 54 MB121 756 theorem · 50 835 lemma
10 842 instance · 1 102 conceptos
El índice de nombres
Unifica lemas y sustantivos. Se consulta para saber si un nombre existe, para cualificarlo antes de compilar y para desmentir a la respuesta cuando afirma que un nombre real no existe.
217 419 nombres183 351 lemas + 34 084 sustantivos
El DAG oficial de imports
No hubo que reconstruirlo: Mathlib trae la herramienta hecha en
.lake/packages/importGraph.
Mathlib no etiqueta por rama — etiqueta por táctica
Se buscó una anotación de área y no existe. Lo que sí hay, en 83 111 líneas y 162 atributos distintos, es qué herramienta puede usar cada lema:
@[simp] 40 736 @[gcongr] 575 @[continuity] 255 @[norm_cast] 2 257 @[grind] 547 @[mono] 244 @[fun_prop] 1 796 @[aesop] 286 @[measurability]
Es un índice herramienta → lemas curado por los mantenedores, y
sirve para algo concreto: el 42,1 % de las premisas
que citan las pruebas ya llevan @[simp], y simp
las conoce sin que nadie se las pase. Filtrarlas movió la selección de
premisas de empatar con un prior de frecuencia (9,4 % contra
9,2 %) a superarlo: 14,0 % contra 11,7 %.
El índice de 217 419 nombres se consulta en la ruta de
formalización con tres reglas deliberadamente conservadoras: un nombre con
punto desconocido que tenga exactamente una terminación válida se
cualifica; un nombre capitalizado suelto se resuelve por los espacios de
nombres que la primera regla descubrió; y se añade el import
del módulo que lo define.
Comprobado sobre las 241 pruebas de miniF2F que ya compilaban: el reparador no modifica ninguna. Sólo actúa donde hay algo roto.
Cobertura de la taxonomía
Los conceptos de nivel 2 que el grafo cubre alcanzan
137 476 de los 173 636 teoremas de Mathlib
(79,2 %) con 211 conceptos de 1 139. La cobertura es muy desigual
por rama —Álgebra 25 410 de 27 560, Análisis 20 178 de
21 240, Geometría algebraica 1 643 de 2 906, y Condensed
0 de 75—, y esa desigualdad es informativa: marca dónde el grafo
curado tiene huecos reales.
Lean: cuatro caminos, ocho veredictos
Hay dos cosas que conviene no mezclar. Lo que Lean dice abre cuatro caminos, y tres de ellos siguen trabajando. El veredicto viene después, y no coincide con «compiló».
| lo que dice Lean | qué pasa después |
|---|---|
| falta un módulo | se repara el encabezado y se reintenta una vez |
| error semántico | el error estructurado vuelve al modelo, máximo 2 rondas |
queda un sorry | entra la cascada de 12 tácticas |
| acepta el archivo | pasa directo al árbol de veredicto |
Nunca se sustituye un resultado por otro peor. Sin esa condición, un reintento que rompe lo que ya compilaba sería indistinguible de uno que arregla, y el sistema iría hacia atrás sin avisar.
La distinción entre error mecánico y error semántico se toma del campo
kind del JSON que Lean emite —por ejemplo
lean.unknownIdentifier— y sólo en segundo lugar de buscar
subcadenas en el texto del error.
El árbol de veredicto, en el orden exacto en que se decide
core.py. Los tres filtros de la izquierda se
aplican antes de conceder el sello, y existen porque los tres
casos se dan en la práctica: un archivo que compila no es lo mismo que una
demostración de lo que se preguntó.| veredicto | qué significa exactamente |
|---|---|
| verificado | hay teorema, Lean lo prueba, no es vacuo y no es la negación de lo pedido |
| parcial | la estructura compila y queda un sorry; la cascada intentó cerrarlo |
| refutado | Lean verificó la negación del enunciado — el enunciado pedido es falso |
| sin_teorema | Lean aceptó el archivo, pero no contiene ningún teorema |
| vacuo | hay teorema y compila, pero su conclusión es exactamente True: no afirma nada |
| no_verificado | Lean rechazó y los reintentos no lo arreglaron |
| timeout | Lean no terminó dentro del límite |
| sin_entorno | no hay lake instalado — no es un fallo de lógica |
El normalizador retira import Mathlib antes de compilar
y pone una cabecera estrecha: compila en 11 s frente a los
24 s de Mathlib entera (data/coste_de_mathlib.json).
Aquí decía «742 s, más que el tiempo límite»: esa cifra no tenía
procedencia, y medida de nuevo el 2026-09-21 queda muy por debajo de
los 360 s del límite. La razón para la cabecera estrecha es el
coste, no que Mathlib entera no quepa. El
resultado guarda el código que realmente se compiló, y es ése el
que se muestra y el que se le pasa al modelo para que lo explique. Así, el
bloque de código de la respuesta y el veredicto hablan del mismo
archivo.
La detección de vacuo es deliberadamente estrecha: sólo
dispara cuando la conclusión es exactamente True tras el
último : de profundidad cero, de manera que
∃ (n : Nat), True —que sí afirma algo— no se marca.
La cascada de tácticas
Cuando Lean acepta la estructura pero queda un sorry, entran
doce tácticas con su tiempo límite. El orden lo propone el grafo a partir de
la forma del objetivo.
Desde el paso 2 del lazo (§18) el bloque ya no se compila como fichero en el camino servido: se elabora en la sesión viva de Lean, con el mismo orden y el mismo texto, y el fichero sólo confirma la táctica ganadora. Lo que sigue describe el bloque, que es el mismo en las dos vías.
rfl 1s simp 2s norm_num 2s ring 2s ring_nf 3s field_simp 3s linarith 3s nlinarith 4s omega 3s exact? 5s apply? 5s aesop 8s
done, que falla si quedan objetivos abiertos, y un
trace con una marca distintiva para que Lean diga qué táctica
ganó — sin eso, la ganancia costaría saber cuál fue, que es justo lo que
la cascada tiene que devolver.Sobre 1 600 pruebas de una línea de Mathlib, con una partición de prueba del 20 %:
orden posición media 1er intento en los 3 regla de hoy 1,26 88,4 % 94,4 % MODELO NULO 1,09 94,4 % 99,4 % clasificador entrenado 1,06 95,9 % 99,4 %
El modelo nulo es «probar simp primero y no mirar nada
más», y sale de mirar la distribución: simp cierra el
95,8 % de los 1 600 casos. La regla pierde contra el
nulo en 24 casos y gana en 2, con diferencia media +0,172 posiciones e
intervalo de confianza bootstrap del 95 % en
[+0,094, +0,253] — entero por encima de cero, o sea real.
El mecanismo se ve en los casos: los patrones del objetivo
desplazan a simp justo en objetivos que
simp cierra.
La conclusión honesta no es «el orden de tácticas es malo», sino que
este banco no puede decidirlo: con el 95,8 % en una
sola clase, casi cualquier cosa que ponga simp primero da lo
mismo. Por eso tampoco se cableó el clasificador entrenado: 1,06 frente a
1,09 no es nada sobre 320 casos.
La regla por área estuvo apagada por el decisor hasta que, el 21 de
septiembre de 2026, se quitó del código con las otras nueve que no batían
a su nulo. Quien ordena la cascada es el TacticRanker, que
mira la forma del objetivo y sí bate al suyo: 1,57 posiciones contra
2,44.
El decisor
Una tabla de mediciones con filas que dicen «no bate al nulo» no sirve de
nada si esas capacidades siguen ejecutándose. nucleo/decisor.py
convierte la evidencia en control de flujo.
El catálogo tiene trece capacidades. Ante una consulta matemática
con Lean disponible corren siete; 0 están apagadas
porque no baten a su nulo; una bate al suyo y no está cableada,
y el decisor dice por qué en cada caso; dos dependen de
la consulta, porque su guarda sólo aplica a algunas; y tres
no tienen medición contra un nulo —entre ellas lazo_por_pasos, el
paso 3 del lazo (§18), cuya puerta con modelo no se ha
corrido—.
Que ninguna capacidad esté hoy apagada por medición no es que la
regla se haya relajado: las diez que no batieron a su nulo se quitaron del
código el 21 de septiembre de 2026. Un candidato evaluado y
descartado es información, así que no desaparece: queda en
data/descartado.json —qué hacía, su cifra, su nulo y el commit
donde se puede leer entero— y en §16.
El respaldo formal en Lean
Las construcciones categóricas del núcleo no se implementan primero en
Python y se justifican después: se demuestran en Lean, y el Python
se audita contra los teoremas. El corpus son 387 teoremas y lemas
en 22 archivos, sin un solo sorry — y eso se
comprueba preguntándole al compilador con #print axioms, no con
grep: ninguna constante depende de sorryAx.
lake build, se verifica que el grafo real satisface las
hipótesis del teorema, y sólo entonces se escribe el Python — que queda
registrado en el mapeo de la auditoría.Fibracion.lean define proyección monótona, levantamiento
cartesiano y fibración, y prueba la unicidad del levantamiento, la
monotonía del reindexado y su composición — sin depender de ningún
axioma, ni siquiera de los tres estándar. También incluye un
contraejemplo finito: hay monótonas que no son fibraciones, sin el cual la
condición no distinguiría nada.
JoinColimit.lean prueba que ser colímite equivale a ser
co-cono con propiedad universal en un preorden delgado, con unicidad
automática. IsColimitBridge.lean conecta ese resultado con
CategoryTheory.Limits.IsColimit de Mathlib, que es lo que
convierte una construcción propia en una instancia de la noción
estándar.
El método de medición
Todo lo que se afirma de este sistema se afirma con una medición detrás, y toda medición cumple cuatro condiciones. No son formalidades: cada una corresponde a una forma concreta de producir una cifra creíble y falsa.
1 · Modelo nulo obligatorio
Cada cifra se compara contra la cosa más tonta que haría el mismo trabajo: responder siempre la clase mayoritaria, ofrecer los elementos más frecuentes, barajar las etiquetas.
Sin él, un 61 % de acierto parece bueno hasta que se sabe que una constante acierta el 89 %.
2 · Volumen igualado
La precisión y la cobertura no se comparan entre configuraciones que ofrecen distinto número de candidatos. Un mando de volumen disfrazado de mando de calidad produce una tabla creíble y una conclusión falsa: basta ofrecer un tercio de los nombres para que suba la precisión sin acertar más.
3 · La medida adecuada al banco
En un banco desequilibrado, la exactitud cruda mide el desequilibrio. La exactitud equilibrada —media de los aciertos dentro de cada clase— es inmune a él, y su nulo baja a 1/k clases.
4 · La alarma dentro del instrumento
Cada script de medición lleva escrita la condición que revelaría que el instrumento está roto y no el sistema: un cero perfecto que incluye al azar, una tasa de exclusión imposible, un resultado demasiado cómodo.
Los bancos que llaman al modelo distinguen explícitamente entre un caso que falló y uno que no se midió porque se agotó el saldo o la API devolvió un error. Un caso sin medir no cuenta ni a favor ni en contra, y mientras queden casos sin medir no hay nota: una cifra parcial presentada como total sería una medición deshonesta.
El bucle que no cuesta dinero
La formalización es el único paso que necesita el modelo. Con
METAMAT_GRABAR=1 se graba el código del modelo
antes de que Lean lo vea, y todo lo que el sistema hace
después —reparación de imports, cascada, premisas, veredicto— se reejecuta
con Lean de juez y coste cero.
La frontera está donde tiene que estar, y hay un test que lo impide mover: si el gancho de grabación se colocara detrás de la normalización, la reejecución mediría el sistema contra su propia salida y daría siempre lo mismo.
Lo que está medido
El resultado que ordena a los demás: ¿aporta el sistema?
Durante mucho tiempo la comparación fue «el sistema con el grafo» contra «el sistema sin el grafo». Esa pareja contesta si aporta el grafo, no si aporta el sistema: la rama sin grafo sigue llevando reparación, premisas, cascada y ejemplos. Faltaba el suelo — el mismo modelo, una llamada, sin nada.
| configuración | verifica | qué es |
|---|---|---|
| modelo solo | 4 de 20 · 20 % | una llamada, sin sistema |
| sin reparación | 6 de 15 · 40 % | el sistema menos el bucle de reparación |
| sin vocabulario | 11 de 20 · 55 % | el sistema menos el grafo |
| sistema completo | 12 de 20 · 60 % | la configuración que se sirve |
| comparación pareada | rescata | rompe | p · McNemar exacto |
|---|---|---|---|
| el sistema frente al modelo solo | 8 | 0 | 0,0078 |
| la reparación completo frente a sin reparación | 4 | 0 | 0,1250 |
| el vocabulario con frente a sin | 3 | 2 | 1,0000 |
El aparato triplica al modelo aislado y es significativo. El grafo, dentro de él, no se distingue del ruido. El vocabulario cambió lo que el modelo escribió en 19 de los 20 casos, pero no movió el veredicto en una dirección consistente.
Y hay una tercera pieza medida contra el mismo suelo: la reparación —devolverle al modelo el error de Lean— rescata cuatro casos y no rompe ninguno. Con 15 pares no llega a significancia, pero es, junto al aparato entero, la única pieza que nunca estropea un caso; el vocabulario rescata tres y rompe dos.
La afirmación sostenible es, por tanto: el grafo ofrece los identificadores correctos —catorce veces mejor que su nulo— y eso es puntería, no resultado. Que haga verificar más a Lean se midió y no se ve a n=20.
Catorce mediciones, cada una con su modelo nulo. La figura las pone todas en la misma escala —cuántas veces bate a su nulo— para que se puedan comparar componentes que miden cosas distintas.
| qué se mide | resultado | modelo nulo | veredicto |
|---|---|---|---|
| Vocabulario contra ProofNet371 ejercicios con formalización de oro · k=2, el valor de producción | 23,9 % precisión 16,5 % cobertura | 1,45 % 3,3 % | 16,5× · aporta |
Dependencias curadas contra el DAG real152 aristas skill→skill medibles · nulo emparejado | 112 confirmadas 73,7 % | 31,3 % | 2,35× · aporta |
Costura de cobertura contra el DAG9 aristas skill→módulo medibles | 9 confirmadas 100 % | 100 % | 1,00× · no dice nada |
| Vocabulario sobre Mathlib entero53 613 docstrings limpios · cubre todos los módulos, no sólo los de grado | 5,0 % precisión 2,2 % cobertura | 0,3 % 0,5 % | 19,2× · aporta |
| Vocabulario contra Herald43 856 enunciados reescritos por otro sistema · el control | 10,6 % precisión 6,6 % cobertura | 1,2 % 2,2 % | 9,0× · aporta |
| La tanda de curación, en su barrio384 y 691 filas de los temas que trajo · sin ella contra con ella | 3,9 → 16,9 % 4,0 → 20,6 % | 0,5 % 0,2 % | ×4,3 y ×5,2 |
| Revisión de sintaxis de la consulta23 243 enunciados de LeanWorkbook, todos correctos | 3,6 % falsos pos. 60,8 % de caza | 3,6 % (moneda) | +57,2 puntos |
| Rasgos del árbol → premisas22 117 pares, bootstrap emparejado, 68 rasgos | 80,9 % cobertura | 56,6 % | +24,3 · aporta |
| Reconocedor de área por la forma5 750 de test sobre 7 temas de MATH · medido, nunca cableado: por el camino real contra ProofNet no pagaba | 75,4 % techo 84,1 % | 23,8 % | 3,2× en su banco quitado · §16 |
| Área de la consulta3 000 consultas etiquetadas · banco 88,6 % algebra | 62,1 % equilibrada 60,3 % cruda | 33,3 % 88,6 % cruda | +25,4 equilibrada |
Selección de premisassin los @[simp], que simp ya conoce | 14,0 % cobertura | 11,7 % | mejora pequeña |
| Elección de importsmódulos vecinos ADEMÁS de los del nombre ofrecido · 20 enunciados, Lean como juez | 18/20 elabora | 18/20 fijo | inerte quitado · §16 |
Orden de tácticas por área1 600 pruebas de Mathlib · simp cierra el 95,8 % | 1,26 posiciones | 1,09 | no bate al nulo |
| Poda por área antes de elegircon localización perfecta — es el techo, no lo real | 6,8 % | 9,8 % | no llega al nulo |
| Fibración π : Skills → Áreas6753 pares (objeto, área por debajo) | 10 se levantan 0,1 % | 4,3 % (áreas al azar) | peor que el azar |
Nombres de los nodos generados447 identificadores, uno a uno con #check | 346 existen | — | 95 no existen |
| Respaldo formal Python ↔ Leanauditoría del mapeo operación → teorema | 62/63 · 98 % | — | 0 mapeos rotos |
El banco de fidelidad
Es el instrumento que responde a la pregunta central: ¿el teorema que Lean verifica es el que se preguntó? Verificar fielmente algo falso es tan malo como verificar infielmente algo cierto, así que un caso sólo cuenta como bueno si salen bien las tres cosas:
- Verificación — qué dijo Lean.
- Fidelidad — un juez independiente decide si el enunciado formalizado es el de la pregunta, sin ver el veredicto de Lean, para que el sello no lo contamine.
- Honestidad — para los enunciados falsos a propósito, el sistema debe rechazarlos o probar su negación, y la respuesta debe abrir diciéndolo.
El banco tiene 24 casos en tres grupos: 16 ciertos y formalizables, 4 falsos a propósito y 4 controles que no son matemáticas y no deberían tocar Lean. La corrida rápida es una muestra estratificada de ocho —4/2/2— para que la parte que discrimina siga estando representada.
La corrida registrada en data/banco_fidelidad.json es la
muestra rápida: 8 casos medidos, 8 correctos, 0 infieles.
La corrida completa de los 24 es la medición pendiente del proyecto, y es
la única que necesita presupuesto de API y de Lean. Hasta que exista, la
cifra que este documento sostiene es la de ocho casos.
Los resultados negativos
Costaron el mismo trabajo que los positivos y valen igual. Están aquí para que nadie los repita, y porque un sistema que sólo publica lo que funciona no se puede evaluar.
Diez capacidades habían quedado apagadas por su propia medición, y el
código seguía ahí. Se quitó. Lo que queda de cada una es su cifra, su
nulo y el commit donde se puede leer entera, en
data/descartado.json; la página de mediciones del sistema lo
enseña como una tabla más. Descartar no es borrar: sin el
registro, dentro de seis meses alguien vuelve a construir lo mismo.
Las diez: el orden de cascada por área, proponer módulos vecinos, el
reconocedor de área, los vecinos de estado como sustituto del rankeador,
apply? como sonda, el encoder denso de premisas, la
localización en dos etapas, la recuperación léxica de lemas, el
emparejador semántico y el enrutado neuronal.
Proponer imports por vecindad no aporta — y no es todo el paso 3
y qué no
Sólo la mitad b: proponer módulos por vecindad ADEMÁS de los del nombre ofrecido. La mitad a —el módulo de cada nombre que el prompt ofrece— no pasa por aquí, va siempre, y sin ella 282 de las 284 consultas de ProofNet que reciben un nombre reciben alguno que Lean no puede resolver; con ella, 0.
Un conjunto fijo de tres módulos hace elaborar 18 de 20 enunciados. Añadirle lo que el grafo propone: los mismos 18, y un 11 % más de tiempo. Contra el azar sí gana —18 frente a 12, rescatando 6 casos sin romper ninguno—, o sea que hace trabajo real, pero redundante con una constante.
De los 2 fallos, ninguno es de imports: uno necesita open Real y otro usa sintaxis vieja de Mathlib.
El «conjunto fijo» no es pequeño: Mathlib.Tactic arrastra 2 972 módulos por transitividad, y los tres juntos cubren el 38,4 % de Mathlib. La medición es aditiva —añadir imports nunca rompe una elaboración— así que la única forma de ganar era rescatar un fallo. En 10 de 20 casos el grafo no añade ni un módulo: las dos ramas son la misma ejecución.
El test discriminante tiene n ≈ 0. «Inerte» está sostenido como no se midió beneficio, no como se demostró que no lo hay.
Las premisas no cierran pruebas, y la aritmética lo explica
Añadir tácticas con premisas costó 231 invocaciones extra de Lean y cerró cero sobre 21 teoremas que la cascada desnuda no cerraba.
La cobertura de premisas es del 14 %, o sea una de cada siete, y una prueba las necesita todas — 2,1 de media. Esperado 0,14² × 21 ≈ 0,4 cierres; observado, 0.
Una métrica de recuperación no se traduce en cierres. El listón real está muy por encima del 14 % que la mejora celebraba.
Tres emparejadores fallaron, y la causa no era el emparejador
Búsqueda plana 43,9 %, descenso anclado en pilares 11,8 %, embeddings 11,9 % — los tres por debajo del 79,4 % de decir siempre «álgebra».
Para (a+b)² = a²+2ab+b² no existía el nodo: las 35 skills de álgebra eran todas álgebra abstracta. Ningún emparejador recupera lo que no está.
MATH es 79 % álgebra escolar contra una ontología de investigación. La conclusión útil no es sobre el emparejador, es sobre qué conceptos faltan en el grafo.
Podar por área no sirve, ni siquiera acertando el área
Localizar primero el área y elegir las premisas sólo dentro de ella. El orden es correcto: buscar sobre los 183 433 hechos de golpe confundiría el fallo de la búsqueda con la ausencia de poda.
Con localización perfecta —el oráculo— la cobertura sube de 6,0 % a 6,8 %. El modelo nulo da 9,8 %. Aunque se acertara el área siempre, se perdería contra ofrecer las premisas más citadas.
empeora
De 27 áreas a 1 095 sub-áreas: el espacio se divide por cuarenta y el techo sube 0,3 puntos, pero acertar la sub-área es la mitad de fácil, así que en la práctica cae de 4,6 % a 4,0 %.
descartada
Se pensó que la poda fallaba porque las premisas venían de otras áreas. No: el 77,1 % está en la misma área que el teorema. La localización sí informa; lo que falla es elegir cuáles dentro.
Los sustantivos del módulo: la lista sirve, la vía no
es real
Mathlib tiene 34 084 sustantivos —29 883 def, 2 949 abbrev, 1 845 class, 1 490 structure, 300 inductive— y el grafo inyecta 169: el 0,50 %. Y es justo la mitad del trabajo que el reparto le asigna, porque el modelo acierta los sustantivos e inventa los lemas.
es buena
Los nombres se leen de la declaración en vez de deducirse de la ruta. #check sobre una muestra: 200 de 200 existen, frente al 77,4 % de los deducidos.
pierde
Ofrecer los sustantivos del módulo de cada nodo generado, a volumen igualado contra ProofNet: en ~1 800–2 000 nombres, 14,0 % / 17,8 % sin ellos contra 11,5 % / 16,5 % con ellos. Pierde precisión en los dos puntos de la curva: no es dilución, son peores nombres.
sin estadística
El módulo de un nodo generado es un rincón de su área. mathlib-analysis-real ofrecía Hyperreal.Infinite y Real.ofDigits, mientras Real y Real.sqrt viven en Data/Real/Basic. La clave «módulo del nodo» no lleva a los sustantivos que hacen falta.
Buscar en los 217 419 nombres no bate al vocabulario curado
El índice de nombres se usa para comprobar y cualificar, nunca para buscar. ¿Y si se usara como buscador, con índice invertido e idf?
Contra ProofNet, mismo K y mismo nulo: el índice completo da 0,0 % de precisión y el de sólo sustantivos 1,5 %, frente al 23,9 % del vocabulario curado y al 1,45 % del nulo. El mejor índice empata con el azar.
Más candidatos no es más información. Las colisiones de palabra dominan: «Banach» lleva a los espacios de Banach, no al teorema del punto fijo.
La recuperación de lemas por contenido pierde contra la moda
Sobre 23 243 pruebas reales: por contenido, 0,6 % de cobertura; ofrecer siempre los 20 lemas más citados, 77 %.
sq_nonneg no aparece en el enunciado ni tiene por qué: es una herramienta que el problema necesita, no un concepto del que hable. No hay parecido que encontrar.
En Mathlib general el nulo sólo vale 9,2 % y el contenido 4,6 %: lo que cambia por dominio es la fuerza del nulo, no la del método.
95 de los 447 nombres generados no existen en Mathlib
De los 447 identificadores que proponen los 125 nodos generados, comprobados uno a uno con #check: 346 existen, 95 no existen en absoluto y 6 son espacios de nombres — agrupan, pero no son un término.
Estaban deducidos de la ruta del módulo. El filtro pedía CamelCase sin guion bajo, y eso deja pasar Basic —un nombre de fichero— igual que Polynomial —un tipo—. La forma no distingue; sólo Lean lo hace.
Cuatro nodos se quedan sin ningún nombre válido, así que aunque se activara la inyección no podrían aportar nada. Por eso los nodos generados no inyectan vocabulario.
La red neuronal aprendió una constante
Un GNN de atención sobre el grafo más un actor-crítico entrenado con PPO —546 820 parámetros— para decidir si una consulta se responde, se reorganiza el grafo o se asiste con Lean.
Una sonda de cuatro entradas deliberadamente distintas —un teorema, un saludo, un hecho no matemático y un fragmento de Lean— recibe la misma acción en las cuatro: 1 acción distinta, exactamente lo que da la política constante.
Se entrenó con el objetivo «todo problema matemático → ASSIST», que se satisface con una constante. El «100 % de precisión» del informe de entrenamiento no era un logro: era el modelo nulo con otro nombre.
Un objetivo que una constante satisface no puede evaluar un modelo. El runtime ya la detectaba degenerada y la ignoraba en cada arranque; ahora el código tampoco está.
Un encoder de premisas entrenado para Lean tampoco entra
Llevar el estado de prueba impreso a las premisas de Mathlib que su prueba usó, con el codificador de premise-selection (Zhu et al., ICLR 2026) y las 382 212 premisas ya codificadas.
Dentro del lazo, con Lean de juez sobre 60 estados y el mismo presupuesto: 39 verificados contra 40 sin él, y 46 contra 48 junto a los vecinos de estado.
el detalle
Ningún camino verificado usa una táctica suya. Lo que sí hace es gastar: trece plantillas por estado que Lean rechaza, y el presupuesto se agota antes de que lleguen las que cierran.
que casi cuela
Los embeddings publicados son una matriz sin nombres. Con el modelo que enlaza el artículo, cada premisa se parecía a su propia fila casi lo mismo que a la vecina —0,33 contra 0,26—: era otro modelo. Sin comprobarlo, el experimento habría medido premisas equivocadas con toda la seguridad del mundo.
Tests y guardianes
1 312 tests en 67 suites, verdes en los dos intérpretes con los que se trabaja. Los que más valen no comprueban que el código funcione: comprueban que el sistema no pueda volver a afirmar más de lo que sabe.
| guardián | qué impide |
|---|---|
| test_cobertura_consultas | que el grafo deje de engancharse con las consultas reales sin que nadie se entere |
| test_auditoria_veredicto | que un archivo sin teorema, vacuo o refutado reciba el sello de verificado |
| test_interpretacion | que un nodo generado se cuele como si estuviera interpretado categóricamente |
| test_decisor | que una capacidad se apague en silencio porque su ruta de evidencia dejó de resolver |
| test_idioma_de_los_prompts | que las instrucciones a Lean y a Mathlib dejen de estar en inglés |
| test_nombres_mathlib | que el reparador de nombres modifique código que ya compilaba |
| test_fibracion | que cambie la proporción de morfismos que cruzan de área sin volver a medir la fibración |
| rutas absolutas en el runtime | que un except mudo degrade el sistema en silencio al mover el proyecto |
| cifras declaradas | que la documentación anuncie números que ya no son ciertos |
| valores por defecto de los medidores | que un banco mida una configuración que el sistema no sirve |
Los tres últimos no comprueban comportamiento: comprueban que lo que se publica siga siendo verdad. Una cifra desactualizada en la portada y un banco que mide otra configuración producen exactamente el mismo daño que un fallo de lógica, y ningún test normal los ve.
El lazo por pasos: en construcción
Las tres vueltas de §1 son un lazo por intentos: el modelo escribe la prueba entera, Lean compila el fichero, y si falla el modelo recibe los errores y la reescribe entera. Lo que se está construyendo es un lazo por pasos: el modelo propone una táctica sobre un estado de prueba, Lean la evalúa, y lo que contesta —el estado nuevo, o por qué falló— condiciona el paso siguiente. Es la categoría de estados de §8 hecha viva: cada flecha la crea Lean en línea, no un corpus.
Se construye en cinco pasos, y cada uno termina en una medición que decide si se sigue. Ninguno entra en el camino servido por estar construido: entra si su capacidad bate a su nulo en el decisor (§12).
| paso | qué | sigue si | estado |
|---|---|---|---|
| 1 | la sesiónnucleo/lean/sesion.py | acepta lo mismo que el fichero, y más rápido | pasa · 20 de 20 |
| 2 | la cascada por estadonucleo/lean/cascada_sesion.py | no pierde ningún cierre y cuesta menos | pasa · encendida |
| 3 | el mediador con el modelonucleo/lazo/ | verifica más que el lazo por intentos | construido · sin medir |
| 4 | la recuperaciónpremisas y tácticas por estado | cada fuente bate a su nulo | vecinos sí · D2 no · denso no |
| 5 | φ y la explicación por pasonucleo/lazo/phi.py | explica con exactitud | construido · exactitud sin revisar |
Paso 1 · una sesión de Lean que no se apaga
Hoy cada comprobación arranca Lean y vuelve a cargar la cabecera: el
coste está ahí, no en la táctica. La sesión es un proceso de Lean
—el repl de leanprover-community, con el mismo toolchain que
el proyecto— que carga la cabecera una vez y después
recibe tácticas sobre estados. Con la cabecera estrecha del camino
servido, medido en esta máquina:
| qué | segundos |
|---|---|
| cargar la cabecera, una vez por proceso | 10,2 |
plantar el teorema con sorry | 0,04 |
| una táctica —cierra, progresa o falla— | 0,009 – 0,093 |
| el mismo teorema como fichero, que es lo de hoy | 10,8 |
La velocidad no era la pregunta difícil. La difícil es si la sesión
acepta lo mismo que el fichero: el repl tiene
registrado un caso en que aceptó pruebas incorrectas. Se enviaron
20 formalizaciones reales por los dos caminos, con la misma
cabecera: 20 de 20 de acuerdo, y
0 aceptadas por la sesión que el fichero
rechace. Aun así, el sello de verificado sigue saliendo siempre
del fichero: un buscador que se equivoca cuesta un camino perdido; un
certificador que se equivoca cuesta un sello falso.
Paso 2 · la cascada, en la sesión
Cuando queda un sorry, la cascada de §11
prueba doce tácticas en un first |. Hoy eso es un
compilado por sorry, cierre o no. En el paso 2 el mismo
bloque, con el mismo orden y sobre el mismo texto, se elabora en la
sesión viva; si cierra, el fichero confirma la prueba con la táctica
ganadora. Medido sobre 30 casos, las dos vías:
| fichero (hoy) | sesión (paso 2) | |
|---|---|---|
| cierran | 9 de 30 | los mismos 9 |
| perdidos · ganados · falsos | — | 0 · 0 · 0 |
| en los 21 que no cierran | 276 s | 40,4 s |
| todo, pagando la cabecera en cada caso | 381 s | 373 s |
| procesos de Lean | 30 | 9 |
Es lo que se predijo antes de medir: empate en cierres y la ganancia en los fallos, que son la mayoría —la sesión no necesita compilar para saber que no cerró—. La fila con cabeceras apenas gana porque en este banco cada teorema trae la cabecera de su fichero; en el sistema servido la cabecera estrecha es casi siempre la misma y se paga una vez por proceso. El decisor lee esa fila, la peor para la sesión, y la enciende; si una medición futura pierde un solo cierre, la cifra desaparece del fichero y la apaga sola.
La cabecera estrecha alcanza cientos de módulos de Mathlib. En
11 de los 30 casos el fichero del propio
teorema está entre ellos, y ahí simp o exact?
pueden cerrarlo con el propio teorema: 7
de los 9 cierres son de ésos. La comparación entre las dos vías
es válida —ven el mismo texto—; el porcentaje de cierre, no.
El modo importa. La primera versión aplicaba el bloque como
táctica sobre el estado, y el repl no aplica ahí el
límite de heartbeats: en un objetivo duro se agotaba a los 120 s. Se
elabora como comando, con su límite, igual que un fichero pero
sin arrancar Lean. El modo táctica queda para el paso 3, que tendrá que
resolver ese límite.
Paso 3 · el mediador, construido y todavía sin su puerta
nucleo/lazo/ hace la búsqueda de la propuesta: cada
sorry es una raíz, los proponentes sugieren tácticas —D0, la
cascada, una a una; D3, el modelo, con la retroalimentación del estado—,
la sesión evalúa cada una, y el fichero verifica la prueba ensamblada. Los
estados se identifican normalizados, cada uno guarda sus fallos para el
paso siguiente, y todo intento va a
data/transiciones_vivas.jsonl: las alternativas que la
categoría de estados construida desde LeanWorkbook no tenía.
Su puerta gasta modelo y no se ha corrido, así que el decisor lo tiene apagado. Lo que sí está medido, gratis, es un suelo: las dos ramas sobre las mismas 20 formalizaciones grabadas, A el camino servido de hoy sobre el código que escribió el modelo, B el lazo sin modelo —sólo D0— sobre su enunciado.
| verifica | |
|---|---|
| A · lo servido, sobre el código del modelo | 9 de 20 |
| B · el lazo sin modelo, sobre el enunciado | 6 de 20 |
| las dos · sólo A · sólo B | 5 · 4 · 1 |
| A o B: el lazo como segunda etapa | 10 de 20 |
El caso que sólo el lazo cierra es «todo subgrupo de un grupo cíclico
es cíclico»: el modelo escribió la prueba con un lema que no
existe, y el lazo la cerró con exact?, que busca por
el tipo sobre toda la biblioteca. Es la frontera de §5
en un solo caso. Con 20 consultas no significa nada estadísticamente, y
A no incluye las rondas de reparación, que gastan modelo.
La puerta del paso 1 midió la sesión en modo comando. El mediador
trabaja en modo táctica, y ahí apareció el caso que justifica que el
veredicto lo dé el fichero: linarith dejó el objetivo sin
metas y el REPL informó kernel type check failed. Una
lectura de «sin objetivos» como «demostrado» habría construido una
flecha falsa. Ahora un cierre exige proofStatus
Completed, y lo demás —apply? admitiendo con
sorry, el kernel rechazando— es un fallo que va a la
memoria del estado.
Paso 4 · de dónde salen las tácticas
La cascada propone nombres: «prueba nlinarith». Lo que
cierra una desigualdad de competición casi nunca es el nombre desnudo,
sino nlinarith [sq_nonneg (a - b), …], con los hechos que hay
que citarle. Esos argumentos están escritos en las transiciones de
LeanWorkbook de §8: los vecinos de estado
buscan el estado más parecido y copian lo que lo cerró. La otra fuente es
D2: apply? lanzado como sonda para leer sus
«Try this». Todo se mide sin modelo, con el índice construido sólo con la
partición de entrenamiento.
| medida | rankeador | vecinos | fusión |
|---|---|---|---|
| posición del nombre que cierra1530 casos · la de la propuesta | 1,57 | 1,84 | 1,46 |
| estados raíz cerrados con Lean150 estados · 3 intentos por rama | 79 | 76 | 88 |
Como sustitutos del rankeador, los vecinos no ganan: 76 frente a 79. Pero son complementarios —22 estados que sólo cierran ellos, 25 que sólo cierra el rankeador—, y la prueba que decide es otra: como fuente añadida del lazo, con los mismos 60 estados y el mismo presupuesto:
| el lazo con | verifica | frente a D0 | p | llamadas a Lean |
|---|---|---|---|---|
| D0 · la cascada | 41 | — | — | 14,6 |
| D0 + vecinos | 48 | +7 −0 | 0,016 | 13,0 |
| D0 + D2 | 40 | +0 −1 | 1,000 | 15,3 |
| D0 + vecinos + D2 | 46 | +7 −2 | 0,180 | 13,4 |
Los vecinos entran como fuente del lazo: rescatan sin romper, y además gastan menos Lean, porque cierran antes. D2 no entra —y su código se quitó—: no rescata ninguno y sus sondas se comen el presupuesto; con las tres fuentes juntas se pierden casos que D0 + vecinos cerraba. Las dos decisiones las tiene escritas el decisor, que lee estos ficheros. Y ninguna llega al alumno todavía: viven dentro del lazo, que sigue sin su puerta con modelo.
La tercera fuente es el índice denso: un codificador
entrenado para Lean —premise-selection, Zhu et al., ICLR
2026— que acerca el estado impreso a las premisas de Mathlib que su
prueba usó. Las 382 212 premisas vienen ya codificadas; cada una se
prueba como exact, apply y rw, y
todas juntas en simp […]. Mismo banco, mismos 60 estados,
y la regla escrita antes: entra si bate a D0, y se queda junto a los
vecinos sólo si les añade algo.
| el lazo con | verifica | frente a | p | llamadas a Lean |
|---|---|---|---|---|
| D0 · la cascada | 40 | — | — | 14,4 |
| D0 + vecinos | 48 | +8 −0 · D0 | 0,008 | 13,0 |
| D0 + denso | 39 | +1 −2 · D0 | 1,000 | 15,4 |
| D0 + vecinos + denso | 46 | +0 −2 · vecinos | 0,500 | 13,5 |
El denso no entra, y su código se quitó. En ninguno de los caminos
verificados aparece una táctica suya; su único «rescate» lo cerró
exact?, que es de D0, en un caso en que el brazo D0 se había
colgado en una sola llamada. Lo que sí hace es gastar: trece plantillas
por estado que Lean rechaza y que agotan el presupuesto antes de que
lleguen las que cierran. La corrida repite a los vecinos —48 otra vez—; D0
dio 40 donde el paso 4 dio 41, y ésa es la escala del ruido de un
presupuesto en segundos.
Los embeddings precalculados son una matriz sin nombres; el nombre
de la fila i sale de reconstruir el corpus en el mismo orden.
Antes de medir se comprobó (scripts/alinear_premisas.py)
re-codificando premisas al azar. Con el modelo de
hanwenzhu/, el que llega a Lean v4.20, cada premisa se
parecía a su propia fila casi lo mismo que a la vecina: coseno 0,33
frente a 0,26. Los embeddings los hizo l3lab/, rama v4.29.0,
y con él dan 1,0000. Además, el lector del corpus, escrito en Linux,
descartaba en silencio en Windows todo fichero con un α.
Ninguna de las dos cosas daba un error: habría dado premisas
equivocadas con toda la seguridad del mundo.
El sesgo de los vecinos queda dicho: LeanWorkbook es matemática de competición, y fuera de desigualdades y álgebra elemental propondrán tácticas que Lean rechazará en centésimas. Los dos codificadores de 7 000 millones de parámetros de la propuesta —LeanSearch-PS y Lean Finder— están descargados, pero con 4,3 GB de GPU no codifican Mathlib a una velocidad útil: no están medidos.
Paso 5 · φ, de qué conceptos habla un estado
φ lleva cada estado de prueba a los conceptos del grafo cuyos nombres
verificados de Mathlib aparecen en él, por coincidencia exacta —la regla
que hizo fiable a L4—. No es un funtor y no se usa para buscar: sirve para
que la explicación diga «este paso pasó de hablar de subgrupos a hablar
de órdenes». La propuesta predecía que leerlo del texto impreso pierde
casi todo, porque Lean escribe ℝ y ∑ y no
Real ni Finset.sum; la otra lectura pide a Lean
las constantes que el estado usa, con una táctica propia. Sobre 168
estados raíz —las consultas de la campaña y LeanWorkbook—:
| estados con al menos un concepto | de 168 |
|---|---|
| leyendo el texto impreso | 21 |
| leyendo las constantes | 168 |
| las constantes, sin los tipos de número | 21 |
La cobertura de las constantes es hueca. 145 de los
150 estados de LeanWorkbook sólo recibían la etiqueta del tipo donde viven
los números —ℝ daba «análisis real», ℕ «teoría
elemental de números»—, y una etiqueta que sale en todo no explica nada.
Quitados los tipos portadores, las dos lecturas empatan: 21 contra 21.
La predicción de la propuesta no se confirma: quitada la notación, lo que
queda apunta a otro límite —que el grafo nombre las constantes que estos
estados usan—, y eso no está medido.
Que «análisis real» sea una buena etiqueta para
2ab ≤ a² + b² lo decide alguien leyendo. La muestra
está en data/phi_muestra_para_revisar.json: 38 estados con
sus etiquetas y pertinente: null en cada uno. Hasta que se
revise, el decisor tiene φ sin evidencia. La explicación por paso
(phi.explicar) está escrita y probada, y los teoremas que el
lazo verifica ya se anotan para que L4 los sume a los de Mathlib; ni lo
uno ni lo otro llega al alumno mientras el lazo no tenga su puerta con
modelo.
Lo que construirlo destapó del sistema de antes
- La cascada servida no cerraba nada desde el 2026-09-04.
field_simpno existe bajo la cabecera estrecha, y unfirst |es una sola pieza de sintaxis: si una táctica no existe, Lean rechaza el bloque entero sin probar ninguna. Nia + b = b + a. Los tests usaban un Lean de mentira y no podían verlo; ahora la cabecera traeFieldSimpy hay un test que compila de verdad. La campaña del lazo por intentos (2026-09-10) se midió con ese fallo; de las 63 formalizaciones grabadas, sólo 2 tenían unsorry, que es donde entra la cascada. apply?dejaba muerto lo que iba detrás. Cuando no encuentra prueba, cierra el objetivo consorry: dentro delfirstes una rama que no falla, y las tácticas posteriores no se probaban. El fichero no daba sello falso; ahora va siempre la última. No hay un caso medido en que eso costara un cierre.import Mathlibno tarda 742 s. La cifra estaba en diez sitios sin procedencia, justificando la cabecera estrecha porque «siempre expira». Medido de nuevo: 24 s entera y 11 s estrecha, las dos muy por debajo del límite de 360 s. La cabecera estrecha se queda, por coste.
Límites y trabajo abierto
Lo que el sistema no hace
- No demuestra teoremas por sí solo. Formaliza y verifica. La prueba la escribe el modelo o la cierra la cascada de tácticas.
- El grafo no decide qué es cierto en ningún punto. Eso es Lean, siempre.
- Sin API no hay formalización, ni chat, ni banco de fidelidad. Lo que sigue vivo sin ella es todo el trabajo de validar y mejorar el núcleo, que es la mayor parte de este documento.
Preguntas abiertas
- El banco de fidelidad completo, los 24 casos con juez ciego. Es la única medida de si la respuesta responde a la pregunta, y la única que sigue necesitando presupuesto de modelo.
- Un banco en español con premisas de oro. Sin él, todo lo que se construye para el alumno hispanohablante se mide en inglés y por analogía.
- Subir el n de la campaña. Los tres contrastes pareados corren sobre 20 casos (15 en la rama sin reparación). Con ese tamaño, «rescata 4 y rompe 0» no llega a significancia aunque el efecto sea real.
Identificado, con el camino claro
- La categoría de estados existe, y lo que le falta no son
datos. Estaba aquí como «las tácticas deberían ser flechas» y ya
está construida (
nucleo/graph/estados.py): 24 752 estados, 25 206 tácticas,no goalsterminal, el 46,4 % de las flechas componen. Lo que no puede dar es la parte que hacía falta: sólo 72 de los 24 752 estados registran dos tácticas distintas, porque el corpus recoge la prueba que alguien escribió y no las que descartó. Aprender a elegir exige generar esas alternativas probando tácticas contra estados reales. Eso ya no cuesta un compilado por intento: con la sesión viva una táctica cuesta centésimas de segundo (§18). Es el paso 3. - La base no es un orden, y por eso no hay viaje general.
Esto decía que faltaban morfismos que crucen de área y que eran
«lo único que puede convertir el funtor en una fibración». Es falso, y lo
refuta el propio repositorio: la clausura transitiva es monótona,
así que una arista nueva sólo puede añadir relaciones a la base, y las
áreas ya forman una componente fuertemente conexa de 21 de 23. Añadir
morfismos que crucen empeora la tasa — medido en
scripts/base_no_es_un_orden.py. Los 105 de 688 que cruzan hoy generan 462 relaciones entre áreas al cerrar: la base afirma de más. La palanca no son los morfismos sino la asignación de áreas. Lo que sí se puede usar son los 105 levantamientos que existen uno a uno (data/viajes.json), al 7,3 % de lo que la base promete y sólo 1,1× sobre el azar. - La fibra de hechos.
data/banco_lemas.jsonlson 23 243 pares (enunciado, lemas que la prueba usó): un grafo bipartito esperando a ser construido, que hoy sólo alimenta un índice plano. - Un índice de sustantivos consultado por la consulta,
como
premisas.pyhace con los lemas. La lista está construida y verificada; lo que no funciona es la clave «módulo del nodo». - No hay lematización en el emparejador, así que
primosno casa conprimo. Añadirla mueve la precisión y necesita su propia medición. - Seis dependencias del grafo siguen bajo sospecha de ir en sentido contrario, según la comparación con el DAG oficial.