Análisis matricial de armaduras con Python

Capítulo 6

Los grados de libertad

Cada barra tiene ya su matriz de rigidez de 4×44\times4, escrita en función de los desplazamientos de sus dos nodos. Para unirlas en una sola matriz de la armadura necesitamos un sistema de referencia común: una numeración de todos los desplazamientos posibles de la estructura, sus grados de libertad. En este capítulo los numeramos, los separamos en libres y restringidos según los apoyos y hacemos una primera revisión de la estabilidad. Por el camino, enfrentamos una diferencia incómoda entre la ingeniería y Python: nosotros contamos desde 1, y Python, desde 0.

6.1 Los grados de libertad de la armadura

En una armadura plana, cada nodo puede desplazarse en dos direcciones, xx e yy. Una armadura de nn nodos tiene, por lo tanto, 2n2n grados de libertad. Los numeramos nodo por nodo, siempre en el mismo orden: primero xx y después yy. Así, el nodo ii tiene los grados de libertad

gdlx=2i−1,gdly=2i.\begin{aligned} \text{gdl}_x = 2i - 1, \qquad \text{gdl}_y = 2i. \end{aligned}

Cada grado de libertad es de uno de dos tipos (figura 6.1):

Figura 6.1:

Grados de libertad del ejemplo A. El nodo 1 (articulado) tiene ambos restringidos; el nodo 2 (rodillo), solo el vertical.

Para cada barra necesitamos además saber a qué grados de libertad de la armadura corresponden sus cuatro grados de libertad locales: los dos de su nodo inicial y los dos de su nodo final. Esta lista, que algunos textos llaman vector de correspondencia (code numbers), es la que nos permitirá colocar cada matriz de 4×44\times4 en el lugar correcto de la matriz de la armadura en el capítulo 7.

6.2 Decisiones con if

Para clasificar un grado de libertad debemos preguntar si el nodo tiene un apoyo en esa dirección y actuar según la respuesta. Las preguntas se escriben con comparaciones (comparisons): > y <, >= y <=, == (igual a, con dos signos) y != (distinto de). Su resultado es un booleano:

area = barras[1]["area"]
print(area > 0.001, len(barras) == 3, len(nodos) != 3)
True True False

La instrucción if ejecuta un bloque solo si la condición es verdadera, y else, el bloque alternativo. Como en los bucles y las funciones, los bloques van sangrados debajo de una línea que termina en dos puntos:

apoyo = apoyos[2]
if apoyo["x"]:
    print("El nodo 2 no puede moverse en x")
else:
    print("El nodo 2 puede moverse en x")
El nodo 2 puede moverse en x

Cuando hay más de dos casos, elif (contracción de else if) agrega condiciones intermedias. Lo usaremos en el apartado 6.6.

6.3 La numeración de los grados de libertad

La ecuación clave se traduce en una función de una línea. Una función puede devolver varios valores separados por comas, y quien la llama los recibe en varias variables:

def gdl_del_nodo(nodo):
    """Grados de libertad (x, y) del nodo, desde 1."""
    return 2 * nodo - 1, 2 * nodo
gdl_x, gdl_y = gdl_del_nodo(3)
print(f"Nodo 3: x → {gdl_x}, y → {gdl_y}")
Nodo 3: x → 5, y → 6

Con ella obtenemos el vector de correspondencia de una barra: los grados de libertad de su nodo inicial seguidos de los de su nodo final, en el mismo orden que u1,…,u4u_1, \dots, u_4 en la figura 5.1.

def gdl_de_barra(barra):
    """Grados de libertad de los extremos de una barra."""
    xi, yi = gdl_del_nodo(barra["nodo_inicial"])
    xj, yj = gdl_del_nodo(barra["nodo_final"])
    return [xi, yi, xj, yj]
for numero, barra in barras.items():
    print(f"Barra {numero}: {gdl_de_barra(barra)}")
Barra 1: [1, 2, 3, 4]
Barra 2: [3, 4, 5, 6]
Barra 3: [1, 2, 5, 6]

Verificación. La barra 2 va del nodo 2 al nodo 3, cuyos grados de libertad son (3,4)(3, 4) y (5,6)(5, 6), como se ve en la figura 6.1.

6.4 Grados de libertad libres y restringidos

