Modelos y Simulación · Tema 35

Verificación

Comprobar que el programa hace lo que el modelo dice: cazar bugs antes de discutir la realidad.

01 · Punto de partida

¿El código obedece al modelo?

En el Tema 34 leímos tablas y gráficos como si el programa fuera fiel. Pero todo análisis pende de una pregunta previa: ¿el simulador implementa de verdad las reglas que escribimos en el modelo? Un signo cambiado, un tope olvidado o una agenda desordenada producen resultados nítidos, estables… y falsos.

Verificar es auditar el programa contra el modelo, con casos de respuesta conocida. No pregunta si el modelo es bueno (eso es validación, Tema 36): pregunta si el código lo ejecuta sin traicionarlo.

  • ¿Cada regla del modelo tiene su caso testigo?
  • ¿Los invariantes se cumplen en cada paso?
  • ¿Dos motores distintos empatan ante lo mismo?
  • ¿Un cambio mínimo rompe algún testigo?

02 · Vocabulario mínimo

Testigo, invariante y traza

◉

Testigo

Caso chico de respuesta calculada a mano: si el programa no lo empata, hay bug.

⬔

Invariante

Condición que vale siempre: nivel entre 0 y tope, agenda ordenada, stock que conserva.

▤

Traza

Paso a paso registrado de una corrida testigo: permite ver dónde se desvía el programa.

Qué testigo custodia cada pieza.
PiezaTestigo mínimoQué delata
TransiciónEntradas [3, 3, 3] con salida 2Signos y acumulaciones mal hechas
ContornoNivel que intenta pasar el tope o bajar de 0Topes y pisos olvidados
AgendaEventos que entran desordenadosCola que no ordena por tiempo
AzarMisma semilla, misma historiaGenerador global contaminado

03 · Tres técnicas

Cómo se verifica un simulador

  1. 1
    Casos testigo.

    Entradas chicas con salida a mano. Rápidos, exactos, automáticos con assert.

  2. 2
    Invariantes.

    Afirmaciones que el programa chequea en cada paso: rangos, conservación, orden de la agenda.

  3. 3
    Doble vía.

    Dos implementaciones (paso fijo vs eventos, o Python vs planilla) deben empatar ante lo mismo.

Probar a ojo

Lento y olvidadizo

Correr, mirar el gráfico y decir «parece bien». No se repite solo, no avisa cuando algo se rompe después.

Testigos automáticos

Rápido y memorioso

Asserts que corren en un segundo y gritan ante cualquier cambio. La verificación viaja con el código.

Verificación
Comprobar que el programa implementa fielmente el modelo.
Testigo
Caso de respuesta conocida que custodia una regla.
Invariante
Propiedad que debe valer en todo paso de toda corrida.
Regresión
Batería de testigos que se corre ante cada cambio.

04 · Protocolo

La auditoría en cinco pasos

Testigos→Invariantes→Trazas→Doble vía→Regresión

Cada pieza nace con su testigo, cada corrida chequea invariantes, cada bug se persigue en la traza, cada motor se empata con su doble vía y todo queda en una regresión que corre ante cada cambio.

05 · En Python

Testigos con assert

El testigo más barato es un assert con historia conocida. Si alguien rompe la transición, grita al instante:

Python en tu navegador. Verificar es stdlib: asserts, chequeos y generadores propios.

def simular(n0, entradas, salida, tope=40):
    n = n0
    h = []
    for e in entradas:
        n = min(max(n + e - salida, 0), tope)
        h.append(n)
    return h


assert simular(10, [3, 3, 3, 3], 2) == [11, 12, 13, 14]
assert simular(39, [5], 0) == [40]  # el tope recorta
assert simular(1, [0], 5) == [0]    # el piso sostiene
print("testigos OK")

Invariantes dentro del motor

El invariante vive en el código y vigila cada paso, no solo los casos de prueba:

def simular_vigilada(n0, entradas, salida, tope=40):
    n = n0
    h = []
    for e in entradas:
        n = min(max(n + e - salida, 0), tope)
        assert 0 <= n <= tope, f"invariante roto: {n}"
        h.append(n)
    return h


print(simular_vigilada(10, [3, 3, 3], 2))

