Análisis matricial de armaduras con Python

Anexo B

Leer un error de Python

Cometer errores al programar es inevitable, y no es una mala señal: es parte del trabajo. Lo que distingue a quien programa con soltura no es que no se equivoque, sino que sabe leer lo que Python le dice cuando algo falla. Este anexo enseña a leer esos mensajes con los errores más frecuentes al construir la calculadora. Todos los mensajes que se muestran son reales: los produjo el código del libro.

B.1 Cómo se lee un mensaje de error

Supongamos que, al describir una armadura, escribimos por descuido un nodo que no existe: la barra 2 del ejemplo A va al nodo 4, pero la armadura solo tiene tres nodos.

barras_mal = {
    1: barra(1, 2, 0.0020),
    2: barra(2, 4, 0.0020),
    3: barra(3, 1, 0.0020),
}
geometria_mal = calcular_geometria(nodos, barras_mal)

Python se detiene y muestra este mensaje:

Traceback (most recent call last):
  File "<celda 44>", line 6, in <module>
    geometria_mal = calcular_geometria(nodos, barras_mal)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<celda 9>", line 10, in calcular_geometria
    final = nodos[barra["nodo_final"]]
            ~~~~~^^^^^^^^^^^^^^^^^^^^^
KeyError: 4

El mensaje se llama traza (traceback) y se lee de abajo hacia arriba:

  1. La última línea dice qué pasó. Primero el tipo de error, KeyError, y después el detalle: la clave 4 no existe en el diccionario.
  2. Las líneas anteriores dicen dónde pasó. Cada bloque que empieza con File es un paso. El primero es nuestra celda; el segundo, la línea 10 de la función que llamamos, calcular_geometria, que busca el nodo final de cada barra. Los signos ~ y ^ subrayan la parte exacta de la línea que falló.
  3. El primer bloque es nuestra celda. Ahí está la línea que escribimos nosotros y que desencadenó el error.

Que el error aparezca dentro de una función no significa que la función esté mal: casi siempre el problema está en los datos que le dimos. Aquí, calcular_geometria buscó el nodo final de una barra y no lo encontró, porque nuestro diccionario barras_mal usa un nodo inexistente.

B.2 Un nombre mal escrito

El error más frecuente de todos es escribir mal un nombre:

geometria = calcular_geometria(nodos, barras)
rigideces = calcular_rigideces(barras, geometira)
Traceback (most recent call last):
  File "<celda 45>", line 2, in <module>
    rigideces = calcular_rigideces(barras, geometira)
                                           ^^^^^^^^^
NameError: name 'geometira' is not defined. Did you mean: 'geometria'?

Python no conoce geometira y, como el nombre se parece a uno que sí existe, nos sugiere el correcto (Did you mean, ¿quisiste decir?).

Un NameError aparece también cuando usamos una variable o una función cuya celda no hemos ejecutado: por ejemplo, al abrir de nuevo un notebook, porque Colab no recuerda nada de la sesión anterior. La solución es ejecutar las celdas anteriores, o todas con Entorno de ejecución > Ejecutar todas.

B.3 Errores de escritura

Algunos errores impiden que Python entienda el código, y aparecen antes de que se ejecute una sola línea. El más común es olvidar los dos puntos al final de un for, un if o un def:

for numero in barras
    print(numero)
  File "<celda 46>", line 1
    for numero in barras
                        ^
SyntaxError: expected ':'

El mensaje no empieza con Traceback, porque el código no llegó a ejecutarse: indica la línea y, con el signo ^, el lugar donde Python esperaba los dos puntos.

El otro error de escritura frecuente es olvidar la sangría del bloque que sigue a un for:

for numero in barras:
print(numero)
  File "<celda 47>", line 2
    print(numero)
    ^^^^^
IndentationError: expected an indented block after 'for' statement on line 1

B.4 Datos equivocados

Si llamamos a una función con menos argumentos de los que necesita, el mensaje dice cuál falta. Aquí olvidamos la longitud de la barra:

k = rigidez_local(2.0e8, 0.0020)
Traceback (most recent call last):
  File "<celda 48>", line 1, in <module>
    k = rigidez_local(2.0e8, 0.0020)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: rigidez_local() missing 1 required positional argument: 'L'

Un IndexError indica que pedimos una posición que no existe. En la calculadora casi siempre se debe a confundir la numeración desde 1 con los índices desde 0. Queremos el desplazamiento vertical del nodo 3, que es el grado de libertad 6, y lo pedimos con U[6]:

resultados = analizar_armadura(nodos, barras,
                               apoyos, cargas)
U = resultados["U"]
uy_3 = U[6]
Traceback (most recent call last):
  File "<celda 49>", line 4, in <module>
    uy_3 = U[6]
           ~^^^
IndexError: index 6 is out of bounds for axis 0 with size 6

U tiene seis elementos, con índices de 0 a 5. El grado de libertad 6 está en la posición 5, y la forma segura de obtenerla es con la función indices del capítulo 6.

Una división por cero suele indicar dos nodos con las mismas coordenadas: la barra que los une tiene longitud nula.

nodos_mal = {
    1: {"x": 0.0, "y": 0.0},
    2: {"x": 6.0, "y": 0.0},
    3: {"x": 6.0, "y": 0.0},
}
geometria_mal = calcular_geometria(nodos_mal, barras)
Traceback (most recent call last):
  File "<celda 50>", line 6, in <module>
    geometria_mal = calcular_geometria(nodos_mal, barras)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<celda 9>", line 15, in calcular_geometria
    "c": dx / L, "s": dy / L}
         ~~~^~~
ZeroDivisionError: float division by zero

B.5 Los errores de la calculadora

Algunos errores los produce la propia calculadora, a propósito, cuando detecta datos que no tienen solución. Si a la armadura le falta un apoyo, Kll\mK_{ll} es singular y resolver_desplazamientos se detiene con un mensaje propio (capítulo 9):

apoyos_mal = {1: {"x": True, "y": True}}
resultados = analizar_armadura(nodos, barras,
                               apoyos_mal, cargas)
Traceback (most recent call last):
  File "<celda 51>", line 2, in <module>
    resultados = analizar_armadura(nodos, barras,
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<celda 37>", line 13, in analizar_armadura
    U_l = resolver_desplazamientos(
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<celda 24>", line 4, in resolver_desplazamientos
    raise ValueError("La armadura es inestable: "
ValueError: La armadura es inestable: K_ll es singular.

La traza recorre tres pasos: nuestra celda llamó a analizar_armadura, esta llamó a resolver_desplazamientos, y ahí se detectó el problema. El mensaje está en español porque lo escribimos nosotros.

B.6 Los errores que no avisan

Los errores más peligrosos son los que no producen ningún mensaje: una carga en la unidad equivocada, una coordenada mal escrita o un apoyo en el nodo equivocado dan resultados, pero resultados falsos. Contra ellos solo sirve la verificación: comprobar el equilibrio, comparar el dibujo con la armadura y preguntarse si los números tienen sentido físico, como hicimos a lo largo del libro. La tabla 12.1 del capítulo 12 resume los síntomas más comunes.