Programming a Transient Truss Deflection Analysis a project done during Spring Quarter 2013-14 Ethan, Austin, You've done a wonderful job on the program, its validation, your presentation, and this report. Thank you for a wonderful quarter, L. O. Project Grade:A By Ethan Cating Austin Nash May 23, 2014 In partial fulfillment of the requirements of the course ME522 Advanced Finite Element Methods Professor Lorraine G. Olson Department of Mechanical Engineering Rose-Hulman Institute of Technology 1 Table of Contents Introduction, 3 Program Structure, 4 generating the input data, 4 elemental matrix assembly, 6 global matrix assembly, 8 forcing vector assembly, 10 constraint application, 12 solving the 2nd order system, 13 Program validation, 16 static case validation, 16 validation of forcing vector, 18 eigenvalue check and convergence check, 20 Creating and using the GUI, 23 Conclusions, 34 References, 35 Appendices, 36 2 Introduction: In this project, a program was created to examine truss deflections. More specifically, a transient simulation was performed to simulate the motion of a vehicle moving across a bridge. Using assembly methods learned in this course and the previous course, global stiffness and mass matrices were assembled along with a global forcing vector in order to calculate deflections of a truss system as the vehicle was driven across the bridge. A transient analysis was done using the trapezoidal method; at each step, a new forcing vector was created, and the deflections were updated. In the end, it was possible to simulate the truss deflection pattern as the vehicle moved across the bridge. A GUI was created to plot the simulation and to allow users of the program to create their own truss design and see how it deformed during transient loading. This report will detail the steps which were used in the programming and analysis; validation of the model will also be discussed, and conclusions will be drawn about what was learned from the project and what could be done in the future. 3 Program Structure: In building the complicated program, an overall structure had to be obtained. A series of Matlab functions were created in order to execute the overall routine needed to solve the problem. Figure 1 shows a basic flow chart of the overall structure of the program. Figure 1: Flow chart of the process in creating solution Input data and formation of the truss shape: As shown in the figure, the first phase of the overall program structure was to create the truss shape using input data. There were a few components necessary. In order to illustrate the formation of these inputs, a simple truss will be considered. This truss can be seen in Figure 2. Figure 2: Simple bar/truss system 4 In Figure 2, blue numbers represent node numbers and red numbers represent element numbers. The first input matrix is the coordinate matrix. This matrix has a row for each node, with columns corresponding to the node number and the x and y positions of that node. For this example, the coordinate matrix would be [ ] (1) The next input is the connectivity matrix. This matrix has a row for each element in the system, with the first column containing the element number, followed by columns containing the nodes which are connected by that element. For this example, the connectivity matrix is [ ] (2) Another input to the system is called the property matrix. In general, a property matrix has a row for each element and columns depicting material properties of each element. For purposes in this project, the Modulus of elasticity, cross-sectional area, and mass density were of concern. Users can type desired material properties into the input function. Here, it is assumed that all elements have the same properties. Thus, for the example in Figure 2, the property matrix is 1 E 2 E A A (3) The number of degrees of freedom was another input. For this project, there are two degrees of freedom for each node: vertical deflection and horizontal deflection. This is due to the need of putting all deflections back into a global coordinate system, which will be discussed later. The final input to the system is the constraint matrix. It contains a row for each degree of freedom being constrained. The first column is the node being constrained, the second column is the degree of freedom constrained for that node (1 for x, 2 for y), and the third column is the 5 constrained displacement, which is always zero for this project. For the current example, the constraint matrix for fixing the first node is [ ] (4) The Matlab function containing the input data for this project is called func_input_data and can be found in Appendix A. NOTE: Users of the program can type their nodal data into this function by hand and simulate the deflections with the plotting GUI. Alternatively, they can use the drawing GUI which allows users to point and click on a grid of points and draw their own truss. In this case, the drawing GUI is set up such that it sends the nodal coordinates and element connections into this input function. Then, the data can be simulated and plotted as usual. Assembly of elemental matrices: Looking back at Figure 1, the next step was to create the elemental mass and stiffness matrices for each of the elements in the system. In local coordinates of a particular element, the elemental mass and stiffness matrices are given by (5) and (6) [ [ ] (5) ] (6) However, the elemental mass and stiffness matrices must be rotated into a global frame to account for rotated elements; this is because the truss elements can only deform in the longitudinal direction. Thus, it is necessary to resolve the local coordinate deflections of rotated elements into global u and v coordinates. Figure 3 shows a truss element rotated at angle α and (7) shows the matrix needed for transforming the deflection from local to global coordinates. 6 Figure 3: Rotated element [ cos 0 sin 0 0 cos 0 ] sin (7) Applying the rotation transformation, the final equations for the elemental stiffness and mass matrices are given by (8) and (9). (8) (9) The Matlab function used in this project to create the element stiffness and mass matrices is called func_2D_truss_element and can be found in Appendix B. A brief overview of the function is depicted below in Figure 4. 7 Figure 4: Basic structure of elemental assembly Matlab function As seen in Figure 4, the elemental mass and stiffness matrices are assembled via a loop over all elements in the top level of the code. The top level sends to the function the nodes of an element and their coordinates along with the element’s properties. The function then calculates the angle of the element and the length of the element and assembles the proper elemental matrices, which are then sent back to the top level and stored in a 3D array. Global matrix assembly: Next, the global mass and stiffness matrices must be formed. This was done via a direct assembly method. For example, in the simple example from Figure 2, global assembly of the stiffness matrix would give 1 0 1 0 0 0 1 0 0 0 0 [ 0 0 2 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 8 0 1 0 0 0] (10) Here, the overlap occurs for the rows and columns associated with the degrees of freedom of node two due to the fact that node two connects the two elements. The global mass matrix is assembled in a similar manner. The Matlab function used in this project to create the global mass and stiffness matrices is called func_direct_assembly and can be found in Appendix C. A brief overview of the function is depicted in Figure 5. Figure 5: Basic structure of direct (global) assembly Matlab function As seen in Figure 5, the top level calls the global assembly function and sends in the connectivity data and the collection of elemental mass and stiffness matrices, as well as the total number of nodes and elements in the system. Inside the function, there is a loop over all of the elements. For the current element, the function uses the connectivity matrix and the number of DOFs to determine which DOFs are associated with that element. Then, the elemental matrices are added into the proper rows and columns of the global matrices. After each element is added into the global matrices, they are then sent back to the top level of the code. 9 Forcing vector assembly: In order to create the load vector, it was assumed that the only two forces acting on the truss system were point loads associated with each wheel set: the front wheels and the back wheels. It was assumed that half of the vehicle’s weight acted on the truss as a point load at the point of contact with the rear wheels and the other half of the vehicle’s weight acted on the truss as a point load at the point of contact with the front wheels. In instances where a wheel set was on a node, that node’s vertical DOF received the entire wheel set’s force. However, in instances in which the wheel(s) were located between the nodes of an element, the point loads of the wheels had to be resolved into nodal loads acting on each node of the element. An example of the proper way to split the point load into nodal loading is depicted below in Figure 6 and (11). Figure 6: Schematic for splitting force into nodal forces If the wheel in question is on an element but between its two nodes, the forces are split. This is done using the shape functions. Here, it can be said that . Now, the nodal forces are given by { } ( ) { } { 10 } { } (11) The Matlab function used in this project to create the global load vector is called myloadvec2 and can be found in Appendix D. A brief overview of the function is depicted below in Figure 7. Figure 7: Flow chart for executing load vector formation Inputs sent into the function are the connectivity and coordinate matrices, as well as the car length and weight and the x-position of the center of the car (d). The locations of the wheel forces are found using the car length and d. In the function, there is a loop over each element. Inside the loop, there are stacked if-statements for each of the point loads due to the wheel sets’ forces (two point loads total). The code determines whether either wheel is on the current element. If it is not, the code simply goes on to the next element. If it is, the code then determines where the wheel is on the element. If it is in between the nodes, the force is split accordingly. If it is on the left node, the code then determines if the left node is the leftmost point of the truss system, in which case the node gets all of the force. If the node is the left node of an internal element, it gets only half of the load because it will then get the other half when 11 the next element is sent through the if-statements. The same logic applies when the wheel is on the right node of an element. If that right node is the rightmost point of the system, the whole force is given to that node. If not, only half of the force is given. It is worth noting that all loads are given to the load vector entry corresponding to the vertical DOF of each node. Constraint application using elimination: The final step before solving for displacements was to constrain the system. It is worth noting that Allaire’s method was used initially. However, it failed to work for truss systems which were completely horizontal or vertical due to matrix singularity. Using elimination fixes this because the rows and columns of zeroes can be eliminated. Elimination is done by simply striking out the rows and columns in the global mass and stiffness matrices and forcing vector which are associated with a constrained degree of freedom. For our simple case from Figure 2, the first two DOFs are constrained. Thus, these would be eliminated from the global matrices. The constrained stiffness matrix is now given by 1 0 1 0 0 0 1 0 0 0 0 [ 0 0 2 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0] 2 0 1 [ 0 0 1 0 0 0 1 0 0 0 0 0 0] (12) The mass matrix and forcing vector are similarly constrained. By applying constraints, systems become non-singular, and deflections can be solved for. (NOTE: In order to solve a system with this particular stiffness matrix, one would have to eliminate rows and columns two and four. This can be done by telling the program that each node is on a roller constraining it in the vertical direction. This simple example was only being used to illustrate the process.) 12 The Matlab function used in this project to apply constraints is called elimination and can be found in Appendix E. A brief overview of the function is depicted below in Figure 8. Figure 8: Flow chart for application of constraints to the system matrices As seen in Figure 8, there is a loop over the constraint matrix in the function. The function reads in the nodes which are constrained and which DOFs of that node are constrained. It then removes the rows and columns associated with that DOF from the mass and stiffness matrices as well as the entry of the load vector associated with that DOF. The function also stores the constrained DOFs into an array so that the proper zero deflections can be added back into their proper places in the displacement vector after solving for the non-constrained displacements. Solving the system via 2nd order trapezoidal method: The transient equation for this system is given by ̈ +Ku = F 13 (13) In this project, the trapezoidal method was used to solve for the transient displacement at each time step. Using the trapezoidal method to solve the equation is beneficial due to the fact that this method is unconditionally stable. Thus, the time step is not as crucial as it would be for other methods. The complete set of equations used in solving the transient problem can be found in the course textbook in section 5.6.2 [1]; a copy of the page can also be found in Appendix G. A basic overview of this method is depicted below in Figure 9. Figure 9: Overview of trapezoidal method for solving transient equation Intermediate variables Khat and Fhat are created. These are used to solve for the deflections at the next time step. Then, the velocity and accelerations are updated. The total displacement vector at the next time step is then created by adding in zeroes for the constrained DOFs. Once the deflections were found, the car moved to a new position given by (14) 14 In (14), d represents the x-position of the center of the car, V represents the car velocity, for which 15 m/s was used, and represents the time step, for which 0.001 seconds was used. With a new car position, the forcing vector is then updated, constraints are reapplied, and new deflections are found. This process is repeated iteratively until the rear wheel of the vehicle reaches the end of the bridge. 15 Program Validation: In order to validate the program, many test cases were run. In each case, the program created for this project was used to obtain an answer which was then checked with a theoretical answer derived from class or from a textbook. Each case is presented below. Static case from course text: The first test case was a simple static truss problem from the course notes [1]. There is a horizontal bar connected to a vertical bar, with each bar constrained at one node. The nonconstrained node is pulled on in the vertical direction. The purpose of using this test case was to determine that the elemental and global matrix assembly functions and the constraint function were all working properly. A schematic of the test case is seen in Figure 10. Figure 10: Schematic for splitting force into nodal forces In Figure 10, the elemental stiffness matrices are given by 0 0 0 1 0 0 [ 0 1 16 0 0 0 1 0 0 0 1 ] (15) 0 1 0 0 0 1 0 0 1 0 1 [ 0 0 0 0 0] Assembling the global system gives 0 0 0 0 0 [0 0 0 0 0 0 u1 1 0 1 0 0 v1 0 1 0 1 0 u2 1 0 1 0 0 v2 0 1 0 1 0 u3 0 0 0 0 0 ] { v3 } 0 0 0 F 0 0 (16) Applying constraints, the system becomes [ This implies that and ]{ } { } (17) . Using the program created for this project and setting F,L,E, and A all equal to unity, the displacement vector was calculated to be {0 0 0 1 0 0} (18) , signaling that the program succeeded in passing the static deflection test case. The success of this case signaled that the stiffness matrix formation and assembly was working correctly as well as the constraint application. 17 Validation of the forcing vector assembly: It was necessary to make sure that the forcing vector function was correctly finding the global forcing vector. In order to do this, a few test cases were determined. It was necessary to make sure that the forcing vector was handling situations where the wheels were on the nodes of an element and between the nodes of an element. Four test cases were applied; schematics can be seen below in Figure 11. Figure 11: Test cases used for forcing vector validation In Case 1, one would expect the global forcing vector to return a force equal to the wheel force for the y-degree of freedom associated with node one (the leftmost node). This case was used because it was necessary to see that the whole load was given to that DOF. With a car weight of 10,000 N (wheel forces of -5,000 N), the program returned { 0 5000 0 0 0 0 0 0 } 18 (19) This is a very small point, but I thought I would mention it for your future writing. The comma goes with the equation, not on the following line. , which signaled a success. For Case 2, the program returned { 0 0 0 0 0 0 0 5000 } (20) , again signaling a success. Case 3 was used in order to determine that the looping through elements didn’t cause too much force to be added to a node. For example, if the whole wheel force was applied when the wheel was on an internal node, it would get doubled when the loop moved onto the next element. For Case 3, the program returned a forcing vector which was { 0 0 0 5000 0 5000 0 0 } (21) , which is the proper forcing vector. Case 4 was used to ensure that the forces were split properly in cases where the wheels were applying forces between the two nodes of an element. For the first wheel, the proper split is given by { } ( ) { } { } (22) } { } (23) For the second wheel, the proper split is given by { } ( ) { Using assembly for Case 4, this would give a forcing vector of 19 F1 y F2 y F3 y { F4 y } { 0.2 1 0.8 0 } (24) The program returned a forcing vector of { 0 1000 0 5000 0 4000 0 0 } (25) , which is the correct forcing vector for a 10,000 N car assuming the geometry of Case 4. The load vector seemed to pass all conditions that it could encounter for the vehicle problem. Thus, it was deemed that the load vector was being assembled properly. Eigenvalue check and convergence check: The last checks were to check convergence and to check that the eigenvalues produced by the constrained system were in accordance with tabulated values of natural frequencies for longitudinally vibrating beams [2]. In order to check convergence, a simple horizontal bar was fixed at one end and pulled on at the other end, as in Figure 12. Figure 12: Simple bar/truss system to check for convergence The idea was to take the bar and find the corresponding eigenvalues and compare them to the theoretical eigenvalues. This would ensure that the mass and stiffness matrices were correctly 20 assembled and constrained. This check would all but ensure the program to be working seamlessly. In theory, the natural frequencies of the bar are given by reference ( √ Using E = 200 GPa and ) (26) = 8000 kg-m-3, the first three theoretical natural frequencies were found to be { } { } (27) Using the program developed in this project, for a bar split into 5 elements and 6 nodes, it was found that { } { } (28) Using the program developed in this project, the number of elements and nodes was then increased to check that the values did not significantly change. For a bar split into 15 elements and 16 nodes, it was found that { } { } (29) The values were extremely close to the theoretical values, again signaling that the program was working correctly. Additionally, the values returned from the program for each mesh were 21 really close to each other, signaling that our mesh was experiencing convergence and that nothing out of the ordinary was happening when the mesh was refined. Using the same two configurations, the tip deflection of the rod was also found and compared to the theoretical value. The theoretical value is given by presumably this is a static tip deflection (30) Using each of the meshes tested above, the tip deflections were found to be 0.0023 m, matching the expected value. This was just another simple check to make sure that there were no issues with refining the mesh and obtaining convergence. This concluded the tests performed on the program. Through testing, the program was deemed to be working correctly to the best of the testers’ knowledge. Each part of the Matlab program was tested for validation, and each passed the test. 22 Creating the GUI: Allowing simulation of motion and user defined GUI Truss Program Animation and Drawing GUI The final aspect of the project was to create a graphical user interface (GUI) that lets the user create and animate a truss design. This section outlines the steps to using the GUI successfully. The code for the two GUI’s, Truss_Program_GUI and Truss_Drawing_GUI, are located in Appendix H and I, respectively. The main GUI, Truss_Program_GUI, is shown in the following form during an animation (see Figure 13). This is what the user sees while the simulation of motion is occurring. Figure 13: Truss_Program_GUI during transient deflection animation Figure 14 is a picture of the drawing GUI, Truss_Drawing_GUI, during node placement and element creation. It is a sub-function of the main GUI. The image is a representation of the process of creating a truss. 23 Figure 14: Truss_Drawing_GUI during element creation state The user interface is a rough design, with much more work that would need to be done before mass distribution. The steps to go through the process of using the GUI are straightforward, but do not allow for much use outside of those steps. Any use of the GUI that strays from what was intended will most likely lead to errors and the program not working. Once the program is run for the first time, the initial screen is as shown below in Figure 15. 24 Figure 15: Truss_Program_GUI after just starting the program From this point, there are three selectable items for the user to choose from and a message box at the top right hand corner telling the user what file they have selected if they chose to load truss data. For this example it is easier to show how to create a new truss design, load that data, and then animate it. The user also has the option to load any set of nodes and elements already created to simulate and animate at any time rather than creating a new design before loading. The Create New Bridge option will be used in this example as shown in Figure 16. Figure 16: Truss_Program_GUI select menu 25 After selecting that option in the select menu, a prompt asks the user for the name of the bridge design they are creating. In this example, the data is saved as Example_Case. Click the save button to move on as shown in Figure 17. Figure 17: Truss_Drawing_GUI save new truss dialog box Once the name of the bridge design is saved as its own .mat file, all of the bridge design data will be saved to that .mat file. Also once it is saved, the drawing GUI appears over the main GUI. It contains another select menu, two radio buttons, a save button, a message box, and a reset button. (The reset button is still not finished. Most of the code is there, but there are a few problems with it. If the user needs to erase points or start over, it is best to just close the drawing GUI and restart from the main GUI.) The select menu has the options for which bridge size the user would like to use for their bridge design, as seen in Figure 18. Short will be used in this example. 26 Figures 18: Truss_Drawing_GUI while loading bridge design Once the bridge size is selected, the select menu is disabled so that the loop for saving the nodes and elements does not have a problem saving the data and sending it to the input function described earlier in the report. The user must then click on the Nodes radio button as depicted in Figure 19. A set of cross hairs will appear for the user to select the points they want their nodes to be located at and selections snap to the closest grid point on the screen. This phase continues until the user presses the E button on the keyboard to exit node selection. 27 Figure 19: Truss_Drawing_GUI during node selection state Once the E button is pressed, the cross hairs go away. The message box lets the user know that they have left the node selection option, and the Nodes radio button is unchecked. The nodes remain on screen for the remainder of use of the drawing GUI as seen in Figure 20. 28 Figure 20: Truss_Drawing_GUI with nodes in place The user then selects the Elements radio button as depicted in Figure 21 and is able to draw lines that connect the nodes to each other. The crosshairs reappear and selections snap to the closest node within a small distance. The function only allows for nodes to be selected so that users cannot create elements with no nodal endpoints. Additionally, the message has changed to show that the user is in element selection mode. The way to exit this mode is by pressing the R button on the keyboard twice. 29 Figure 21: Truss_Drawing_GUI during element creation state Once the user has finished placing all the elements they would like for their design and have pressed the R button twice, the user has exited element creation mode. The message box shows this and the Elements radio button is unchecked. The save button is now enabled as in Figure 22 and once clicked, it saves the bridge node and element data to the .mat file and closes the drawing GUI. 30 Figure 22: Saving a finished truss design The user is then returned to the main GUI depicted in Figure 23 on the following page. Now one must load the input data of the newly created bridge. Using the option of loading the bridge in the select menu, the bridge data is loaded for use in the program. The name of the loaded .mat file is now in the message box. 31 Figure 23: Loading a saved bridge design After selecting the Load Bridge option, the user selects the bridge from the folder they saved it to and it loads in, as in Figure 24. Figure 24: Selection of proper truss input file 32 Once the coordinate and connectivity data is loaded, it needs to be simulated to get the deflection data. Press the Simulate button to solve for the deflection data. Doing this causes the top level script Simple_FEA_Program to run, which solves for deflections at each time step. That data is then appended to the .mat file. Once this is complete the user can press the Animate button as depicted in Figure 25 and watch the fruits of their labor. For the case of bridge data already made, the user can just hit the animate button once they load the data. Figure 25: Truss_Program_GUI animating truss deflection as the car moves This completes the process of creating a truss design and simulating its subsequent deflections as a vehicle is driven across its elements. This also concludes the content of the program created in this project. There are two phases: the input data is created and gathered/saved, and the deflections are then calculated via the functions described in this report. The motion is then plotted using the GUI developed above. 33 Conclusions: In doing this project, we found that creating a complex code takes many steps and phases. For example, there were many functions used in the program. It was necessary to examine each function individually in order to ensure that each was working properly. There are several areas of this project that could use additional study and work. For example, it would be nice to do some post-processing on the deflection data. Using the nodal deflections, we could then take the element cross-sectional areas and calculate the stress in each element at each time step. These stresses could then be displayed on the GUI as the car drives across the bridge. This could be useful for a fun example in an EM121 course. Another possibility for improvement would be to perform additional work to incorporate other forces into the model. Currently, the only forces in the system are point forces associated with the wheels. Other forces could be added into the analysis, such as horizontal friction forces due to the wheels contacting the trusses. Another option could be to consider the weight of the beams. An area for future work on the GUI could be to use a point-and-click method which will display nodes and elements at the same time, rather than in separate processes. Adding error codes into the GUI would be nice, as well. This could be beneficial so that users would know why a design fails. Overall, we feel that the project was a success. It was a good learning experience in creating a rather complex analysis by combining several simple ideas. We feel that this project could be beneficial to us later on in our careers. We will most likely encounter problems such as this one, where an immediate solution is not apparent and one must create complex programs that work together to develop a finished product. We would also like to thank Dr. Jones for allowing us to use and amend some of his global assembly code from the ME422 course [3]. It was a great help to us in this project. 34 References: [1] L. G. Olson, Advanced Finite Element Methods, Terre Haute, IN: Rose-Hulman Institute of Technology, 2014. [2] R. D. Blevins, Formulas for Natural Frequency and Mode Shape, New York, NY: Van Nostrand Reinhold Company, 1979. [3] S. Jones, Finite Element Analysis, Terre Haute, IN: Rose-Hulman Institute of Technology, 2013-14. 35 Appendix A: func_input_data (Input data function) function [CoordMat,ConnMat,PropMat,nDOF,ConstrMat]=func_input_data rho = 8000; E = 200e9; A = 3250/(1000^2); % Coordinate Matrix lists the position of each node % format: node number, x position, y position [m] CoordMat = sortrows([... 1 0 0; 2 5 0; 3 10 0; 4 15 0; 5 12.5 8; 6 7.5 8; 7 2.5 8;]); % Connectivity Matrix details which nodes are in each element % format: element number, node 1, node 2 ConnMat = [... 1 1 7; 2 1 2; 3 2 7; 4 6 7; 5 2 6; 6 2 3; 7 3 6; 8 5 6; 9 3 5; 10 3 4; 11 4 5]; % Property Matrix lists the properties for each element % format: element number, elastic modulus [Pa], cross-sectional area [m^2] PropMat = zeros(size(ConnMat,1),4); PropMat(1:size(ConnMat,1),1) = [1:1:size(ConnMat,1)]; PropMat(1:size(ConnMat,1),2) = E; PropMat(1:size(ConnMat,1),3) = A; PropMat(1:size(ConnMat,1),4) = rho; % Number of degrees of freedom for each node nDOF = 2; % Constraint Matrix lists the constraints on particular nodes % format: node number, direction (x=1, y=2), prescribed displacement ConstrMat = [... 1 1 0; 1 2 0; 4 1 0; 4 2 0]; 36 Appendix B: func_2D_truss_element (elemental matrix assembly function) function [ke, me] = func_2D_truss_element(numElem,CoordMat,ConnMat,PropMat) % read in the properties for the current element E = PropMat(numElem,2); A = PropMat(numElem,3); rho = PropMat(numElem,4); % pick out the 2 nodes contained in this element node1 = ConnMat(numElem,2); node2 = ConnMat(numElem,3); % find the position of each node x1 y1 x2 y2 = = = = CoordMat(node1,2); CoordMat(node1,3); CoordMat(node2,2); CoordMat(node2,3); % compute the element length (ell) and the inclination angle (theta) ell = sqrt((x2-x1)^2 + (y2-y1)^2); theta = atan((y2-y1)/(x2-x1)); % shorthand notation for cosine and sine c = cos(theta); s = sin(theta); % compile the element stiffness matrix ke = E*A/ell*[... c^2, c*s, -c^2, -c*s; c*s, s^2, -c*s, -s^2; -c^2, -c*s, c^2, c*s; -c*s, -s^2, c*s, s^2]; T = [cos(theta),sin(theta),0,0;0,0,cos(theta),sin(theta)]; M = rho*A*ell/6*[2,1;1,2]; me = T'*M*T; end 37 Appendix C: func_direct_assembly (global matrix assembly function) function [K,M] = func_direct_assembly(nDOF,numNode,numElem,ConnMat,Ke,Me) % predefine the size of the global stiffness matrix K = zeros(nDOF*numNode,nDOF*numNode); M = zeros(nDOF*numNode,nDOF*numNode); % use the direct assembly method to compile the global stiffness matrix for II = 1:numElem n1 = ConnMat(II,2); n2 = ConnMat(II,3); % node 1 % node 2 n1_dofs = (2*(n1-1)+(1:2)); n2_dofs = (2*(n2-1)+(1:2)); % dofs associated with node 1 % dofs associated with node 2 K(n1_dofs,n1_dofs) K(n1_dofs,n2_dofs) K(n2_dofs,n1_dofs) K(n2_dofs,n2_dofs) = = = = K(n1_dofs,n1_dofs) K(n1_dofs,n2_dofs) K(n2_dofs,n1_dofs) K(n2_dofs,n2_dofs) + + + + Ke(1:2,1:2,II); Ke(1:2,3:4,II); Ke(3:4,1:2,II); Ke(3:4,3:4,II); M(n1_dofs,n1_dofs) M(n1_dofs,n2_dofs) M(n2_dofs,n1_dofs) M(n2_dofs,n2_dofs) = = = = M(n1_dofs,n1_dofs) M(n1_dofs,n2_dofs) M(n2_dofs,n1_dofs) M(n2_dofs,n2_dofs) + + + + Me(1:2,1:2,II); Me(1:2,3:4,II); Me(3:4,1:2,II); Me(3:4,3:4,II); end 38 Appendix D: myloadvec2 (load vector assembly function) function [F] = myloadvec2(CoordMat,ConnMat,nDOF,d,Wc,Lc) % d1 is the rear wheelset and d2 is the front wheelset F1 = Wc/2; F2 = Wc/2; d1 = d - Lc/2; F = zeros(size(CoordMat,1)*nDOF,1); CoordMat = sortrows(CoordMat); for i = 1:size(ConnMat,1) d2 = d + Lc/2; % initialize the load vector % loop through elements node1 = ConnMat(i,2); node2 = ConnMat(i,3); % nodes for the elements if CoordMat(node1,3) == 0 & CoordMat(node2,3) == 0 a ground element? % is current element x1 = CoordMat(node1,2); x2 = CoordMat(node2,2); % element x coords. % make sure that we get the left/right nodes correctly % interpreted if x1 < x2 left_node = x1; right_node = x2; else left_node = x2; right_node = x1; node1 = ConnMat(i,3); node2 = ConnMat(i,2); end L = right_node - left_node; % element length % if-else statement for both wheels --- start with rear wheel if d1 > left_node & d1 < right_node % wheel between the two nodes? F(node1*nDOF) = F(node1*nDOF) + F1/L*(right_node - d1); F(node2*nDOF) = F(node2*nDOF) + F1 - F1/L*(right_node - d1); elseif d1 == left_node % wheel on the first node? if left_node == min(CoordMat(:,2)) F(node1*nDOF) = F(node1*nDOF) + F1; else F(node1*nDOF) = F(node1*nDOF) + F1/2; end elseif d1 == right_node % wheel on the second node? if right_node == max(CoordMat(:,2)) F(node2*nDOF) = F(node2*nDOF) + F1; else % if here, wheel isn't on current element F(node2*nDOF) = F(node2*nDOF) + F1/2; 39 end else F(node1*nDOF) = F(node1*nDOF) + 0; F(node2*nDOF) = F(node2*nDOF) + 0; end % repeat for the other wheel if d2 > left_node & d2 < right_node F(node1*nDOF) = F(node1*nDOF) + F2/L*(right_node - d2); F(node2*nDOF) = F(node2*nDOF) + F2 - F2/L*(right_node - d2); elseif d2 == left_node if left_node == min(CoordMat(:,2)) F(node1*nDOF) = F(node1*nDOF) + F2; else F(node1*nDOF) = F(node1*nDOF) + F2/2; end elseif d2 == right_node if right_node == max(CoordMat(:,2)) F(node2*nDOF) = F(node2*nDOF) + F2; else F(node2*nDOF) = F(node2*nDOF) + F2/2; end else F(node1*nDOF) = F(node1*nDOF) + 0; F(node2*nDOF) = F(node2*nDOF) + 0; end else F(node1*nDOF) = F(node1*nDOF) + 0; F(node2*nDOF) = F(node2*nDOF) + 0; end end end 40 Appendix E: elimination (constraint application function) function [K_c,M_c,F_c,cdof] = elimination(K,M,F,ConstrMat) % loop through each constraint for k = 1:size(ConstrMat,1) % decide which dof the matrix is telling us to constrain --% i.e. - is it constraining an x-dir dof or a y-dir dof? if rem(ConstrMat(k,2),2) == 0 cdof(k) = ConstrMat(k,1)*ConstrMat(k,2); else cdof(k) = 2*ConstrMat(k,1)*ConstrMat(k,2)-1; end end % apply elimination method to the mass and stiffness matrices and the % load vector K(cdof,:)=[]; K(:,cdof) = []; M(cdof,:)=[]; M(:,cdof) = []; F(cdof) = []; K_c = K; M_c = M; F_c = F; end 41 Appendix F: Simple_FEA_Program (top level code calling all functions) %Simple_FEA_program clear all close all clc % grab the input data [CoordMat,ConnMat,PropMat,nDOF,ConstrMat]=func_input_data; numElem = size(ConnMat,1); numNode = size(CoordMat,1); % create the element matrices (do for each element) for II = 1:numElem [ke, me] = func_2D_truss_element(II,CoordMat,ConnMat,PropMat); Ke(:,:,II) = ke; clear ke Me(:,:,II) = me; clear me end % global assembly --- direct assembly method [K,M] = func_direct_assembly(nDOF,numNode,numElem,ConnMat,Ke,Me); % initialize car location; get ready for time stepping Lc = 4; d = -Lc/2; dtot(1) = d; Wc = -10000; i = 1; u = zeros(numNode*nDOF-size(ConstrMat,1),1); ud = u; udd = u; time(i) = 0; dt = 0.001; V = 15; % transient loop for driving car; % forcing vector each time, get new values for the while d - Lc/2 <= max(CoordMat(:,2)) [f] = load_vector_v2(CoordMat,ConnMat,nDOF,d,Wc,Lc); [K_c,M_c,F_c,cdof] = elimination(K,M,f,ConstrMat); K_hat = K_c + 4/(dt^2)*M_c; F_hat = F_c + M_c*(4/(dt^2)*u(:,i) + 4/dt*ud + udd(:,i)); u(:,i+1) = K_hat\F_hat; ncdof = [1:nDOF*numNode]; ut(cdof,i+1) = 0; ncdof(cdof) = []; ut(ncdof,i+1) = u(:,i+1); 42 udd(:,i+1) = 4/(dt^2)*(u(:,i+1)-u(:,i)) - 4/dt*ud - udd(:,i); ud = ud + dt/2*udd(:,i) + dt/2*udd(:,i+1); time(i+1) = time(i) + dt; dtot(i+1) = d + V*dt; i = i + 1; d = d + V*dt; end % store the new nodal positions at each time step for i = 1:size(ut,2) utot(1:numNode,i) = ut(1:2:numNode*nDOF-1,i) + CoordMat(:,2); vtot(1:numNode,i) = ut(2:2:numNode*nDOF,i) + CoordMat(:,3); end save('Simple_Truss.mat', 'CoordMat', 'ConnMat', 'utot', 'vtot', 'numNode',... 'nDOF', 'ut', 'dtot','Lc') 43 Appendix G: Trapezoidal Method from Course Notes 44 Appendix H: Truss_Program_GUI (main GUI function) function Truss_Program_GUI % Truss_Program_GUI animates the deflection % Create and hide the GUI as it is being constructed. f = figure('Visible','on','Position',[320,120,1280,800],'Resize','off'); set(gcf,'Color','w') set(f,'Name','Truss Deflection Simulation and Animation') % Construct the components hanim = uicontrol('Style','pushbutton','String','Animate',... 'Position',[1050,30,200,100],'Callback',{@hanim_Callback}); hsim = uicontrol('Style','pushbutton','String','Simulate',... 'Position',[1050,150,200,100],'Callback',{@hsim_Callback}); hselect = uicontrol('Style','popupmenu','String',{'....','Load Bridge','Create New Bridge'},... 'Position',[1060,650,180,50],'Callback',{@hselect_Callback}); hpanel = uipanel(f,'Units','pixels','Position',[30,30,1000,748],... 'Title','Animation','BackgroundColor','w'); hpanel2 = uipanel(f,'Units','pixels','Position',[1050,728,200,50],... 'Title','Bridge Selected','BackgroundColor','w'); hpanel3 = uipanel(f,'Units','pixels','Position',[1050,668,200,50],... 'Title','Load or Create','BackgroundColor','w'); ha = axes('Units','pixels','Position',[35,35,990,740],... 'Visible','off','HandleVisibility','callback'); htext = uicontrol('Style','text','String','- - - - - - - - - - - - - - - - -',... 'Position',[1060,738,180,20],'BackgroundColor','w'); % Function Callbacks function hanim_Callback(source,eventdata) % Animates the deflection data % Load in Saved Data from Simple_FEA_program fn = guidata(f); Data = load(fn); CoordMat = Data.CoordMat; ConnMat = Data.ConnMat; utot = Data.utot; vtot = Data.vtot; numNode = Data.numNode; 45 nDOF = Data.nDOF; ut = Data.ut; sf = .25/max([max(max(ut)) abs(min(min(ut)))]); dtot = Data.dtot; Lc = Data.Lc; xbase = Data.xbase; % Find the largest deflection in the system for i = 1:size(ut,2) for l = 1:length(ConnMat) diffu1 = (utot(ConnMat(l,2),i)-utot(ConnMat(l,2),1)); diffu2 = (utot(ConnMat(l,3),i)-utot(ConnMat(l,3),1)); diffv1 = (vtot(ConnMat(l,2),i)-vtot(ConnMat(l,2),1)); diffv2 = (vtot(ConnMat(l,3),i)-vtot(ConnMat(l,3),1)); diffu = abs(diffu2-diffu1); diffv = abs(diffv2-diffv1); max_diff_frac = sqrt(diffu^2 + diffv^2); frac(i) = max_diff_frac; end end col_frac = frac(:)./max(frac(:)); % Plot transient deflections for i = 1:size(ut,2) dutot(1:numNode,i) = ut(1:2:numNode*nDOF-1,i).*sf; dvtot(1:numNode,i) = ut(2:2:numNode*nDOF,i).*sf; utot(1:numNode,i) = dutot(1:numNode,i) + CoordMat(:,2); vtot(1:numNode,i) = dvtot(1:numNode,i) + CoordMat(:,3); aa = xbase(end) + 2; bb = max(CoordMat(:,3))*7/4; axis([-2 aa -2 bb]) box off set(gcf,'Color','w') hold on % draw and plot moving car ct = rectangle('Position',[dtot(i)1.75,0.75,3,1.5],'Curvature',[1,0.875],... 'FaceColor','r'); cw = rectangle('Position',[dtot(i)1.625,0.875,2.75,1.25],'Curvature',[1,0.875],... 'FaceColor','w'); cp = rectangle('Position',[dtot(i).375,0.75,.25,1.375],'FaceColor','r'); cb = rectangle('Position',[dtot(i)-1Lc/2,0.5,6,1],'Curvature',[0.8,0.4],... 'FaceColor','r'); dft = rectangle('Position',[dtot(i)0.5+Lc/2,0,1,1],'Curvature',[1,1],... 'FaceColor','k'); drt = rectangle('Position',[dtot(i)-0.5Lc/2,0,1,1],'Curvature',[1,1],... 'FaceColor','k'); 46 dfw = rectangle('Position',[dtot(i)0.375+Lc/2,0.125,0.75,0.75],'Curvature',[1,1],... 'FaceColor','w'); drw = rectangle('Position',[dtot(i)-0.375Lc/2,0.125,0.75,0.75],'Curvature',[1,1],... 'FaceColor','w'); % plot truss lines for l = 1:length(ConnMat) inline(l) = line([utot(ConnMat(l,2),1) utot(ConnMat(l,3),1)],... [vtot(ConnMat(l,2),1) vtot(ConnMat(l,3),1)],... 'LineStyle','--','LineWidth',0.1,'Color','k'); hline(l) = line([utot(ConnMat(l,2),i) utot(ConnMat(l,3),i)],... [vtot(ConnMat(l,2),i) vtot(ConnMat(l,3),i)],... 'Color',[col_frac(i) 0 1-col_frac(i)],'LineWidth',5); end % plot water and road entras and exits rectangle('Position',[0,-2,aa-2,1],'FaceColor','c','EdgeColor','c') rectangle('Position',[-2,-2,2,2],'FaceColor','k') rectangle('Position',[aa-2,-2,2,2],'FaceColor','k') a_orig = plot([utot(:,1);utot(1,1)],... [vtot(:,1);vtot(1,1)],'ok'); a1 = plot([utot(:,i);utot(1,i)],... [vtot(:,i);vtot(1,i)],... 'og','MarkerFaceColor','g'); set(gca,'Visible','off') % pause(0.001); hold off cla(ha) M(i) = getframe; % use to create movie end axis([-2 aa -2 bb]) box off set(gcf,'Color','w') hold on for l = 1:length(ConnMat) hline(l) = line([utot(ConnMat(l,2),i) utot(ConnMat(l,3),i)],... [vtot(ConnMat(l,2),i) vtot(ConnMat(l,3),i)],'Color','b'); end % replots water and road after cla(ha) set(hline(:),'Color','b','LineWidth',5) rectangle('Position',[0,-2,aa-2,1],'FaceColor','c','EdgeColor','c') rectangle('Position',[-2,-2,2,2],'FaceColor','k') rectangle('Position',[aa-2,-2,2,2],'FaceColor','k') 47 a1 = plot([utot(:,end);utot(1,end)],... [vtot(:,end);vtot(1,end)],... 'og','MarkerFaceColor','g'); end function hselect_Callback(source,eventdata) % Selects an existing bridge design or lets the user create his or her own value = get(hselect,'Value'); switch value case 1 set(hselect,'String',{'....','Existing Bridge','Create New Bridge'}); case 2 if(exist('fn')==0)|(fn==0) fn='Documents\*.mat'; else fn=['Documents\',fn]; end [fn,pn]=uigetfile('Documents\*.mat','Specify the Bridge Design Desired',fn); name = [pn,fn]; guidata(f,fn) fn_new = strrep(fn,'.mat',''); set(htext,'String',fn_new); case 3 fn = uiputfile('*.mat','Specify the name of the Truss Design'); Truss_Drawing_GUI(fn); end end function hsim_Callback(source,eventdata) fn = guidata(f); Simple_FEA_program(fn); end end 48 Appendix I: Truss_Drawing_GUI (truss drawing sub-function GUI) function [] = Truss_Drawing_GUI(fid) % This GUI function lets you draw new truss designs to be animated f2 = figure('Visible','on','Position',[320,220,1280,600],'Resize','off'); set(gcf,'Color','w') set(f2,'Name','Truss Design Manager') % Create the components hsize = uicontrol('Style','popupmenu','String',{'......','Short','Meduim','Long'},... 'Position',[30,532,100,40],'Callback',{@hsize_Callback}); hsave = uicontrol('Style','pushbutton','String','Save',... 'Position',[360,540,100,45],'Callback',{@hsave_Callback}); set(hsave,'handlevisibility','off','Enable','off'); hreset = uicontrol('Style','pushbutton','String','Reset',... 'Position',[1160,540,100,45],'Callback',{@hreset_Callback}); set(hreset,'handlevisibility','off','Enable','on'); hpanel = uipanel(f2,'Units','pixels','Position',[20,540,120,50],... 'Title','Bridge Size','BackgroundColor','w'); hpanel2 = uipanel(f2,'Units','pixels','Position',[20,540,120,50],... 'Title','Select Bridge Size','BackgroundColor','w'); hbpanel = uipanel(f2,'Units','pixels','Position',[20,20,1240,510],... 'Title','Bridge Design','BackgroundColor','w'); hpanel3 = uipanel(f2,'Units','pixels','Position',[150,540,200,50],... 'Title','Draw','BackgroundColor','w'); hpanel4 = uipanel(f2,'Units','pixels','Position',[470,540,300,50],... 'Title','Message','BackgroundColor','w'); hnode = uicontrol('Style','radiobutton','String','Nodes','BackgroundColor','w',... 'Position',[220,546,50,30],'Callback',{@hnode_Callback}); set(hnode,'handlevisibility','off','Enable','off'); helement = uicontrol('Style','radiobutton','String','Elements','BackgroundColor','w',... 'Position',[280,546,65,30],'Callback',{@helement_Callback}); set(helement,'handlevisibility','off','Enable','off'); htext = uicontrol('Style','text','String','- - - - - - - - - - - - - - ','BackgroundColor','w',... 'Position',[480,550,280,20]); ha = axes('Units','pixels','Position',[100,60,1100,450],... 'Visible','off'); set(ha,'handlevisibility','callback'); 49 % Select Size of Truss layout and plots point layout and axes size respectively function hsize_Callback(source,eventdata) value = get(hsize,'Value'); switch value case 1 set(hsize,'String',{'....','Small','Medium','Large'}); case 2 cla(ha) short = 15; [X,Y] = meshgrid(0:short,1:10); xbase = 0:5:short; guidata(f2,X) guidata(f2,Y) guidata(f2,xbase) plot(X,Y,'ok') hold on rectangle('Position',[0,2,short,1],'FaceColor','c','EdgeColor','c') rectangle('Position',[-1,-2,1,2],'FaceColor','k') rectangle('Position',[short,-2,1,2],'FaceColor','k') for i = 1:length(xbase)-1 line([xbase(i),xbase(i+1)],[0,0],... 'Color','b','LineWidth',5); end plot(xbase,0,'og','MarkerFaceColor','g') box off axis([(min(min(X))-1) (max(max(X))+1) (min(min(Y))-3) (max(max(Y))+1)]) save(fid,'xbase') set(hsize,'Enable','off') set(hnode,'Enable','on') set(helement,'Enable','on') case 3 cla(ha) med = 30; [X,Y] = meshgrid(0:med,1:15); xbase = 0:5:med; 50 guidata(f2,X) guidata(f2,Y) guidata(f2,xbase) plot(X,Y,'ok') hold on rectangle('Position',[0,2,med,1],'FaceColor','c','EdgeColor','c') rectangle('Position',[-1,-2,1,2],'FaceColor','k') rectangle('Position',[med,-2,1,2],'FaceColor','k') for i = 1:length(xbase)-1 line([xbase(i),xbase(i+1)],[0,0],... 'Color','b','LineWidth',5); end plot(xbase,0,'og','MarkerFaceColor','g') box off axis([(min(min(X))-1) (max(max(X))+1) (min(min(Y))-3) (max(max(Y))+1)]) save(fid,'xbase') set(hsize,'Enable','off') set(hnode,'Enable','on') set(helement,'Enable','on') case 4 cla(ha) long = 45; [X,Y] = meshgrid(0:long,1:20); xbase = 0:5:long; guidata(f2,X) guidata(f2,Y) guidata(f2,xbase) plot(X,Y,'ok') hold on rectangle('Position',[0,2,long,1],'FaceColor','c','EdgeColor','c') rectangle('Position',[-1,-2,1,2],'FaceColor','k') rectangle('Position',[long,-2,1,2],'FaceColor','k') for i = 1:length(xbase)-1 line([xbase(i),xbase(i+1)],[0,0],... 'Color','b','LineWidth',5); end plot(xbase,0,'og','MarkerFaceColor','g') 51 box off axis([(min(min(X))-1) (max(max(X))+1) (min(min(Y))-3) (max(max(Y))+1)]) save(fid,'xbase') set(hsize,'Enable','off') set(hnode,'Enable','on') set(helement,'Enable','on') end end % Lets the user place nodes on the point grid function hnode_Callback(source,eventdata) node = get(hnode,'Value'); if node == 1 set(helement,'Value',0) i = 1; X = guidata(f2); Y = guidata(f2); xbase = guidata(f2); for i = 1:length(xbase) Node(:,i) = [i;xbase(i);0] end j = 1; k = 1; while node == 1 [x,y,key] = ginput(1); if (key == 'e') set(htext,'String','Exited node selection.') break; elseif (key == 1) set(htext,'String','Select node or press E to exit') xn(k) = round(x); yn(k) = round(y); Node(:,k+length(xbase)) = [k+length(xbase);xn(k);yn(k)] plot(xn,yn,'og','MarkerFaceColor','g') k = k+1; else set(htext,'String','Select node or press E to exit') end j = j+1; end 52 guidata(f2,Node) save(fid,'Node','-append') set(hnode,'Value',0); else set(helement,'Value',1) end end % Lets the user draw elements between the two nodes of their choosing function helement_Callback(source,eventdata) element = get(helement,'Value'); if element == 1 set(hnode,'Value',0) data = load(fid); xbase = data.xbase; Node = data.Node'; for i = 1:(length(xbase)-1) Element(:,i) = [i, i, i+1]; end j = 1; while element == 1 [x,y,key] = ginput(2); if (key == 'r') set(htext,'String','Exited element creation.') break; elseif (key == 1) set(htext,'String','Select element points or press R twice to exit') xn(:,j) = round(x(1:2)); yn(:,j) = round(y(1:2)); % Find the nodes for the element selected Node1x(:,j) Node1y(:,j) Node2x(:,j) Node2y(:,j) = = = = (Node(:,2)==xn(1,j)); (Node(:,3)==yn(1,j)); (Node(:,2)==xn(2,j)); (Node(:,3)==yn(2,j)); Node1(j) = find((Node1x(:,j) + Node1y(:,j))==2); Node2(j) = find((Node2x(:,j) + Node2y(:,j))==2); Element(:,j+i) = [j+i,Node1(j),Node2(j)]; 53 plot([Node(Node1(j),2),Node(Node2(j),2)],[Node(Node1(j),3),Node(Node2(j),3)], ... '-b','LineWidth',5); hold on for k = 1:length(Node(:,1)) plot(Node(k,2),Node(k,3),'og','MarkerFaceColor','g') end else set(htext,'String','Select element points or press R to exit') end j = j+1; end guidata(f2,Element) save(fid,'Element','-append') set(helement,'Value',0); set(hsave,'Enable','on'); else set(hnode,'Value',1) end end % Resets the drawing window. Allows the user to restart. function hreset_Callback(source,eventdata) set(hnode,'Enable','off','Value',0) set(helement,'Enable','off','Value',0) set(hsize,'Enable','on') set(hsize,'String',{'....','Small','Medium','Large'}); cla(ha); set(ha,'Visible','off') close(clf) Truss_Drawing_GUI end % Lets the user save the truss design. Name of saved file is that of % what was chosen to use to create the truss in the first place. function hsave_Callback(source,eventdata) 54 rho = 8000; E = 200e9; A = 3250/(1000^2); save(fid,'rho','E','A','-append') close(clf) end end 55