Capítulo 3
Describir una armadura
Antes de calcular cualquier cosa, nuestra calculadora necesita conocer la armadura: dónde están sus nodos, qué barras los unen, de qué material y sección son, cómo está apoyada y qué cargas recibe. En el capítulo anterior guardamos cada coordenada en una variable suelta; con tres barras eso ya resulta incómodo, y con cincuenta sería imposible. En este capítulo aprendemos a organizar toda esa información en estructuras de datos de Python, y construimos el modelo completo del ejemplo A.
3.1 Los datos de una armadura
Recordemos las hipótesis con las que analizamos armaduras: las barras son rectas y se unen en nodos articulados, las cargas actúan solo en los nodos y el material es elástico lineal. Con ellas, cada barra trabaja únicamente a fuerza axial y la armadura queda definida por cuatro grupos de datos:
- Nodos. Las coordenadas de cada nodo.
- Barras. El nodo inicial y el nodo final de cada barra, su módulo de elasticidad y el área de su sección.
- Apoyos. En qué direcciones está impedido el desplazamiento de cada nodo apoyado: un apoyo articulado impide ambas; un rodillo, solo una.
- Cargas. Las componentes y de las fuerzas aplicadas en los nodos.
La figura 3.1 y la tabla 3.1 reúnen estos datos para el ejemplo A, que nos acompañará en los próximos capítulos.
Ejemplo A: armadura triangular isostática.
Tabla 3.1: Datos del ejemplo A. Todas las barras son de acero () con un área de 20 cm².
| Nodo | Coordenadas (m) | Apoyo | Carga (kN) |
|---|---|---|---|
| 1 | articulado | — | |
| 2 | rodillo (impide ) | — | |
| 3 | — | , |
| Barra | Nodo inicial | Nodo final |
|---|---|---|
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 1 | 3 |
3.2 Listas
Una lista (list) es una colección ordenada de valores, escrita entre corchetes y separada por comas. Por ejemplo, las coordenadas del nodo 3:
coordenadas_nodo_3 = [3.0, 4.0]
coordenadas_nodo_3
[3.0, 4.0]
Cada elemento ocupa una posición, llamada índice (index), y se obtiene escribiendo el índice entre corchetes:
coordenadas_nodo_3[0], coordenadas_nodo_3[1]
(3.0, 4.0)
Ese desfase se vuelve peligroso cuando usamos una lista para guardar propiedades de las barras. Si guardamos las áreas de las barras 1, 2 y 3 en una lista (aquí con un área distinta en la barra 3, para notar la diferencia), el área de la barra 3 está en la posición 2:
# Áreas de las barras 1, 2 y 3, en m²
areas = [0.0020, 0.0020, 0.0015]
areas[2]
0.0015
Las listas pueden crecer: el método append agrega un elemento al final, y
la función len cuenta los elementos:
areas.append(0.0030)
len(areas), areas
(4, [0.002, 0.002, 0.0015, 0.003])
3.3 Diccionarios
Un diccionario (dictionary) guarda pares clave: valor, entre llaves. A diferencia de la lista, cada valor se obtiene por su clave, no por su posición:
nodo_3 = {"x": 3.0, "y": 4.0}
nodo_3["y"]
4.0
Las claves pueden ser textos, como "x" e "y", o números. Con números
como claves resolvemos el problema de la sección anterior: el área de la
barra 3 se obtiene con la clave 3, sin traducciones.
# Las claves son los números de barra, empezando en 1
areas = {1: 0.0020, 2: 0.0020, 3: 0.0015}
areas[3]
0.0015
3.4 El modelo del ejemplo A
Un valor de un diccionario puede ser, a su vez, otro diccionario. Con esta idea, los diccionarios anidados (nested dictionaries), escribimos el modelo completo. Primero los nodos, cada uno con sus coordenadas:
# Ejemplo A. Unidades: kN y m
nodos = {
1: {"x": 0.0, "y": 0.0},
2: {"x": 6.0, "y": 0.0},
3: {"x": 3.0, "y": 4.0},
}
Después las barras, cada una con sus nodos y sus propiedades. Un diccionario puede ocupar varias líneas: Python sabe que continúa mientras la llave no se haya cerrado.
# E en kN/m² (acero) y área en m²
barras = {
1: {"nodo_inicial": 1, "nodo_final": 2,
"elasticidad": 2.0e8, "area": 0.0020},
2: {"nodo_inicial": 2, "nodo_final": 3,
"elasticidad": 2.0e8, "area": 0.0020},
3: {"nodo_inicial": 1, "nodo_final": 3,
"elasticidad": 2.0e8, "area": 0.0020},
}
Los apoyos indican, para cada nodo apoyado, si el desplazamiento en y en
está impedido. Los valores True (verdadero) y False (falso) son
los booleanos (booleans) de Python:
# True: el desplazamiento en esa dirección está impedido
apoyos = {
1: {"x": True, "y": True}, # articulado
2: {"x": False, "y": True}, # rodillo
}
Por último, las cargas, con su signo según los ejes globales:
# Cargas en kN, positivas en el sentido de los ejes
cargas = {
3: {"x": 20.0, "y": -50.0},
}
Separar los apoyos y las cargas de los nodos tiene una ventaja: los nodos sin apoyo o sin carga simplemente no aparecen en esos diccionarios, sin entradas vacías que mantener.
3.5 Consultar el modelo
Para leer un valor anidado encadenamos las claves. Los nodos de la barra 2:
barras[2]["nodo_inicial"], barras[2]["nodo_final"]
(2, 3)
Y las coordenadas del nodo inicial de la barra 2, usando el número de nodo
como clave del diccionario nodos:
inicial = barras[2]["nodo_inicial"]
nodos[inicial]["x"], nodos[inicial]["y"]
(6.0, 0.0)
Esta consulta encadenada, de la barra a sus nodos y de los nodos a sus coordenadas, es exactamente la que necesitaremos en el capítulo 4 para calcular la geometría de todas las barras.
El operador in pregunta si una clave existe en un diccionario. El nodo 3
no tiene apoyo, pero sí carga:
3 in apoyos, 3 in cargas
(False, True)
Pedir una clave que no existe, como apoyos[3], produce el error
KeyError y detiene el programa. El método get devuelve en cambio el
valor None («nada»), o el valor que indiquemos:
print(apoyos.get(3))
print(apoyos.get(3, "sin apoyo"))
None sin apoyo
Finalmente, len también cuenta las entradas de un diccionario:
print(f"Nodos: {len(nodos)}, barras: {len(barras)}")
Nodos: 3, barras: 3
3.6 Ver la armadura
Un error en las coordenadas o en la conectividad es fácil de cometer y difícil
de detectar leyendo números. La mejor verificación es ver la armadura.
Para dibujar usamos Matplotlib, la biblioteca de gráficos más usada
de Python, que importamos con el nombre corto plt:
import matplotlib.pyplot as plt
plt.subplots() crea una figura con unos ejes; ax.plot dibuja una
línea entre los puntos cuyas abscisas y ordenadas recibe en dos listas, y el
texto "k-o" pide línea negra (k) continua (-) con círculos en
los extremos (o). Dibujamos así cada barra:
n1, n2, n3 = nodos[1], nodos[2], nodos[3]
fig, ax = plt.subplots()
# Barra 1: del nodo 1 al nodo 2
ax.plot([n1["x"], n2["x"]], [n1["y"], n2["y"]], "k-o")
# Barra 2: del nodo 2 al nodo 3
ax.plot([n2["x"], n3["x"]], [n2["y"], n3["y"]], "k-o")
# Barra 3: del nodo 1 al nodo 3
ax.plot([n1["x"], n3["x"]], [n1["y"], n3["y"]], "k-o")
ax.set_aspect("equal")
ax.set_xlabel("x (m)")
ax.set_ylabel("y (m)")
ax.set_title("Ejemplo A")
plt.show()
La instrucción ax.set_aspect("equal") usa la misma escala en ambos ejes,
para que la armadura no se vea deformada.
Verificación. El dibujo coincide con la figura 3.1: tres nodos en , y , unidos por tres barras.
Ejemplo B: cuadro arriostrado hiperestático.
Problema resuelto 3.1 · El modelo del ejemplo B
Escribir el modelo de datos del ejemplo B (figura 3.2): un cuadro de 3 m por 4 m con sus dos diagonales, articulado en el nodo 1 y con un rodillo en el nodo 2. Todas las barras son de acero, con un área de 20 cm². Verificar la cantidad de nodos y de barras.
Solución
Seguimos la estrategia de resolución: la numeración ya está en la figura, las
unidades son kN y m, y los diccionarios llevan el
sufijo _b para no reemplazar los del ejemplo A:
# Ejemplo B. Unidades: kN y m
nodos_b = {
1: {"x": 0.0, "y": 0.0},
2: {"x": 3.0, "y": 0.0},
3: {"x": 0.0, "y": 4.0},
4: {"x": 3.0, "y": 4.0},
}
barras_b = {
1: {"nodo_inicial": 1, "nodo_final": 2,
"elasticidad": 2.0e8, "area": 0.0020},
2: {"nodo_inicial": 2, "nodo_final": 4,
"elasticidad": 2.0e8, "area": 0.0020},
3: {"nodo_inicial": 3, "nodo_final": 4,
"elasticidad": 2.0e8, "area": 0.0020},
4: {"nodo_inicial": 1, "nodo_final": 3,
"elasticidad": 2.0e8, "area": 0.0020},
5: {"nodo_inicial": 1, "nodo_final": 4,
"elasticidad": 2.0e8, "area": 0.0020},
6: {"nodo_inicial": 2, "nodo_final": 3,
"elasticidad": 2.0e8, "area": 0.0020},
}
apoyos_b = {
1: {"x": True, "y": True}, # articulado
2: {"x": False, "y": True}, # rodillo
}
cargas_b = {
3: {"x": 30.0, "y": -40.0},
4: {"x": 0.0, "y": -40.0},
}
Verificación. Contamos los nodos y las barras, y revisamos una de las diagonales:
inicial = barras_b[5]["nodo_inicial"]
final = barras_b[5]["nodo_final"]
print(f"Nodos: {len(nodos_b)}, barras: {len(barras_b)}")
print(f"Barra 5: del nodo {inicial} al nodo {final}")
Nodos: 4, barras: 6 Barra 5: del nodo 1 al nodo 4
La cantidad de nodos y de barras coincide con la figura, y la barra 5 es la diagonal que va del nodo 1 al nodo 4. Este modelo nos acompañará, junto con el del ejemplo A, en los próximos capítulos.
Repaso y resumen del capítulo
- Una armadura plana queda definida por sus nodos (coordenadas), sus barras (nodos, y ), sus apoyos (direcciones impedidas) y sus cargas (componentes en los nodos).
- Una lista es una colección ordenada; sus posiciones se cuentan desde cero. Un diccionario asocia claves con valores.
- El modelo de la calculadora usa cuatro diccionarios:
nodos,barras,apoyosycargas, con los números de nodo y de barra como claves, empezando en 1. - Los valores anidados se leen encadenando claves entre corchetes, de la barra a sus nodos y de los nodos a sus coordenadas.
inpregunta si una clave existe;getlee una clave sin producir un error si no existe;lencuenta los elementos.- Todo modelo se verifica dibujándolo con
plt.subplotsyax.plot, con la misma escala en ambos ejes.
Ejercicios
Ejercicio 3.1
Usando los diccionarios del ejemplo A, obtener las coordenadas de los nodos
de la barra 3 a partir de barras[3], sin escribir los números de nodo a
mano, y calcular la longitud de la barra.
Ver la solución
inicial = nodos[barras[3]["nodo_inicial"]]
final = nodos[barras[3]["nodo_final"]]
dx = final["x"] - inicial["x"]
dy = final["y"] - inicial["y"]
L = math.sqrt(dx**2 + dy**2)
print(f"L = {L:.3f} m")
L = 5.000 m
La consulta encadenada nodos[barras[3]["nodo_inicial"]] obtiene el nodo
desde la barra, sin escribir su número a mano.
Ejercicio 3.2
Una armadura de cubierta tiene los nodos 1 en , 2 en , 3 en
y 4 en , en metros. Sus barras son 1: 1–2, 2: 2–3,
3: 1–4, 4: 4–3 y 5: 2–4, todas de acero; las cuatro primeras tienen un área
de 15 cm² y la barra 5, de
10 cm². Está articulada en el nodo 1, tiene un rodillo
en el nodo 3 y una carga de 30 kN hacia abajo en el nodo 4.
Escribir su modelo con los nombres nodos_cubierta, barras_cubierta,
apoyos_cubierta y cargas_cubierta.
Ver la solución
# Armadura de cubierta. Unidades: kN y m
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: {"nodo_inicial": 1, "nodo_final": 2,
"elasticidad": 2.0e8, "area": 0.0015},
2: {"nodo_inicial": 2, "nodo_final": 3,
"elasticidad": 2.0e8, "area": 0.0015},
3: {"nodo_inicial": 1, "nodo_final": 4,
"elasticidad": 2.0e8, "area": 0.0015},
4: {"nodo_inicial": 4, "nodo_final": 3,
"elasticidad": 2.0e8, "area": 0.0015},
5: {"nodo_inicial": 2, "nodo_final": 4,
"elasticidad": 2.0e8, "area": 0.0010},
}
apoyos_cubierta = {
1: {"x": True, "y": True}, # articulado
3: {"x": False, "y": True}, # rodillo
}
cargas_cubierta = {
4: {"x": 0.0, "y": -30.0},
}
print(f"Nodos: {len(nodos_cubierta)}")
print(f"Barras: {len(barras_cubierta)}")
Nodos: 4 Barras: 5
Ejercicio 3.3
Predecir, sin ejecutarlo, qué muestran apoyos.get(3) y
3 in cargas, 1 in cargas para el ejemplo A. Comprobar la predicción en
el notebook.
Ver la solución
print(apoyos.get(3))
print(3 in cargas, 1 in cargas)
None True False
El nodo 3 no tiene apoyo, de modo que get devuelve None. El nodo 3
tiene carga y el nodo 1 no.
Ejercicio 3.4
Dibujar la armadura del ejemplo B con Matplotlib y comparar el dibujo con la figura 3.2.
Ver la solución
n1, n2 = nodos_b[1], nodos_b[2]
n3, n4 = nodos_b[3], nodos_b[4]
fig, ax = plt.subplots()
ax.plot([n1["x"], n2["x"]], [n1["y"], n2["y"]], "k-o")
ax.plot([n2["x"], n4["x"]], [n2["y"], n4["y"]], "k-o")
ax.plot([n3["x"], n4["x"]], [n3["y"], n4["y"]], "k-o")
ax.plot([n1["x"], n3["x"]], [n1["y"], n3["y"]], "k-o")
ax.plot([n1["x"], n4["x"]], [n1["y"], n4["y"]], "k-o")
ax.plot([n2["x"], n3["x"]], [n2["y"], n3["y"]], "k-o")
ax.set_aspect("equal")
ax.set_title("Ejemplo B")
plt.show()