Try on DesignSafe

Run OpenSeesPy App#

Submit Jobs to the NEW! OpenSeesPy App

by Silvia Mazzoni, DesignSafe, 2026

In this Notebook we are going to run a set of OpenSeesPy jobs. We will run different types of anaysis:

  1. Single Process Thread

  2. Multi-Process using concurrent futures (can run only on a single node)

  3. Multi-Process multi-node using mpi4py

We are using previously-defined python function to streamline the process.

NEW: unique to OpenSeesPy#

UseMPI: Flag indicating whether the application should launch the main program with an MPI parallel-execution command (ibrun).

  • True: enable distributed-memory parallelism, allowing multi-core or multi-node execution. (Suitable for OpenSeesMP / OpenSeesSP / Python with mpi4py (OpenSeesPy)).

  • False: execution stays on one node. (Suitable for OpenSees, Python, or Python with concurrent.futures for one-node parallelism.)

Unique to OpenSeesPy on TACC#

The Tapis app uses the Stampede3-compiled OpenSeesPy build that matches the app’s Python environment. Using a pip-installed OpenSeesPy can lead to missing shared-library dependencies (e.g., unresolved .so files) at runtime.

To avoid this, include the following block at the top of your Python script. The conditional logic makes it portable: on Stampede3 it finds the compiled opensees.so, and elsewhere it falls back to the standard import.

# Import the local version of OpenSees, if it exists
if os.path.exists('opensees.so'):
    import opensees as ops;
else:
    import openseespy.opensees as ops

If you have a simple job, or to learn which output you need, you can submit a job via the web portal: https://designsafe-ci.org/workspace/designsafe-openseespy-s3


Initialize#

Initialize Python#

from pathlib import Path
import ipywidgets as widgets

Load Specialized Utilities Library#

I have developed a collection of python defs that are intended to simplify coding.

# We will use this utility often to view the utility functions:
OpsUtils.show_text_file_in_accordion(PathOpsUtils, 'show_text_file_in_accordion.py')

Examples-Files Location#

# We need to break the path up for Tapis because it uses different root paths
OpsScriptsPath_Base = 'Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic'
OpsScriptsPath_StorageSystem = 'CommunityData'

to run scripts locally on jupyter hub:#

# Define file paths in local/jupyter terms so we can view the input files here and we can check that the directory exists!
OpsScriptsPath_Local = os.path.join(OpsScriptsPath_StorageSystem, OpsScriptsPath_Base)
OpsScriptsPath_Local = '~/' + OpsScriptsPath_Local
OpsScriptsPath_Local = os.path.expanduser(OpsScriptsPath_Local)
print('OpsScriptsPath_Local:',OpsScriptsPath_Local)
Path_OpsScriptsPath_Local = Path(OpsScriptsPath_Local)

if Path_OpsScriptsPath_Local.is_dir():
    print('\nDirectory Exists! \n Content:')
    display(os.listdir(Path_OpsScriptsPath_Local))
else:
    print('\nERROR!!! Directory DOES NOT Exist!')
OpsScriptsPath_Local: /home/jupyter/CommunityData/Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic

Directory Exists! 
 Content:
['Ex1a_verymany.Canti2D.Push.mp.tcl',
 'Ex1a.Canti2D.Push.argv.tacc.runsList.txt',
 'Ex1a.Canti2D.Push.argv.tcl.callPylauncher.py',
 'Ex1a.Canti2D.Push.mpi4py.tacc.py',
 'Ex1.Canti2D.Push.mpi.mod.tacc.py',
 'Ex1a.Canti2D.Push.tacc.py',
 'Ex1a.Canti2D.Push.mp.tcl',
 'simpleSP.tcl',
 'Ex1a.Canti2D.Push.mpi.tacc.py',
 'Ex1a.Canti2D.Push.argv.tacc.py.callPylauncher.py',
 'Ex1a.Canti2D.Push.py',
 'Ex1a.Canti2D.Push.argv.tacc.callPylauncher.py',
 'Ex1a.Canti2D.Push.mpi4py.py',
 'Ex1a_many.Canti2D.Push.mp.tcl',
 'Ex1a.Canti2D.Push.futures.tacc.py',
 'Ex1a.Canti2D.Push.tcl',
 'Ex1a.Canti2D.Push.argv.tcl.runsList.txt',
 'Ex1a.Canti2D.Push.argv.tacc.py.runsList.txt',
 'Ex1a.Canti2D.Push.argv.tcl',
 'Ex1a.Canti2D.Push.futures.py',
 'Ex1a.Canti2D.Push.mpi.py',
 'Ex1a.Canti2D.Push.argv.tacc.py']

