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.
Files changed (25) hide show
  1. openseespy_solvers-0.1.0/.gitignore +13 -0
  2. openseespy_solvers-0.1.0/CHANGELOG.md +43 -0
  3. openseespy_solvers-0.1.0/LICENSE +29 -0
  4. openseespy_solvers-0.1.0/PKG-INFO +166 -0
  5. openseespy_solvers-0.1.0/README.md +101 -0
  6. openseespy_solvers-0.1.0/examples/README.md +129 -0
  7. openseespy_solvers-0.1.0/pyproject.toml +85 -0
  8. openseespy_solvers-0.1.0/src/openseespy_solvers/__init__.py +41 -0
  9. openseespy_solvers-0.1.0/src/openseespy_solvers/_base.py +428 -0
  10. openseespy_solvers-0.1.0/src/openseespy_solvers/_docstrings.py +64 -0
  11. openseespy_solvers-0.1.0/src/openseespy_solvers/_dtype.py +45 -0
  12. openseespy_solvers-0.1.0/src/openseespy_solvers/_factorization.py +86 -0
  13. openseespy_solvers-0.1.0/src/openseespy_solvers/_hybrid.py +319 -0
  14. openseespy_solvers-0.1.0/src/openseespy_solvers/_sparse.py +134 -0
  15. openseespy_solvers-0.1.0/src/openseespy_solvers/cupy/__init__.py +845 -0
  16. openseespy_solvers-0.1.0/src/openseespy_solvers/cupy/_base.py +99 -0
  17. openseespy_solvers-0.1.0/src/openseespy_solvers/cupy/precond.py +208 -0
  18. openseespy_solvers-0.1.0/src/openseespy_solvers/exceptions.py +34 -0
  19. openseespy_solvers-0.1.0/src/openseespy_solvers/hybrid.py +5 -0
  20. openseespy_solvers-0.1.0/src/openseespy_solvers/nvmath/__init__.py +235 -0
  21. openseespy_solvers-0.1.0/src/openseespy_solvers/nvmath/_base.py +250 -0
  22. openseespy_solvers-0.1.0/src/openseespy_solvers/scipy/__init__.py +712 -0
  23. openseespy_solvers-0.1.0/src/openseespy_solvers/scipy/_base.py +78 -0
  24. openseespy_solvers-0.1.0/src/openseespy_solvers/scipy/precond.py +145 -0
  25. openseespy_solvers-0.1.0/src/openseespy_solvers/stats.py +32 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .mypy_cache/
10
+ .venv/
11
+ venv/
12
+ site/
13
+ *.csv
@@ -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}")