# OpenSeesPy Documentation ## Introduction OpenSeesPy is a Python 3 interpreter of OpenSees, providing a comprehensive finite element analysis framework for structural and geotechnical engineering simulations. It enables engineers and researchers to perform advanced computational analysis of structures including static, dynamic, and earthquake response simulations through a pythonic API that wraps the powerful OpenSees computational engine. The library provides extensive capabilities for modeling complex structural systems with nonlinear materials, geometric nonlinearity, and advanced element formulations. OpenSeesPy supports various analysis types including static, transient, and PFEM (Particle Finite Element Method) analyses, making it suitable for applications ranging from simple truss systems to complex reinforced concrete frames and fluid-structure interaction problems. The package is freely available for research and educational purposes through PyPI for Windows, Linux, and Mac platforms. ## APIs and Key Functions ### Model Initialization Create the basic structural model with specified dimensions and degrees of freedom. ```python import openseespy.opensees as ops # Remove any existing model ops.wipe() # Create a 2D model with 3 DOF per node (x-displacement, y-displacement, rotation) ops.model('basic', '-ndm', 2, '-ndf', 3) # For 3D models with 6 DOF per node ops.model('basic', '-ndm', 3, '-ndf', 6) ``` ### Node Creation Define nodal points in the structural model with coordinates and optional properties. ```python import openseespy.opensees as ops ops.model('basic', '-ndm', 2, '-ndf', 3) # Create nodes with tag and coordinates ops.node(1, 0.0, 0.0) # Node 1 at origin ops.node(2, 360.0, 0.0) # Node 2 at (360, 0) ops.node(3, 0.0, 144.0) # Node 3 at (0, 144) ops.node(4, 360.0, 144.0) # Node 4 at (360, 144) # Fix boundary conditions: tag, x-fixed, y-fixed, rotation-fixed ops.fix(1, 1, 1, 1) # Fix all DOF at base ops.fix(2, 1, 1, 1) # Fix all DOF at base # Create node with mass ops.node(5, 180.0, 288.0, '-mass', 0.49, 1.0e-10, 1.0e-10) ``` ### Material Definition Define material behavior for structural elements using uniaxial material models. ```python import openseespy.opensees as ops ops.model('basic', '-ndm', 2, '-ndf', 3) # Elastic material: tag, E (Young's modulus) ops.uniaxialMaterial('Elastic', 1, 3000.0) # Concrete01: tag, fpc, epsc0, fpcu, epscu # Core concrete (confined) ops.uniaxialMaterial('Concrete01', 1, -6.0, -0.004, -5.0, -0.014) # Cover concrete (unconfined) ops.uniaxialMaterial('Concrete01', 2, -5.0, -0.002, 0.0, -0.006) # Steel01: tag, fy (yield stress), E0, b (strain hardening ratio) ops.uniaxialMaterial('Steel01', 3, 60.0, 30000.0, 0.01) ``` ### Fiber Section Definition Create fiber sections for beam-column elements with distributed material properties. ```python import openseespy.opensees as ops ops.model('basic', '-ndm', 2, '-ndf', 3) # Define materials first ops.uniaxialMaterial('Concrete01', 1, -6.0, -0.004, -5.0, -0.014) ops.uniaxialMaterial('Concrete01', 2, -5.0, -0.002, 0.0, -0.006) ops.uniaxialMaterial('Steel01', 3, 60.0, 30000.0, 0.01) # Section parameters colWidth = 15 colDepth = 24 cover = 1.5 As = 0.60 # area of reinforcing bars y1 = colDepth / 2.0 z1 = colWidth / 2.0 # Create fiber section with tag ops.section('Fiber', 1) # Create concrete core fibers: matTag, numSubdivY, numSubdivZ, yI, zI, yJ, zJ ops.patch('rect', 1, 10, 1, cover-y1, cover-z1, y1-cover, z1-cover) # Create concrete cover fibers ops.patch('rect', 2, 10, 1, -y1, z1-cover, y1, z1) ops.patch('rect', 2, 10, 1, -y1, -z1, y1, cover-z1) ops.patch('rect', 2, 2, 1, -y1, cover-z1, cover-y1, z1-cover) ops.patch('rect', 2, 2, 1, y1-cover, cover-z1, y1, z1-cover) # Create reinforcing fibers: matTag, numFiber, areaFiber, yStart, zStart, yEnd, zEnd ops.layer('straight', 3, 3, As, y1-cover, z1-cover, y1-cover, cover-z1) ops.layer('straight', 3, 2, As, 0.0, z1-cover, 0.0, cover-z1) ops.layer('straight', 3, 3, As, cover-y1, z1-cover, cover-y1, cover-z1) ``` ### Element Creation Define structural elements connecting nodes with material properties. ```python import openseespy.opensees as ops ops.model('basic', '-ndm', 2, '-ndf', 2) # Create nodes ops.node(1, 0.0, 0.0) ops.node(2, 144.0, 0.0) ops.node(3, 168.0, 0.0) ops.node(4, 72.0, 96.0) # Fix supports ops.fix(1, 1, 1) ops.fix(2, 1, 1) ops.fix(3, 1, 1) # Define material ops.uniaxialMaterial('Elastic', 1, 3000.0) # Truss element: eleType, eleTag, iNode, jNode, Area, matTag ops.element('Truss', 1, 1, 4, 10.0, 1) ops.element('Truss', 2, 2, 4, 5.0, 1) ops.element('Truss', 3, 3, 4, 5.0, 1) # Elastic beam-column: tag, nodeI, nodeJ, A, E, Iz, transfTag ops.geomTransf('Linear', 1) ops.element('elasticBeamColumn', 3, 1, 2, 360.0, 4030.0, 8640.0, 1) # Nonlinear beam-column with fiber section ops.geomTransf('PDelta', 2) ops.beamIntegration('Lobatto', 1, 1, 5) # tag, secTag, numIntegrationPoints ops.element('forceBeamColumn', 1, 1, 3, 2, 1) ``` ### Load Pattern Definition Apply loads to the structure with time-dependent patterns. ```python import openseespy.opensees as ops ops.model('basic', '-ndm', 2, '-ndf', 3) # Create nodes and elements (abbreviated) ops.node(1, 0.0, 0.0) ops.node(2, 360.0, 144.0) ops.fix(1, 1, 1, 1) # Create linear time series ops.timeSeries('Linear', 1) # Create plain load pattern: patternTag, timeSeriesTag ops.pattern('Plain', 1, 1) # Apply nodal loads: nodeTag, Fx, Fy, Mz ops.load(2, 100.0, -50.0, 0.0) # For earthquake analysis with ground motion dt = 0.01 g = 386.4 ops.timeSeries('Path', 2, '-filePath', 'earthquake.dat', '-dt', dt, '-factor', g) ops.pattern('UniformExcitation', 2, 1, '-accel', 2) # dir: 1=X, 2=Y, 3=Z # Set loads constant (after gravity analysis) ops.loadConst('-time', 0.0) ``` ### Static Analysis Perform static structural analysis with nonlinear solution algorithms. ```python import openseespy.opensees as ops # Model setup (abbreviated) ops.wipe() ops.model('basic', '-ndm', 2, '-ndf', 3) # ... create nodes, materials, elements, loads ... # Define system of equations solver ops.system('BandGeneral') # or 'BandSPD', 'ProfileSPD', 'SparseGeneral' # Define constraint handler ops.constraints('Transformation') # or 'Plain', 'Penalty', 'Lagrange' # Define DOF numberer ops.numberer('RCM') # Reverse Cuthill-McKee algorithm # Define convergence test ops.test('NormDispIncr', 1.0e-12, 10, 3) # tolerance, maxIter, printFlag # Define solution algorithm ops.algorithm('Newton') # or 'Linear', 'ModifiedNewton', 'NewtonLineSearch' # Define integrator ops.integrator('LoadControl', 0.1) # load step # Create analysis object ops.analysis('Static') # Perform analysis ops.analyze(10) # 10 load steps # Extract results u = ops.nodeDisp(2, 1) # x-displacement at node 2 forces = ops.eleResponse(1, 'forces') ``` ### Transient Analysis Perform dynamic time-history analysis for earthquake or time-varying loads. ```python import openseespy.opensees as ops # Model setup with mass (abbreviated) ops.wipe() ops.model('basic', '-ndm', 2, '-ndf', 3) # ... create nodes, materials, elements, gravity loads ... # Apply masses m = 0.466 # mass value ops.mass(3, m, m, 0.0) ops.mass(4, m, m, 0.0) # Set Rayleigh damping: alphaM, betaK, betaKinit, betaKcomm ops.rayleigh(0.0, 0.0, 0.0, 0.000625) # System and constraints ops.system('BandGeneral') ops.constraints('Plain') # Convergence test ops.test('NormDispIncr', 1.0e-12, 10) # Solution algorithm ops.algorithm('Newton') # DOF numberer ops.numberer('RCM') # Newmark integrator: gamma, beta ops.integrator('Newmark', 0.5, 0.25) # Create transient analysis ops.analysis('Transient') # Eigenvalue analysis numEigen = 2 eigenValues = ops.eigen(numEigen) # Perform time history analysis dt = 0.01 nSteps = 1000 tCurrent = 0.0 ok = 0 while ok == 0 and tCurrent < nSteps * dt: ok = ops.analyze(1, dt) if ok != 0: # Try modified Newton if regular Newton fails ops.test('NormDispIncr', 1.0e-12, 100, 0) ops.algorithm('ModifiedNewton', '-initial') ok = ops.analyze(1, dt) ops.algorithm('Newton') tCurrent = ops.getTime() disp = ops.nodeDisp(3, 1) ``` ### Recording Results Record analysis results to files for post-processing. ```python import openseespy.opensees as ops ops.model('basic', '-ndm', 2, '-ndf', 3) # ... model setup ... # Node recorder: recorderType, filename, '-time', '-node', nodeTags, '-dof', dofs, responseType ops.recorder('Node', '-file', 'node_displacements.out', '-time', '-node', 3, 4, '-dof', 1, 2, 3, 'disp') ops.recorder('Node', '-file', 'node_velocities.out', '-time', '-node', 3, 4, '-dof', 1, 2, 'vel') ops.recorder('Node', '-file', 'node_accelerations.out', '-time', '-node', 3, 4, '-dof', 1, 2, 'accel') # Element recorder: recorderType, filename, '-time', '-ele', eleTags, responseType ops.recorder('Element', '-file', 'element_forces.out', '-time', '-ele', 1, 2, 'forces') ops.recorder('Element', '-file', 'element_deformations.out', '-time', '-ele', 1, 2, 'deformations') # PVD recorder for visualization (ParaView format) ops.recorder('PVD', 'output_folder', 'disp', 'vel', 'pressure') # Perform analysis ops.analysis('Static') ops.analyze(10) # Save initial state ops.record() ``` ### PFEM Analysis for Fluid-Structure Interaction Perform Particle Finite Element Method analysis for fluid dynamics and FSI problems. ```python import openseespy.opensees as ops import os # Remove existing model ops.wipe() # 2D model with 2 DOF per node (vx, vy for fluid) ops.model('basic', '-ndm', 2, '-ndf', 2) # Geometry L = 0.146 H = L * 2 h = 0.005 # mesh size # Fluid material properties rho = 1000.0 # density mu = 0.0001 # dynamic viscosity b1 = 0.0 # body force x b2 = -9.81 # body force y (gravity) thk = 0.012 # thickness kappa = -1.0 # bulk modulus # Create nodes for domain boundaries ops.node(1, 0.0, 0.0) ops.node(2, L, 0.0) ops.node(3, L, H) ops.node(4, 0.0, H) # Mesh identifiers wall_id = 1 water_body_id = -2 ndf = 2 # Create wall mesh (fixed boundary) wall_tag = 3 ops.mesh('line', 1, 4, 1, 2, 3, 4, wall_id, ndf, h) ops.mesh('tri', wall_tag, 2, 1, 2, wall_id, ndf, h) # Create fluid mesh with PFEM elements fluid_tag = 4 eleArgs = ['PFEMElementBubble', rho, mu, b1, b2, thk, kappa] ops.mesh('tri', fluid_tag, 2, 2, 3, water_body_id, ndf, h, *eleArgs) # Fix wall nodes for nd in ops.getNodeTags('-mesh', wall_tag): ops.fix(nd, 1, 1) # Setup recorder if not os.path.exists('dambreak'): os.makedirs('dambreak') ops.recorder('PVD', 'dambreak', 'disp', 'vel', 'pressure') # Record initial state ops.record() # Analysis parameters ops.system('PFEM') ops.numberer('Plain') ops.constraints('Plain') ops.integrator('PFEM') ops.analysis('PFEM', dt=1e-3, dtmax=1e-3, dtmin=1e-6, gravity=[b1, b2], ratio=0.5) # Run analysis totaltime = 1.0 ops.analyze(totaltime) ``` ### Parallel Processing Execute OpenSeesPy with parallel processing for large-scale simulations. ```python import openseespy.opensees as ops # Get process ID and number of processes pid = ops.getPID() np = ops.getNP() print(f'Process ID: {pid}') if pid == 0: print(f'Total number of processes: {np}') # Broadcast data from process 0 to all processes if pid == 0: data = [1.0, 2.0, 3.0] ops.send('-pid', 1, *data) else: data = ops.recv('-pid', 0, 3) # receive 3 values # Barrier synchronization ops.barrier() # Perform parallel analysis # Each process can work on different parts of the model ops.model('basic', '-ndm', 2, '-ndf', 3) # ... model setup distributed across processes ... ``` ## Summary and Integration OpenSeesPy serves as a powerful tool for structural and geotechnical engineering analysis, enabling researchers and practitioners to model complex systems with nonlinear behavior, time-dependent loading, and advanced material models. The primary use cases include seismic analysis of buildings and bridges, nonlinear pushover analysis for performance-based design, moment-curvature analysis for section design, and fluid-structure interaction problems using PFEM. The library's comprehensive element library supports truss, beam-column, shell, brick, and zero-length elements, while material models range from simple elastic to sophisticated hysteretic and damage models. Integration patterns typically follow a structured workflow: initialize the model with `wipe()` and `model()`, define geometry with `node()` and `fix()`, create materials with `uniaxialMaterial()` or section definitions, assemble elements with `element()`, apply loads through `pattern()` and `load()`, configure the analysis solver with `system()`, `constraints()`, `numberer()`, `test()`, `algorithm()`, and `integrator()`, then execute with `analysis()` and `analyze()`. Results are extracted using `nodeDisp()`, `eleResponse()`, or recorded to files via `recorder()` for post-processing. The library integrates seamlessly with the Python scientific stack including NumPy for numerical operations and Matplotlib for visualization, making it an accessible yet powerful solution for computational structural mechanics.