Composición, invariantes y condicionales de Hoare

Pregunta central

¿Cómo se demuestra paso a paso que una secuencia de asignaciones o un condicional transforma cualquier estado inicial permitido en un estado que satisface la postcondición?

Mapa de la clase

flowchart LR
    A["Axioma de asignación"] --> WP["Calcular precondiciones hacia atrás"]
    WP --> SEC["Composición secuencial"]
    SEC --> R["Aserciones intermedias"]
    SEC --> INV["Propiedades preservadas"]
    INV --> IP["Invariante de programa"]
    A --> IF["Reglas para condicionales"]
    IF --> T["Rama verdadera"]
    IF --> F["Rama falsa"]
    T --> Q["Postcondición común Q"]
    F --> Q

La idea unificadora es que la ejecución avanza de izquierda a derecha, pero la demostración suele construirse de derecha a izquierda. Se parte de la postcondición deseada, se calcula qué debía ser cierto antes de la última instrucción y se repite hasta llegar al inicio. Cuando el flujo se divide por una condición, se demuestra cada camino por separado y ambos deben terminar garantizando la misma postcondición. (Diap. 1–19; láminas 82–100.)


1. Herramienta básica: sustitución en una asignación

Para una asignación , el axioma de asignación establece:

significa sustituir en cada aparición libre de por ; se lee “ con sustituida por ”. El resultado es la precondición más débil que garantiza .

Sentido de la sustitución

No se “despeja el programa”. Se imagina el valor que tendrá la variable después de la asignación y se reemplaza ese valor dentro de la postcondición.

flowchart RL
    Q["Postcondición Q"] --> S["Sustituir V por E"]
    S --> WP["Q con E en lugar de V"]
    WP --> H["Precondición de V := E"]

En las fórmulas siguientes, representa la aserción vacía { }: no impone ninguna restricción y es verdadera para todo estado.


2. Ejercicios de asignación

Ejemplo a: precondición de

Se quiere completar:

Paso 1. Identificar , y .

Paso 2. Sustituir por :

Paso 3. Simplificar:

Por tanto:

flowchart RL
    Q["Se desea j mayor que 0"] --> S["Sustituir j por i + 1"]
    S --> I["i + 1 mayor que 0"]
    I --> P["i mayor que -1"]

Comprobación: produce ; produce , que no cumple la desigualdad estricta.

Ejemplo b: precondición de

Paso 1. Sustituir por :

Paso 2. Resolver:

Así:

flowchart TD
    Q["Se desea y mayor que 1"] --> S["Sustituir y por x al cuadrado"]
    S --> A["x al cuadrado mayor que 1"]
    A --> B{"¿En qué región está x?"}
    B -->|"x menor que -1"| OK1["Sirve"]
    B -->|"x entre -1 y 1"| NO["No sirve"]
    B -->|"x mayor que 1"| OK2["Sirve"]

Los extremos y se excluyen porque producen , no .

Ejemplo c: postcondición de

Para distinguir momentos, escribimos para el valor inicial y para el final:

  1. .
  2. La asignación produce .
  3. .
  4. Por tanto, .
flowchart LR
    S0["Inicio: x0 mayor que 2"] --> C["x := x al cuadrado"]
    C --> E["x1 = x0 al cuadrado"]
    E --> Q["Final: x1 mayor que 4"]

Note

En una terna se reutiliza normalmente , pero evita confundir el valor anterior con el posterior.

Ejemplo d: precondición de

Paso 1. Sustituir por : .

Paso 2. Incluir el dominio: .

Paso 3. Analizar signos:

Por tanto:

flowchart TD
    X["Valor inicial x"] --> D{"¿x es cero?"}
    D -->|"Sí"| U["La división no está definida"]
    D -->|"No"| SG{"¿x es positivo?"}
    SG -->|"Sí"| POS["El recíproco es positivo; cumple"]
    SG -->|"No"| NEG["El recíproco es negativo; no cumple"]

3. Concatenación de código

La concatenación ejecuta primero y después . El estado final de se convierte en el inicial de . La regla de composición es:

es la aserción intermedia.

flowchart LR
    P["P"] --> C1["Ejecutar C1"]
    C1 --> R["Condición intermedia R"]
    R --> C2["Ejecutar C2"]
    C2 --> Q["Q"]

Para más instrucciones se aplica la regla repetidamente. Las aserciones intermedias suelen calcularse desde hacia atrás.


4. Ejemplo: mitad de una suma

Demostrar:

Paso 1. Última instrucción. Sustituir por :

Paso 2. Primera instrucción. Sustituir por :

Paso 3. Componer (2) y (1):

flowchart LR
    P["Sin restricción"] --> A["c := a + b"]
    A --> R["c = a + b"]
    R --> B["c := c / 2"]
    B --> Q["c = (a + b) / 2"]
    Q -. "verificar hacia atrás" .-> R
    R -. "verificar hacia atrás" .-> P

