Capítulo 12
Mi calculadora de armaduras
A lo largo de los capítulos anteriores construimos, pieza por pieza, todas las etapas del método de rigidez. Cada una es una función que podemos leer, entender y verificar. Pero para analizar una armadura todavía tenemos que llamarlas una por una, en el orden correcto, como hicimos cada vez con el ejemplo B. En este capítulo damos el último paso: reunimos todas las funciones en una calculadora que, con los datos de una armadura, entrega sus desplazamientos, reacciones y fuerzas, verifica los resultados y los dibuja. Y la ponemos a prueba con una armadura de puente de 21 barras.
12.1 El camino recorrido
La figura 12.1 resume la calculadora. Los datos describen la armadura (capítulo 3); de ellos salen la geometría y la rigidez de cada barra (capítulos 4 y 5); la numeración de los grados de libertad (capítulo 6) permite ensamblar el sistema y particionarlo (capítulos 7 y 8); su solución da los desplazamientos y las reacciones (capítulo 9), y de los desplazamientos salen las fuerzas axiales (capítulo 10). Por último, los resultados se presentan, se verifican y se dibujan.
Las etapas de la calculadora, cada una con el capítulo donde la construimos. La función analizar\_armadura encadena los pasos de cálculo.
12.2 Una función que lo hace todo
Una función puede llamar a otras funciones. La que escribimos ahora no calcula nada por sí misma: llama, en orden, a todas las que escribimos antes, pasando el resultado de cada etapa a la siguiente. Es la traducción directa de la figura 12.1:
def analizar_armadura(nodos, barras, apoyos, cargas):
"""Análisis completo de una armadura plana.
Devuelve un diccionario con los resultados."""
geometria = calcular_geometria(nodos, barras)
rigideces = calcular_rigideces(barras, geometria)
K = ensamblar_rigidez(nodos, barras, rigideces)
F = ensamblar_cargas(nodos, cargas)
libres, restringidos = clasificar_gdl(nodos, apoyos)
K_ll, K_lr, K_rl, K_rr = particionar(K, libres,
restringidos)
U_r = np.zeros(len(restringidos))
U_l = resolver_desplazamientos(
K_ll, K_lr, F[indices(libres)], U_r)
U = np.zeros(2 * len(nodos))
U[indices(libres)] = U_l
U[indices(restringidos)] = U_r
R = calcular_reacciones(K_rl, K_rr, U_l, U_r,
F[indices(restringidos)])
N = fuerzas_axiales(barras, geometria, U)
return {"geometria": geometria, "K": K, "F": F,
"U": U, "R": R, "N": N, "libres": libres,
"restringidos": restringidos}
Con una sola llamada obtenemos todos los resultados del ejemplo A, y las fuerzas son, como debe ser, las mismas del capítulo 10:
resultados = analizar_armadura(nodos, barras, apoyos,
cargas)
print(list(resultados.keys()))
for numero, fuerza in resultados["N"].items():
print(f"Barra {numero}: N = {fuerza:.2f} kN")
['geometria', 'K', 'F', 'U', 'R', 'N', 'libres', 'restringidos'] Barra 1: N = 28.75 kN Barra 2: N = -47.92 kN Barra 3: N = -14.58 kN
12.3 El informe de resultados
Con los resultados en un diccionario, escribimos una función que los presenta en tres tablas: los desplazamientos de cada nodo, las reacciones y las fuerzas axiales. Para las reacciones necesitamos el nodo y la dirección de cada grado de libertad restringido , que obtenemos invirtiendo la numeración del capítulo 6: el nodo es redondeado hacia abajo, y la dirección es si es impar.
def mostrar_resultados(nodos, barras, resultados):
"""Tablas de desplazamientos, reacciones y fuerzas."""
U = resultados["U"]
R = resultados["R"]
N = resultados["N"]
print("Desplazamientos de los nodos (mm)")
filas = []
for nodo in nodos:
ix, iy = indices(gdl_del_nodo(nodo))
filas.append([str(nodo), U[ix] * 1000,
U[iy] * 1000])
mostrar_tabla(["Nodo", "ux", "uy"], filas,
decimales=3)
print("\nReacciones (kN)")
filas = []
restringidos = resultados["restringidos"]
for posicion, g in enumerate(restringidos):
nodo = (g + 1) // 2
if g % 2 == 1:
direccion = "x"
else:
direccion = "y"
filas.append([str(nodo), direccion, R[posicion]])
mostrar_tabla(["Nodo", "Dirección", "R"], filas)
print("\nFuerzas axiales")
filas = []
for numero, fuerza in N.items():
sigma = fuerza / barras[numero]["area"] / 1000
filas.append([str(numero), fuerza, sigma,
clasificar_fuerza(fuerza)])
encabezados = ["Barra", "N (kN)", "σ (MPa)", "Estado"]
mostrar_tabla(encabezados, filas)
mostrar_resultados(nodos, barras, resultados)
Desplazamientos de los nodos (mm)
Nodo ux uy
1 0.000 0.000
2 0.431 0.000
3 0.563 -0.650
Reacciones (kN)
Nodo Dirección R
1 x -20.00
1 y 11.67
2 y 38.33
Fuerzas axiales
Barra N (kN) σ (MPa) Estado
1 28.75 14.38 tracción
2 -47.92 -23.96 compresión
3 -14.58 -7.29 compresión
La verificación del equilibrio, global y de cada nodo, también se convierte en una función, que reúne las de los capítulos 9 y 10 e informa si ambas se cumplen:
def verificar_resultados(nodos, barras, resultados,
tolerancia=1e-6):
"""Equilibrio global y de cada nodo. Devuelve True
si ambos se cumplen dentro de la tolerancia."""
F, R = resultados["F"], resultados["R"]
restringidos = resultados["restringidos"]
suma_x, suma_y, momento = comprobar_equilibrio(
nodos, F, R, restringidos)
global_ok = max(abs(suma_x), abs(suma_y),
abs(momento)) < tolerancia
nodal_ok = equilibrio_nodal(
nodos, barras, resultados["geometria"],
resultados["N"], F, R, restringidos) < tolerancia
for nombre, correcto in (("global", global_ok),
("nodal", nodal_ok)):
if correcto:
print(f"Equilibrio {nombre}: correcto")
else:
print(f"Equilibrio {nombre}: ERROR")
return global_ok and nodal_ok
Y lo mismo con los gráficos del capítulo 11. Para que las armaduras largas y bajas, como las de los puentes, se vean bien, la función coloca los dos paneles uno sobre otro cuando la armadura es más de dos veces más ancha que alta:
def graficar_resultados(nodos, barras, resultados,
titulo="", decimales=1,
tamano=None):
"""Deformada y diagrama de fuerzas, en dos paneles:
uno sobre otro si la armadura es muy alargada."""
U, N = resultados["U"], resultados["N"]
xs = []
ys = []
for punto in nodos.values():
xs.append(punto["x"])
ys.append(punto["y"])
ancho = max(xs) - min(xs)
alto = max(ys) - min(ys)
if ancho > 2 * alto:
fig, (ax1, ax2) = plt.subplots(2, 1,
figsize=(4.5, 3.6))
else:
fig, (ax1, ax2) = plt.subplots(1, 2,
figsize=(4.5, 2.4))
escala = escala_automatica(nodos, U)
dibujar_armadura(ax1, nodos, barras, estilo="--")
deformados = nodos_desplazados(nodos, U, escala)
dibujar_armadura(ax1, deformados, barras,
color=PRINCIPAL)
ax1.set_title(f"Deformada (×{escala:.0f})")
ax1.axis("off")
dibujar_fuerzas(ax2, nodos, barras, N, decimales,
tamano)
ax2.set_title("Fuerzas axiales (kN)")
fig.suptitle(titulo)
fig.tight_layout()
plt.show()
Con estas funciones, el ejemplo B, que en cada capítulo nos costó una docena de líneas, se resuelve, verifica y dibuja en tres:
resultados_b = analizar_armadura(nodos_b, barras_b,
apoyos_b, cargas_b)
verificar_resultados(nodos_b, barras_b, resultados_b)
graficar_resultados(nodos_b, barras_b, resultados_b,
"Ejemplo B")
Equilibrio global: correcto Equilibrio nodal: correcto
12.4 El ejemplo C: un puente peatonal
Pongamos la calculadora a prueba con una armadura de verdad: un puente peatonal de tipo Pratt, de 18 m de luz y 3 m de altura, con seis paneles de 3 m (figura 12.2). Los cordones y las diagonales extremas tienen 30 cm² de área; los montantes y las diagonales interiores, 15 cm². El tablero transmite una carga de 25 kN a cada nodo interior del cordón inferior.
Ejemplo C: puente peatonal tipo Pratt, con la numeración de sus 12 nodos y 21 barras.
Con 12 nodos y 21 barras, escribir los datos a mano como en el capítulo 3 sería largo y propenso a errores. Las coordenadas siguen un patrón, así que las generamos con bucles. Para las barras escribimos una pequeña función que arma el diccionario de cada una, de modo que cada barra ocupa una sola expresión. Las áreas se definen una vez como constantes, en mayúsculas por convención:
# Ejemplo C: puente peatonal tipo Pratt, 6 paneles de
# 3 m (luz de 18 m) y 3 m de altura. Unidades: kN y m
# Nodos: cordón inferior (1 a 7) y superior (8 a 12)
nodos_c = {}
for k in range(7):
nodos_c[k + 1] = {"x": 3.0 * k, "y": 0.0}
for k in range(5):
nodos_c[k + 8] = {"x": 3.0 * (k + 1), "y": 3.0}
def barra(i, j, area, elasticidad=2.0e8):
"""Datos de una barra del nodo i al nodo j."""
return {"nodo_inicial": i, "nodo_final": j,
"elasticidad": elasticidad, "area": area}
A_CORDON = 0.0030 # m², cordones y diagonales extremas
A_ALMA = 0.0015 # m², montantes y diagonales
barras_c = {
# Cordón inferior
1: barra(1, 2, A_CORDON), 2: barra(2, 3, A_CORDON),
3: barra(3, 4, A_CORDON), 4: barra(4, 5, A_CORDON),
5: barra(5, 6, A_CORDON), 6: barra(6, 7, A_CORDON),
# Cordón superior
7: barra(8, 9, A_CORDON), 8: barra(9, 10, A_CORDON),
9: barra(10, 11, A_CORDON),
10: barra(11, 12, A_CORDON),
# Diagonales extremas
11: barra(1, 8, A_CORDON), 12: barra(12, 7, A_CORDON),
# Montantes
13: barra(2, 8, A_ALMA), 14: barra(3, 9, A_ALMA),
15: barra(4, 10, A_ALMA), 16: barra(5, 11, A_ALMA),
17: barra(6, 12, A_ALMA),
# Diagonales, descendiendo hacia el centro
18: barra(8, 3, A_ALMA), 19: barra(9, 4, A_ALMA),
20: barra(11, 4, A_ALMA), 21: barra(12, 5, A_ALMA),
}
apoyos_c = {
1: {"x": True, "y": True}, # articulado
7: {"x": False, "y": True}, # rodillo
}
# 25 kN en cada nodo interior del cordón inferior
cargas_c = {}
for nodo in range(2, 7):
cargas_c[nodo] = {"x": 0.0, "y": -25.0}
El análisis completo, con su verificación y sus gráficos, es igual que para cualquier otra armadura:
resultados_c = analizar_armadura(nodos_c, barras_c,
apoyos_c, cargas_c)
verificar_resultados(nodos_c, barras_c, resultados_c)
graficar_resultados(nodos_c, barras_c, resultados_c,
"Ejemplo C: puente Pratt",
decimales=0, tamano=7)
Equilibrio global: correcto Equilibrio nodal: correcto
Los resultados muestran el comportamiento típico de una armadura Pratt: el cordón superior trabaja en compresión y el inferior en tracción, como las fibras superior e inferior de una viga; las diagonales interiores, inclinadas hacia el centro, trabajan en tracción, mientras que las extremas, que llevan la carga a los apoyos, y los montantes interiores trabajan en compresión. El montante central no tiene fuerza, y los montantes extremos, de los que «cuelga» la carga de los nodos 2 y 6, trabajan en tracción. La flecha en el centro del vano es de 6.74 mm, del orden de , y cada apoyo recibe 62.5 kN, la mitad de la carga total. Las tablas completas son:
mostrar_resultados(nodos_c, barras_c, resultados_c)
Desplazamientos de los nodos (mm)
Nodo ux uy
1 0.000 0.000
2 0.313 -3.321
3 0.625 -5.695
4 1.125 -6.736
5 1.625 -5.695
6 1.938 -3.321
7 2.250 0.000
8 2.188 -3.071
9 1.688 -5.820
10 1.125 -6.736
11 0.563 -5.820
12 0.063 -3.071
Reacciones (kN)
Nodo Dirección R
1 x 0.00
1 y 62.50
7 y 62.50
Fuerzas axiales
Barra N (kN) σ (MPa) Estado
1 62.50 20.83 tracción
2 62.50 20.83 tracción
3 100.00 33.33 tracción
4 100.00 33.33 tracción
5 62.50 20.83 tracción
6 62.50 20.83 tracción
7 -100.00 -33.33 compresión
8 -112.50 -37.50 compresión
9 -112.50 -37.50 compresión
10 -100.00 -33.33 compresión
11 -88.39 -29.46 compresión
12 -88.39 -29.46 compresión
13 25.00 16.67 tracción
14 -12.50 -8.33 compresión
15 0.00 0.00 sin fuerza
16 -12.50 -8.33 compresión
17 25.00 16.67 tracción
18 53.03 35.36 tracción
19 17.68 11.79 tracción
20 17.68 11.79 tracción
21 53.03 35.36 tracción
Problema resuelto 12.1 · El método de las secciones
Verificar las fuerzas de las barras 3 (cordón inferior) y 8 (cordón superior) del ejemplo C con el método de las secciones.
Solución
Cortamos la armadura por el tercer panel, entre y m, y estudiamos el tramo izquierdo. Por simetría, cada reacción vale la mitad de la carga total, . Tomando momentos respecto del nodo 9, en , solo la barra 3 tiene momento entre las barras cortadas; respecto del nodo 4, en , solo la barra 8:
# Corte por el tercer panel (x entre 6 y 9 m)
R1 = 5 * 25.0 / 2 # reacción en cada apoyo, por simetría
h = 3.0
# Momentos respecto del nodo 9 (x = 6): cordón inferior
N_inferior = (R1 * 6 - 25.0 * 3) / h
# Momentos respecto del nodo 4 (x = 9): cordón superior
N_superior = -(R1 * 9 - 25.0 * 6 - 25.0 * 3) / h
N_c = resultados_c["N"]
print(f"Barra 3: secciones {N_inferior:8.2f} kN, "
f"calculadora {N_c[3]:8.2f} kN")
print(f"Barra 8: secciones {N_superior:8.2f} kN, "
f"calculadora {N_c[8]:8.2f} kN")
Barra 3: secciones 100.00 kN, calculadora 100.00 kN Barra 8: secciones -112.50 kN, calculadora -112.50 kN
Verificación. Ambos métodos dan los mismos valores: la calculadora reproduce el análisis clásico de una armadura isostática.
12.5 Mi calculadora
Llegamos al producto final del libro: un notebook propio, organizado en tres secciones.
- Motor. Todas las funciones que escribimos en el libro. Se ejecuta una vez y no hace falta modificarlo.
- Datos. Los cuatro diccionarios que describen la armadura. Es lo único que cambia de un problema a otro.
- Resultados. La llamada a
analizar_armadura, la verificación, los gráficos y las tablas.
Separar el motor de los datos es la lección más valiosa de este capítulo: la misma herramienta, escrita y verificada una vez, sirve para cualquier armadura.
Los errores más frecuentes al usar la calculadora, y cómo reconocerlos, son los de la tabla 12.1.
Tabla 12.1: Errores frecuentes al usar la calculadora.
| Síntoma | Causa probable | Solución |
|---|---|---|
KeyError | Barra con un nodo inexistente | Revisar barras |
ZeroDivisionError | Dos nodos coinciden | Revisar nodos |
| «Es inestable» | Faltan barras o apoyos | Revisar el dibujo |
| Desplazamientos enormes | Unidades mezcladas | Pasar a kN y m |
| El dibujo no coincide | Datos mal escritos | Corregir los datos |
Repaso y resumen del capítulo
- La función
analizar_armaduraencadena todas las etapas del método y devuelve los resultados en un diccionario. - Tres funciones más presentan, verifican y dibujan los resultados de cualquier armadura.
//es la división entera y%, el resto; con ellos se obtienen el nodo y la dirección de un grado de libertad.- Los datos de una armadura grande pueden generarse con bucles y funciones auxiliares, como
barra. - El notebook final separa el motor, que no cambia, de los datos, que describen cada problema, y de los resultados.
- Toda solución nueva se verifica: equilibrio, dibujo y sentido físico.
Ejercicios
Ejercicio 12.1
Calcular la flecha (el desplazamiento vertical hacia abajo) del nodo 4 del ejemplo C, en el centro del vano, y la relación entre la luz y la flecha.
Ver la solución
El nodo 4 tiene los grados de libertad 7 y 8; la flecha es el desplazamiento vertical con el signo cambiado:
resultados_c = analizar_armadura(nodos_c, barras_c,
apoyos_c, cargas_c)
iy = indices([8])[0] # gdl 8: nodo 4, dirección y
flecha = -resultados_c["U"][iy] * 1000
print(f"Flecha en el centro: {flecha:.2f} mm")
print(f"Luz / flecha: {18000 / flecha:.0f}")
Flecha en el centro: 6.74 mm Luz / flecha: 2672
Una relación luz/flecha del orden de 2700 es muy holgada: los límites de servicio habituales están entre 300 y 1000.
Ejercicio 12.2
Analizar con la calculadora la armadura de cubierta del ejercicio 3.2. ¿Por qué la barra 5 no tiene fuerza?
Ver la solución
Usamos la función barra del ejemplo C para escribir las barras:
nodos_cubierta = {
1: {"x": 0.0, "y": 0.0},
2: {"x": 4.0, "y": 0.0},
3: {"x": 8.0, "y": 0.0},
4: {"x": 4.0, "y": 1.5},
}
barras_cubierta = {
1: barra(1, 2, 0.0015), 2: barra(2, 3, 0.0015),
3: barra(1, 4, 0.0015), 4: barra(4, 3, 0.0015),
5: barra(2, 4, 0.0010),
}
apoyos_cubierta = {
1: {"x": True, "y": True},
3: {"x": False, "y": True},
}
cargas_cubierta = {4: {"x": 0.0, "y": -30.0}}
resultados = analizar_armadura(
nodos_cubierta, barras_cubierta, apoyos_cubierta,
cargas_cubierta)
verificar_resultados(nodos_cubierta, barras_cubierta,
resultados)
mostrar_resultados(nodos_cubierta, barras_cubierta,
resultados)
Equilibrio global: correcto
Equilibrio nodal: correcto
Desplazamientos de los nodos (mm)
Nodo ux uy
1 0.000 0.000
2 0.533 -3.155
3 1.067 0.000
4 0.533 -3.155
Reacciones (kN)
Nodo Dirección R
1 x 0.00
1 y 15.00
3 y 15.00
Fuerzas axiales
Barra N (kN) σ (MPa) Estado
1 40.00 26.67 tracción
2 40.00 26.67 tracción
3 -42.72 -28.48 compresión
4 -42.72 -28.48 compresión
5 0.00 0.00 sin fuerza
En el nodo 2 concurren las barras 1 y 2, horizontales, y la barra 5, vertical. Como el nodo 2 no tiene carga, el equilibrio vertical exige que la barra 5 no tenga fuerza.
Ejercicio 12.3
Agregar al ejemplo C una carga de viento de 10 kN horizontales en cada nodo del cordón superior, además de las cargas verticales, y comparar el diagrama de fuerzas con el original.
Ver la solución
# Viento: 10 kN horizontales en cada nodo superior
cargas_viento = {}
for nodo in range(2, 7):
cargas_viento[nodo] = {"x": 0.0, "y": -25.0}
for nodo in range(8, 13):
cargas_viento[nodo] = {"x": 10.0, "y": 0.0}
resultados_v = analizar_armadura(nodos_c, barras_c,
apoyos_c, cargas_viento)
verificar_resultados(nodos_c, barras_c, resultados_v)
graficar_resultados(nodos_c, barras_c, resultados_v,
"Ejemplo C con viento", decimales=0,
tamano=7)
El viento empuja el puente hacia la derecha: las fuerzas dejan de ser simétricas y la reacción horizontal del apoyo articulado equilibra los 50 kN de viento.
Ejercicio 12.4
Quitar del ejemplo C las diagonales 18 y 19 e intentar analizarlo. Explicar
el mensaje de la calculadora con ayuda de la figura 12.2. La
expresión numero not in (18, 19) es verdadera si el número no es 18 ni 19.
Ver la solución
# Sin las diagonales 18 y 19, el tramo izquierdo
# queda formado por rectángulos sin arriostrar
barras_sin_diagonales = {}
for numero, datos in barras_c.items():
if numero not in (18, 19):
barras_sin_diagonales[numero] = datos
try:
analizar_armadura(nodos_c, barras_sin_diagonales,
apoyos_c, cargas_c)
except ValueError as error:
print(f"No se puede analizar. {error}")
No se puede analizar. La armadura es inestable: K_ll es singular.
Sin las diagonales 18 y 19, los paneles segundo y tercero son rectángulos formados por cordones y montantes, sin ninguna barra que impida que se deformen como un paralelogramo: la armadura es un mecanismo. Además, .