Object-oriented native python version of OpenSees
This package provides:
- A 'pythonic' version of OpenSees - all input parameters to create OpenSees objects are all lowercase key-value arguments - all class objects are CamelCase - static string variables defined in ALL_CAPS - Where possible the exact name used in the original TCL version has been kept
- Fully namespaced package allowing full auto-complete e.g. 'o3.uniaxial_material.Steel01(...)'
- Replication of underlying object-oriented C++ source code using Python objects.
- Type checking of inputs before calling C++ OpenSees code, so that python debugging and errors can be viewed
- In code documentation using python docstrings - can view the documentation within your IDE
- Additional features for using OpenSees in python: - saving and loading data directly from OpenSees into numpy arrays - saving and loading data directly from OpenSees into json files - Save an entire model as a json file - allows efficient passing of models between servers
- All object numbering handled by objects - no need for number tags!
- Additional logic checking of optional inputs
The eco-system of Python packages that relate to this package are outlined in the figure below.
pip install o3seespy
import numpy as np
import matplotlib.pyplot as plt
import o3seespy as o3
from tests.conftest import TEST_DATA_DIR
# Load a ground motion
dt = 0.01
rec = np.loadtxt(TEST_DATA_DIR + 'test_motion_dt0p01.txt')
# Define inelastic SDOF
period = 1.0
xi = 0.05
mass = 1.0
f_yield = 1.5 # Reduce this to make it nonlinear
r_post = 0.0
# Initialise OpenSees instance
osi = o3.OpenSeesInstance(ndm=2, state=0)
# Establish nodes
bot_node = o3.node.Node(osi, 0, 0)
top_node = o3.node.Node(osi, 0, 0)
# Fix bottom node
o3.Fix3DOF(osi, top_node, o3.cc.FREE, o3.cc.FIXED, o3.cc.FIXED)
o3.Fix3DOF(osi, bot_node, o3.cc.FIXED, o3.cc.FIXED, o3.cc.FIXED)
# Set out-of-plane DOFs to be constrained
o3.EqualDOF(osi, top_node, bot_node, [o3.cc.DOF_Y, o3.cc.ROTZ])
# nodal mass (weight / g):
o3.Mass(osi, top_node, mass, 0., 0.)
# Define material
k_spring = 4 * np.pi ** 2 * mass / period ** 2
bilinear_mat = o3.uniaxial_material.Steel01(osi, fy=f_yield, e0=k_spring, b=r_post)
# Assign zero length element, # Note: pass actual node and material objects into element
o3.element.ZeroLength(osi, [bot_node, top_node], mats=[bilinear_mat], dirs=[o3.cc.DOF_X], r_flag=1)
# Define the dynamic analysis
load_tag_dynamic = 1
pattern_tag_dynamic = 1
# Define the dynamic analysis
acc_series = o3.time_series.Path(osi, dt=dt, values=-1 * rec) # should be negative
o3.pattern.UniformExcitation(osi, dir=o3.cc.X, accel_series=acc_series)
# set damping based on first eigen mode
angular_freq = o3.get_eigen(osi, solver='fullGenLapack', n=1)[0] ** 0.5
beta_k = 2 * xi / angular_freq
o3.rayleigh.Rayleigh(osi, alpha_m=0.0, beta_k=beta_k, beta_k_init=0.0, beta_k_comm=0.0)
# Run the dynamic analysis
o3.wipe_analysis(osi)
# Run the dynamic analysis
o3.algorithm.Newton(osi)
o3.system.SparseGeneral(osi)
o3.numberer.RCM(osi)
o3.constraints.Transformation(osi)
o3.integrator.Newmark(osi, gamma=0.5, beta=0.25)
o3.analysis.Transient(osi)
o3.test_check.EnergyIncr(osi, tol=1.0e-10, max_iter=10)
analysis_time = (len(rec) - 1) * dt
analysis_dt = 0.001
outputs = {
"time": [],
"rel_disp": [],
"rel_accel": [],
"rel_vel": [],
"force": []
}
while o3.get_time(osi) < analysis_time:
o3.analyze(osi, 1, analysis_dt)
curr_time = o3.get_time(osi)
outputs["time"].append(curr_time)
outputs["rel_disp"].append(o3.get_node_disp(osi, top_node, o3.cc.X))
outputs["rel_vel"].append(o3.get_node_vel(osi, top_node, o3.cc.X))
outputs["rel_accel"].append(o3.get_node_accel(osi, top_node, o3.cc.X))
o3.gen_reactions(osi)
outputs["force"].append(-o3.get_node_reaction(osi, bot_node, o3.cc.X)) # Negative since diff node
o3.wipe(osi)
for item in outputs:
outputs[item] = np.array(outputs[item])
plt.plot(outputs['time'], outputs['rel_disp'], label='o3seespy')
periods = np.array([period])
# Compare closed form elastic solution
from eqsig import sdof
resp_u, resp_v, resp_a = sdof.response_series(motion=rec, dt=dt, periods=periods, xi=xi)
plt.plot(np.arange(len(rec)) * dt, resp_u[0], ls='--', label='Elastic')
plt.legend()
plt.savefig('readme_example.png')
plt.show()
- Run
pip install -r requirements.txt
- All names should be the same as the OpenSees tcl version, except:
- The name should be converted to snake_case for a parameter or function
- The name should be converted to CamelCase for an Object
- The name should be converted to ALL_CAPS for static variables
- If the name matches a python special name (e.g. lambda, in) then it should be adjusted according to the dictionary
- Objects should be namespaced based on the object type (e.g. element, material)
- For parameter that are used across many objects (e.g. atmospheric pressure) a standard name should be used
- OpenSees functions that collect a result are named 'get_<function-name>'
- OpenSees functions that generate results are named 'gen_<function-name>'
- How should youngs modulus be named?
- Should all two node elements use i_node, j_node. Or make them all enter nodes as a list?
Tests are run with pytest
- Locally run:
pytest
on the command line. - Tests are run on every push using travis, see the
.travis.yml
file
To deploy the package to pypi.com you need to:
- Push to the pypi branch. This executes the tests on circleci.com
- Create a git tag and push to github, run:
trigger_deploy.py
or manually:
git tag 0.5.2 -m "version 0.5.2"
git push --tags origin pypi
Built via Sphinx following: https://codeandchaos.wordpress.com/2012/07/30/sphinx-autodoc-tutorial-for-dummies/
For development mode
- cd to docs
- Run
make html
Docstrings follow numpy convention (in progress): https://numpydoc.readthedocs.io/en/latest/format.html
To fix long_description in setup.py: pip install collective.checkdocs
, python setup.py checkdocs