La tautología explica por qué no hace falta restringir o , suponiendo que dividir entre esté definido.


5. Ejemplo: construir

Se supone que no cambia.

Paso 1. Retroceder por :

Paso 2. Retroceder por :

Paso 3. Retroceder por :

Paso 4. Componer (3), (2) y (1).

flowchart LR
    P["Sin restricción"] --> C1["s := 1"]
    C1 --> R1["s = 1"]
    R1 --> C2["s := s + r"]
    C2 --> R2["s = 1 + r"]
    R2 --> C3["s := s + r al cuadrado"]
    C3 --> Q["s = 1 + r + r al cuadrado"]

Cada aserción intermedia registra cuánto de la postcondición ya se construyó.


6. Ejemplo: intercambiar variables

y representan valores iniciales y no cambian.

Paso 1. Antes de :

Paso 2. Antes de :

Paso 3. Antes de :

Paso 4. Concatenar (3), (2) y (1).

flowchart LR
    S0["Inicio: a = A; b = B"] --> C1["h := a"]
    C1 --> S1["h = A; b = B"]
    S1 --> C2["a := b"]
    C2 --> S2["a = B; h = A"]
    S2 --> C3["b := h"]
    C3 --> S3["Final: a = B; b = A"]

conserva el valor inicial de , que se perdería al ejecutar .


7. Invariante de un programa

Un invariante de programa para un bloque es una aserción preservada:

No exige que las variables conserven sus valores; exige que se conserve la relación expresada por .

Es decir, para cualquier aserción que es a la vez , pre condición y post condición de un código se le denomina invariante.

flowchart LR
    S0["El estado inicial satisface I"] --> C["El bloque cambia el estado"]
    C --> S1["El estado final también satisface I"]

Ejemplo: preservar

Demostrar que es invariante de:

i := i + 1
r := r * 2

Paso 1. Retroceder por :

Paso 2. Retroceder por :

Paso 3. Componer:

flowchart LR
    A["Antes: r = 2 elevado a i"] --> C1["i := i + 1"]
    C1 --> M["r = 2 elevado a i - 1 con el nuevo i"]
    M --> C2["r := 2r"]
    C2 --> B["Después: r = 2 elevado a i"]

Con valores históricos:

Comprobaciones:

  • produce .
  • produce .
flowchart TD
    E1["i = 2; r = 4"] --> B1["Incrementar i; duplicar r"]
    B1 --> F1["i = 3; r = 8"]
    E2["i = 6; r = 64"] --> B2["Incrementar i; duplicar r"]
    B2 --> F2["i = 7; r = 128"]

Relación con los ciclos

Si el bloque es el cuerpo de un ciclo y la propiedad también vale antes de la primera iteración, entonces funciona como invariante de ciclo.


8. Regla para if sin else

En if B then C1:

  1. Si es verdadera, se ejecuta .
  2. Si es falsa, el estado queda igual.

La regla condicional es:

La segunda premisa representa la rama skip.

flowchart TD
    P["Estado satisface P"] --> B{"¿B es verdadera?"}
    B -->|"Sí: P y B"| C1["Ejecutar C1"]
    B -->|"No: P y no B"| SKIP["No hacer nada"]
    C1 --> Q1["Probar Q"]
    SKIP --> Q2["Probar que Q ya se cumplía"]
    Q1 --> Q["Q"]
    Q2 --> Q

Ejemplo: actualizar un máximo

Demostrar:

, , .

Es decir:

Rama falsa. En un orden total:

Como no cambia el estado:

Rama verdadera.

Buscamos demostrar que:

Demostrando la precondición:

Con lo que obtenemos que por debilitamiento:

Asi que podemos fortalecer:

Lo cual es una tautología:

Como cualquier aseveración fortalece la vacía entonces: , por consecuencia:

Las dos ramas garantizan la misma postcondición:

flowchart TD
    I["Valores max y a"] --> B{"¿max es menor que a?"}
    B -->|"Sí"| ASG["max := a"]
    ASG --> EQ["max = a"]
    EQ --> Q["max mayor o igual que a"]
    B -->|"No"| GE["max ya era mayor o igual que a"]
    GE --> Q

9. Regla para if con else

Para if B then C1 else C2, ambas ramas ejecutan código:

flowchart TD
    P["Estado satisface P"] --> B{"Evaluar B"}
    B -->|"Verdadera"| C1["Ejecutar C1 con P y B"]
    B -->|"Falsa"| C2["Ejecutar C2 con P y no B"]
    C1 --> Q1["Probar Q"]
    C2 --> Q2["Probar Q"]
    Q1 --> Q["Postcondición común Q"]
    Q2 --> Q