Para clasificar, recorremos los nodos y preguntamos, en cada dirección, si hay un apoyo. Un nodo sin apoyo no aparece en el diccionario apoyos: el método get, que vimos en el capítulo 3, nos permite tratarlo como un nodo «sin restricciones». Cada grado de libertad se agrega con append a la lista que le corresponde:

def clasificar_gdl(nodos, apoyos):
    """Grados de libertad libres y restringidos."""
    libres = []
    restringidos = []
    sin_apoyo = {"x": False, "y": False}
    for nodo in nodos:
        gdl_x, gdl_y = gdl_del_nodo(nodo)
        apoyo = apoyos.get(nodo, sin_apoyo)
        if apoyo["x"]:
            restringidos.append(gdl_x)
        else:
            libres.append(gdl_x)
        if apoyo["y"]:
            restringidos.append(gdl_y)
        else:
            libres.append(gdl_y)
    return libres, restringidos
libres, restringidos = clasificar_gdl(nodos, apoyos)
print(f"Libres:       {libres}")
print(f"Restringidos: {restringidos}")
Libres:       [3, 5, 6]
Restringidos: [1, 2, 4]

Verificación. El nodo 1 aporta sus dos grados de libertad, 1 y 2, a los restringidos, y el rodillo del nodo 2, el vertical (4). Los libres son el horizontal del nodo 2 y los dos del nodo 3. La suma, 3+3=6=2n3 + 3 = 6 = 2n, da todos los grados de libertad.

6.5 De la numeración desde 1 a los índices de Python

En el capítulo 3 vimos que Python cuenta las posiciones desde cero. En los próximos capítulos guardaremos fuerzas, desplazamientos y rigideces en arreglos de NumPy, cuya primera posición es la 0: el grado de libertad 1 estará en la posición 0, el 2 en la posición 1, y así sucesivamente.

Podríamos restar 1 cada vez que accedemos a un arreglo, pero sería peligroso: bastaría con olvidarlo una vez para que el programa use el grado de libertad equivocado, sin dar ningún error. Por eso concentramos la traducción en una sola función, que convierte una lista de grados de libertad en la lista de índices correspondiente:

def indices(gdl):
    """Traduce grados de libertad (desde 1) a índices
    de NumPy (desde 0). Es el único lugar del libro donde
    se hace esta traducción."""
    resultado = []
    for g in gdl:
        resultado.append(g - 1)
    return resultado

NumPy permite además seleccionar varios elementos de un arreglo a la vez, indicando la lista de sus índices entre corchetes. Por ejemplo, los valores que ocupan los grados de libertad libres:

# Un valor por cada grado de libertad del ejemplo A
valores = np.array([10.0, 20.0, 30.0, 40.0, 50.0, 60.0])
print(indices(libres))
valores[indices(libres)]
[2, 4, 5]
array([30., 50., 60.])

Los grados de libertad libres 3, 5 y 6 ocupan las posiciones 2, 4 y 5, y seleccionan los valores 30, 50 y 60. Este mecanismo es la base del ensamblaje y de la partición de los próximos capítulos.

6.6 Una revisión previa de la estabilidad

Una armadura de bb barras y rr reacciones tiene b+rb + r fuerzas desconocidas, y el equilibrio de sus nn nodos aporta 2n2n ecuaciones. La diferencia es el grado de indeterminación (degree of indeterminacy):

g=b+r−2n.g = b + r - 2n.

Si g<0g < 0, faltan barras o apoyos y la armadura es inestable; si g=0g = 0, es estáticamente determinada (isostática); si g>0g > 0, es hiperestática de grado gg. Como cada grado de libertad restringido corresponde a una reacción, rr es la cantidad de grados de libertad restringidos:

def indeterminacion(nodos, barras, restringidos):
    """b + r - 2n: incógnitas menos ecuaciones."""
    b, r, n = len(barras), len(restringidos), len(nodos)
    return b + r - 2 * n

Para interpretar el resultado usamos if, elif y else:

grado = indeterminacion(nodos, barras, restringidos)
if grado < 0:
    print(f"Inestable: faltan {-grado} barras o apoyos")
elif grado == 0:
    print("Estáticamente determinada (isostática)")
else:
    print(f"Hiperestática de grado {grado}")
Estáticamente determinada (isostática)

Problema resuelto 6.1 · Los grados de libertad del ejemplo B

