Package: fapp
Frame Analysis Program in PythonA lightweight and easy-to-use Python implementation of the Finite Element Method (FEM). Perform first-order elastic analyses of any 3-D frame structure and visualize results with a fully interactive web browser interface.IntroductionQuick StartInstallationUsageNotes and AssumptionsLicenseIntroductionfapp, pronounced "F - app", stands for Frame Analysis Program in Python. As the name suggests, only line elements are currently supported (no surface or solid elements).Analyze any 3-D frame structuresTimoshenko beam elements with shear deformationFirst-order elastic analysesNodal and uniform member loadsImposed displacementsObject-oriented design. Easy to access and store results.Beautiful and fully interactive visualization in your web browser. Pan, zoom, orbit, and hover.Features are somewhat limited in its current implementation but I'm sure you'll find it satisfactory for 95% of use-cases in structural engineering. The entire source code is only around 500 lines if you don't count comments and the visualization functionalities. It was a joy to develop and I hope you'll love how simple it is to use!I made a conscious effort to keep things pedagogically clear and concise during development. I hope it will serve as excellent reference material for educators looking for a python implementation of the direct stiffness method.The underlying theory, procedure, and notations used in fapp is explained in the textbook "Matrix Structural Analysis, 2nd Edition" by William McGuire, Richard Gallagher, and Ronald Ziemian. You can get a free PDF copy here:https://digitalcommons.bucknell.edu/books/7/. The object-oriented design of fapp is partly inspired by my Graduate school matrix analysis course project, where we had to implement something similar in MATLAB. The course was taught by Professor Gregory Deierlein. I relied on several other online resources during development, such as COMSOL documentation for shape functions, and Professor Henri Gavin's course reader for stiffness matrix of a Timoshenko beam. These references are saved as pdfs in the \doc folder.Disclaimer:this package is meant for personal or educational use only. As evident from the empty "\tests" folder, fapp is NOT robust enough to be used for commercial purposes of any kind! There are plenty of edge cases that I simply haven't had the time to explore/debug.Quick StartInstallationSee "Installation" section below for more info. For casual users, simply use Anaconda Python, download this module, and open "main.py" in Spyder IDE.Using fapp# import fappimportfapp.analysisasfpaimportfapp.plotterasfpp# initialize structuremy_structure=fpa.Analysis()# define nodesmy_structure.add_node(node_tag=0,x=0,y=0,z=0)my_structure.add_node(node_tag=1,x=0,y=4000,z=0)my_structure.add_node(node_tag=2,x=12000,y=7000,z=0)my_structure.add_node(node_tag=3,x=12000,y=0,z=0)# define elementsmy_structure.add_element(ele_tag=0,i_tag=0,j_tag=1,A=2e4,Ayy=2e4,Azz=2e4,Iy=999,Iz=1.5e9,J=999,E=200,v=0.3,beta=0)my_structure.add_element(ele_tag=1,i_tag=1,j_tag=2,A=4e4,Ayy=4e4,Azz=4e4,Iy=999,Iz=3.8e9,J=999,E=200,v=0.3,beta=0)my_structure.add_element(ele_tag=2,i_tag=2,j_tag=3,A=2e4,Ayy=2e4,Azz=2e4,Iy=999,Iz=1.5e9,J=999,E=200,v=0.3,beta=0)# define fixitymy_structure.add_fixity(node_tag=0,ux=0,uy=0,uz=0,rx=0,ry=0,rz=0)my_structure.add_fixity(node_tag=3,ux=0,uy=0,uz=0)# define loadingmy_structure.add_load_member(ele_tag=1,wx=-0.00363,wy=-0.01455,wz=0)my_structure.add_load_nodal(node_tag=1,Fx=5,Fy=0,Fz=0,Mx=0,My=0,Mz=0)# start analysismy_structure.solve()# visualize resultsfig1=fpp.plot(my_structure)fig2=fpp.plot_results(my_structure,display="d")fig3=fpp.plot_diagrams(my_structure,ele_tag=0)fig1.show()fig2.show()fig3.show()InstallationOption 1: Anaconda Python DistributionFor the casual users, Anaconda Python distribution is recommended. This is by far the easiest method of installation. Users don't need to worry about dependency management and setting up virtual environments. The following open source packages are used in this project:NumpyScipyPlotlyInstallation procedure:Download Anaconda pythonDownload this package (click the green "Code" button and download zip file)Open "main.py" in Anaconda's Spyder IDE. Make sure working directory is correctly configured.RunOption 2: Command Prompt + Plain PythonPip install is available.pip install fappAlternatively:Download this project to a folder of your choosinggit clone https://github.com/wcfrobert/wcfrobert.github.ioCreate virtual environmentpy -m venv venvActivate virtual environmentvenv/Scripts/activateInstall requirementspip install -r requirements.txtrun fapppy main.pyUsageStep 0: Instantiate analysis objectfapp has an object-oriented design that is quite intuitive to understand and use. Start by instantiating an "Analysis" object. All your results and information related to the structure will be stored here.importfapp.Analysismy_structure=fapp.analysis.Analysis()Step 1: Add nodesAnalysis.add_node(node_tag, x, y, z)adds a node to the structure. Parameters:node_tag: intNode tag must be defined chronologically from 0 to N. Node 0 will have DOF [0,1,2,3,4,5], node 1 will have DOF [6,7,8,9,10,11], and so on...x: floatx coordinatey: floaty coordinatez: floatz coordinateAdd nodes to your structure with ".add_node" method. Please notenode tag must be defined chronologicallyfrom 0 to N. This is because all node objects are stored in a list and its tag should correspond to its index. Furthermore, degree of freedoms (DOFs) are numbered chronologically for ease of implementation(also known as the plain numberer). It would have been simple enough to add a mapping step such that the node tag (used to determine DOF) is decoupled from a user-specified label, but that adds a layer of obfuscation that I was not particularly fond of.# named arguments recommended for claritymy_structure.add_node(0,10,0,0)my_structure.add_node(node_tag=1,x=0,y=0,z=0)# nodes must be defined chronologically starting at 0. THe following is NOT allowed:my_structure.add_node(node_tag=1,x=0,y=0,z=0)# ERROR! Must start from 0!my_structure.add_node(node_tag=999,x=0,y=0,z=0)# ERROR! Second node so tag = 1Step 2: Add elementsAnalysis.add_element(ele_tag,i_tag,j_tag,A,Ayy,Azz,Iy,Iz,J,E,v=0.3,beta=0)adds an element to the structure. Parameters:ele_tag: intElement tag must be defined chronologically from 0 to Ni_tag: intStart node tagj_tag: intEnd node tagA: floatSection areaAyy: floatShear area along y axis (major bending direction). Input very large value to neglect shear deformationAzz: floatShear area along z axis (minor bending direction). Input very large value to neglect shear deformationIy: floatMoment of inertia about y axis (minor bending direction)Iz: floatMoment of inertia about z axis (major bending direction)J: floatTorsion constantE: floatYoung's modulusv: float (optional)Poisson's ratio. Default = 0.3. Used to determine shear modulus $G = \frac{E}{2(1+v)}$beta: float (optional)Member rotation along its longitudinal axis in degrees. Default  = 0. Refer to "Notes and Assumptions" section for default geometric transformationOnce all the nodes have been defined, create elements by linking nodes together. In this step, we are defining both the connectivity as well as section/material properties. Again,element tag must be defined chronologicallyfrom 0 to N.# Named arguments recommended for claritymy_structure.add_element(0,0,1,2e4,2e4,2e4,999,1.5e9,999,200,0.3,0)my_structure.add_element(ele_tag=1,i_tag=1,j_tag=2,A=4e4,Ayy=4e4,Azz=4e4,Iy=999,Iz=3.8e9,J=999,E=200,v=0.3,beta=0)Step 3: Add fixityAnalysis.add_fixity(node_tag, ux="nan", uy="nan", uz="nan", rx="nan", ry="nan", rz="nan")adds fixity to the structure. Parameters:node_tag: intNode you wish to fixux: int, float, string (optional)Displacement in global X direction. Default = "nan"uy: int, float, string (optional)Displacement in global Y direction. Default = "nan"uz: int, float, string (optional)Displacement in global Z direction. Default = "nan"rx: int, float, string (optional)Rotation about global X axis. Default = "nan"ry: int, float, string (optional)Rotation about global Y axis. Default = "nan"rz: int, float, string (optional)Rotation about global Z axis. Default = "nan"Apply boundary conditions. You may wish to fix a node, or impose a prescribed displacement. Enter 0 for fixed, "nan" for free, float for prescribed displacement.# fully fixed supportmy_structure.add_fixity(node_tag=0,ux=0,uy=0,uz=0,rx=0,ry=0,rz=0)# pin support. Note you only need to specify DOFs to fix. "nan" is default inputmy_structure.add_fixity(node_tag=3,ux=0,uy=0,uz=0)# fix displacement in X, Y, Z and rotation about X and Y. 2D pin if modeling in XY planemy_structure.add_fixity(node_tag=4,ux=0,uy=0,uz=0,rx=0,ry=0)Step 4: Add LoadingAnalysis.add_load_member(ele_tag, wx=0, wy=0, wz=0)adds uniform load to a member. Defined with respect to itslocal axis. Parameters:ele_tag: intElement tag to identify element you wish to loadwx: float (optional)uniform load along member local x axis. Axial direction. Default = 0wy: float (optional)uniform load along member local y axis. Major bending direction. Don't forget to put negative number for gravity load. Default = 0wz: float (optional)uniform load along member local z axis. Minor bending direction. Default = 0# apply uniform load of -0.015 to element 1 in its local y axismy_structure.add_load_member(ele_tag=1,wy=-0.015)Analysis.add_load_nodal(node_tag, Fx=0, Fy=0, Fz=0, Mx=0, My=0, Mz=0)adds external nodal load. Defined with respect to global axis. Parameters:node_tag: intNode tag to identify node you wish to loadFx: float (optional)Nodal load in global X direction. Default = 0Fy: float (optional)Nodal load in global Y direction. Default = 0Fz: float (optional)Nodal load in global Z direction. Default = 0Mx: float (optional)Concentrated moment about global X axis. Default = 0My: float (optional)Concentrated moment about global Y axis. Default = 0Mz: float (optional)Concentrated moment about global Z axis. Default = 0# Apply nodal load of 5.5 to node 1 in global X directionmy_structure.add_load_nodal(node_tag=1,Fx=5.5)Step 5: Solve And Post-ProcessAnalysis.solve(print_info=True)starts analysis routine.print_info: Boolean (optional)Set to False if you do not want anything printed to terminalThe beauty of fapp's object-oriented design is that all the information related to the structure, from geometry, connectivity, to force/displacement results will be stored in one variable. The "Analysis" object doubles as a factory and stores a list of "Node" objects and "Element" objects. You can use dot notation to access everything. For example:# retrieve x,y,z coordinate of node 2my_structure.node_list[2].coord# view the stiffness matrix of element 0my_structure.element_list[0].k_local# view nodal displacement at node 6 (after solving)my_structure.node_list[6].dispFor ease of access, results are also collected and stored in matrices.# N_node x 6 matrix containing nodal displacementsmy_structure.DEFL# N_node x 6 matrix containing reaction forces at fixed nodesmy_structure.REACT# N_element x 12 matrix containing member-end forces in local coordinatemy_structure.ELE_FOR# for example, return nodal displacement at node 3my_structure.DEFL[3,:]Spyder IDE's variable explorer is highly recommended!Step 6: VisualizeTo help with debugging, I built a visualization engine in Plotly. It allows the user to zoom, pan, orbit, and hover over nodes and elements for more information. All in a fully interactive web browser environment. I also added some camera buttons to help user switch between views.There are currently three visualization options:plot() - for visualizing geometry, node and element tags, and connectivityNode hoverinfo shows tag, xyz coordinate, fixity, and loadingElement hoverinfo shows tag, start and end node, length, member loading, and member rotationplot_result() - for visualizing results after analysisNode hoverinfo shows tag, displacements, and reactions if applicableElement hoverinfo shows tag, and element end forcesplot_diagram() - for force or displacement diagrams of a single element in its local coordinate system.You can click on the legend to show/hide the results you wish to seeFor some reason, turntable orbit in Plotly assumes by default +Z as the vertical axis. This is sadly incongruous with fapp default assumption which is +Y. Clicking on turntable view will flip the structure, but you can click axonometric view to reset view and things will work as intended.fapp.plotter.plot(structure)for visualizing geometry, node and element tags, and connectivity.structure: {fapp.Analysis}Pass in the structure you want to visualizefapp.plotter.plot_results(structure, display="d", scale=15, show_value = False)for results after analysis such as displacement and force diagrams.structure: {fapp.Analysis}Pass in the structure you want to visualizedisplay: string (optional)Indicate what result you wish to see. Default = "d""d" - displacement"N" - axial"Vy" - shear in local y axis (major bending direction)"Vz" - shear in local z axis (minor bending direction)"Tx" - torsion"My" - moment about local y axis (minor bending direction)"Mz" - moment about local z axis (major bending direction)scale: float (optional)Scale of displacement or force diagram. Adjust manually as needed. 15 seems to be a good number. Larger number = larger scale. Default = 15show_value: boolean (optional)Show force diagram magnitude at the two ends. Gets quite cluttered so it is off by default. Default = Falsefapp.plotter.plot_diagrams(structure, ele_tag)for displacement and force diagrams for a single element about its local coordinate system.structure: {fapp.Analysis}Pass in the structure you want to visualizeele_tag: intElement tag to identify which element you would like to plotNotes and AssumptionsGlobal Coordinate (X, Y, Z):Y is the vertical axis (Elevation)X and Z are the axes within the horizontal plane (Plan)Recommend modeling 2-D plane structures in the X-Y planeRecommend modeling 2-D floor grids in the X-Z planeLocal Coordinate (x, y, z):x-axis = element longitudinal axisz-axis = element major bending axis (relevant section properties: Iz, Ayy)y-axis = element minor bending axis (relevant section properties: Iy, Azz)Default geometric transformation (i.e. member orientation):Element major axis (web direction vector) always points skywardFor elements that are completely vertical (columns), major axis always points towards -XThis default setting works well because beams will always have major section properties resisting gravity load, and columns will always have major section properties when modeling within the X-Y planefapp is agnostic when it comes to unit. Please ensure your input is consistentLicenseMIT LicenseCopyright (c) 2023 Robert Wang