Skip to main content

Frame Analysis Program in Python

Project description


logo
Frame Analysis Program in Python

A 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.

logo

Introduction

fapp, 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 structures
  • Timoshenko beam elements with shear deformation
  • First-order elastic analyses
  • Nodal and uniform member loads
  • Imposed displacements
  • Object-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 Start

Installation

See "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 fapp
import fapp.analysis as fpa
import fapp.plotter as fpp

# initialize structure
my_structure = fpa.Analysis()

# define nodes
my_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 elements
my_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 fixity
my_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 loading
my_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 analysis
my_structure.solve()

# visualize results
fig1 = 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()

Installation

Option 1: Anaconda Python Distribution

For 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:

  • Numpy
  • Scipy
  • Plotly

Installation procedure:

  1. Download Anaconda python
  2. Download this package (click the green "Code" button and download zip file)
  3. Open "main.py" in Anaconda's Spyder IDE. Make sure working directory is correctly configured.
  4. Run

Option 2: Command Prompt + Plain Python

Pip install is available.

pip install fapp

Alternatively:

  1. Download this project to a folder of your choosing
    git clone https://github.com/wcfrobert/wcfrobert.github.io
    
  2. Create virtual environment
    py -m venv venv
    
  3. Activate virtual environment
    venv/Scripts/activate
    
  4. Install requirements
    pip install -r requirements.txt
    
  5. run fapp
    py main.py
    

Usage

Step 0: Instantiate analysis object

fapp 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.

import fapp.Analysis

my_structure = fapp.analysis.Analysis()

Step 1: Add nodes

Analysis.add_node(node_tag, x, y, z) adds a node to the structure. Parameters:

  • node_tag: int
    • Node 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: float
    • x coordinate
  • y: float
    • y coordinate
  • z: float
    • z coordinate

Add nodes to your structure with ".add_node" method. Please note node tag must be defined chronologically from 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 clarity
my_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 = 1

Step 2: Add elements

Analysis.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: int
    • Element tag must be defined chronologically from 0 to N
  • i_tag: int
    • Start node tag
  • j_tag: int
    • End node tag
  • A: float
    • Section area
  • Ayy: float
    • Shear area along y axis (major bending direction). Input very large value to neglect shear deformation
  • Azz: float
    • Shear area along z axis (minor bending direction). Input very large value to neglect shear deformation
  • Iy: float
    • Moment of inertia about y axis (minor bending direction)
  • Iz: float
    • Moment of inertia about z axis (major bending direction)
  • J: float
    • Torsion constant
  • E: float
    • Young's modulus
  • v: 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 transformation

Once 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 chronologically from 0 to N.

# Named arguments recommended for clarity
my_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 fixity

Analysis.add_fixity(node_tag, ux="nan", uy="nan", uz="nan", rx="nan", ry="nan", rz="nan") adds fixity to the structure. Parameters:

  • node_tag: int
    • Node you wish to fix
  • ux: 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 support
my_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 input
my_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 plane
my_structure.add_fixity(node_tag=4, ux=0, uy=0, uz=0, rx=0, ry=0)

Step 4: Add Loading

Analysis.add_load_member(ele_tag, wx=0, wy=0, wz=0) adds uniform load to a member. Defined with respect to its local axis. Parameters:

  • ele_tag: int
    • Element tag to identify element you wish to load
  • wx: float (optional)
    • uniform load along member local x axis. Axial direction. Default = 0
  • wy: float (optional)
    • uniform load along member local y axis. Major bending direction. Don't forget to put negative number for gravity load. Default = 0
  • wz: 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 axis
my_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: int
    • Node tag to identify node you wish to load
  • Fx: float (optional)
    • Nodal load in global X direction. Default = 0
  • Fy: float (optional)
    • Nodal load in global Y direction. Default = 0
  • Fz: float (optional)
    • Nodal load in global Z direction. Default = 0
  • Mx: float (optional)
    • Concentrated moment about global X axis. Default = 0
  • My: float (optional)
    • Concentrated moment about global Y axis. Default = 0
  • Mz: float (optional)
    • Concentrated moment about global Z axis. Default = 0
# Apply nodal load of 5.5 to node 1 in global X direction
my_structure.add_load_nodal(node_tag=1, Fx=5.5)

Step 5: Solve And Post-Process

Analysis.solve(print_info=True) starts analysis routine.

  • print_info: Boolean (optional)
    • Set to False if you do not want anything printed to terminal

The 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 2
my_structure.node_list[2].coord

# view the stiffness matrix of element 0
my_structure.element_list[0].k_local

# view nodal displacement at node 6 (after solving)
my_structure.node_list[6].disp

For ease of access, results are also collected and stored in matrices.

# N_node x 6 matrix containing nodal displacements
my_structure.DEFL
# N_node x 6 matrix containing reaction forces at fixed nodes
my_structure.REACT
# N_element x 12 matrix containing member-end forces in local coordinate
my_structure.ELE_FOR
# for example, return nodal displacement at node 3
my_structure.DEFL[3, :]

Spyder IDE's variable explorer is highly recommended!

logo

Step 6: Visualize

To 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 connectivity
    • Node hoverinfo shows tag, xyz coordinate, fixity, and loading
    • Element hoverinfo shows tag, start and end node, length, member loading, and member rotation
  • plot_result() - for visualizing results after analysis
    • Node hoverinfo shows tag, displacements, and reactions if applicable
    • Element hoverinfo shows tag, and element end forces
  • plot_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 see

For 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.


logo

fapp.plotter.plot(structure) for visualizing geometry, node and element tags, and connectivity.

  • structure: {fapp.Analysis}
    • Pass in the structure you want to visualize

logo

fapp.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 visualize
  • display: 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 = 15
  • show_value: boolean (optional)
    • Show force diagram magnitude at the two ends. Gets quite cluttered so it is off by default. Default = False

logo

fapp.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 visualize
  • ele_tag: int
    • Element tag to identify which element you would like to plot

Notes and Assumptions

logo
  • Global 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 plane
    • Recommend modeling 2-D floor grids in the X-Z plane
  • Local Coordinate (x, y, z):
    • x-axis = element longitudinal axis
    • z-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 skyward
    • For elements that are completely vertical (columns), major axis always points towards -X
    • This 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 plane
  • fapp is agnostic when it comes to unit. Please ensure your input is consistent

License

MIT License

Copyright (c) 2023 Robert Wang

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fapp-1.0.0.tar.gz (2.4 MB view details)

Uploaded Source

Built Distribution

fapp-1.0.0-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

File details

Details for the file fapp-1.0.0.tar.gz.

File metadata

  • Download URL: fapp-1.0.0.tar.gz
  • Upload date:
  • Size: 2.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.0

File hashes

Hashes for fapp-1.0.0.tar.gz
Algorithm Hash digest
SHA256 81e7bee219b4820a6afb334463ddc3408bd93e0ca0761d45217e7439e2cfe535
MD5 434063c02389179d7794dc613f8869aa
BLAKE2b-256 4497c5095630996f780c60a97dee79a57db8434cce9d2147d80781bec542964d

See more details on using hashes here.

File details

Details for the file fapp-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: fapp-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 19.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.0

File hashes

Hashes for fapp-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6858ab41582f0e3ef1e197436f062fdee0399b1a383dbcb9ffc07ff313e9b6d2
MD5 b46da0c6fc61ec106d6951024c85a4f0
BLAKE2b-256 a41a087d28a42ad77990c76021bdc70c381d35572a12c3d944ed3a0756a65fe6

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page