Connect to Tapis#

OpsUtils.show_text_file_in_accordion(PathOpsUtils, 'connect_tapis.py')
force_connect = False
# force_connect = True; # REMOVE do this only if you want to restart the clock on the token.
t=OpsUtils.connect_tapis(force_connect=force_connect)
 -- Checking Tapis token --
 Token loaded from file. Token is still valid!
 Token expires at: 2026-02-14T21:44:08+00:00
 Token expires in: 3:40:26.393652
-- AUTHENTICATED VIA SAVED TOKEN --

Initialize ALL-Job Input#

tapisInputAll = {}

SLURM-Specific Input#

tapisInputAll["maxMinutes"] = 6

tapisInputAll["execSystemId"] = "stampede3" # the app runs on stampede only
tapisInputAll["execSystemLogicalQueue"] = "skx-dev" # "skx", "skx-dev"
tapisInputAll["nodeCount"] = 1 # limits set by which compute nodes you use
tapisInputAll["coresPerNode"] = 48 # limits set by which compute nodes you use
tapisInputAll["allocation"] = "DS-HPC1"

tapisInputAll['archive_system']=OpsScriptsPath_StorageSystem # Options: MyData or Work

Common Input for All Jobs#

# The folder path needs to be converted into tapis-format, we have a utility for that, which is why we have to separate the system and the folder.
tapisInputAll['storage_system'] = 'CommunityData'
tapisInputAll['input_folder'] = 'Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic'

App-Specific Input#

app_id = "designsafe-openseespy-s3" 

Define App-Info Input#

tapisInputAll["appId"] = app_id
tapisInputAll["appVersion"] = "latest" # always use latest in this Notebook Template

Check if App Exists#

If it exists you will see a version number.

current_app_version = OpsUtils.get_latest_app_version(t,app_id)
print('current_app_version',current_app_version)
current_app_version 1.2.15

Access App Schema on Tapis#

OpsUtils.show_text_file_in_accordion(PathOpsUtils, ['getAppLatestVersion.py','display_tapis_app_schema.py'])
appMetaData = t.apps.getAppLatestVersion(appId=app_id)

here_out = widgets.Output()
here_accordion = widgets.Accordion(children=[here_out])
# here_accordion.selected_index = 0
here_accordion.set_title(0, f'List the new app')
display(here_accordion)

with here_out:
    OpsUtils.display_tapis_app_schema(appMetaData)
thisAppVersion = appMetaData.version
isPublic = appMetaData.isPublic
here_accordion.set_title(0, f'app schema: {app_id}  -- version = {thisAppVersion}    --  isPublic = {isPublic}')

Display All Output thus far#

display(tapisInputAll)
{'maxMinutes': 6,
 'execSystemId': 'stampede3',
 'execSystemLogicalQueue': 'skx-dev',
 'nodeCount': 1,
 'coresPerNode': 48,
 'allocation': 'DS-HPC1',
 'archive_system': 'CommunityData',
 'storage_system': 'CommunityData',
 'input_folder': 'Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic',
 'appId': 'designsafe-openseespy-s3',
 'appVersion': 'latest'}

Job-Execution Controls#

askConfirmJob = False
askConfirmMonitorRT = False

Input Files#

The scripts used in this notebook, with the .tacc.py in the filename have a logical process to check whether the TACC-compiles OpenSeesPy is available – recommended for TACC.

