Package: o3seespy
o3seespyObject-oriented native python version of OpenSeesFeaturesThis 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 keptFully 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 viewedIn code documentation using python docstrings - can view the documentation within your IDEAdditional 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 serversAll object numbering handled by objects - no need for number tags!Additional logic checking of optional inputsRelated packagesThe eco-system of Python packages that relate to this package are outlined in the figure below.How to UseInstallationpipinstallo3seespyExample: Dynamic inelastic SDOF analysisimportnumpyasnpimportmatplotlib.pyplotaspltimporto3seespyaso3fromtests.conftestimportTEST_DATA_DIR# Load a ground motiondt=0.01rec=np.loadtxt(TEST_DATA_DIR+'test_motion_dt0p01.txt')# Define inelastic SDOFperiod=1.0xi=0.05mass=1.0f_yield=1.5# Reduce this to make it nonlinearr_post=0.0# Initialise OpenSees instanceosi=o3.OpenSeesInstance(ndm=2,state=0)# Establish nodesbot_node=o3.node.Node(osi,0,0)top_node=o3.node.Node(osi,0,0)# Fix bottom nodeo3.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 constrainedo3.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 materialk_spring=4*np.pi**2*mass/period**2bilinear_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 elemento3.element.ZeroLength(osi,[bot_node,top_node],mats=[bilinear_mat],dirs=[o3.cc.DOF_X],r_flag=1)# Define the dynamic analysisload_tag_dynamic=1pattern_tag_dynamic=1# Define the dynamic analysisacc_series=o3.time_series.Path(osi,dt=dt,values=-1*rec)# should be negativeo3.pattern.UniformExcitation(osi,dir=o3.cc.X,accel_series=acc_series)# set damping based on first eigen modeangular_freq=o3.get_eigen(osi,solver='fullGenLapack',n=1)[0]**0.5beta_k=2*xi/angular_freqo3.rayleigh.Rayleigh(osi,alpha_m=0.0,beta_k=beta_k,beta_k_init=0.0,beta_k_comm=0.0)# Run the dynamic analysiso3.wipe_analysis(osi)# Run the dynamic analysiso3.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)*dtanalysis_dt=0.001outputs={"time":[],"rel_disp":[],"rel_accel":[],"rel_vel":[],"force":[]}whileo3.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 nodeo3.wipe(osi)foriteminoutputs:outputs[item]=np.array(outputs[item])plt.plot(outputs['time'],outputs['rel_disp'],label='o3seespy')periods=np.array([period])# Compare closed form elastic solutionfromeqsigimportsdofresp_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()Useful materialContributingHow do I get set up?Runpip install-rrequirements.txtPackage conventionsAll names should be the same as the OpenSees tcl version, except:The name should be converted to snake_case for a parameter or functionThe name should be converted to CamelCase for an ObjectThe name should be converted to ALL_CAPS for static variablesIf the name matches a python special name (e.g. lambda, in) then it should be adjusted according to the dictionaryObjects 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 usedOpenSees 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?TestingTests are run with pytestLocally run:pyteston the command line.Tests are run on every push using travis, see the.travis.ymlfileDeploymentTo deploy the package to pypi.com you need to:Push to thepypibranch. This executes the tests on circleci.comCreate a git tag and push to github, run:trigger_deploy.pyor manually:gittag0.5.2-m"version 0.5.2"gitpush--tagsoriginpypiDocumentationBuilt via Sphinx following:https://codeandchaos.wordpress.com/2012/07/30/sphinx-autodoc-tutorial-for-dummies/For development modecd to docsRunmake htmlDocstrings follow numpy convention (in progress):https://numpydoc.readthedocs.io/en/latest/format.htmlTo fix long_description in setup.py:pip install collective.checkdocs,python setup.py checkdocs