Obtener los vectores de correspondencia de las barras del ejemplo B (figura 3.2), clasificar sus grados de libertad y calcular su grado de indeterminación.

Solución

Las funciones del capítulo sirven sin cambios para cualquier armadura:

for numero, barra in barras_b.items():
    print(f"Barra {numero}: {gdl_de_barra(barra)}")

libres_b, restringidos_b = clasificar_gdl(
    nodos_b, apoyos_b)
print(f"Libres:       {libres_b}")
print(f"Restringidos: {restringidos_b}")

grado_b = indeterminacion(nodos_b, barras_b,
                          restringidos_b)
print(f"Grado de indeterminación: {grado_b}")
Barra 1: [1, 2, 3, 4]
Barra 2: [3, 4, 7, 8]
Barra 3: [5, 6, 7, 8]
Barra 4: [1, 2, 5, 6]
Barra 5: [1, 2, 7, 8]
Barra 6: [3, 4, 5, 6]
Libres:       [3, 5, 6, 7, 8]
Restringidos: [1, 2, 4]
Grado de indeterminación: 1

Verificación. El ejemplo B tiene 4 nodos y 8 grados de libertad: 3 restringidos (el apoyo articulado del nodo 1 y el rodillo del nodo 2) y 5 libres. Con 6 barras, g=6+3−8=1g = 6 + 3 - 8 = 1: una barra más de las necesarias, la segunda diagonal. Por eso el método de los nudos no basta para resolverlo, y el método de rigidez sí.

Repaso y resumen del capítulo

Ejercicios

Ejercicio 6.1

Obtener el vector de correspondencia de la barra 6 del ejemplo B y comprobarlo en la figura 3.2.

Ver la solución
# La barra 6 va del nodo 2 al nodo 3
gdl_de_barra(barras_b[6])
[3, 4, 5, 6]

Los grados de libertad del nodo 2 son 3 y 4, y los del nodo 3, 5 y 6.

Ejercicio 6.2

Si el rodillo del nodo 2 del ejemplo A se reemplaza por un apoyo articulado, ¿cuáles son los grados de libertad libres y restringidos? Calcular el nuevo grado de indeterminación.

Ver la solución
apoyos_articulados = {
    1: {"x": True, "y": True},
    2: {"x": True, "y": True},
}
libres_2, restringidos_2 = clasificar_gdl(
    nodos, apoyos_articulados)
print(f"Libres:       {libres_2}")
print(f"Restringidos: {restringidos_2}")
grado = indeterminacion(nodos, barras, restringidos_2)
print(f"Grado de indeterminación: {grado}")
Libres:       [5, 6]
Restringidos: [1, 2, 3, 4]
Grado de indeterminación: 1

Con dos apoyos articulados hay 4 reacciones, y g=3+4−6=1g = 3 + 4 - 6 = 1: la armadura pasa a ser hiperestática. Solo el nodo 3 puede desplazarse.

Ejercicio 6.3

Calcular el grado de indeterminación del ejemplo A sin su barra 1 e interpretar el resultado. ¿Qué movimiento puede hacer la armadura?

Ver la solución
barras_sin_1 = {2: barras[2], 3: barras[3]}
grado = indeterminacion(nodos, barras_sin_1, restringidos)
print(f"Grado de indeterminación: {grado}")
Grado de indeterminación: -1

Con g=−1g = -1 la armadura es inestable: sin la barra 1, nada impide que el rodillo del nodo 2 se deslice horizontalmente y que las barras 2 y 3 giren como un mecanismo.

Ejercicio 6.4

Construir la correspondencia entre la numeración natural del ejemplo A y la de los textos que renumeran primero los grados de libertad libres. La función enumerate(lista, 1) recorre una lista y entrega, además de cada elemento, su posición contando desde 1.

Ver la solución
nueva_numeracion = libres + restringidos
for nuevo, original in enumerate(nueva_numeracion, 1):
    print(f"GDL {original} → {nuevo}")
GDL 3 → 1
GDL 5 → 2
GDL 6 → 3
GDL 1 → 4
GDL 2 → 5
GDL 4 → 6

Los grados de libertad libres 3, 5 y 6 pasan a ser los primeros (1, 2 y 3), y los restringidos 1, 2 y 4, los últimos (4, 5 y 6).