OpenSeesPy_script_PY = 'Ex1a.Canti2D.Push.py'
OpenSeesPy_script_PY_TACC = 'Ex1a.Canti2D.Push.tacc.py'
OpenSeesPy_script_PY_TACC_PyLauncher = 'Ex1a.Canti2D.Push.argv.tacc.pylauncher.py'
OpenSeesPy_script_PY_futures = 'Ex1a.Canti2D.Push.futures.py'
OpenSeesPy_script_PY_futures_TACC = 'Ex1a.Canti2D.Push.futures.tacc.py'
OpenSeesPy_script_PY_mpi = 'Ex1a.Canti2D.Push.mpi.py'
OpenSeesPy_script_PY_mpi_TACC = 'Ex1a.Canti2D.Push.mpi.tacc.py'
OpenSeesPy_script_PY_mpi4py = 'Ex1a.Canti2D.Push.mpi4py.py'
OpenSeesPy_script_PY_mpi4py_TACC = 'Ex1a.Canti2D.Push.mpi4py.tacc.py'



# PyLauncher Files:
PyLauncher_Input_Script = 'Ex1a.Canti2D.Push.argv.tacc.py.callPylauncher.py'
PyLauncher_FileList_File = 'Ex1a.Canti2D.Push.argv.tacc.py.runsList.txt'
OpenSeesPy_Script_wArgv = 'Ex1a.Canti2D.Push.argv.tacc.py'

A. OpenSeesPy: Single Thread#

Test File Locally#

# test here first
os.system(f'python {OpsScriptsPath_Local}/Ex1a.Canti2D.Push.tacc.py')
using OpenSeesPy wheel
['/home/jupyter/CommunityData/Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic/Ex1a.Canti2D.Push.tacc.py']
Analysis-0 execution done
Analysis-1 execution done
Analysis-2 execution done
Analysis-3 execution done
Analysis-4 execution done
Analysis-5 execution done
Analysis-6 execution done
Analysis-7 execution done
ALL DONE!!!
Process 0 Terminating
0

app input for this run#

# initalize
tapisInput = tapisInputAll.copy()

tapisInput['storage_system'] = OpsScriptsPath_StorageSystem
tapisInput['input_folder'] = OpsScriptsPath_Base
tapisInput['Main Script'] = 'Ex1a.Canti2D.Push.tacc.py' # ########################## unique

# NEW: unique to OpenSeesPy
# UseMPI: Flag indicating whether the application should launch the main program with an MPI parallel-execution command (ibrun). 
#     **True**: enable distributed-memory parallelism, allowing multi-core or multi-node execution. (Suitable for OpenSeesMP / OpenSeesSP / Python with mpi4py (OpenSeesPy)). 
#     **False**: execution stays on one node. (Suitable for OpenSees, Python, or Python with concurrent.futures for one-node parallelism.)
tapisInput['UseMPI'] = 'False'  # ########################## unique


tapisInput['name'] = tapisInput['appId'] + '_' + "OpenSeesPyApp" + '_' + tapisInput['Main Script']
display(tapisInput)
{'maxMinutes': 6,
 'execSystemId': 'stampede3',
 'execSystemLogicalQueue': 'skx-dev',
 'nodeCount': 1,
 'coresPerNode': 48,
 'allocation': 'DS-HPC1',
 'archive_system': 'CommunityData',
 'storage_system': 'CommunityData',
 'input_folder': 'Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic',
 'appId': 'designsafe-openseespy-s3',
 'appVersion': 'latest',
 'Main Script': 'Ex1a.Canti2D.Push.tacc.py',
 'UseMPI': 'False',
 'name': 'designsafe-openseespy-s3_OpenSeesPyApp_Ex1a.Canti2D.Push.tacc.py'}

Submit#

# -----------------------------------------------------
jobReturns = OpsUtils.run_tapis_job(t,tapisInput,get_job_metadata=True,get_job_history=True,get_job_filedata=True,askConfirmJob = askConfirmJob,askConfirmMonitorRT = askConfirmMonitorRT)
# -----------------------------------------------------

