├── README.md ├── fem_bar.py ├── fem_bar2.py ├── fem_beam.py └── truss.py /README.md: -------------------------------------------------------------------------------- 1 | # FEM 2 | Some Python code showing the implementation of various finite elements. 3 | -------------------------------------------------------------------------------- /fem_bar.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.linalg import eigh 3 | import math 4 | from matplotlib import pyplot as plt 5 | 6 | def bar(num_elems): 7 | restrained_dofs = [0,] 8 | 9 | # element mass and stiffness matrices for a bar 10 | m = np.array([[2,1],[1,2]]) / (6. * num_elems) 11 | k = np.array([[1,-1],[-1,1]]) * float(num_elems) 12 | 13 | # construct global mass and stiffness matrices 14 | M = np.zeros((num_elems+1,num_elems+1)) 15 | K = np.zeros((num_elems+1,num_elems+1)) 16 | 17 | # assembly of elements 18 | for i in range(num_elems): 19 | M_temp = np.zeros((num_elems+1,num_elems+1)) 20 | K_temp = np.zeros((num_elems+1,num_elems+1)) 21 | M_temp[i:i+2,i:i+2] = m 22 | K_temp[i:i+2,i:i+2] = k 23 | M += M_temp 24 | K += K_temp 25 | 26 | # remove the fixed degrees of freedom 27 | for dof in restrained_dofs: 28 | for i in [0,1]: 29 | M = np.delete(M, dof, axis=i) 30 | K = np.delete(K, dof, axis=i) 31 | 32 | # eigenvalue problem 33 | evals, evecs = eigh(K,M) 34 | frequencies = np.sqrt(evals) 35 | return M, K, frequencies, evecs 36 | 37 | 38 | exact_frequency = math.pi/2 39 | results = [] 40 | for i in range(1,11): 41 | M, K, frequencies, evecs = bar(i) 42 | error = ( frequencies[0] - exact_frequency ) / exact_frequency * 100.0 43 | results.append( (i, error) ) 44 | print 'Num Elems: {} \tFund. Frequency: {} \t Error: {}%'.format(i, round(frequencies[0],3), round(error,3)) 45 | 46 | print 'Exact frequency: ', round(exact_frequency,3) 47 | 48 | # plot the results 49 | elements = np.array([x[0] for x in results]) 50 | errors = np.array([x[1] for x in results]) 51 | 52 | plt.plot(elements,errors, 'o-') 53 | plt.xlim(elements[0], elements[-1]) 54 | plt.xlabel('Number of Elements') 55 | plt.ylabel('Error (%)') 56 | plt.show() 57 | 58 | -------------------------------------------------------------------------------- /fem_bar2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.linalg import eigh 3 | import math 4 | from matplotlib import pyplot as plt 5 | import time 6 | 7 | def bar2(num_elems): 8 | restrained_dofs = [0, ] 9 | 10 | # element mass and stiffnessm matrices 11 | m = np.array([[2,1],[1,2]]) / (6.0 * num_elems) 12 | k = np.array([[1,-1],[-1,1]]) * float(num_elems) 13 | 14 | # construct global mass and stiffness matrices 15 | M = np.zeros((num_elems+1,num_elems+1)) 16 | K = np.zeros((num_elems+1,num_elems+1)) 17 | 18 | # for each element, change to global coordinates 19 | for i in range(num_elems): 20 | M_temp = np.zeros((num_elems+1,num_elems+1)) 21 | K_temp = np.zeros((num_elems+1,num_elems+1)) 22 | M_temp[i:i+2, i:i+2] = m 23 | K_temp[i:i+2, i:i+2] = k 24 | M += M_temp 25 | K += K_temp 26 | 27 | # remove the fixed degrees of freedom 28 | for dof in restrained_dofs: 29 | for i in [0,1]: 30 | M = np.delete(M, dof, axis=i) 31 | K = np.delete(K, dof, axis=i) 32 | 33 | evals, evecs = eigh(K,M) 34 | frequencies = np.sqrt(evals) 35 | return M, K, frequencies, evecs 36 | 37 | def bar3(num_elems): 38 | restrained_dofs = [0, ] 39 | 40 | # element mass and stiffnessm matrices 41 | m = np.array([[4,2,-1],[2,16,2],[-1,2,4]]) / (30.0 * num_elems) 42 | k = np.array([[7,-8,1],[-8,16,-8],[1,-8,7]]) / 3.0 * num_elems 43 | 44 | # construct global mass and stiffness matrices 45 | M = np.zeros((2*num_elems+1,2*num_elems+1)) 46 | K = np.zeros((2*num_elems+1,2*num_elems+1)) 47 | 48 | # for each element, change to global coordinates 49 | for i in range(num_elems): 50 | M_temp = np.zeros((2*num_elems+1,2*num_elems+1)) 51 | K_temp = np.zeros((2*num_elems+1,2*num_elems+1)) 52 | 53 | first_node = 2*i 54 | M_temp[first_node:first_node+3, first_node:first_node+3] = m # 3 x 3 55 | K_temp[first_node:first_node+3, first_node:first_node+3] = k # 3 x 3 56 | M += M_temp 57 | K += K_temp 58 | 59 | # remove the fixed degrees of freedom 60 | for dof in restrained_dofs: 61 | for i in [0,1]: 62 | M = np.delete(M, dof, axis=i) 63 | K = np.delete(K, dof, axis=i) 64 | 65 | evals, evecs = eigh(K,M) 66 | frequencies = np.sqrt(evals) 67 | return M, K, frequencies, evecs 68 | 69 | # 2 node bar element 70 | print '2 Node bar element' 71 | exact_frequency = math.pi/2 72 | errors = [] 73 | for i in range(1,11): 74 | start = time.clock() 75 | M, K, frequencies, evecs = bar2(i) 76 | time_taken = time.clock() - start 77 | error = (frequencies[0] - exact_frequency) / exact_frequency * 100.0 78 | errors.append( (i, error) ) 79 | print 'Num Elems: {} \tFrequency: {}\tError: {}% \tShape: {} \tTime: {}'.format( i, round(frequencies[0],4), round(error, 4), K.shape, round(time_taken*1000, 3) ) 80 | 81 | print 'Exact Freq:', round(exact_frequency, 4) 82 | 83 | element = np.array([x[0] for x in errors]) 84 | error2 = np.array([x[1] for x in errors]) 85 | 86 | 87 | # 3 node bar element 88 | print '3 Node bar element' 89 | exact_frequency = math.pi/2 90 | errors = [] 91 | for i in range(1,11): 92 | start = time.clock() 93 | M, K, frequencies, evecs = bar3(i) 94 | time_taken = time.clock() - start 95 | error = (frequencies[0] - exact_frequency) / exact_frequency * 100.0 96 | errors.append( (i, error) ) 97 | print 'Num Elems: {} \tFrequency: {}\tError: {}% \tShape: {} \tTime: {}'.format( i, round(frequencies[0],4), round(error, 4), K.shape, round(time_taken*1000, 4) ) 98 | 99 | print 'Exact Freq:', round(exact_frequency, 4) 100 | 101 | element = np.array([x[0] for x in errors]) 102 | error3 = np.array([x[1] for x in errors]) 103 | 104 | 105 | # plot the result 106 | plt.plot(element, zip(error2, error3), 'o-') 107 | plt.xlim(1, element[-1]) 108 | plt.xlabel('Number of Elements') 109 | plt.ylabel('Errror (%)') 110 | plt.show() 111 | 112 | -------------------------------------------------------------------------------- /fem_beam.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.linalg import eigh 3 | import math 4 | from matplotlib import pyplot as plt 5 | import time 6 | 7 | def beam(num_elems): 8 | restrained_dofs = [1, 0, -2, -1] 9 | 10 | l = 1.0 / num_elems 11 | Cm = 1.0 # rho.A 12 | Ck = 1.0 # E.I 13 | 14 | # element mass and stiffness matrices 15 | m = np.array([[156, 22*l, 54, -13*l], 16 | [22*l, 4*l*l, 13*l, -3*l*l], 17 | [54, 13*l, 156, -22*l], 18 | [-13*l, -3*l*l, -22*l, 4*l*l]]) * Cm * l / 420 19 | 20 | k = np.array([[12, 6*l, -12, 6*l], 21 | [6*l, 4*l*l, -6*l, 2*l*l], 22 | [-12, -6*l, 12, -6*l], 23 | [6*l, 2*l*l, -6*l, 4*l*l]]) * Ck / l**3 24 | 25 | # construct global mass and stiffness matrices 26 | M = np.zeros((2*num_elems+2,2*num_elems+2)) 27 | K = np.zeros((2*num_elems+2,2*num_elems+2)) 28 | 29 | # for each element, change to global coordinates 30 | for i in range(num_elems): 31 | M_temp = np.zeros((2*num_elems+2,2*num_elems+2)) 32 | K_temp = np.zeros((2*num_elems+2,2*num_elems+2)) 33 | M_temp[2*i:2*i+4, 2*i:2*i+4] = m 34 | K_temp[2*i:2*i+4, 2*i:2*i+4] = k 35 | M += M_temp 36 | K += K_temp 37 | 38 | # remove the fixed degrees of freedom 39 | for dof in restrained_dofs: 40 | for i in [0,1]: 41 | M = np.delete(M, dof, axis=i) 42 | K = np.delete(K, dof, axis=i) 43 | 44 | evals, evecs = eigh(K,M) 45 | frequencies = np.sqrt(evals) 46 | return M, K, frequencies, evecs 47 | 48 | # beam element 49 | print 'Beam element' 50 | # exact_frequency = math.pi**2 # simply supported 51 | # exact_frequency = 1.875104**2 # cantilever beam 52 | # exact_frequency = 3.926602**2 # built in - pinned beam 53 | exact_frequency = 4.730041**2 # fixed-fixed 54 | 55 | errors = [] 56 | for i in range(2,6): # number of elements 57 | start = time.clock() 58 | M, K, frequencies, evecs = beam(i) 59 | time_taken = time.clock() - start 60 | error = (frequencies[0] - exact_frequency) / exact_frequency * 100.0 61 | errors.append( (i, error) ) 62 | print 'Num Elems: {} \tFrequency: {}\tError: {}% \tShape: {} \tTime: {}'.format( i, round(frequencies[0],3), round(error, 3), K.shape, round(time_taken*1000, 3) ) 63 | 64 | print 'Exact Freq:', round(exact_frequency, 3) 65 | 66 | element = np.array([x[0] for x in errors]) 67 | error = np.array([x[1] for x in errors]) 68 | 69 | 70 | # plot the result 71 | plt.plot(element, error, 'o-') 72 | plt.xlim(1, element[-1]) 73 | plt.xlabel('Number of Elements') 74 | plt.ylabel('Errror (%)') 75 | plt.show() 76 | 77 | -------------------------------------------------------------------------------- /truss.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.linalg import norm 3 | from scipy.linalg import eigh 4 | import matplotlib.pyplot as plt 5 | 6 | def setup(): 7 | # define the coordinate system 8 | x_axis = np.array([1,0]) 9 | y_axis = np.array([0,1]) 10 | 11 | # define the model 12 | nodes = { 1:[0,10], 2:[0,0], 3:[10,5]} 13 | degrees_of_freedom = { 1:[1,2], 2:[3,4], 3:[5,6] } 14 | elements = { 1:[1,3], 2:[2,3] } 15 | restrained_dofs = [1, 2, 3, 4] 16 | forces = { 1:[0,0], 2:[0,0], 3:[0,-200] } 17 | 18 | # material properties - AISI 1095 Carbon Steel (Spring Steel) 19 | densities = {1:0.284, 2:0.284} 20 | stiffnesses = {1:30.0e6, 2:30.0e6} 21 | # geometric properties 22 | areas = {1:1.0, 2:2.0} 23 | 24 | ndofs = 2 * len(nodes) 25 | 26 | # assertions 27 | assert len(densities) == len(elements) == len(stiffnesses) == len(areas) 28 | assert len(restrained_dofs) < ndofs 29 | assert len(forces) == len(nodes) 30 | 31 | return { 'x_axis':x_axis, 'y_axis':y_axis, 'nodes':nodes, 'degrees_of_freedom':degrees_of_freedom, \ 32 | 'elements':elements, 'restrained_dofs':restrained_dofs, 'forces':forces, 'ndofs':ndofs, \ 33 | 'densities':densities, 'stiffnesses':stiffnesses, 'areas':areas } 34 | 35 | def plot_nodes(nodes): 36 | x = [i[0] for i in nodes.values()] 37 | y = [i[1] for i in nodes.values()] 38 | size = 400 39 | offset = size/4000. 40 | plt.scatter(x, y, c='y', s=size, zorder=5) 41 | for i, location in enumerate(zip(x,y)): 42 | plt.annotate(i+1, (location[0]-offset, location[1]-offset), zorder=10) 43 | 44 | def points(element, properties): 45 | elements = properties['elements'] 46 | nodes = properties['nodes'] 47 | degrees_of_freedom = properties['degrees_of_freedom'] 48 | 49 | # find the nodes that the lements connects 50 | fromNode = elements[element][0] 51 | toNode = elements[element][1] 52 | 53 | # the coordinates for each node 54 | fromPoint = np.array(nodes[fromNode]) 55 | toPoint = np.array(nodes[toNode]) 56 | 57 | # find the degrees of freedom for each node 58 | dofs = degrees_of_freedom[fromNode] 59 | dofs.extend(degrees_of_freedom[toNode]) 60 | dofs = np.array(dofs) 61 | 62 | return fromPoint, toPoint, dofs 63 | 64 | def draw_element(fromPoint, toPoint, element, areas): 65 | x1 = fromPoint[0] 66 | y1 = fromPoint[1] 67 | x2 = toPoint[0] 68 | y2 = toPoint[1] 69 | plt.plot([x1, x2], [y1, y2], color='g', linestyle='-', linewidth=7*areas[element], zorder=1) 70 | 71 | def direction_cosine(vec1, vec2): 72 | return np.dot(vec1,vec2) / (norm(vec1) * norm(vec2)) 73 | 74 | def rotation_matrix(element_vector, x_axis, y_axis): 75 | # find the direction cosines 76 | x_proj = direction_cosine(element_vector, x_axis) 77 | y_proj = direction_cosine(element_vector, y_axis) 78 | return np.array([[x_proj,y_proj,0,0],[0,0,x_proj,y_proj]]) 79 | 80 | def get_matrices(properties): 81 | # construct the global mass and stiffness matrices 82 | ndofs = properties['ndofs'] 83 | nodes = properties['nodes'] 84 | elements = properties['elements'] 85 | forces = properties['forces'] 86 | areas = properties['areas'] 87 | x_axis = properties['x_axis'] 88 | y_axis = properties['y_axis'] 89 | 90 | plot_nodes(nodes) 91 | 92 | M = np.zeros((ndofs,ndofs)) 93 | K = np.zeros((ndofs,ndofs)) 94 | 95 | for element in elements: 96 | # find the element geometry 97 | fromPoint, toPoint, dofs = points(element, properties) 98 | element_vector = toPoint - fromPoint 99 | 100 | draw_element(fromPoint, toPoint, element, areas) # display the element 101 | 102 | # find element mass and stiffness matrices 103 | length = norm(element_vector) 104 | rho = properties['densities'][element] 105 | area = properties['areas'][element] 106 | E = properties['stiffnesses'][element] 107 | 108 | Cm = rho * area * length / 6.0 109 | Ck = E * area / length 110 | 111 | m = np.array([[2,1],[1,2]]) 112 | k = np.array([[1,-1],[-1,1]]) 113 | 114 | # find rotated mass and stiffness element matrices 115 | tau = rotation_matrix(element_vector, x_axis, y_axis) 116 | m_r = tau.T.dot(m).dot(tau) 117 | k_r = tau.T.dot(k).dot(tau) 118 | 119 | # change from element to global coordinates 120 | index = dofs-1 121 | B = np.zeros((4,ndofs)) 122 | for i in range(4): 123 | B[i,index[i]] = 1.0 124 | M_rG = B.T.dot(m_r).dot(B) 125 | K_rG = B.T.dot(k_r).dot(B) 126 | 127 | M += Cm * M_rG 128 | K += Ck * K_rG 129 | 130 | # construct the force vector 131 | F = [] 132 | for f in forces.values(): 133 | F.extend(f) 134 | F = np.array(F) 135 | 136 | # remove the restrained dofs 137 | remove_indices = np.array(properties['restrained_dofs']) - 1 138 | for i in [0,1]: 139 | M = np.delete(M, remove_indices, axis=i) 140 | K = np.delete(K, remove_indices, axis=i) 141 | 142 | F = np.delete(F, remove_indices) 143 | 144 | return M, K, F 145 | 146 | def get_stresses(properties, X): 147 | x_axis = properties['x_axis'] 148 | y_axis = properties['y_axis'] 149 | elements = properties['elements'] 150 | E = properties['stiffnesses'] 151 | 152 | # find the stresses in each member 153 | stresses = [] 154 | for element in elements: 155 | # find the element geometry 156 | fromPoint, toPoint, dofs = points(element, properties) 157 | element_vector = toPoint - fromPoint 158 | 159 | # find rotation matrix 160 | tau = rotation_matrix(element_vector, x_axis, y_axis) 161 | global_displacements = np.array([0,0,X[0],X[1]]) 162 | q = tau.dot(global_displacements) 163 | 164 | # calculate the strains and stresses 165 | strain = (q[1] - q[0]) / norm(element_vector) 166 | stress = E[element] * strain 167 | stresses.append(stress) 168 | 169 | return stresses 170 | 171 | def show_results(X, stresses, frequencies): 172 | print 'Nodal Displacments:', X 173 | print 'Stresses:', stresses 174 | print 'Frequencies:', frequencies 175 | print 'Displacment Magnitude:', round(norm(X),5) 176 | print 177 | 178 | 179 | def main(): 180 | # problem setup 181 | properties = setup() 182 | 183 | # determine the global matrices 184 | M, K, F = get_matrices(properties) 185 | 186 | # find the natural frequencies 187 | evals, evecs = eigh(K,M) 188 | frequencies = np.sqrt(evals) 189 | 190 | # calculate the static displacement of each element 191 | X = np.linalg.inv(K).dot(F) 192 | 193 | # determine the stresses in each element 194 | stresses = get_stresses(properties, X) 195 | 196 | # output results 197 | show_results(X, stresses, frequencies) 198 | 199 | plt.title('Analysis of Truss Structure') 200 | plt.show() 201 | 202 | 203 | 204 | if __name__ == '__main__': 205 | main() 206 | --------------------------------------------------------------------------------