Nivel 7 · Completitud en Lean (experto)
El lema de Kalmár constructivo
Se demuestra que toda tautología es derivable a partir de . El argumento clásico de Kalmár (Kalmár, 1935) , esbozado en el Teorema 9.12, procede en dos etapas.
- Derivación por valuación (lema de Kalmár). Se fija una valuación y sea el contexto de literales de : para cada átomo relevante para , se incluye el literal si y si . Se demuestra, por inducción sobre , que demuestra cuando es verdadera, y demuestra cuando es falsa. Intuitivamente, la derivación «sigue la fila de la tabla de verdad».
- Descarga de los átomos. Si es una tautología, todo contexto de literales demuestra ; los átomos se eliminan entonces uno a uno con el análisis por casos derivado. Esta etapa es el contenido de la página siguiente.
No se necesita maquinaria de conjuntos maximales consistentes / Lindenbaum: el argumento es finitario, guiado por la lista finita de átomos que aparecen en . El diagrama siguiente recorre las dos etapas, con el nombre de la función de Lean que realiza cada una.
El algoritmo de Kalmár, ejecutable
De la tabla de verdad a la derivación: el contenido constructivo del teorema de completitud.
Etapa 1 · La tabla de verdad (eval v φ)
| derivación de la fila | ||
|---|---|---|
| T | T | |
| F | T |
Etapa 2 · El lema de Kalmár en la fila 1 (kalmar_lemma)
El contexto de literales de la fila deriva la fórmula (bilateral: derivaría su negación si la fila la falsara). Cada paso lleva su regla.
Etapa 3 · Descarga de los átomos (discharge / byCases)
El análisis por casos clásico (byCases, vía RAA) funde las 2 ramas en una derivación sin hipótesis. (Se muestran los últimos niveles; las hojas son las derivaciones de la Etapa 2.)
✓ Verificador independiente: las 3 derivaciones construidas son instancias correctas de las reglas.
Átomos, literales y contextos de literales
es la lista de átomos que aparecen en (con repetición):
Para una valuación y un átomo ,
Para una lista de átomos , el contexto de literales es
definido por recursión: y .
Si entonces .
A probar: , bajo la hipótesis . Por inducción sobre la lista .
- . La hipótesis es imposible, así que el caso es vacuo.
- . Por definición, . De hay dos posibilidades: si , entonces es el elemento insertado; si , la hipótesis de inducción da , y como , se concluye.
Extracto de Completeness.lean.
def atoms : Formula → List String| .atom s => [s]| .neg p => atoms p| .impl p q => atoms p ++ atoms q
noncomputable def lit (v : Valuation) (p : String) : Formula := if v p then .atom p else ~(.atom p)
noncomputable def litCtx (v : Valuation) : List String → Set Formula| [] => ∅| p :: rest => insert (lit v p) (litCtx v rest)
theorem lit_mem {v : Valuation} {p : String} {ats : List String} (h : p ∈ ats) : lit v p ∈ litCtx v ats := by induction ats with | nil => simp at h | cons a rest ih => rcases List.mem_cons.1 h with rfl | h · exact Set.mem_insert _ _ · exact Set.mem_insert_of_mem _ (ih h)(lit y litCtx son noncomputable porque el test «» es una proposición; bajo la
instancia local Classical.propDecidable el if está bien definido.)
El lema de Kalmár
Fíjense una valuación y una lista con . Entonces
A probar: para , las dos implicaciones
Por inducción sobre la estructura de .
Caso átomo . Como , se tiene , y por el Lema 11.17, .
- (P) Si (esto es, ): entonces , luego y
- (N) Si : entonces , luego y
Caso negación . Como , valen las dos hipótesis de inducción para : (P) y (N).
- (P) Si , es decir : por (N) se obtiene directamente la meta .
- (N) Si , es decir : clásicamente , así que (P) da , y la introducción de doble negación (Lema 11.9) da la meta:
Caso implicación . Como , valen las hipótesis de inducción para y para .
(P) . Se distingue según .
Si : de se sigue ; por (P), . Entonces:
Si : por (N), . Se deriva y se cierra con I:
(N) . De se obtiene clásicamente (si no, la implicación sería vacuamente verdadera) y (si no, también lo sería). Por (P), ; por (N), . Se cierra con I, descargando el supuesto :
Código de Lean (tal como se implementa)
Extracto de Completeness.lean.
theorem kalmar (v : Valuation) (ats : List String) : ∀ φ : Formula, atoms φ ⊆ ats → (eval v φ → ND (litCtx v ats) φ) ∧ (¬ eval v φ → ND (litCtx v ats) (~φ)) := by intro φ induction φ with | atom s => intro hsub have hs : s ∈ ats := hsub (by simp [atoms]) refine ⟨?_, ?_⟩ · intro hev have hvs : v s := hev have hmem := lit_mem (v := v) hs rw [lit, if_pos hvs] at hmem exact ND.hyp hmem · intro hev have hvs : ¬ v s := hev have hmem := lit_mem (v := v) hs rw [lit, if_neg hvs] at hmem exact ND.hyp hmem | neg p ih => intro hsub have hsub' : atoms p ⊆ ats := hsub obtain ⟨ihpos, ihneg⟩ := ih hsub' refine ⟨?_, ?_⟩ · intro hev; exact ihneg hev · intro hev; exact dni (ihpos (not_not.1 hev)) | impl p q ihp ihq => intro hsub have hsp : atoms p ⊆ ats := by intro a ha; exact hsub (by simp only [atoms, List.mem_append]; exact Or.inl ha) have hsq : atoms q ⊆ ats := by intro a ha; exact hsub (by simp only [atoms, List.mem_append]; exact Or.inr ha) obtain ⟨ihp_pos, ihp_neg⟩ := ihp hsp obtain ⟨ihq_pos, ihq_neg⟩ := ihq hsq refine ⟨?_, ?_⟩ · intro hev by_cases hp : eval v p · exact ND.impI (weakening (ihq_pos (hev hp)) (Set.subset_insert _ _)) · apply ND.impI have h1 : ND (insert p (litCtx v ats)) (~p) := weakening (ihp_neg hp) (Set.subset_insert _ _) have h2 : ND (insert p (litCtx v ats)) p := ND.hyp (Set.mem_insert _ _) exact ND.botE (ND.negE h1 h2) · intro hev have hp : eval v p := by by_contra h; exact hev (fun hpp => absurd hpp h) have hq : ¬ eval v q := fun hq => hev (fun _ => hq) apply ND.negI have hpq : ND (insert (p ⟶ q) (litCtx v ats)) (p ⟶ q) := ND.hyp (Set.mem_insert _ _) have hpp : ND (insert (p ⟶ q) (litCtx v ats)) p := weakening (ihp_pos hp) (Set.subset_insert _ _) have hnq : ND (insert (p ⟶ q) (litCtx v ats)) (~q) := weakening (ihq_neg hq) (Set.subset_insert _ _) exact ND.negE hnq (ND.impE hpq hpp)El código sigue, rama por rama, la demostración anterior: las tres ramas de induction φ
(atom, neg, impl) son los tres casos ya probados, y cada paso matemático reaparece como una
línea de Lean.
Para el lector que no lee Lean, vale la pena traducir los nombres de las tácticas a su contenido
lógico: not_not.1 es la eliminación de la doble negación (), clásicamente
válida, que interviene en el caso de la negación; by_cases hp : eval v p es el análisis por
casos según se cumpla o no , en el caso positivo de la implicación; y
las dos líneas by_contra/absurd extraen y
a partir de
, apoyándose en la equivalencia
clásica .
Vista interna (CIC). Aquí induction φ es Formula.rec (no ND.rec): se recurre sobre la
sintaxis, con motivo
En las ramas neg e impl el recursor proporciona
las hipótesis de inducción ih, ihp, ihq para las subfórmulas inmediatas; la demostración
consume esas hipótesis (a nivel meta) y produce términos ND con constructores (ND.impI,
ND.negI, ND.botE, ND.negE, …) y la regla derivada dni. Así, una sola ejecución de
Formula.rec convierte el valor de verdad de bajo en un árbol de derivación
efectivo: este es el contenido de «completitud como programa» del
Lema 11.18 hecho literal.