B. OpenSeesPy: Multi-Process using concurrent.futures#

keep UseMPI False

OpsUtils.show_text_file_in_accordion(OpsScriptsPath_Local, [OpenSeesPy_script_PY_futures_TACC])

Test File Locally#

# test here first --
os.system(f'python {OpsScriptsPath_Local}/{OpenSeesPy_script_PY_futures_TACC}')
['/home/jupyter/CommunityData/Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic/Ex1a.Canti2D.Push.futures.tacc.py']
Analysis-4 execution done with ok=0
Analysis-2 execution done with ok=0
Analysis-3 execution done with ok=0
Analysis-1 execution done with ok=0
Analysis-8 execution done with ok=0
Analysis-5 execution done with ok=0
Analysis-7 execution done with ok=0
Analysis-6 execution done with ok=0
[case 5], status=0
[case 1], status=0
[case 4], status=0
[case 3], status=0
[case 2], status=0
[case 6], status=0
[case 7], status=0
[case 8], status=0

All results:
{'case_id': 1, 'status': 0}
{'case_id': 2, 'status': 0}
{'case_id': 3, 'status': 0}
{'case_id': 4, 'status': 0}
{'case_id': 5, 'status': 0}
{'case_id': 6, 'status': 0}
{'case_id': 7, 'status': 0}
{'case_id': 8, 'status': 0}
ALL DONE!!!
Process 0 Terminating
0

app input for this run#

# initalize
tapisInput = tapisInputAll.copy()

tapisInput['storage_system'] = OpsScriptsPath_StorageSystem
tapisInput['input_folder'] = OpsScriptsPath_Base
tapisInput['Main Script'] = OpenSeesPy_script_PY_futures_TACC # ########################## unique

# NEW: unique to OpenSeesPy
# UseMPI: Flag indicating whether the application should launch the main program with an MPI parallel-execution command (ibrun). 
#     **True**: enable distributed-memory parallelism, allowing multi-core or multi-node execution. (Suitable for OpenSeesMP / OpenSeesSP / Python with mpi4py (OpenSeesPy)). 
#     **False**: execution stays on one node. (Suitable for OpenSees, Python, or Python with concurrent.futures for one-node parallelism.)
tapisInput['UseMPI'] = 'False' # keep false


tapisInput['name'] = tapisInput['appId'] + '_' + "OpenSeesPyApp" + '_' + tapisInput['Main Script']
display(tapisInput)
{'maxMinutes': 6,
 'execSystemId': 'stampede3',
 'execSystemLogicalQueue': 'skx-dev',
 'nodeCount': 1,
 'coresPerNode': 48,
 'allocation': 'DS-HPC1',
 'archive_system': 'CommunityData',
 'storage_system': 'CommunityData',
 'input_folder': 'Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic',
 'appId': 'designsafe-openseespy-s3',
 'appVersion': 'latest',
 'Main Script': 'Ex1a.Canti2D.Push.futures.tacc.py',
 'UseMPI': 'False',
 'name': 'designsafe-openseespy-s3_OpenSeesPyApp_Ex1a.Canti2D.Push.futures.tacc.py'}

Submit#

# -----------------------------------------------------
jobReturns = OpsUtils.run_tapis_job(t,tapisInput,get_job_metadata=True,get_job_history=True,get_job_filedata=True,askConfirmJob = askConfirmJob,askConfirmMonitorRT = askConfirmMonitorRT)
# -----------------------------------------------------

C. OpenSeesPy: Multi-Process using mpi4py#

OpsUtils.show_text_file_in_accordion(OpsScriptsPath_Local, [OpenSeesPy_script_PY_mpi4py_TACC])

Test File Locally#