06 · Exploración

Laboratorio: cazar el bug del tope

Este simulador trae un bug inyectable: con el interruptor en sin tope, el nivel crece sin cota y viola el invariante. Activá el bug, subí las entradas y mirá cómo los testigos lo delatan.

EXPERIMENTO 35

Testigos contra bug

invariante: 0 ≤ nivel ≤ tope

Los resultados numéricos aparecen debajo.
Nivel final0
Testigos3 de 3
InvarianteOK
LecturaVerificado

Con el tope puesto, los 3 testigos pasan y el invariante se cumple.

Curva del nivel en 12 pasos. Línea punteada: tope 40. Sin tope, la curva lo atraviesa y los testigos fallan.

Preguntas para explorar

  1. Sacá el tope con entrada 5. ¿Cuántos testigos fallan y cuál es el primero en gritar?
  2. Bajá la entrada a 1 sin tope. ¿El bug se esconde? ¿Qué enseña eso sobre testigos «cómodos»?
  3. ¿Por qué la semilla no cambia este resultado? ¿Cuándo sí importaría?
Ver respuestas sugeridas
  1. Fallan los que tocan el tope: el nivel supera 40 y el invariante se rompe. El testigo de recorte es el primero.
  2. Sí: con entradas chicas el nivel nunca llega al tope y el bug duerme. Los testigos deben forzar los bordes, no el caso cómodo.
  3. Porque este modelo es determinístico: misma entrada, misma salida. La semilla importaría con llegadas o ruidos aleatorios.

07 · Comprensión

Confusiones frecuentes

«Si corre, está verificado»

Correr prueba sintaxis, no fidelidad. Verificado es empatar testigos calculados a mano.

«Verificado es válido»

No: verificado obedece al modelo; válido representa a la realidad. Un modelo equivocado puede estar perfectamente verificado.

«Los asserts son para novatos»

Son la regresión del profesional: corren en un segundo y custodian cada cambio futuro.

«Con un testigo alcanza»

Un testigo cubre un camino. Hacen falta bordes: tope, piso, agenda desordenada, semilla repetida.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: escribir el testigo del tope

Completá el assert que custodia el recorte en 40.

def simular(n0, entradas, salida, tope=40):
    n = n0
    h = []
    for e in entradas:
        n = min(max(n + e - salida, 0), tope)
        h.append(n)
    return h


assert simular(39, [5], 0) == [40]
print("tope OK")
Ver solución razonada

Sin min(..., 40) daría [44] y el assert fallaría: el testigo delata el tope olvidado.

Ejercicio 2: vigilar el invariante

Agregá el chequeo que rompe la corrida en cuanto el nivel sale de rango.

def simular_vigilada(n0, entradas, salida, tope=40):
    n = n0
    for e in entradas:
        n = min(max(n + e - salida, 0), tope)
        assert 0 <= n <= tope
    return n


print(simular_vigilada(10, [3, 3, 3], 2))
Ver solución

Devuelve 13 sin protestar. Si el invariante se violara, el mensaje señalaría el paso exacto del crimen.

Ejercicio 3: doble vía con dos motores

Empatá el motor paso a paso con su versión «de una línea» ante el mismo escenario.

Ver una posible respuesta
def motor_a(n0, entradas, salida):
    n = n0
    return [n := max(n + e - salida, 0) for e in entradas]

def motor_b(n0, entradas, salida):
    h, n = [], n0
    for e in entradas:
        n = max(n + e - salida, 0)
        h.append(n)
    return h


assert motor_a(10, [3, 3, 3], 2) == motor_b(10, [3, 3, 3], 2)
print("doble vía OK")

Si ambas vías empatan en [11, 12, 13], el motor pasa la doble vía; si difieren, al menos una miente.

09 · Síntesis

Ideas para recordar

  • Verificar es comprobar que el programa obedece al modelo, no a la realidad.
  • Cada regla tiene su testigo calculado a mano y automático.
  • Los invariantes vigilan cada paso; las trazas localizan el desvío.
  • La doble vía empata dos implementaciones ante lo mismo.
  • En Python: assert para testigos e invariantes, regresión ante cada cambio.

En el próximo tema cruzaremos el puente hacia la realidad: la validación.