Capítulo 8
Cargas y partición del sistema
La matriz global relaciona todas las fuerzas nodales con todos los desplazamientos, . Pero no podemos resolver ese sistema tal como está: es singular, y en cada grado de libertad conocemos o la fuerza o el desplazamiento, nunca ambos. Antes de resolver debemos ordenar lo que sabemos y lo que no. En este capítulo ensamblamos el vector de cargas y dividimos el sistema en bloques de grados de libertad libres y restringidos. Al hacerlo, veremos cómo los apoyos eliminan la singularidad.
8.1 Lo conocido y lo desconocido
Ordenemos los grados de libertad en dos grupos, los libres () y los restringidos (), y escribamos el sistema por bloques:
En los grados de libertad libres conocemos las cargas y buscamos los desplazamientos . En los restringidos conocemos los desplazamientos , que son nulos salvo que haya asentamientos, y buscamos las fuerzas , que contienen las reacciones. El sistema por bloques equivale a dos ecuaciones:
La primera tiene una sola incógnita, : la resolveremos en el capítulo 9. Con conocido, la segunda da directamente las reacciones.
La figura 8.1 muestra los cuatro bloques para el ejemplo A. El
dibujo ordena los grados de libertad poniendo primero los libres para que los
bloques se vean contiguos; en el programa no hace falta reordenar nada,
porque cada bloque se selecciona con las listas libres y
restringidos.
Partición de la matriz global del ejemplo A en bloques de grados de libertad libres (3, 5, 6) y restringidos (1, 2, 4).
8.2 El vector de cargas
El vector de cargas tiene un término por cada grado de libertad. Se
ensambla como la matriz: empieza en ceros y cada carga se suma en los grados
de libertad de su nodo. Usamos += para que, si un mismo nodo recibiera
varias cargas, se acumulen.
def ensamblar_cargas(nodos, cargas):
"""Vector de cargas nodales F de la armadura."""
F = np.zeros(2 * len(nodos))
for nodo, carga in cargas.items():
fila_x, fila_y = indices(gdl_del_nodo(nodo))
F[fila_x] += carga["x"]
F[fila_y] += carga["y"]
return F
F = ensamblar_cargas(nodos, cargas)
Como con las matrices, escribimos una función para mostrar un vector junto con sus grados de libertad:
def mostrar_vector(vector, gdl, decimales=2, ancho=10):
"""Imprime un vector con sus grados de libertad."""
for i, g in enumerate(gdl):
valor = round(vector[i], decimales) + 0.0
print(f"{g:>4}{valor:>{ancho}.{decimales}f}")
print(" gdl F (kN)")
mostrar_vector(F, list(range(1, len(F) + 1)))
gdl F (kN) 1 0.00 2 0.00 3 0.00 4 0.00 5 20.00 6 -50.00
8.3 Rebanadas
Además de seleccionar elementos con una lista de índices, NumPy permite
tomar rebanadas (slices): tramos regulares de un arreglo. La
notación es [inicio:fin:paso], donde el fin no se incluye y cualquiera
de los tres valores puede omitirse. Como los grados de libertad en
ocupan las posiciones pares (0, 2, 4, …) y los de las impares, las
rebanadas [0::2] y [1::2] separan las componentes:
print(F[0:2]) # gdl 1 y 2: el nodo 1
print(F[0::2]) # gdl 1, 3, 5: las componentes en x
print(F[1::2]) # gdl 2, 4, 6: las componentes en y
[0. 0.] [ 0. 0. 20.] [ 0. 0. -50.]
Con el método sum obtenemos la resultante de las cargas en cada
dirección, una verificación rápida de que el vector se ensambló bien:
print(f"Suma de cargas en x: {F[0::2].sum():7.2f} kN")
print(f"Suma de cargas en y: {F[1::2].sum():7.2f} kN")
Suma de cargas en x: 20.00 kN Suma de cargas en y: -50.00 kN
8.4 La partición del sistema
Los cuatro bloques se obtienen con np.ix_, combinando los índices de los
grados de libertad libres y restringidos. Una función los devuelve juntos, y
quien la llama los recibe en cuatro variables, en el mismo orden:
def particionar(K, libres, restringidos):
"""Bloques Kll, Klr, Krl y Krr de la matriz K."""
l = indices(libres)
r = indices(restringidos)
K_ll = K[np.ix_(l, l)]
K_lr = K[np.ix_(l, r)]
K_rl = K[np.ix_(r, l)]
K_rr = K[np.ix_(r, r)]
return K_ll, K_lr, K_rl, K_rr
K_ll, K_lr, K_rl, K_rr = particionar(K_global, libres,
restringidos)
F_l = F[indices(libres)]
La selección de es la misma que hicimos en el
capítulo 6 con un vector de prueba. Veamos los bloques del
ejemplo A; para , cuyas filas y columnas son distintas, indicamos
ambas listas a mostrar_matriz:
print("K_ll en kN/mm:")
mostrar_matriz(K_ll / 1000, libres)
print("K_lr en kN/mm:")
mostrar_matriz(K_lr / 1000, libres, restringidos)
print(" gdl F_l (kN)")
mostrar_vector(F_l, libres)
K_ll en kN/mm: gdl 3 5 6 3 95.5 -28.8 38.4 5 -28.8 57.6 0.0 6 38.4 0.0 102.4 K_lr en kN/mm: gdl 1 2 4 3 -66.7 0.0 -38.4 5 -28.8 -38.4 38.4 6 -38.4 -51.2 -51.2 gdl F_l (kN) 3 0.00 5 20.00 6 -50.00
8.5 Verificación
La matriz global era singular porque la armadura podía moverse como cuerpo rígido. Al separar los grados de libertad restringidos, impedimos esos movimientos: si la armadura está bien apoyada, tiene rango completo y puede resolverse. Además, como es simétrica, .
print(f"K_ll: {K_ll.shape}, rango "
f"{np.linalg.matrix_rank(K_ll)}")
print(f"K_lr es la transpuesta de K_rl: "
f"{np.allclose(K_lr, K_rl.T)}")
K_ll: (3, 3), rango 3 K_lr es la transpuesta de K_rl: True
Problema resuelto 8.1 · El ejemplo B
Ensamblar el vector de cargas del ejemplo B, particionar su sistema y comprobar que tiene rango completo.
Solución
Calculamos primero la matriz global del ejemplo B con las funciones de los capítulos anteriores, y después aplicamos las de este capítulo:
geometria_b = calcular_geometria(nodos_b, barras_b)
rigideces_b = calcular_rigideces(barras_b, geometria_b)
K_b = ensamblar_rigidez(nodos_b, barras_b, rigideces_b)
F_b = ensamblar_cargas(nodos_b, cargas_b)
libres_b, restringidos_b = clasificar_gdl(nodos_b,
apoyos_b)
K_ll_b, K_lr_b, K_rl_b, K_rr_b = particionar(
K_b, libres_b, restringidos_b)
F_l_b = F_b[indices(libres_b)]
print("K_ll en kN/mm:")
mostrar_matriz(K_ll_b / 1000, libres_b, decimales=0,
ancho=6)
print(f"Rango de K_ll: {np.linalg.matrix_rank(K_ll_b)}")
print(" gdl F_l (kN)")
mostrar_vector(F_l_b, libres_b)
K_ll en kN/mm: gdl 3 5 6 7 8 3 162 -29 38 0 0 5 -29 162 -38 -133 0 6 38 -38 151 0 0 7 0 -133 0 162 38 8 0 0 0 38 151 Rango de K_ll: 5 gdl F_l (kN) 3 0.00 5 30.00 6 -40.00 7 0.00 8 -40.00
Verificación. es de y su rango es 5: la armadura está bien apoyada. En aparecen las cargas de los nodos 3 y 4 en sus grados de libertad (5 y 6 para el nodo 3, 8 para el nodo 4), y ceros en los demás.
Repaso y resumen del capítulo
- El sistema se divide en bloques de grados de libertad libres y restringidos. En los libres se conocen las cargas; en los restringidos, los desplazamientos.
- da los desplazamientos, y , las reacciones.
- El vector de cargas se ensambla sumando cada carga en los grados de libertad de su nodo.
- Las rebanadas
[inicio:fin:paso]seleccionan tramos regulares de un arreglo;sumsuma sus elementos. particionardevuelve los cuatro bloques de seleccionados connp.ix_.- Si la armadura es estable, tiene rango completo.
Ejercicios
Ejercicio 8.1
Calcular la resultante de las cargas del ejemplo B en cada dirección y compararla con la figura 3.2.
Ver la solución
F_b = ensamblar_cargas(nodos_b, cargas_b)
print(f"Ejemplo B, en x: {F_b[0::2].sum():7.2f} kN")
print(f"Ejemplo B, en y: {F_b[1::2].sum():7.2f} kN")
Ejemplo B, en x: 30.00 kN Ejemplo B, en y: -80.00 kN
La resultante es de 30 kN hacia la derecha y 80 kN hacia abajo, la suma de las tres cargas de la figura.
Ejercicio 8.2
Agregar al ejemplo A una carga horizontal de 15 kN en el
nodo 2, sin modificar el diccionario cargas original. Mostrar los
vectores y .
Ver la solución
Creamos un diccionario nuevo con las dos cargas, para no modificar los datos del ejemplo:
cargas_2 = {
2: {"x": 15.0, "y": 0.0},
3: {"x": 20.0, "y": -50.0},
}
F_2 = ensamblar_cargas(nodos, cargas_2)
print(" gdl F (kN)")
mostrar_vector(F_2, list(range(1, 7)))
print(" gdl F_l (kN)")
mostrar_vector(F_2[indices(libres)], libres)
gdl F (kN) 1 0.00 2 0.00 3 15.00 4 0.00 5 20.00 6 -50.00 gdl F_l (kN) 3 15.00 5 20.00 6 -50.00
El grado de libertad 3 (nodo 2, dirección ) es libre, de modo que la nueva carga aparece también en .
Ejercicio 8.3
Ensamblar la matriz global del ejemplo A sin la barra 1, particionarla con los mismos grados de libertad y calcular el rango de . Relacionar el resultado con el ejercicio 6.3.
Ver la solución
barras_sin_1 = {2: barras[2], 3: barras[3]}
K_sin_1 = ensamblar_rigidez(nodos, barras_sin_1,
rigideces)
K_ll_sin_1 = particionar(K_sin_1, libres,
restringidos)[0]
print(f"Rango de K_ll: "
f"{np.linalg.matrix_rank(K_ll_sin_1)} de 3")
Rango de K_ll: 2 de 3
Con rango 2 de 3, es singular: existe un movimiento de los nodos libres que no deforma ninguna barra, el mecanismo que encontramos en el ejercicio 6.3.
Ejercicio 8.4
Construir la matriz global del ejemplo A ordenada como en la
figura 8.1, con los grados de libertad libres primero, y mostrarla.
Dos listas se unen en una sola con el operador +.
Ver la solución
orden = libres + restringidos
K_ordenada = K_global[np.ix_(indices(orden),
indices(orden))]
mostrar_matriz(K_ordenada / 1000, orden)
gdl 3 5 6 1 2 4 3 95.5 -28.8 38.4 -66.7 0.0 -38.4 5 -28.8 57.6 0.0 -28.8 -38.4 38.4 6 38.4 0.0 102.4 -38.4 -51.2 -51.2 1 -66.7 -28.8 -38.4 95.5 38.4 0.0 2 0.0 -38.4 -51.2 38.4 51.2 0.0 4 -38.4 38.4 -51.2 0.0 0.0 51.2
El bloque superior izquierdo de es , y el superior derecho, .