# test here first
os.system(f'mpiexec -np 4 python {OpsScriptsPath_Local}/{OpenSeesPy_script_PY_mpi4py_TACC}')
mpi4py -- python pid 1 of 4 started
mpi4py -- python pid 0 of 4 started
mpi4py -- python pid 2 of 4 started
mpi4py -- python pid 3 of 4 started
pid 1 of np=4 Analysis-1 execution done
pid 2 of np=4 Analysis-2 execution done
pid 0 of np=4 Analysis-0 execution done
pid 3 of np=4 Analysis-3 execution done
pid 1 of np=4 Analysis-5 execution done
pid 1 of np=4 ALL DONE!!!
pid 2 of np=4 Analysis-6 execution done
pid 2 of np=4 ALL DONE!!!
pid 0 of np=4 Analysis-4 execution done
pid 0 of np=4 ALL DONE!!!
pid 3 of np=4 Analysis-7 execution done
pid 3 of np=4 ALL DONE!!!
Process 0 Terminating
Process 0 Terminating
Process 0 Terminating
Process 0 Terminating
0

app input for this run#

# initalize
tapisInput = tapisInputAll.copy()

tapisInput['storage_system'] = OpsScriptsPath_StorageSystem
tapisInput['input_folder'] = OpsScriptsPath_Base
tapisInput['Main Script'] = OpenSeesPy_script_PY_mpi4py_TACC # ########################## unique

# NEW: unique to OpenSeesPy
# UseMPI: Flag indicating whether the application should launch the main program with an MPI parallel-execution command (ibrun). 
#     **True**: enable distributed-memory parallelism, allowing multi-core or multi-node execution. (Suitable for OpenSeesMP / OpenSeesSP / Python with mpi4py (OpenSeesPy)). 
#     **False**: execution stays on one node. (Suitable for OpenSees, Python, or Python with concurrent.futures for one-node parallelism.)
tapisInput['UseMPI'] = 'True' 

tapisInput['name'] = tapisInput['appId'] + '_' + "OpenSeesPyApp" + '_' + tapisInput['Main Script']
display(tapisInput)
{'maxMinutes': 6,
 'execSystemId': 'stampede3',
 'execSystemLogicalQueue': 'skx-dev',
 'nodeCount': 1,
 'coresPerNode': 48,
 'allocation': 'DS-HPC1',
 'archive_system': 'CommunityData',
 'storage_system': 'CommunityData',
 'input_folder': 'Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic',
 'appId': 'designsafe-openseespy-s3',
 'appVersion': 'latest',
 'Main Script': 'Ex1a.Canti2D.Push.mpi4py.tacc.py',
 'UseMPI': 'True',
 'name': 'designsafe-openseespy-s3_OpenSeesPyApp_Ex1a.Canti2D.Push.mpi4py.tacc.py'}

Submit#

# -----------------------------------------------------
jobReturns = OpsUtils.run_tapis_job(t,tapisInput,get_job_metadata=True,get_job_history=True,get_job_filedata=True,askConfirmJob = askConfirmJob,askConfirmMonitorRT = askConfirmMonitorRT)
# -----------------------------------------------------

OpenSeesPy: PyLauncher#

you can let pylauncher manage your analysis tasks – you can do this for both the TCL and Python versions of OpenSees.

Test Locally#

we can only test the analysis script

# test the script first
os.system(f'python {OpsScriptsPath_Local}/{OpenSeesPy_Script_wArgv} --NodalMass 11.59 --outDir outCase38') # adding the -h to show help
using OpenSeesPy wheel
['/home/jupyter/CommunityData/Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic/Ex1a.Canti2D.Push.argv.tacc.py', '--NodalMass', '11.59', '--outDir', 'outCase38']
we have input arguments
NodalMass 11.59
LColList [101, 121, 200, 240, 301, 360, 401, 481]
outDir outCase38
Lcol 101
2 0 101
<class 'int'>
Analysis-0 execution done
Lcol 121
2 0 121
<class 'int'>
Analysis-1 execution done
Lcol 200
2 0 200
<class 'int'>
Analysis-2 execution done
Lcol 240
2 0 240
<class 'int'>
Analysis-3 execution done
Lcol 301
2 0 301
<class 'int'>
Analysis-4 execution done
Lcol 360
2 0 360
<class 'int'>
Analysis-5 execution done
Lcol 401
2 0 401
<class 'int'>
Analysis-6 execution done
Lcol 481
2 0 481
<class 'int'>
Analysis-7 execution done
ALL DONE!!!
Process 0 Terminating
0

