Capítulo 4
La geometría de todas las barras
En el capítulo 2 calculamos la geometría de una barra, y en el capítulo 3 describimos la armadura completa con diccionarios. Ahora unimos ambas cosas: calculamos la longitud y los cosenos directores de todas las barras, sin importar cuántas sean. La herramienta que lo hace posible es el bucle, la instrucción que repite un mismo cálculo para cada elemento de una colección. Con ella, nuestra calculadora deja de depender del tamaño de la armadura.
4.1 El mismo cálculo para cada barra
La geometría de una barra depende solo de las coordenadas de sus dos nodos:
Para el ejemplo A, con tres barras, podríamos copiar tres veces el cálculo del capítulo 2. Pero una armadura de cubierta típica tiene entre 20 y 50 barras, y la de un puente, varios cientos. Copiar el cálculo sería tedioso y, sobre todo, una fuente segura de errores: basta con olvidar cambiar un número en una de las copias.
Lo que necesitamos es escribir el cálculo una sola vez y pedirle a
Python que lo repita para cada barra del diccionario barras.
4.2 El bucle for
Un bucle (loop) for repite un bloque de instrucciones para cada
elemento de una colección. En cada repetición, la variable que sigue a
for toma el valor del elemento siguiente:
for numero in [1, 2, 3]:
print(f"Barra {numero}")
Barra 1 Barra 2 Barra 3
Si recorremos un diccionario, la variable toma, una por una, sus claves. Con cada clave podemos leer el valor correspondiente:
for numero in barras:
print(numero, barras[numero]["area"])
1 0.002 2 0.002 3 0.002
Casi siempre necesitamos la clave y el valor a la vez. El método
.items() entrega ambos en cada repetición, y los recibimos en dos
variables separadas por una coma:
for numero, barra in barras.items():
i = barra["nodo_inicial"]
j = barra["nodo_final"]
print(f"Barra {numero}: del nodo {i} al nodo {j}")
Barra 1: del nodo 1 al nodo 2 Barra 2: del nodo 2 al nodo 3 Barra 3: del nodo 1 al nodo 3
Un uso muy frecuente de los bucles es acumular un resultado. Empezamos
con un valor inicial y, en cada repetición, le sumamos algo. El operador
+= suma al valor que la variable ya tenía: a += b equivale a
a = a + b. Así obtenemos, por ejemplo, la longitud total de las barras:
longitud_total = 0.0
for numero, barra in barras.items():
inicial = nodos[barra["nodo_inicial"]]
final = nodos[barra["nodo_final"]]
dx = final["x"] - inicial["x"]
dy = final["y"] - inicial["y"]
longitud_total += math.sqrt(dx**2 + dy**2)
print(f"Longitud total: {longitud_total:.3f} m")
Longitud total: 16.000 m
4.3 La geometría de todas las barras
Ya tenemos todas las piezas. Recorremos las barras; para cada una, buscamos
sus nodos en el diccionario nodos, calculamos las proyecciones, la
longitud y los cosenos directores, y guardamos el resultado en un diccionario
nuevo, geometria, con el número de barra como clave:
geometria = {}
for numero, barra in barras.items():
inicial = nodos[barra["nodo_inicial"]]
final = nodos[barra["nodo_final"]]
dx = final["x"] - inicial["x"]
dy = final["y"] - inicial["y"]
L = math.sqrt(dx**2 + dy**2)
geometria[numero] = {"longitud": L,
"c": dx / L, "s": dy / L}
Este código forma parte de nuestra calculadora: es la primera pieza que funciona para cualquier armadura descrita con los diccionarios del capítulo 3.
Para presentar los resultados recorremos geometria e imprimimos una fila
por barra. En las f-strings, el número antes del punto indica el
ancho de la columna: :7.3f reserva siete caracteres con tres
decimales, y :>5 alinea el número de barra a la derecha en cinco
caracteres. Así las columnas quedan alineadas:
print("Barra L (m) c s")
for numero, g in geometria.items():
L, c, s = g["longitud"], g["c"], g["s"]
print(f"{numero:>5} {L:7.3f} {c:7.4f} {s:7.4f}")
Barra L (m) c s
1 6.000 1.0000 0.0000
2 5.000 -0.6000 0.8000
3 5.000 0.6000 0.8000
4.4 Verificación
Los valores coinciden con los que calculamos a mano en el capítulo 2: la barra 1 es horizontal (, ), y las barras 2 y 3 son las hipotenusas de dos triángulos 3-4-5, con , y . El signo de en la barra 2 es negativo porque va del nodo 2 al nodo 3, hacia la izquierda.
Una verificación que no depende del ejemplo es que, para toda barra, debe cumplirse :
for numero, g in geometria.items():
suma = g["c"] ** 2 + g["s"] ** 2
print(f"Barra {numero}: c² + s² = {suma:.12f}")
Barra 1: c² + s² = 1.000000000000 Barra 2: c² + s² = 1.000000000000 Barra 3: c² + s² = 1.000000000000
Finalmente, comprobamos que los datos de entrada no cambiaron: la barra 1 conserva sus cuatro datos originales y nada más.
barras[1]
{'nodo_inicial': 1, 'nodo_final': 2, 'elasticidad': 200000000.0, 'area': 0.002}
4.5 Dibujar cualquier armadura
En el capítulo 3 escribimos una línea ax.plot por
cada barra. Con un bucle, el mismo dibujo funciona para cualquier armadura.
Aprovechamos además para escribir el número de cada barra en su punto medio,
con ax.text(x, y, texto):
fig, ax = plt.subplots()
for numero, barra in barras.items():
inicial = nodos[barra["nodo_inicial"]]
final = nodos[barra["nodo_final"]]
ax.plot([inicial["x"], final["x"]],
[inicial["y"], final["y"]], "k-o")
# Número de la barra en su punto medio
xm = (inicial["x"] + final["x"]) / 2
ym = (inicial["y"] + final["y"]) / 2
ax.text(xm, ym, f" {numero}", color="tab:blue")
ax.set_aspect("equal")
ax.set_title("Ejemplo A")
plt.show()
Problema resuelto 4.1 · La geometría del ejemplo B
Calcular la longitud y los cosenos directores de las seis barras del ejemplo B (figura 3.2) y verificar los resultados.
Solución
Aplicamos el mismo bucle a los diccionarios nodos_b y barras_b del
capítulo 3, y guardamos los resultados en
geometria_b:
geometria_b = {}
for numero, barra in barras_b.items():
inicial = nodos_b[barra["nodo_inicial"]]
final = nodos_b[barra["nodo_final"]]
dx = final["x"] - inicial["x"]
dy = final["y"] - inicial["y"]
L = math.sqrt(dx**2 + dy**2)
geometria_b[numero] = {"longitud": L,
"c": dx / L, "s": dy / L}
print("Barra L (m) c s")
for numero, g in geometria_b.items():
L, c, s = g["longitud"], g["c"], g["s"]
print(f"{numero:>5} {L:7.3f} {c:7.4f} {s:7.4f}")
Barra L (m) c s
1 3.000 1.0000 0.0000
2 4.000 0.0000 1.0000
3 3.000 1.0000 0.0000
4 4.000 0.0000 1.0000
5 5.000 0.6000 0.8000
6 5.000 -0.6000 0.8000
Verificación. Las barras 1 y 3 son horizontales de 3 m y las barras 2 y 4, verticales de 4 m. Las diagonales 5 y 6 miden 5 m; la diagonal 5 sube hacia la derecha () y la 6, hacia la izquierda (), como se ve en la figura.
Para resolver el problema copiamos el bucle y cambiamos los nombres de los diccionarios. Funciona, pero es justamente la clase de repetición que queríamos evitar. En el capítulo 5 aprenderemos a darle un nombre a un cálculo, con una función, para usarlo con cualquier armadura sin copiarlo.
Repaso y resumen del capítulo
- El bucle
forrepite el bloque sangrado debajo de él para cada elemento de una colección. - Al recorrer un diccionario,
forentrega sus claves;.items()entrega la clave y el valor a la vez. - Un acumulador empieza con un valor inicial y crece en cada repetición con
+=. - La geometría de todas las barras se guarda en un diccionario nuevo,
geometria, con el número de barra como clave. Los datos de entrada no se modifican. - En las f-strings,
:7.3ffija el ancho y los decimales de una columna, y:>5alinea a la derecha. - Para toda barra debe cumplirse .
Ejercicios
Ejercicio 4.1
Modificar el bucle de la geometría para calcular también el ángulo de cada barra del ejemplo A, en grados, y mostrarlo para las tres barras.
Ver la solución
geometria_con_angulo = {}
for numero, barra in barras.items():
inicial = nodos[barra["nodo_inicial"]]
final = nodos[barra["nodo_final"]]
dx = final["x"] - inicial["x"]
dy = final["y"] - inicial["y"]
theta = math.degrees(math.atan2(dy, dx))
geometria_con_angulo[numero] = {"theta": theta}
for numero, g in geometria_con_angulo.items():
print(f"Barra {numero}: θ = {g['theta']:.2f}°")
Barra 1: θ = 0.00° Barra 2: θ = 126.87° Barra 3: θ = 53.13°
Ejercicio 4.2
El peso específico del acero es . Calcular el peso propio total de las barras del ejemplo A, sabiendo que el peso de una barra es .
Ver la solución
Usamos las longitudes ya calculadas en el diccionario geometria:
gamma = 78.5 # peso específico del acero, en kN/m³
peso_total = 0.0
for numero, barra in barras.items():
L = geometria[numero]["longitud"]
peso_total += gamma * barra["area"] * L
print(f"Peso propio: {peso_total:.3f} kN")
Peso propio: 2.512 kN
Ejercicio 4.3
Calcular la longitud total de las barras del ejemplo B con un acumulador.
Ver la solución
longitud_total_b = 0.0
for numero, barra in barras_b.items():
inicial = nodos_b[barra["nodo_inicial"]]
final = nodos_b[barra["nodo_final"]]
dx = final["x"] - inicial["x"]
dy = final["y"] - inicial["y"]
longitud_total_b += math.sqrt(dx**2 + dy**2)
print(f"Longitud total: {longitud_total_b:.3f} m")
Longitud total: 24.000 m
El resultado coincide con la suma de las longitudes de la tabla del problema resuelto 4.1: .
Ejercicio 4.4
Dibujar la armadura del ejemplo B con un bucle, numerando sus barras, y comparar el dibujo con la figura 3.2.
Ver la solución
fig, ax = plt.subplots()
for numero, barra in barras_b.items():
inicial = nodos_b[barra["nodo_inicial"]]
final = nodos_b[barra["nodo_final"]]
ax.plot([inicial["x"], final["x"]],
[inicial["y"], final["y"]], "k-o")
xm = (inicial["x"] + final["x"]) / 2
ym = (inicial["y"] + final["y"]) / 2
ax.text(xm, ym, f" {numero}", color="tab:blue")
ax.set_aspect("equal")
ax.set_title("Ejemplo B")
plt.show()
Los números de las diagonales 5 y 6 se superponen, porque ambas barras tienen
el mismo punto medio. Una mejora sencilla es ubicar cada número en otro punto
de su barra, por ejemplo al 40 % de su longitud desde el nodo
inicial: xm = inicial["x"] + 0.4 * (final["x"] - inicial["x"]), y lo
mismo para ym.