stochops 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stochops-0.1.0/.github/workflows/ci.yml +34 -0
- stochops-0.1.0/.github/workflows/publish.yml +57 -0
- stochops-0.1.0/.gitignore +218 -0
- stochops-0.1.0/.python-version +1 -0
- stochops-0.1.0/LICENSE +21 -0
- stochops-0.1.0/PKG-INFO +140 -0
- stochops-0.1.0/README.md +107 -0
- stochops-0.1.0/RELEASE_NOTES.md +28 -0
- stochops-0.1.0/cliff.toml +32 -0
- stochops-0.1.0/git-cliff-2.13.1/CHANGELOG.md +1886 -0
- stochops-0.1.0/git-cliff-2.13.1/LICENSE-APACHE +176 -0
- stochops-0.1.0/git-cliff-2.13.1/LICENSE-MIT +22 -0
- stochops-0.1.0/git-cliff-2.13.1/README.md +100 -0
- stochops-0.1.0/git-cliff-2.13.1/completions/_git-cliff +92 -0
- stochops-0.1.0/git-cliff-2.13.1/completions/_git-cliff.ps1 +90 -0
- stochops-0.1.0/git-cliff-2.13.1/completions/git-cliff.bash +210 -0
- stochops-0.1.0/git-cliff-2.13.1/completions/git-cliff.elv +84 -0
- stochops-0.1.0/git-cliff-2.13.1/completions/git-cliff.fish +48 -0
- stochops-0.1.0/git-cliff-2.13.1/git-cliff +0 -0
- stochops-0.1.0/git-cliff-2.13.1/git-cliff-completions +0 -0
- stochops-0.1.0/git-cliff-2.13.1/git-cliff-mangen +0 -0
- stochops-0.1.0/git-cliff-2.13.1/man/git-cliff.1 +250 -0
- stochops-0.1.0/git-cliff.tar.gz +0 -0
- stochops-0.1.0/pyproject.toml +96 -0
- stochops-0.1.0/src/stochops/__init__.py +55 -0
- stochops-0.1.0/src/stochops/adaptive/__init__.py +6 -0
- stochops-0.1.0/src/stochops/adaptive/solver_fallback.py +89 -0
- stochops-0.1.0/src/stochops/builder.py +140 -0
- stochops-0.1.0/src/stochops/exceptions.py +5 -0
- stochops-0.1.0/src/stochops/execution/__init__.py +5 -0
- stochops-0.1.0/src/stochops/execution/process_pool.py +151 -0
- stochops-0.1.0/src/stochops/execution/subprocess_pool.py +0 -0
- stochops-0.1.0/src/stochops/parameters.py +152 -0
- stochops-0.1.0/src/stochops/protocols/__init__.py +11 -0
- stochops-0.1.0/src/stochops/protocols/engine.py +35 -0
- stochops-0.1.0/src/stochops/protocols/model.py +19 -0
- stochops-0.1.0/src/stochops/protocols/sampler.py +17 -0
- stochops-0.1.0/src/stochops/protocols/sink.py +18 -0
- stochops-0.1.0/src/stochops/py.typed +0 -0
- stochops-0.1.0/src/stochops/samplers/__init__.py +6 -0
- stochops-0.1.0/src/stochops/samplers/lhs.py +141 -0
- stochops-0.1.0/src/stochops/samplers/mc.py +140 -0
- stochops-0.1.0/src/stochops/samplers/nataf.py +0 -0
- stochops-0.1.0/src/stochops/sinks/__init__.py +5 -0
- stochops-0.1.0/src/stochops/sinks/hdf5.py +0 -0
- stochops-0.1.0/src/stochops/sinks/parquet.py +136 -0
- stochops-0.1.0/tests/test_builder.py +176 -0
- stochops-0.1.0/tests/test_integration.py +145 -0
- stochops-0.1.0/tests/test_mc.py +223 -0
- stochops-0.1.0/tests/test_process_pool.py +121 -0
- stochops-0.1.0/tests/test_samplers.py +319 -0
- stochops-0.1.0/tests/test_sinks.py +100 -0
- stochops-0.1.0/uv.lock +1445 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout Code
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
|
|
21
|
+
- name: Install OpenSeesPy system libraries
|
|
22
|
+
run: sudo apt-get update && sudo apt-get install -y libblas3 liblapack3
|
|
23
|
+
|
|
24
|
+
- name: Install package with dev dependencies
|
|
25
|
+
run: pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: pytest -q
|
|
29
|
+
|
|
30
|
+
- name: Lint
|
|
31
|
+
run: ruff check src tests
|
|
32
|
+
|
|
33
|
+
- name: Type check
|
|
34
|
+
run: mypy src
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Publish Release & Changelog
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
contents: write # Allows workflow to attach release notes
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout Code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.11"
|
|
25
|
+
|
|
26
|
+
- name: Install OpenSeesPy system libraries
|
|
27
|
+
run: sudo apt-get update && sudo apt-get install -y libblas3 liblapack3
|
|
28
|
+
|
|
29
|
+
- name: Install package with dev dependencies
|
|
30
|
+
run: pip install -e ".[dev]"
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: pytest -q
|
|
34
|
+
|
|
35
|
+
- name: Generate Release Notes
|
|
36
|
+
id: git-cliff
|
|
37
|
+
run: |
|
|
38
|
+
curl -sSfL \
|
|
39
|
+
https://github.com/orhun/git-cliff/releases/download/v2.13.1/git-cliff-2.13.1-x86_64-unknown-linux-gnu.tar.gz \
|
|
40
|
+
-o git-cliff.tar.gz
|
|
41
|
+
tar -xzf git-cliff.tar.gz
|
|
42
|
+
git-cliff-2.13.1/git-cliff --config cliff.toml --latest --strip header -o RELEASE_NOTES.md
|
|
43
|
+
|
|
44
|
+
- name: Build & Publish to PyPI
|
|
45
|
+
run: |
|
|
46
|
+
python -m pip install build
|
|
47
|
+
python -m build
|
|
48
|
+
|
|
49
|
+
- name: Publish Package
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
51
|
+
|
|
52
|
+
- name: Create GitHub Release
|
|
53
|
+
uses: softprops/action-gh-release@v2
|
|
54
|
+
with:
|
|
55
|
+
body_path: RELEASE_NOTES.md
|
|
56
|
+
env:
|
|
57
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
stochops-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dahel Ihab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
stochops-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: stochops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fluent Monte Carlo & Latin Hypercube Sampling simulation engine for OpenSeesPy.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ihab65/stochops
|
|
6
|
+
Project-URL: Repository, https://github.com/ihab65/stochops
|
|
7
|
+
Project-URL: Documentation, https://github.com/ihab65/stochops#readme
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/ihab65/stochops/issues
|
|
9
|
+
Author: Ihab Eddine Dahel
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: earthquake-engineering,latin-hypercube-sampling,monte-carlo,opensees,openseespy,reliability,structural-engineering,uncertainty-quantification
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: numpy>=1.22.0
|
|
23
|
+
Requires-Dist: openseespy>=3.4.0
|
|
24
|
+
Requires-Dist: pandas>=2.0.0
|
|
25
|
+
Requires-Dist: pyarrow>=12.0.0
|
|
26
|
+
Requires-Dist: scipy>=1.10.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# stochops
|
|
35
|
+
|
|
36
|
+
A fluent Python framework for parallel **Latin Hypercube Sampling (LHS)** and uncertainty-quantification simulations on top of **OpenSeesPy**.
|
|
37
|
+
|
|
38
|
+
`stochops` runs large batches of structural analysis realizations in parallel, each with a different set of sampled input parameters (material strengths, section choices, ground motions...), and streams every result to disk — flat RAM regardless of campaign size.
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
A simulation campaign is assembled from four pluggable strategies, wired together by a fluent builder:
|
|
43
|
+
|
|
44
|
+
| Strategy | Protocol | What it does |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `ParametricModel` | `callable(dict) -> dict` | Runs a single realization of the FE model and returns Engineering Demand Parameters (EDPs) such as `{"max_drift": 0.012}`. |
|
|
47
|
+
| `Sampler` | `sample(count) -> list[dict]` | Generates parameter realizations. Implementations: `LHSSampler` (stratified LHS), `MCSampler` (independent draws). |
|
|
48
|
+
| `ExecutionEngine` | `run_batch(model, samples) -> list[dict]` | Dispatches batches across worker processes (`ProcessPoolEngine`, spawn-isolated for OpenSeesPy). |
|
|
49
|
+
| `ResultSink` | `write(batch)` / `close()` | Persists results (`ParquetSink`, streaming). |
|
|
50
|
+
|
|
51
|
+
`SimulationEngine.run(total_samples)` loops in chunks: sample a batch → dispatch it → stream it to the sink. A single failing realization is trapped and recorded with `status="DIVERGED"` / `status="FAILED"` metadata instead of killing the campaign.
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from stochops import SimulationBuilder
|
|
57
|
+
from stochops.execution.process_pool import ProcessPoolEngine
|
|
58
|
+
from stochops.parameters import ContinuousParam, DiscreteParam, Distribution
|
|
59
|
+
from stochops.samplers.lhs import LHSSampler
|
|
60
|
+
from stochops.samplers.mc import MCSampler
|
|
61
|
+
from stochops.sinks.parquet import ParquetSink
|
|
62
|
+
|
|
63
|
+
def evaluate_realization(params: dict) -> dict:
|
|
64
|
+
"""Single OpenSeesPy analysis. Here a cheap stand-in."""
|
|
65
|
+
# ... build the OpenSees model from `params`, run the analysis ...
|
|
66
|
+
return {
|
|
67
|
+
"max_drift": (params["fy"] / params["fc"]) * 0.001,
|
|
68
|
+
"base_shear_kN": (params["fc"] / 1e6) * 12.5,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
# 1. Define the uncertain parameters
|
|
73
|
+
parameters = [
|
|
74
|
+
ContinuousParam("fc", Distribution.NORMAL, mean=30e6, std=3e6, min_value=20e6),
|
|
75
|
+
ContinuousParam("fy", Distribution.LOGNORMAL, mean=400e6, std=20e6),
|
|
76
|
+
DiscreteParam("section", choices=["RECT_300x500", "RECT_400x600"]),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
# 2. Assemble the simulation
|
|
80
|
+
simulation = (
|
|
81
|
+
SimulationBuilder()
|
|
82
|
+
.model(evaluate_realization)
|
|
83
|
+
# Use MCSampler instead for plain, independent Monte Carlo draws
|
|
84
|
+
.sampler(LHSSampler(parameters=parameters, seed=2026))
|
|
85
|
+
.execution_engine(ProcessPoolEngine(num_workers=8))
|
|
86
|
+
.sink(ParquetSink("results.parquet"))
|
|
87
|
+
.batch_size(100)
|
|
88
|
+
.build()
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# 3. Run 10,000 realizations in batches of 100
|
|
92
|
+
simulation.run(total_samples=10_000)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> **Important:** the model function must be defined at module level (top of the
|
|
96
|
+
> file). Workers run in `spawn`-isolated processes, which pickle the model by
|
|
97
|
+
> name — a function defined inside `if __name__ == "__main__":` or another
|
|
98
|
+
> function cannot be sent to the workers. The orchestration itself is wrapped
|
|
99
|
+
> in `if __name__ == "__main__":` so the workers' re-import of the script does
|
|
100
|
+
> not re-trigger the campaign.
|
|
101
|
+
|
|
102
|
+
Results can be read with anything that understands Parquet:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import pandas as pd
|
|
106
|
+
df = pd.read_parquet("results.parquet")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Columns include the input parameters, `sample_id`, `status` (`CONVERGED` / `DIVERGED` / `FAILED`), failure diagnostics, and every returned EDP.
|
|
110
|
+
|
|
111
|
+
## Sampling notes
|
|
112
|
+
|
|
113
|
+
- **Two strategies.** `LHSSampler` draws stratified, space-filling Latin hypercube points; `MCSampler` draws independent, identically distributed realizations — the classic Monte Carlo default, with no stratification property but simpler statistical analysis. Both are drop-in swaps for the builder's `.sampler(...)` slot.
|
|
114
|
+
- **Deterministic streams.** Each sampler's RNG engine is created once and advanced across batches, so consecutive `sample()` calls draw *distinct* realizations. Two samplers built with the same `seed` reproduce the exact same concatenated stream; call `sampler.reset()` to restart.
|
|
115
|
+
- **Correlated parameters.** Pass a symmetric positive-definite `correlation_matrix` to impose correlations via a Gaussian copula. For `LHSSampler` the copula transformation reorders points and forfeits exact LHS stratification — use it only when correlation matters more than space-filling. For `MCSampler`, independent draws mean the copula has no such trade-off.
|
|
116
|
+
- **Discrete parameters.** `DiscreteParam.probabilities` are normalized automatically; `GroundMotionSetParam` draws uniformly from a catalog of ground-motion records.
|
|
117
|
+
|
|
118
|
+
## Analysis robustness
|
|
119
|
+
|
|
120
|
+
Nonlinear transient analyses diverge. Two guards are built in:
|
|
121
|
+
|
|
122
|
+
- `AdaptiveAnalysisRunner` cycles through solution algorithms (Newton → KrylovNewton → NewtonWithLineSearch → BFGS → Broyden) and recursively halves the time step before giving up.
|
|
123
|
+
- The process pool isolates each worker with `spawn`, so OpenSeesPy C++ static state cannot leak across realizations and one segfaulting worker is replaced and retried rather than failing the campaign.
|
|
124
|
+
|
|
125
|
+
## Limitations
|
|
126
|
+
|
|
127
|
+
- Nataf-correlated sampling (an alternative to the Gaussian copula) is planned but not yet implemented.
|
|
128
|
+
- The Parquet schema is frozen the first time the writer opens (after the first success row or `max_buffered_rows` records). Results introducing brand-new columns after that point emit a warning and drop those columns.
|
|
129
|
+
- Solver-divergence classification uses `ConvergenceError` type checks with a best-effort message fallback; unusual user exceptions may be mislabeled.
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv sync --extra dev
|
|
135
|
+
uv run pytest
|
|
136
|
+
uv run ruff check src tests
|
|
137
|
+
uv run mypy src
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Tested against Python 3.10+.
|
stochops-0.1.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# stochops
|
|
2
|
+
|
|
3
|
+
A fluent Python framework for parallel **Latin Hypercube Sampling (LHS)** and uncertainty-quantification simulations on top of **OpenSeesPy**.
|
|
4
|
+
|
|
5
|
+
`stochops` runs large batches of structural analysis realizations in parallel, each with a different set of sampled input parameters (material strengths, section choices, ground motions...), and streams every result to disk — flat RAM regardless of campaign size.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
A simulation campaign is assembled from four pluggable strategies, wired together by a fluent builder:
|
|
10
|
+
|
|
11
|
+
| Strategy | Protocol | What it does |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `ParametricModel` | `callable(dict) -> dict` | Runs a single realization of the FE model and returns Engineering Demand Parameters (EDPs) such as `{"max_drift": 0.012}`. |
|
|
14
|
+
| `Sampler` | `sample(count) -> list[dict]` | Generates parameter realizations. Implementations: `LHSSampler` (stratified LHS), `MCSampler` (independent draws). |
|
|
15
|
+
| `ExecutionEngine` | `run_batch(model, samples) -> list[dict]` | Dispatches batches across worker processes (`ProcessPoolEngine`, spawn-isolated for OpenSeesPy). |
|
|
16
|
+
| `ResultSink` | `write(batch)` / `close()` | Persists results (`ParquetSink`, streaming). |
|
|
17
|
+
|
|
18
|
+
`SimulationEngine.run(total_samples)` loops in chunks: sample a batch → dispatch it → stream it to the sink. A single failing realization is trapped and recorded with `status="DIVERGED"` / `status="FAILED"` metadata instead of killing the campaign.
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from stochops import SimulationBuilder
|
|
24
|
+
from stochops.execution.process_pool import ProcessPoolEngine
|
|
25
|
+
from stochops.parameters import ContinuousParam, DiscreteParam, Distribution
|
|
26
|
+
from stochops.samplers.lhs import LHSSampler
|
|
27
|
+
from stochops.samplers.mc import MCSampler
|
|
28
|
+
from stochops.sinks.parquet import ParquetSink
|
|
29
|
+
|
|
30
|
+
def evaluate_realization(params: dict) -> dict:
|
|
31
|
+
"""Single OpenSeesPy analysis. Here a cheap stand-in."""
|
|
32
|
+
# ... build the OpenSees model from `params`, run the analysis ...
|
|
33
|
+
return {
|
|
34
|
+
"max_drift": (params["fy"] / params["fc"]) * 0.001,
|
|
35
|
+
"base_shear_kN": (params["fc"] / 1e6) * 12.5,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
# 1. Define the uncertain parameters
|
|
40
|
+
parameters = [
|
|
41
|
+
ContinuousParam("fc", Distribution.NORMAL, mean=30e6, std=3e6, min_value=20e6),
|
|
42
|
+
ContinuousParam("fy", Distribution.LOGNORMAL, mean=400e6, std=20e6),
|
|
43
|
+
DiscreteParam("section", choices=["RECT_300x500", "RECT_400x600"]),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# 2. Assemble the simulation
|
|
47
|
+
simulation = (
|
|
48
|
+
SimulationBuilder()
|
|
49
|
+
.model(evaluate_realization)
|
|
50
|
+
# Use MCSampler instead for plain, independent Monte Carlo draws
|
|
51
|
+
.sampler(LHSSampler(parameters=parameters, seed=2026))
|
|
52
|
+
.execution_engine(ProcessPoolEngine(num_workers=8))
|
|
53
|
+
.sink(ParquetSink("results.parquet"))
|
|
54
|
+
.batch_size(100)
|
|
55
|
+
.build()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# 3. Run 10,000 realizations in batches of 100
|
|
59
|
+
simulation.run(total_samples=10_000)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> **Important:** the model function must be defined at module level (top of the
|
|
63
|
+
> file). Workers run in `spawn`-isolated processes, which pickle the model by
|
|
64
|
+
> name — a function defined inside `if __name__ == "__main__":` or another
|
|
65
|
+
> function cannot be sent to the workers. The orchestration itself is wrapped
|
|
66
|
+
> in `if __name__ == "__main__":` so the workers' re-import of the script does
|
|
67
|
+
> not re-trigger the campaign.
|
|
68
|
+
|
|
69
|
+
Results can be read with anything that understands Parquet:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import pandas as pd
|
|
73
|
+
df = pd.read_parquet("results.parquet")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Columns include the input parameters, `sample_id`, `status` (`CONVERGED` / `DIVERGED` / `FAILED`), failure diagnostics, and every returned EDP.
|
|
77
|
+
|
|
78
|
+
## Sampling notes
|
|
79
|
+
|
|
80
|
+
- **Two strategies.** `LHSSampler` draws stratified, space-filling Latin hypercube points; `MCSampler` draws independent, identically distributed realizations — the classic Monte Carlo default, with no stratification property but simpler statistical analysis. Both are drop-in swaps for the builder's `.sampler(...)` slot.
|
|
81
|
+
- **Deterministic streams.** Each sampler's RNG engine is created once and advanced across batches, so consecutive `sample()` calls draw *distinct* realizations. Two samplers built with the same `seed` reproduce the exact same concatenated stream; call `sampler.reset()` to restart.
|
|
82
|
+
- **Correlated parameters.** Pass a symmetric positive-definite `correlation_matrix` to impose correlations via a Gaussian copula. For `LHSSampler` the copula transformation reorders points and forfeits exact LHS stratification — use it only when correlation matters more than space-filling. For `MCSampler`, independent draws mean the copula has no such trade-off.
|
|
83
|
+
- **Discrete parameters.** `DiscreteParam.probabilities` are normalized automatically; `GroundMotionSetParam` draws uniformly from a catalog of ground-motion records.
|
|
84
|
+
|
|
85
|
+
## Analysis robustness
|
|
86
|
+
|
|
87
|
+
Nonlinear transient analyses diverge. Two guards are built in:
|
|
88
|
+
|
|
89
|
+
- `AdaptiveAnalysisRunner` cycles through solution algorithms (Newton → KrylovNewton → NewtonWithLineSearch → BFGS → Broyden) and recursively halves the time step before giving up.
|
|
90
|
+
- The process pool isolates each worker with `spawn`, so OpenSeesPy C++ static state cannot leak across realizations and one segfaulting worker is replaced and retried rather than failing the campaign.
|
|
91
|
+
|
|
92
|
+
## Limitations
|
|
93
|
+
|
|
94
|
+
- Nataf-correlated sampling (an alternative to the Gaussian copula) is planned but not yet implemented.
|
|
95
|
+
- The Parquet schema is frozen the first time the writer opens (after the first success row or `max_buffered_rows` records). Results introducing brand-new columns after that point emit a warning and drop those columns.
|
|
96
|
+
- Solver-divergence classification uses `ConvergenceError` type checks with a best-effort message fallback; unusual user exceptions may be mislabeled.
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv sync --extra dev
|
|
102
|
+
uv run pytest
|
|
103
|
+
uv run ruff check src tests
|
|
104
|
+
uv run mypy src
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Tested against Python 3.10+.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
## [0.1.0] - 2026-08-30
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Correct git-cliff template filter for current versions (6594a5c)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Chores
|
|
12
|
+
|
|
13
|
+
- Include ci and chore commits in generated changelogs (9778eba)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Continuous Integration
|
|
17
|
+
|
|
18
|
+
- Run tests, lint, and type checks on push and pull requests (8fc1d43)
|
|
19
|
+
|
|
20
|
+
- Install OpenSeesPy system BLAS libraries on runners (c669feb)
|
|
21
|
+
|
|
22
|
+
- Generate release notes with a direct git-cliff binary (f3cc259)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
- Add plain Monte Carlo sampler (b6f2310)
|
|
28
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[changelog]
|
|
2
|
+
header = """
|
|
3
|
+
# Changelog\n
|
|
4
|
+
All notable changes to `stochops` will be documented in this file.\n
|
|
5
|
+
"""
|
|
6
|
+
body = """
|
|
7
|
+
{% if version %}
|
|
8
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
9
|
+
{% else %}
|
|
10
|
+
## [unreleased]
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
|
14
|
+
### {{ group | upper_first }}
|
|
15
|
+
{% for commit in commits %}
|
|
16
|
+
- {% if commit.breaking %}**[BREAKING]** {% endif %}{{ commit.message | upper_first }} ({{ commit.id | truncate(length=7, end="") }})
|
|
17
|
+
{% endfor %}
|
|
18
|
+
{% endfor %}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
[git]
|
|
22
|
+
conventional_commits = true
|
|
23
|
+
filter_unconventional = true
|
|
24
|
+
commit_parsers = [
|
|
25
|
+
{ message = "^feat", group = "Features" },
|
|
26
|
+
{ message = "^fix", group = "Bug Fixes" },
|
|
27
|
+
{ message = "^doc", group = "Documentation" },
|
|
28
|
+
{ message = "^perf", group = "Performance" },
|
|
29
|
+
{ message = "^refactor", group = "Refactoring" },
|
|
30
|
+
{ message = "^ci", group = "Continuous Integration" },
|
|
31
|
+
{ message = "^chore", group = "Chores" },
|
|
32
|
+
]
|