PyLauncher Input#

You need 3 files:

  1. Your OpenSees-Analysis script needs to accept and process command-line arguments – these are your analysis variables. Example:

print(sys.argv)
if len(sys.argv)>1:
    print('we have input arguments')
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--NodalMass", type=float, default=5.18)
parser.add_argument("--LCol", type=float, default=None)  # NEW: single run value
parser.add_argument("--LColList", type=csv_floats, default=[101,121,200,240,301,360,401,481])
parser.add_argument("--outDir", type=str, default='outData_PY_tacc')
args = parser.parse_args()
NodalMass = args.NodalMass
LColList = args.LColList
outDir = args.outDir

if args.LCol is not None:
    LColList = [args.LCol]          # launcher mode: one case per process
else:
    LColList = args.LColList        # interactive mode: multiple cases

print('NodalMass',NodalMass)
print('LColList',LColList)
print('outDir',outDir)

  1. your Main Script calls the PyLauncher:

Example:

import pylauncher
pylauncher.ClassicLauncher("Ex1a.Canti2D.Push.argv.tacc.runsList.txt",debug="host+job")
  1. The Task File containing the commands. Example:

python Ex1a.Canti2D.Push.argv.tacc.py --NodalMass 11.59 --outDir outCase38
python Ex1a.Canti2D.Push.argv.tacc.py --NodalMass 11.79 --outDir outCase39
python Ex1a.Canti2D.Push.argv.tacc.py --NodalMass 11.99 --outDir outCase40
python Ex1a.Canti2D.Push.argv.tacc.py --LcolList 100,200,300 --outDir outCase41
python Ex1a.Canti2D.Push.argv.tacc.py --LcolList 105,205,305 --outDir outCase42
python Ex1a.Canti2D.Push.argv.tacc.py --LcolList 110,210,310 --outDir outCase43
....

app input for this run#

# initalize

tapisInput = tapisInputAll.copy()

tapisInput['storage_system'] = OpsScriptsPath_StorageSystem
tapisInput['input_folder'] = OpsScriptsPath_Base
tapisInput['Main Script'] = PyLauncher_Input_Script # ########################## unique, not the openseesPy script!!!

tapisInput['UseMPI'] = 'False'; # am using the classic launcher

tapisInput['name'] = tapisInput['appId'] + '_' + "OpenSeesPyApp" + '_' + tapisInput['Main Script']
display(tapisInput)
{'maxMinutes': 6,
 'execSystemId': 'stampede3',
 'execSystemLogicalQueue': 'skx-dev',
 'nodeCount': 1,
 'coresPerNode': 48,
 'allocation': 'DS-HPC1',
 'archive_system': 'CommunityData',
 'storage_system': 'CommunityData',
 'input_folder': 'Training/Computational-Workflows-on-DesignSafe/Examples/OpenSees_Basic',
 'appId': 'designsafe-openseespy-s3',
 'appVersion': 'latest',
 'Main Script': 'Ex1a.Canti2D.Push.argv.tacc.py.callPylauncher.py',
 'UseMPI': 'False',
 'name': 'designsafe-openseespy-s3_OpenSeesPyApp_Ex1a.Canti2D.Push.argv.tacc.py.callPylauncher.py'}

Submit#

# -----------------------------------------------------
jobReturns = OpsUtils.run_tapis_job(t,tapisInput,get_job_metadata=True,get_job_history=True,get_job_filedata=True,askConfirmJob = askConfirmJob,askConfirmMonitorRT = askConfirmMonitorRT)
# -----------------------------------------------------
print('done!')
done!