acompaña a ambas ramas; lo adicional es o . Las dos deben establecer , o una condición más fuerte que implique .

Ejemplo complementario: máximo de dos valores

No aparece desarrollado en las diapositivas, pero aplica la regla:

if a >= b then max := a else max := b

Se desea .

  • Si , produce .
  • Si , produce .
flowchart TD
    S["Valores a y b"] --> D{"¿a es mayor o igual que b?"}
    D -->|"Sí"| A["max := a"]
    D -->|"No"| B["max := b"]
    A --> Q["max domina a y b"]
    B --> Q

10. Método general de demostración

Para una secuencia

  1. Escribir la postcondición final .
  2. Tomar la última asignación y calcular .
  3. Usar el resultado como postcondición de la instrucción anterior.
  4. Repetir hasta llegar al principio.
  5. Verificar que la precondición declarada implica la calculada.
  6. Unir las ternas con la regla de composición.
flowchart RL
    Q["Postcondición final Q"] --> W3["wp de la última instrucción"]
    W3 --> R2["Condición intermedia R2"]
    R2 --> W2["wp de la instrucción anterior"]
    W2 --> R1["Condición intermedia R1"]
    R1 --> W1["wp de la primera instrucción"]
    W1 --> P0["Precondición calculada"]
    P["Precondición dada"] --> IMP["Comprobar P implica P0"]
    P0 --> IMP

Para un condicional

  1. Identificar , y .
  2. Demostrar .
  3. En la rama falsa, añadir .
  4. Sin else, probar .
  5. Con else, demostrar .
  6. Concluir con la regla condicional.

11. Precisiones importantes

Dominio

En , además de sustituir, debe exigirse . Toda operación parcial aporta obligaciones de dominio.

Aserción vacía

{ } representa , la aserción verdadera para todo estado; no una condición imposible.

Valores anteriores y posteriores

En , el lado derecho usa el valor anterior y el izquierdo recibe el nuevo. elimina la ambigüedad.

Invariante de bloque y de ciclo

prueba preservación por un bloque. Para un ciclo también debe demostrarse que vale antes de la primera iteración.

if sin else

La rama falsa no desaparece: ejecuta skip, por lo que debe implicar .


12. Complemento de AllLectures: del programa anotado a la prueba

Una secuencia larga se vuelve más fácil cuando se inserta una aserción en cada punto importante. Por ejemplo, el intercambio queda anotado así:

{X=x₀ ∧ Y=y₀}
R := X;
{R=x₀ ∧ Y=y₀}
X := Y;
{R=x₀ ∧ X=y₀}
Y := R
{Y=x₀ ∧ X=y₀}

Paso 1. Cada asignación se demuestra con sustitución.

Paso 2. La postcondición de un renglón coincide con la precondición del siguiente.

Paso 3. La regla de composición elimina las aserciones intermedias y deja la terna del programa completo.

En un condicional no hay una sola condición intermedia: hay dos obligaciones, una bajo y otra bajo . Ambas deben llegar a la misma .

Ejercicio corto

Demuestra .

Véase también la explicación guiada de condicionales.

13. Referencia diapositiva por diapositiva

Diap.LáminaContenidoSección
182Precondiciones de y §2 a–b
283Postcondición de ; precondición de §2 c–d
384Concatenación de código§3
485Regla de concatenación y ejemplo de la media§3–4
586Demostración de la media§4
687Inicio del ejemplo §5
788Fin del polinomio e inicio del intercambio§5–6
889Demostración del intercambio§6
990Invariante y ejemplo §7
1091Demostración formal del invariante§7
1192Casos e §7
1293Comprobación algebraica§7
1394if sin else§8
1495Regla y diagrama sin else§8
1596Ejemplo del máximo§8
1697Rama verdadera y consecuencia§8
1798Conclusión del ejemplo§8
1899if con else§9
19100Regla y diagrama con else§9

Conceptos atómicos

Dudas para revisar

  • ¿Cómo se extienden estas reglas al while y cómo se demuestra terminación? Véase Ciclos: invariante, salida y variante.
  • ¿Qué notación usará el curso para skip?
  • ¿Los ejercicios asumen números reales, racionales o punto flotante?

Resumen después de clase

El axioma de asignación permite calcular precondiciones por sustitución y demostrar desde la salida hacia la entrada. La composición conecta instrucciones mediante aserciones intermedias, como en la media, el polinomio y el intercambio. Un invariante es una relación preservada aun cuando cambien los valores individuales. En un condicional se analizan por separado y , y ambos caminos deben garantizar la misma postcondición.

Referencia

  • Programación Avanzada Notas 5, diapositivas 1–19 (láminas 82–100).
  • /Users/diegovillalba/Downloads/AllLectures.pdf, páginas PDF 23–33 (composición, condicionales, regla de while e invariantes).