openseespy-solvers 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.
- openseespy_solvers-0.1.0/.gitignore +13 -0
- openseespy_solvers-0.1.0/CHANGELOG.md +43 -0
- openseespy_solvers-0.1.0/LICENSE +29 -0
- openseespy_solvers-0.1.0/PKG-INFO +166 -0
- openseespy_solvers-0.1.0/README.md +101 -0
- openseespy_solvers-0.1.0/examples/README.md +129 -0
- openseespy_solvers-0.1.0/pyproject.toml +85 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/__init__.py +41 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/_base.py +428 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/_docstrings.py +64 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/_dtype.py +45 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/_factorization.py +86 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/_hybrid.py +319 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/_sparse.py +134 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/cupy/__init__.py +845 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/cupy/_base.py +99 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/cupy/precond.py +208 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/exceptions.py +34 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/hybrid.py +5 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/nvmath/__init__.py +235 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/nvmath/_base.py +250 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/scipy/__init__.py +712 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/scipy/_base.py +78 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/scipy/precond.py +145 -0
- openseespy_solvers-0.1.0/src/openseespy_solvers/stats.py +32 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Documentation: [Recommended solvers](recommended-solvers.md) (CPU/GPU defaults, performance notes vs native OpenSees); installation quick-start recipes.
|
|
13
|
+
- CuPy `eigsh` for `K x = λ M x` with shift-invert (`mass_mode`: `diagonal`, `lumped`, `general`).
|
|
14
|
+
- nvMath `direct_solver` factory: NVIDIA `nvmath.sparse.advanced.DirectSolver` with plan/factorize/solve caching; namespace `openseespy_solvers.nvmath`; optional `[nvmath]` extra (`nvmath-python[cpu]>=0.8.0`). CPU execution uses SciPy sparse matrices; GPU execution also requires `[cupy]` and a GPU-capable nvmath-python install (e.g. `nvmath-python[cu12-dx]`).
|
|
15
|
+
- SciPy `umfpack` solver: 64-bit UMFPACK direct solver (`scikits.umfpack.UmfpackContext("dl")`) with cached symbolic/numeric factorization; optional `[umfpack]` extra (`scikit-umfpack>=0.3.3`).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- CuPy `eigsh` default `mass_mode` is `general` (was `diagonal`).
|
|
20
|
+
- `nvmath.direct_solver` default `device` is `'gpu'` (was auto CPU when CuPy missing).
|
|
21
|
+
- CuPy `eigsh` `diagonal` / `lumped` use GPU shift-invert with cached diagonal mass (replaces mass-normalized Lanczos).
|
|
22
|
+
- nvMath documented and packaged as a **GPU** backend (like CuPy): install `nvmath-python[cu12]` or `[cu13]` manually; removed `[nvmath]` pip extra.
|
|
23
|
+
- nvMath factory named `direct_solver` (not `spsolve`) to mirror `nvmath.sparse.advanced.direct_solver` / `DirectSolver`; SciPy and CuPy backends keep `spsolve` for their respective `scipy.sparse.linalg.spsolve` / `cupyx.scipy.sparse.linalg.spsolve` wrappers.
|
|
24
|
+
- Rename `.to_opensees()` to `.to_openseespy()` on solver objects (breaking API change).
|
|
25
|
+
- Require Python ≥ 3.12, NumPy ≥ 1.26, SciPy ≥ 1.12; optional CuPy ≥ 13 via `[cupy]`.
|
|
26
|
+
- Rename optional extra `[gpu]` → `[cupy]` (one extra per backend).
|
|
27
|
+
- Simplify examples to two brick-bar solver-comparison scripts (`brick_bar.py` static, `brick_bar_eigen.py` eigen); remove the RC frame and shell examples.
|
|
28
|
+
|
|
29
|
+
## [0.1.0] - 2026-06-03
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Per-backend namespaces `openseespy_solvers.scipy` and `openseespy_solvers.cupy`.
|
|
34
|
+
- SciPy solvers: `spsolve`, `cg`, `gmres`, `eigsh`, `lobpcg`.
|
|
35
|
+
- CuPy solvers: `spsolve`, `cg`, `gmres`, `lobpcg` (no generalized `eigsh`).
|
|
36
|
+
- `.to_opensees()` helper for OpenSeesPy `PythonSparse` system and eigen commands.
|
|
37
|
+
- `formAp` matrix-vector product on all linear solvers.
|
|
38
|
+
- `copy.copy` support via `__copy__` / `__deepcopy__` for OpenSees SOE cloning.
|
|
39
|
+
- Exposed solver arrays: `A`, `b`, `x` (linear) and `K`, `M` (eigen).
|
|
40
|
+
- Preconditioner factories: `M=` accepts a ready operator or callable `M(A)`.
|
|
41
|
+
- Built-in SciPy preconditioners in `openseespy_solvers.scipy.precond` (`jacobi`, `ilu`).
|
|
42
|
+
- Solver statistics via `.stats`.
|
|
43
|
+
- Examples and tests that do not require OpenSeesPy.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, openseespy-solvers contributors
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openseespy-solvers
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ready-to-use, SciPy-style PythonSparse solvers for OpenSeesPy
|
|
5
|
+
Project-URL: Homepage, https://github.com/gaaraujo/openseespy-solvers
|
|
6
|
+
Project-URL: Documentation, https://openseespy-solvers.readthedocs.io/
|
|
7
|
+
Project-URL: Issues, https://github.com/gaaraujo/openseespy-solvers/issues
|
|
8
|
+
Author: openseespy-solvers contributors
|
|
9
|
+
License: BSD 3-Clause License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026, openseespy-solvers contributors
|
|
12
|
+
All rights reserved.
|
|
13
|
+
|
|
14
|
+
Redistribution and use in source and binary forms, with or without
|
|
15
|
+
modification, are permitted provided that the following conditions are met:
|
|
16
|
+
|
|
17
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
18
|
+
list of conditions and the following disclaimer.
|
|
19
|
+
|
|
20
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
and/or other materials provided with the distribution.
|
|
23
|
+
|
|
24
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
25
|
+
contributors may be used to endorse or promote products derived from
|
|
26
|
+
this software without specific prior written permission.
|
|
27
|
+
|
|
28
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
31
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
32
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
33
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
34
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
35
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
36
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
37
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
38
|
+
License-File: LICENSE
|
|
39
|
+
Keywords: eigen,finite-element,opensees,openseespy,scipy,solver,sparse
|
|
40
|
+
Classifier: Development Status :: 3 - Alpha
|
|
41
|
+
Classifier: Intended Audience :: Science/Research
|
|
42
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
43
|
+
Classifier: Operating System :: OS Independent
|
|
44
|
+
Classifier: Programming Language :: Python :: 3
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
47
|
+
Classifier: Topic :: Scientific/Engineering
|
|
48
|
+
Requires-Python: >=3.12
|
|
49
|
+
Requires-Dist: numpy>=1.26
|
|
50
|
+
Requires-Dist: scipy>=1.12
|
|
51
|
+
Provides-Extra: cupy
|
|
52
|
+
Requires-Dist: cupy>=13.0; extra == 'cupy'
|
|
53
|
+
Provides-Extra: dev
|
|
54
|
+
Requires-Dist: build; extra == 'dev'
|
|
55
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
56
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
57
|
+
Provides-Extra: docs
|
|
58
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
59
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
60
|
+
Provides-Extra: opensees
|
|
61
|
+
Requires-Dist: openseespy; extra == 'opensees'
|
|
62
|
+
Provides-Extra: umfpack
|
|
63
|
+
Requires-Dist: scikit-umfpack>=0.3.3; extra == 'umfpack'
|
|
64
|
+
Description-Content-Type: text/markdown
|
|
65
|
+
|
|
66
|
+
# openseespy-solvers
|
|
67
|
+
|
|
68
|
+
Sparse linear algebra solvers for OpenSeesPy
|
|
69
|
+
[`PythonSparse`](https://opensees.github.io/OpenSeesDocumentation/user/manual/analysis/system/PythonSparse.html)
|
|
70
|
+
commands.
|
|
71
|
+
|
|
72
|
+
**Documentation:** [openseespy-solvers.readthedocs.io](https://openseespy-solvers.readthedocs.io/)
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
Requires **Python ≥ 3.12**, **NumPy ≥ 1.26**, **SciPy ≥ 1.12**, and **serial OpenSeesPy**
|
|
77
|
+
(parallel/MPI OpenSeesPy builds are not supported — see
|
|
78
|
+
[docs](https://openseespy-solvers.readthedocs.io/en/latest/user-guide/pythonsparse-interface/#parallelism)).
|
|
79
|
+
|
|
80
|
+
### Full setup from scratch
|
|
81
|
+
|
|
82
|
+
Copy-paste overview — see the [installation guide](https://openseespy-solvers.readthedocs.io/en/latest/installation/#full-setup) for platform notes (UMFPACK, GPU wheels) and the complete verification checklist.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# 1. Environment (conda or venv — all platforms)
|
|
86
|
+
conda create -n openseespy-solvers python=3.12 -y
|
|
87
|
+
conda activate openseespy-solvers
|
|
88
|
+
|
|
89
|
+
# 2. Clone (examples and pytest are in the repo, not the pip wheel)
|
|
90
|
+
git clone https://github.com/gaaraujo/openseespy-solvers.git
|
|
91
|
+
cd openseespy-solvers
|
|
92
|
+
|
|
93
|
+
# 3. Install
|
|
94
|
+
pip install -e ".[dev,opensees]"
|
|
95
|
+
|
|
96
|
+
# 4–5. Optional backends — UMFPACK, CuPy, nvMath (see installation guide)
|
|
97
|
+
# 6. pip check
|
|
98
|
+
# 7. Verify
|
|
99
|
+
pytest
|
|
100
|
+
cd examples && python solvers/scipy_spsolve.py && python solvers/scipy_eigsh.py
|
|
101
|
+
python brick_bar.py && python brick_bar_eigen.py
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
GPU tests in `pytest` run when CuPy/nvMath are installed; skipped otherwise.
|
|
105
|
+
|
|
106
|
+
### Quick install (library only)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install openseespy-solvers
|
|
110
|
+
pip install openseespy-solvers[opensees] # if OpenSeesPy is missing
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Recommended solvers
|
|
114
|
+
|
|
115
|
+
| Analysis | GPU (CUDA) | CPU |
|
|
116
|
+
|----------|------------|-----|
|
|
117
|
+
| Linear (`PythonSparse`, static/transient/…) | `nvmath.direct_solver` | `scipy.spsolve` or `scipy.umfpack` |
|
|
118
|
+
| Eigen | `cupy.eigsh` | `scipy.eigsh` |
|
|
119
|
+
|
|
120
|
+
These `PythonSparse` backends are **often faster** than native OpenSees sparse/direct/eigen
|
|
121
|
+
solvers on medium-to-large models because they use optimized library implementations.
|
|
122
|
+
Details: [recommended solvers](https://openseespy-solvers.readthedocs.io/en/latest/recommended-solvers/).
|
|
123
|
+
|
|
124
|
+
## Example
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from openseespy_solvers import hybrid
|
|
128
|
+
from openseespy_solvers.scipy import spsolve
|
|
129
|
+
|
|
130
|
+
solver = hybrid(spsolve(), rtol=1e-6, restart=50)
|
|
131
|
+
ops.system("PythonSparse", solver.to_openseespy())
|
|
132
|
+
ops.analyze(n)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For Newton or transient steps where the tangent changes slowly, ``hybrid`` factorizes once
|
|
136
|
+
then reuses that factorization as a GMRES preconditioner until the system size changes or
|
|
137
|
+
GMRES fails to converge.
|
|
138
|
+
|
|
139
|
+
## Submodules
|
|
140
|
+
|
|
141
|
+
| Module | Functions |
|
|
142
|
+
|--------|-----------|
|
|
143
|
+
| `openseespy_solvers.scipy` | `spsolve`, `umfpack`, `cg`, `gmres`, `eigsh`, `lobpcg` |
|
|
144
|
+
| `openseespy_solvers.scipy.precond` | `jacobi`, `ilu`, `direct` |
|
|
145
|
+
| `openseespy_solvers.cupy` | `spsolve`, `cg`, `gmres`, `eigsh`, `lobpcg` (GPU) |
|
|
146
|
+
| `openseespy_solvers.cupy.precond` | `jacobi`, `ilu`, `direct` |
|
|
147
|
+
| `openseespy_solvers.nvmath` | `direct_solver` (GPU) |
|
|
148
|
+
| `openseespy_solvers.hybrid` | `hybrid(direct=...)` — frozen factorization + GMRES |
|
|
149
|
+
|
|
150
|
+
Factory signatures match [`scipy.sparse.linalg`](https://docs.scipy.org/doc/scipy/reference/sparse.linalg.html)
|
|
151
|
+
and [`cupyx.scipy.sparse.linalg`](https://docs.cupy.dev/en/stable/reference/scipy_sparse.html);
|
|
152
|
+
`A` and `b` are supplied by OpenSees at solve time.
|
|
153
|
+
|
|
154
|
+
See the [tutorial](https://openseespy-solvers.readthedocs.io/en/latest/getting-started/) and
|
|
155
|
+
[API reference](https://openseespy-solvers.readthedocs.io/en/latest/api/scipy/) for details.
|
|
156
|
+
|
|
157
|
+
## GitHub
|
|
158
|
+
|
|
159
|
+
Repository: [github.com/gaaraujo/openseespy-solvers](https://github.com/gaaraujo/openseespy-solvers).
|
|
160
|
+
Report bugs and ask questions via
|
|
161
|
+
[issues](https://github.com/gaaraujo/openseespy-solvers/issues); send contributions
|
|
162
|
+
(pull requests) there as well.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
BSD 3-Clause — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# openseespy-solvers
|
|
2
|
+
|
|
3
|
+
Sparse linear algebra solvers for OpenSeesPy
|
|
4
|
+
[`PythonSparse`](https://opensees.github.io/OpenSeesDocumentation/user/manual/analysis/system/PythonSparse.html)
|
|
5
|
+
commands.
|
|
6
|
+
|
|
7
|
+
**Documentation:** [openseespy-solvers.readthedocs.io](https://openseespy-solvers.readthedocs.io/)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Requires **Python ≥ 3.12**, **NumPy ≥ 1.26**, **SciPy ≥ 1.12**, and **serial OpenSeesPy**
|
|
12
|
+
(parallel/MPI OpenSeesPy builds are not supported — see
|
|
13
|
+
[docs](https://openseespy-solvers.readthedocs.io/en/latest/user-guide/pythonsparse-interface/#parallelism)).
|
|
14
|
+
|
|
15
|
+
### Full setup from scratch
|
|
16
|
+
|
|
17
|
+
Copy-paste overview — see the [installation guide](https://openseespy-solvers.readthedocs.io/en/latest/installation/#full-setup) for platform notes (UMFPACK, GPU wheels) and the complete verification checklist.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. Environment (conda or venv — all platforms)
|
|
21
|
+
conda create -n openseespy-solvers python=3.12 -y
|
|
22
|
+
conda activate openseespy-solvers
|
|
23
|
+
|
|
24
|
+
# 2. Clone (examples and pytest are in the repo, not the pip wheel)
|
|
25
|
+
git clone https://github.com/gaaraujo/openseespy-solvers.git
|
|
26
|
+
cd openseespy-solvers
|
|
27
|
+
|
|
28
|
+
# 3. Install
|
|
29
|
+
pip install -e ".[dev,opensees]"
|
|
30
|
+
|
|
31
|
+
# 4–5. Optional backends — UMFPACK, CuPy, nvMath (see installation guide)
|
|
32
|
+
# 6. pip check
|
|
33
|
+
# 7. Verify
|
|
34
|
+
pytest
|
|
35
|
+
cd examples && python solvers/scipy_spsolve.py && python solvers/scipy_eigsh.py
|
|
36
|
+
python brick_bar.py && python brick_bar_eigen.py
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
GPU tests in `pytest` run when CuPy/nvMath are installed; skipped otherwise.
|
|
40
|
+
|
|
41
|
+
### Quick install (library only)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install openseespy-solvers
|
|
45
|
+
pip install openseespy-solvers[opensees] # if OpenSeesPy is missing
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Recommended solvers
|
|
49
|
+
|
|
50
|
+
| Analysis | GPU (CUDA) | CPU |
|
|
51
|
+
|----------|------------|-----|
|
|
52
|
+
| Linear (`PythonSparse`, static/transient/…) | `nvmath.direct_solver` | `scipy.spsolve` or `scipy.umfpack` |
|
|
53
|
+
| Eigen | `cupy.eigsh` | `scipy.eigsh` |
|
|
54
|
+
|
|
55
|
+
These `PythonSparse` backends are **often faster** than native OpenSees sparse/direct/eigen
|
|
56
|
+
solvers on medium-to-large models because they use optimized library implementations.
|
|
57
|
+
Details: [recommended solvers](https://openseespy-solvers.readthedocs.io/en/latest/recommended-solvers/).
|
|
58
|
+
|
|
59
|
+
## Example
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from openseespy_solvers import hybrid
|
|
63
|
+
from openseespy_solvers.scipy import spsolve
|
|
64
|
+
|
|
65
|
+
solver = hybrid(spsolve(), rtol=1e-6, restart=50)
|
|
66
|
+
ops.system("PythonSparse", solver.to_openseespy())
|
|
67
|
+
ops.analyze(n)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For Newton or transient steps where the tangent changes slowly, ``hybrid`` factorizes once
|
|
71
|
+
then reuses that factorization as a GMRES preconditioner until the system size changes or
|
|
72
|
+
GMRES fails to converge.
|
|
73
|
+
|
|
74
|
+
## Submodules
|
|
75
|
+
|
|
76
|
+
| Module | Functions |
|
|
77
|
+
|--------|-----------|
|
|
78
|
+
| `openseespy_solvers.scipy` | `spsolve`, `umfpack`, `cg`, `gmres`, `eigsh`, `lobpcg` |
|
|
79
|
+
| `openseespy_solvers.scipy.precond` | `jacobi`, `ilu`, `direct` |
|
|
80
|
+
| `openseespy_solvers.cupy` | `spsolve`, `cg`, `gmres`, `eigsh`, `lobpcg` (GPU) |
|
|
81
|
+
| `openseespy_solvers.cupy.precond` | `jacobi`, `ilu`, `direct` |
|
|
82
|
+
| `openseespy_solvers.nvmath` | `direct_solver` (GPU) |
|
|
83
|
+
| `openseespy_solvers.hybrid` | `hybrid(direct=...)` — frozen factorization + GMRES |
|
|
84
|
+
|
|
85
|
+
Factory signatures match [`scipy.sparse.linalg`](https://docs.scipy.org/doc/scipy/reference/sparse.linalg.html)
|
|
86
|
+
and [`cupyx.scipy.sparse.linalg`](https://docs.cupy.dev/en/stable/reference/scipy_sparse.html);
|
|
87
|
+
`A` and `b` are supplied by OpenSees at solve time.
|
|
88
|
+
|
|
89
|
+
See the [tutorial](https://openseespy-solvers.readthedocs.io/en/latest/getting-started/) and
|
|
90
|
+
[API reference](https://openseespy-solvers.readthedocs.io/en/latest/api/scipy/) for details.
|
|
91
|
+
|
|
92
|
+
## GitHub
|
|
93
|
+
|
|
94
|
+
Repository: [github.com/gaaraujo/openseespy-solvers](https://github.com/gaaraujo/openseespy-solvers).
|
|
95
|
+
Report bugs and ask questions via
|
|
96
|
+
[issues](https://github.com/gaaraujo/openseespy-solvers/issues); send contributions
|
|
97
|
+
(pull requests) there as well.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
BSD 3-Clause — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
## Layout
|
|
4
|
+
|
|
5
|
+
| Location | Purpose |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| [`brick_bar.py`](brick_bar.py) | Benchmark-style static sweep vs native OpenSees solvers |
|
|
8
|
+
| [`brick_bar_eigen.py`](brick_bar_eigen.py) | Benchmark-style eigen sweep vs native OpenSees solvers |
|
|
9
|
+
| [`solvers/`](solvers/) | **One script per solver factory** (small mesh, fast smoke test) |
|
|
10
|
+
| [`solvers/_brick_common.py`](solvers/_brick_common.py) | Shared brick-bar model helpers (not run directly) |
|
|
11
|
+
|
|
12
|
+
We use **one script per factory** under `solvers/` so each backend stays easy to find,
|
|
13
|
+
copy, and run. The benchmark scripts stay separate for timing sweeps.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
Clone the repo and follow [Full setup](../docs/installation.md#full-setup) (environment →
|
|
18
|
+
install → [verify](../docs/installation.md#verify)). Then from `examples/`:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Benchmark comparisons (mesh sweeps)
|
|
22
|
+
python brick_bar.py
|
|
23
|
+
python brick_bar_eigen.py
|
|
24
|
+
|
|
25
|
+
# Finer meshes, more equations (much slower)
|
|
26
|
+
python brick_bar.py --large-test
|
|
27
|
+
python brick_bar_eigen.py --large-test
|
|
28
|
+
|
|
29
|
+
# Single-solver smoke tests
|
|
30
|
+
python solvers/scipy_spsolve.py
|
|
31
|
+
python solvers/scipy_eigsh.py
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Optional backends (UMFPACK, nvMath, CuPy): see the
|
|
35
|
+
[installation guide](../docs/installation.md).
|
|
36
|
+
|
|
37
|
+
## Solver catalog (`solvers/`)
|
|
38
|
+
|
|
39
|
+
Each script builds a small 3-D brick bar, runs one analysis, and prints **Passed!** /
|
|
40
|
+
**Failed!**. All use `solver.to_openseespy()` with `ops.system("PythonSparse", ...)`.
|
|
41
|
+
|
|
42
|
+
Eigen scripts and `brick_bar_eigen` use **two-tier verification**: compare PythonSparse to
|
|
43
|
+
OpenSees **`genBandArpack`** first; if results disagree, fall back to **`fullGenLapack`**
|
|
44
|
+
(dense tiebreaker). The benchmark sweep uses only recommended eigen solvers. Experimental
|
|
45
|
+
`lobpcg` scripts use a larger mesh, share settings in `_lobpcg_example.py`, and verify
|
|
46
|
+
against the matching backend's `eigsh` (not native OpenSees eigen solvers). They are
|
|
47
|
+
**manual-only** (not in pytest smoke tests).
|
|
48
|
+
Use the default mesh sweep for CI; add `--large-test` only when you want heavier runs.
|
|
49
|
+
|
|
50
|
+
### SciPy (`openseespy_solvers.scipy`)
|
|
51
|
+
|
|
52
|
+
| Script | Factory |
|
|
53
|
+
|--------|---------|
|
|
54
|
+
| [`scipy_spsolve.py`](solvers/scipy_spsolve.py) | `spsolve()` |
|
|
55
|
+
| [`scipy_umfpack.py`](solvers/scipy_umfpack.py) | `umfpack()` — needs `[umfpack]` |
|
|
56
|
+
| [`hybrid_spsolve.py`](solvers/hybrid_spsolve.py) | `hybrid(spsolve(), ...)` |
|
|
57
|
+
| [`scipy_cg.py`](solvers/scipy_cg.py) | `cg()` |
|
|
58
|
+
| [`scipy_cg_jacobi.py`](solvers/scipy_cg_jacobi.py) | `cg(M=precond.jacobi)` |
|
|
59
|
+
| [`scipy_gmres.py`](solvers/scipy_gmres.py) | `gmres()` |
|
|
60
|
+
| [`scipy_gmres_ilu.py`](solvers/scipy_gmres_ilu.py) | `gmres(M=precond.ilu)` |
|
|
61
|
+
| [`scipy_eigsh.py`](solvers/scipy_eigsh.py) | `eigsh()` |
|
|
62
|
+
| [`scipy_lobpcg.py`](solvers/scipy_lobpcg.py) | `lobpcg()` |
|
|
63
|
+
|
|
64
|
+
Preconditioner factories live in `openseespy_solvers.scipy.precond`: **`jacobi`**, **`ilu`**,
|
|
65
|
+
**`direct`** (used via `M=` on `cg` / `gmres` / `lobpcg`, as in the scripts above).
|
|
66
|
+
|
|
67
|
+
### CuPy (`openseespy_solvers.cupy`)
|
|
68
|
+
|
|
69
|
+
| Script | Factory |
|
|
70
|
+
|--------|---------|
|
|
71
|
+
| [`cupy_spsolve.py`](solvers/cupy_spsolve.py) | `spsolve()` |
|
|
72
|
+
| [`cupy_cg.py`](solvers/cupy_cg.py) | `cg()` |
|
|
73
|
+
| [`cupy_cg_jacobi.py`](solvers/cupy_cg_jacobi.py) | `cg(M=precond.jacobi)` |
|
|
74
|
+
| [`cupy_gmres.py`](solvers/cupy_gmres.py) | `gmres()` |
|
|
75
|
+
| [`cupy_gmres_ilu.py`](solvers/cupy_gmres_ilu.py) | `gmres(M=precond.ilu)` |
|
|
76
|
+
| [`cupy_eigsh.py`](solvers/cupy_eigsh.py) | `eigsh` (default `mass_mode="general"`) |
|
|
77
|
+
| [`cupy_lobpcg.py`](solvers/cupy_lobpcg.py) | `lobpcg()` |
|
|
78
|
+
|
|
79
|
+
`openseespy_solvers.cupy.precond` exports **`jacobi`**, **`ilu`**, and **`direct`**.
|
|
80
|
+
[`eigsh`](solvers/cupy_eigsh.py) uses shift-invert (`diagonal` / `lumped` on GPU;
|
|
81
|
+
`general` with full mass).
|
|
82
|
+
|
|
83
|
+
### nvMath (`openseespy_solvers.nvmath`) — GPU
|
|
84
|
+
|
|
85
|
+
| Script | Factory |
|
|
86
|
+
|--------|---------|
|
|
87
|
+
| [`nvmath_direct_solver.py`](solvers/nvmath_direct_solver.py) | `direct_solver()` — needs `cupy-cuda12x`/`cupy-cuda13x` and matching `nvmath-python[cu12]`/`[cu13]` |
|
|
88
|
+
|
|
89
|
+
## Benchmark scripts
|
|
90
|
+
|
|
91
|
+
Change the mesh sweep in one line (bigger number = finer mesh):
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
MESH_FACTORS = [1.5, 2.0, 2.5, 3.0]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Benchmarks compare [recommended solvers](../docs/recommended-solvers.md) to native
|
|
98
|
+
OpenSees backends. **Static** (`brick_bar.py`): `scipy.spsolve`, optional
|
|
99
|
+
`scipy.umfpack`, optional `nvmath.direct_solver` (GPU), vs `BandGeneral`,
|
|
100
|
+
`SuperLU`, `UmfPack`. **Eigen** (`brick_bar_eigen.py`): `scipy.eigsh`, optional
|
|
101
|
+
`cupy.eigsh`, vs `genBandArpack` (`fullGenLapack` tiebreaker on mismatch). Other
|
|
102
|
+
factories remain in `solvers/` as smoke tests only.
|
|
103
|
+
|
|
104
|
+
String-based solver loop (no lambdas):
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from openseespy_solvers.scipy import spsolve
|
|
108
|
+
|
|
109
|
+
solver = spsolve()
|
|
110
|
+
NATIVE_SOLVERS = brick.NATIVE_STATIC_SOLVERS # BandGeneral, SuperLU, UmfPack
|
|
111
|
+
|
|
112
|
+
for factor in MESH_FACTORS:
|
|
113
|
+
nx, ny, nz = mesh_counts(factor)
|
|
114
|
+
for name in solvers:
|
|
115
|
+
build_model(nx, ny, nz)
|
|
116
|
+
apply_load()
|
|
117
|
+
if name == "PythonSparse":
|
|
118
|
+
ops.system("PythonSparse", solver.to_openseespy())
|
|
119
|
+
else:
|
|
120
|
+
ops.system(name)
|
|
121
|
+
# numberer / constraints / integrator / test / algorithm / analysis ...
|
|
122
|
+
status = ops.analyze(NUM_STEPS)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## See also
|
|
126
|
+
|
|
127
|
+
- OpenSees [`benchmark_python_sparse.py`](https://github.com/OpenSees/OpenSees/blob/master/EXAMPLES/SolverBenchmark/benchmark_python_sparse.py)
|
|
128
|
+
- OpenSees [`benchmark_python_sparse_eigen.py`](https://github.com/OpenSees/OpenSees/blob/master/EXAMPLES/SolverBenchmark/benchmark_python_sparse_eigen.py)
|
|
129
|
+
- Docs: [Read the Docs](https://openseespy-solvers.readthedocs.io/en/latest/examples/)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openseespy-solvers"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Ready-to-use, SciPy-style PythonSparse solvers for OpenSeesPy"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "openseespy-solvers contributors" }]
|
|
13
|
+
keywords = ["opensees", "openseespy", "solver", "scipy", "sparse", "eigen", "finite-element"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: BSD License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Scientific/Engineering",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
# SciPy 1.12+ exposes rtol/atol on sparse iterative solvers (used by cg/gmres).
|
|
26
|
+
"numpy>=1.26",
|
|
27
|
+
"scipy>=1.12",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
opensees = ["openseespy"]
|
|
32
|
+
# One extra per optional backend (matches openseespy_solvers.cupy, …).
|
|
33
|
+
# Install the CuPy wheel matching your CUDA toolkit, e.g. cupy-cuda12x (see docs).
|
|
34
|
+
# nvMath: GPU-only — install nvmath-python[cu12] or [cu13] manually (see docs).
|
|
35
|
+
cupy = ["cupy>=13.0"]
|
|
36
|
+
# 64-bit UMFPACK direct solver (CPU) for openseespy_solvers.scipy.umfpack.
|
|
37
|
+
# On Windows, prefer: conda install -c conda-forge scikit-umfpack (PyPI is sdist-only).
|
|
38
|
+
umfpack = ["scikit-umfpack>=0.3.3"]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=8",
|
|
41
|
+
"ruff>=0.4",
|
|
42
|
+
"build",
|
|
43
|
+
]
|
|
44
|
+
docs = [
|
|
45
|
+
"mkdocs-material>=9.5",
|
|
46
|
+
"mkdocstrings[python]>=0.24",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.urls]
|
|
50
|
+
Homepage = "https://github.com/gaaraujo/openseespy-solvers"
|
|
51
|
+
Documentation = "https://openseespy-solvers.readthedocs.io/"
|
|
52
|
+
Issues = "https://github.com/gaaraujo/openseespy-solvers/issues"
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel]
|
|
55
|
+
packages = ["src/openseespy_solvers"]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.sdist]
|
|
58
|
+
include = ["src/openseespy_solvers", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
line-length = 100
|
|
62
|
+
src = ["src", "tests"]
|
|
63
|
+
target-version = "py312"
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint]
|
|
66
|
+
select = ["E", "F", "I", "N", "UP", "B"]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint.per-file-ignores]
|
|
69
|
+
"src/openseespy_solvers/_base.py" = ["N802", "N803", "N806"]
|
|
70
|
+
"src/openseespy_solvers/scipy/_base.py" = ["N802", "N803", "N806"]
|
|
71
|
+
"src/openseespy_solvers/cupy/_base.py" = ["N802", "N803", "N806"]
|
|
72
|
+
"src/openseespy_solvers/nvmath/_base.py" = ["N802", "N803", "N806"]
|
|
73
|
+
"src/openseespy_solvers/nvmath/__init__.py" = ["F401", "N802", "N803", "N806"]
|
|
74
|
+
"src/openseespy_solvers/scipy/precond.py" = ["N803"]
|
|
75
|
+
"src/openseespy_solvers/scipy/__init__.py" = ["F401", "N802", "N803", "N806"]
|
|
76
|
+
"src/openseespy_solvers/cupy/__init__.py" = ["F401", "N802", "N803", "N806"]
|
|
77
|
+
"src/openseespy_solvers/__init__.py" = ["F401"]
|
|
78
|
+
"tests/**" = ["N803", "N806", "I001"]
|
|
79
|
+
"examples/**" = ["I001", "E402"]
|
|
80
|
+
"src/openseespy_solvers/_hybrid.py" = ["N803", "N806"]
|
|
81
|
+
"src/openseespy_solvers/cupy/precond.py" = ["N803", "N806"]
|
|
82
|
+
|
|
83
|
+
[tool.pytest.ini_options]
|
|
84
|
+
testpaths = ["tests"]
|
|
85
|
+
addopts = "-q"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Ready-to-use PythonSparse solvers for OpenSeesPy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from openseespy_solvers.exceptions import (
|
|
9
|
+
BackendNotAvailableError,
|
|
10
|
+
InvalidOpenSeesDataError,
|
|
11
|
+
OpenSeesSolverError,
|
|
12
|
+
SolverConvergenceError,
|
|
13
|
+
UnsupportedComputeDtypeError,
|
|
14
|
+
UnsupportedStorageSchemeError,
|
|
15
|
+
)
|
|
16
|
+
from openseespy_solvers.hybrid import hybrid
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"BackendNotAvailableError",
|
|
22
|
+
"InvalidOpenSeesDataError",
|
|
23
|
+
"OpenSeesSolverError",
|
|
24
|
+
"SolverConvergenceError",
|
|
25
|
+
"UnsupportedComputeDtypeError",
|
|
26
|
+
"UnsupportedStorageSchemeError",
|
|
27
|
+
"__version__",
|
|
28
|
+
"cupy",
|
|
29
|
+
"hybrid",
|
|
30
|
+
"scipy",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from openseespy_solvers import cupy as cupy
|
|
35
|
+
from openseespy_solvers import scipy as scipy
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def __getattr__(name: str):
|
|
39
|
+
if name in {"scipy", "cupy"}:
|
|
40
|
+
return import_module(f"openseespy_solvers.{name}")
|
|
41
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|