PyNiteFEA 3.0.0__tar.gz → 3.2.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.
- {pynitefea-3.0.0 → pynitefea-3.2.0}/PKG-INFO +26 -29
- {pynitefea-3.0.0 → pynitefea-3.2.0}/PyNiteFEA.egg-info/PKG-INFO +26 -29
- {pynitefea-3.0.0 → pynitefea-3.2.0}/PyNiteFEA.egg-info/SOURCES.txt +1 -1
- {pynitefea-3.0.0 → pynitefea-3.2.0}/PyNiteFEA.egg-info/requires.txt +11 -13
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Analysis.py +206 -32
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/FEModel3D.py +30 -49
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Member3D.py +92 -47
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/PhysMember.py +19 -1
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Rendering.py +6 -13
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Report_Template.html +86 -86
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Reporting.py +53 -24
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Section.py +0 -1
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/ShearWall.py +196 -64
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Visualization.py +29 -42
- {pynitefea-3.0.0 → pynitefea-3.2.0}/README.md +10 -0
- pynitefea-3.2.0/pyproject.toml +70 -0
- pynitefea-3.0.0/setup.py +0 -40
- {pynitefea-3.0.0 → pynitefea-3.2.0}/LICENSE +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/PyNiteFEA.egg-info/dependency_links.txt +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/PyNiteFEA.egg-info/top_level.txt +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/BeamSegY.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/BeamSegZ.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/FixedEndReactions.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/LoadCombo.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/MainStyleSheet.css +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/MatFoundation.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Material.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Mesh.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Node3D.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Plate3D.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Quad3D.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Spring3D.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/Tri3D.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/VTKWriter.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/Pynite/__init__.py +0 -0
- {pynitefea-3.0.0 → pynitefea-3.2.0}/setup.cfg +0 -0
|
@@ -1,55 +1,42 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyNiteFEA
|
|
3
|
-
Version: 3.
|
|
4
|
-
Summary: A simple
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: A simple 3D structural finite element library for Python.
|
|
5
|
+
Author-email: "D. Craig Brinck, PE, SE" <Building.Code@outlook.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JWock82/Pynite
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
12
|
Classifier: Operating System :: OS Independent
|
|
14
13
|
Requires-Python: >=3.11
|
|
15
14
|
Description-Content-Type: text/markdown
|
|
16
15
|
License-File: LICENSE
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
17
|
Requires-Dist: numpy>=2.4.0
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: prettytable
|
|
19
19
|
Requires-Dist: scipy
|
|
20
|
-
Requires-Dist: matplotlib
|
|
21
20
|
Provides-Extra: all
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist: vtk>=9.3.0; extra == "all"
|
|
24
|
-
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "all"
|
|
25
|
-
Requires-Dist: trame_jupyter_extension; extra == "all"
|
|
21
|
+
Requires-Dist: ipython; extra == "all"
|
|
26
22
|
Requires-Dist: ipywidgets; extra == "all"
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: jinja2; extra == "all"
|
|
24
|
+
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "all"
|
|
25
|
+
Requires-Dist: trame-jupyter-extension; extra == "all"
|
|
26
|
+
Requires-Dist: vtk>=9.3.0; extra == "all"
|
|
29
27
|
Provides-Extra: vtk
|
|
30
|
-
Requires-Dist:
|
|
28
|
+
Requires-Dist: ipython; extra == "vtk"
|
|
31
29
|
Requires-Dist: vtk>=9.3.0; extra == "vtk"
|
|
32
30
|
Provides-Extra: pyvista
|
|
33
|
-
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "pyvista"
|
|
34
|
-
Requires-Dist: trame_jupyter_extension; extra == "pyvista"
|
|
35
31
|
Requires-Dist: ipywidgets; extra == "pyvista"
|
|
32
|
+
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "pyvista"
|
|
33
|
+
Requires-Dist: trame-jupyter-extension; extra == "pyvista"
|
|
36
34
|
Provides-Extra: reporting
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-Dist: Jinja2; extra == "reporting"
|
|
35
|
+
Requires-Dist: jinja2; extra == "reporting"
|
|
39
36
|
Provides-Extra: derivations
|
|
40
37
|
Requires-Dist: jupyterlab; extra == "derivations"
|
|
41
38
|
Requires-Dist: sympy; extra == "derivations"
|
|
42
|
-
Dynamic: author
|
|
43
|
-
Dynamic: author-email
|
|
44
|
-
Dynamic: classifier
|
|
45
|
-
Dynamic: description
|
|
46
|
-
Dynamic: description-content-type
|
|
47
|
-
Dynamic: home-page
|
|
48
39
|
Dynamic: license-file
|
|
49
|
-
Dynamic: provides-extra
|
|
50
|
-
Dynamic: requires-dist
|
|
51
|
-
Dynamic: requires-python
|
|
52
|
-
Dynamic: summary
|
|
53
40
|
|
|
54
41
|
<div align="center">
|
|
55
42
|
<img src="https://github.com/JWock82/Pynite/raw/main/Resources/Full Logo No Buffer.png" width=40% align="center"/>
|
|
@@ -122,6 +109,16 @@ Here's a list of projects that use Pynite:
|
|
|
122
109
|
* Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)
|
|
123
110
|
|
|
124
111
|
# What's New?
|
|
112
|
+
3.2.0
|
|
113
|
+
* Added shear wall pier top results and coupling beam right hand side results to the pier/coupling beam force results output.
|
|
114
|
+
* Adjusted shear wall pier and coupling beam internal force results sign convention to match the sign convention used for members.
|
|
115
|
+
* Added targeted testing for shear wall pier and coupling beam sign convention and load magnitudes.
|
|
116
|
+
* Added shear wall docstrings and a readthedocs documentation page.
|
|
117
|
+
|
|
118
|
+
3.1.0
|
|
119
|
+
* Improved detection of unstable structures.
|
|
120
|
+
* Bug fix for shear walls. Shear walls were erroneously tracking other plates in the model for internal pier/coupling beam force summations, rather than just the plates related to the individual shear wall.
|
|
121
|
+
|
|
125
122
|
3.0.0
|
|
126
123
|
* Added pushover analysis! Currently, only steel sections are supported for pushover analysis. The assumed interaction equation used to estimate plastic behavior is currently calibrated toward I-shaped members.
|
|
127
124
|
* Refactored all elastic stiffness matrix references from `K` and `k` to `Ke` and `ke`. Most users will not notice this change. Power users who were accessing the stiffness matrices directly will want to note this refactor. This clarifies the code base by indicating which type of stiffness these matrices represent, and reduces ambiguity in preparation for addition of further geometric `Kg/kg` and plastic `Km/km` stiffness matrices.
|
|
@@ -1,55 +1,42 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyNiteFEA
|
|
3
|
-
Version: 3.
|
|
4
|
-
Summary: A simple
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: A simple 3D structural finite element library for Python.
|
|
5
|
+
Author-email: "D. Craig Brinck, PE, SE" <Building.Code@outlook.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JWock82/Pynite
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
12
|
Classifier: Operating System :: OS Independent
|
|
14
13
|
Requires-Python: >=3.11
|
|
15
14
|
Description-Content-Type: text/markdown
|
|
16
15
|
License-File: LICENSE
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
17
|
Requires-Dist: numpy>=2.4.0
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: prettytable
|
|
19
19
|
Requires-Dist: scipy
|
|
20
|
-
Requires-Dist: matplotlib
|
|
21
20
|
Provides-Extra: all
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist: vtk>=9.3.0; extra == "all"
|
|
24
|
-
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "all"
|
|
25
|
-
Requires-Dist: trame_jupyter_extension; extra == "all"
|
|
21
|
+
Requires-Dist: ipython; extra == "all"
|
|
26
22
|
Requires-Dist: ipywidgets; extra == "all"
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: jinja2; extra == "all"
|
|
24
|
+
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "all"
|
|
25
|
+
Requires-Dist: trame-jupyter-extension; extra == "all"
|
|
26
|
+
Requires-Dist: vtk>=9.3.0; extra == "all"
|
|
29
27
|
Provides-Extra: vtk
|
|
30
|
-
Requires-Dist:
|
|
28
|
+
Requires-Dist: ipython; extra == "vtk"
|
|
31
29
|
Requires-Dist: vtk>=9.3.0; extra == "vtk"
|
|
32
30
|
Provides-Extra: pyvista
|
|
33
|
-
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "pyvista"
|
|
34
|
-
Requires-Dist: trame_jupyter_extension; extra == "pyvista"
|
|
35
31
|
Requires-Dist: ipywidgets; extra == "pyvista"
|
|
32
|
+
Requires-Dist: pyvista[all,trame]>=0.43.4; extra == "pyvista"
|
|
33
|
+
Requires-Dist: trame-jupyter-extension; extra == "pyvista"
|
|
36
34
|
Provides-Extra: reporting
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-Dist: Jinja2; extra == "reporting"
|
|
35
|
+
Requires-Dist: jinja2; extra == "reporting"
|
|
39
36
|
Provides-Extra: derivations
|
|
40
37
|
Requires-Dist: jupyterlab; extra == "derivations"
|
|
41
38
|
Requires-Dist: sympy; extra == "derivations"
|
|
42
|
-
Dynamic: author
|
|
43
|
-
Dynamic: author-email
|
|
44
|
-
Dynamic: classifier
|
|
45
|
-
Dynamic: description
|
|
46
|
-
Dynamic: description-content-type
|
|
47
|
-
Dynamic: home-page
|
|
48
39
|
Dynamic: license-file
|
|
49
|
-
Dynamic: provides-extra
|
|
50
|
-
Dynamic: requires-dist
|
|
51
|
-
Dynamic: requires-python
|
|
52
|
-
Dynamic: summary
|
|
53
40
|
|
|
54
41
|
<div align="center">
|
|
55
42
|
<img src="https://github.com/JWock82/Pynite/raw/main/Resources/Full Logo No Buffer.png" width=40% align="center"/>
|
|
@@ -122,6 +109,16 @@ Here's a list of projects that use Pynite:
|
|
|
122
109
|
* Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)
|
|
123
110
|
|
|
124
111
|
# What's New?
|
|
112
|
+
3.2.0
|
|
113
|
+
* Added shear wall pier top results and coupling beam right hand side results to the pier/coupling beam force results output.
|
|
114
|
+
* Adjusted shear wall pier and coupling beam internal force results sign convention to match the sign convention used for members.
|
|
115
|
+
* Added targeted testing for shear wall pier and coupling beam sign convention and load magnitudes.
|
|
116
|
+
* Added shear wall docstrings and a readthedocs documentation page.
|
|
117
|
+
|
|
118
|
+
3.1.0
|
|
119
|
+
* Improved detection of unstable structures.
|
|
120
|
+
* Bug fix for shear walls. Shear walls were erroneously tracking other plates in the model for internal pier/coupling beam force summations, rather than just the plates related to the individual shear wall.
|
|
121
|
+
|
|
125
122
|
3.0.0
|
|
126
123
|
* Added pushover analysis! Currently, only steel sections are supported for pushover analysis. The assumed interaction equation used to estimate plastic behavior is currently calibrated toward I-shaped members.
|
|
127
124
|
* Refactored all elastic stiffness matrix references from `K` and `k` to `Ke` and `ke`. Most users will not notice this change. Power users who were accessing the stiffness matrices directly will want to note this refactor. This clarifies the code base by indicating which type of stiffness these matrices represent, and reduces ambiguity in preparation for addition of further geometric `Kg/kg` and plastic `Km/km` stiffness matrices.
|
|
@@ -1,30 +1,28 @@
|
|
|
1
|
+
matplotlib
|
|
1
2
|
numpy>=2.4.0
|
|
2
|
-
|
|
3
|
+
prettytable
|
|
3
4
|
scipy
|
|
4
|
-
matplotlib
|
|
5
5
|
|
|
6
6
|
[all]
|
|
7
|
-
|
|
8
|
-
vtk>=9.3.0
|
|
9
|
-
pyvista[all,trame]>=0.43.4
|
|
10
|
-
trame_jupyter_extension
|
|
7
|
+
ipython
|
|
11
8
|
ipywidgets
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
jinja2
|
|
10
|
+
pyvista[all,trame]>=0.43.4
|
|
11
|
+
trame-jupyter-extension
|
|
12
|
+
vtk>=9.3.0
|
|
14
13
|
|
|
15
14
|
[derivations]
|
|
16
15
|
jupyterlab
|
|
17
16
|
sympy
|
|
18
17
|
|
|
19
18
|
[pyvista]
|
|
20
|
-
pyvista[all,trame]>=0.43.4
|
|
21
|
-
trame_jupyter_extension
|
|
22
19
|
ipywidgets
|
|
20
|
+
pyvista[all,trame]>=0.43.4
|
|
21
|
+
trame-jupyter-extension
|
|
23
22
|
|
|
24
23
|
[reporting]
|
|
25
|
-
|
|
26
|
-
Jinja2
|
|
24
|
+
jinja2
|
|
27
25
|
|
|
28
26
|
[vtk]
|
|
29
|
-
|
|
27
|
+
ipython
|
|
30
28
|
vtk>=9.3.0
|
|
@@ -2,8 +2,10 @@ from __future__ import annotations # Allows more recent type hints features
|
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
from math import isclose
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
from numpy import array, atleast_2d, zeros, subtract, matmul, divide, seterr, nanmax, asarray, isfinite
|
|
8
|
+
from numpy.linalg import solve, norm, LinAlgError
|
|
7
9
|
|
|
8
10
|
from Pynite.LoadCombo import LoadCombo
|
|
9
11
|
|
|
@@ -166,6 +168,79 @@ def _check_stability(model: FEModel3D, K: NDArray[float64]) -> None:
|
|
|
166
168
|
return
|
|
167
169
|
|
|
168
170
|
|
|
171
|
+
# Exception message shared by the global-instability checks below
|
|
172
|
+
_SINGULAR_MSG = ('The stiffness matrix is singular, which implies rigid body motion. The structure '
|
|
173
|
+
'is unstable. Aborting analysis.')
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _solve_unknown_disp(K11, rhs, sparse: bool = True, check_stability: bool = True, tol: float = 1e-6):
|
|
177
|
+
"""Solves ``K11 @ x = rhs`` for the unknown displacements, raising on a singular stiffness matrix.
|
|
178
|
+
|
|
179
|
+
The nodal stability check (:func:`_check_stability`) only catches degrees of freedom with a zero
|
|
180
|
+
stiffness on the diagonal. It does not catch *global* instability, where every diagonal term is
|
|
181
|
+
non-zero but the assembled matrix is still rank-deficient -- for example a mechanism created by
|
|
182
|
+
member end releases (a beam with an internal hinge and no support there), or a structure with
|
|
183
|
+
insufficient supports (rigid body motion). In those cases the underlying solvers do not reliably
|
|
184
|
+
report the problem: ``scipy.sparse.linalg.spsolve`` may return ``NaN`` (with only a
|
|
185
|
+
``MatrixRankWarning``) or even finite-but-meaningless values, and ``numpy.linalg.solve`` may miss
|
|
186
|
+
the singularity due to floating point round-off. The model then silently reports erroneous
|
|
187
|
+
results.
|
|
188
|
+
|
|
189
|
+
When ``check_stability`` is ``True`` the computed displacements are verified against equilibrium
|
|
190
|
+
by measuring the relative residual ``||K11 @ x - rhs|| / ||rhs||``. A stable structure -- even a
|
|
191
|
+
numerically ill-conditioned one -- solves to within round-off (the direct solvers are backward
|
|
192
|
+
stable), whereas a singular system yields ``NaN``/``inf`` or finite values with an O(1) residual.
|
|
193
|
+
For the homogeneous ``rhs == 0`` edge case, an invertible system has the unique solution
|
|
194
|
+
``x == 0``; a non-zero solved displacement is therefore treated as unstable. These checks are
|
|
195
|
+
robust indicators of global instability that, unlike inspecting the factorization's pivot
|
|
196
|
+
magnitudes, do not misclassify legitimately ill-conditioned but stable models. When
|
|
197
|
+
``check_stability`` is ``False`` the bare solvers are used unchanged.
|
|
198
|
+
|
|
199
|
+
:param K11: The partitioned stiffness matrix for the unknown displacements (square, ``n``x``n``).
|
|
200
|
+
:param rhs: The right-hand side vector (shape ``(n, 1)``).
|
|
201
|
+
:param sparse: Whether ``K11`` is a SciPy sparse matrix. Defaults to ``True``.
|
|
202
|
+
:param check_stability: Whether to check for global instability after solving. Defaults to ``True``.
|
|
203
|
+
:param tol: Relative residual above which the matrix is treated as singular. Defaults to ``1e-6``.
|
|
204
|
+
:raises Exception: If the stiffness matrix is singular (the structure is unstable).
|
|
205
|
+
:return: The solved displacement vector, shape ``(n, 1)``.
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
singular_warning = False
|
|
209
|
+
if sparse:
|
|
210
|
+
from scipy.sparse.linalg import MatrixRankWarning, spsolve
|
|
211
|
+
|
|
212
|
+
K = K11.tocsr()
|
|
213
|
+
# A singular matrix makes `spsolve` emit a `MatrixRankWarning`; we detect that case below via
|
|
214
|
+
# the residual/finiteness check and raise a descriptive error, so suppress the bare warning.
|
|
215
|
+
with warnings.catch_warnings(record=True) as caught_warnings:
|
|
216
|
+
warnings.simplefilter('always', MatrixRankWarning)
|
|
217
|
+
x = spsolve(K, rhs)
|
|
218
|
+
singular_warning = any(warning.category is MatrixRankWarning for warning in caught_warnings)
|
|
219
|
+
else:
|
|
220
|
+
K = asarray(K11, dtype=float)
|
|
221
|
+
try:
|
|
222
|
+
x = solve(K, rhs)
|
|
223
|
+
except LinAlgError as exc:
|
|
224
|
+
raise Exception(_SINGULAR_MSG) from exc
|
|
225
|
+
|
|
226
|
+
x = asarray(x, dtype=float).reshape(len(x), 1)
|
|
227
|
+
|
|
228
|
+
if check_stability:
|
|
229
|
+
residual = norm(K @ x - rhs)
|
|
230
|
+
rhs_norm = norm(rhs)
|
|
231
|
+
if rhs_norm > 0:
|
|
232
|
+
unstable = residual > tol * rhs_norm
|
|
233
|
+
else:
|
|
234
|
+
# For a homogeneous system, an invertible matrix has the unique solution x == 0, so an
|
|
235
|
+
# absolute displacement tolerance is appropriate here.
|
|
236
|
+
unstable = norm(x) > tol
|
|
237
|
+
|
|
238
|
+
if (sparse and singular_warning) or not isfinite(x).all() or unstable:
|
|
239
|
+
raise Exception(_SINGULAR_MSG)
|
|
240
|
+
|
|
241
|
+
return x
|
|
242
|
+
|
|
243
|
+
|
|
169
244
|
def _first_order(model: FEModel3D, combo_name: str, P1: NDArray[float64], FER1: NDArray[float64], D1_indices: List[int], D2_indices: List[int], D2: NDArray[float64], log: bool = True, sparse: bool = True, check_stability: bool = False, max_iter: int = 30, spring_tolerance: float = 0, member_tolerance: float = 0, num_steps: int = 1) -> None:
|
|
170
245
|
"""Performs the shared first-order elastic/tension-compression-only solution path.
|
|
171
246
|
|
|
@@ -173,10 +248,6 @@ def _first_order(model: FEModel3D, combo_name: str, P1: NDArray[float64], FER1:
|
|
|
173
248
|
first-order workflow lives in one place.
|
|
174
249
|
"""
|
|
175
250
|
|
|
176
|
-
# Import `scipy` features if the sparse solver is being used
|
|
177
|
-
if sparse == True:
|
|
178
|
-
from scipy.sparse.linalg import spsolve
|
|
179
|
-
|
|
180
251
|
# Calculate the incremental enforced displacement vector
|
|
181
252
|
Delta_D2 = D2/num_steps
|
|
182
253
|
|
|
@@ -217,19 +288,15 @@ def _first_order(model: FEModel3D, combo_name: str, P1: NDArray[float64], FER1:
|
|
|
217
288
|
# All displacements are known, so Delta_D1 is an empty vector
|
|
218
289
|
Delta_D1 = []
|
|
219
290
|
else:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
Delta_D1 = solve(K11, subtract(subtract(Delta_P1, Delta_FER1), matmul(K12, Delta_D2)))
|
|
230
|
-
except:
|
|
231
|
-
# Return out of the method if 'K' is singular and provide an error message
|
|
232
|
-
raise Exception('The stiffness matrix is singular, which implies rigid body motion. The structure is unstable. Aborting analysis.')
|
|
291
|
+
# Calculate the unknown displacements Delta_D1. The partitioned stiffness matrix
|
|
292
|
+
# originates as `coo` and is converted to `csr`/`csc` for mathematical operations.
|
|
293
|
+
# `_solve_unknown_disp` also detects global instability (a singular matrix) that
|
|
294
|
+
# the bare solvers can silently miss.
|
|
295
|
+
if sparse == True:
|
|
296
|
+
rhs = subtract(subtract(Delta_P1, Delta_FER1), K12 @ Delta_D2)
|
|
297
|
+
else:
|
|
298
|
+
rhs = subtract(subtract(Delta_P1, Delta_FER1), matmul(K12, Delta_D2))
|
|
299
|
+
Delta_D1 = _solve_unknown_disp(K11, rhs, sparse, check_stability)
|
|
233
300
|
|
|
234
301
|
# Store or sum the calculated displacements to the model and the nodes in the model
|
|
235
302
|
if load_step == 1:
|
|
@@ -603,19 +670,12 @@ def _pushover_step(model: FEModel3D, combo_name: str, push_combo: str, step_num:
|
|
|
603
670
|
|
|
604
671
|
# Calculate change in the local end force vector for this load step
|
|
605
672
|
Delta_f = sub_member.f(combo_name, Delta_d, Delta_fer)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
sub_member.
|
|
612
|
-
sub_member._mzi[combo_name] += Delta_f[5, 0]
|
|
613
|
-
sub_member._fxj[combo_name] += Delta_f[6, 0]
|
|
614
|
-
sub_member._fyj[combo_name] += Delta_f[7, 0]
|
|
615
|
-
sub_member._fzj[combo_name] += Delta_f[8, 0]
|
|
616
|
-
sub_member._mxj[combo_name] += Delta_f[9, 0]
|
|
617
|
-
sub_member._myj[combo_name] += Delta_f[10, 0]
|
|
618
|
-
sub_member._mzj[combo_name] += Delta_f[11, 0]
|
|
673
|
+
|
|
674
|
+
# TODO: Before accepting `Delta_f`, check if we've overshot any member section
|
|
675
|
+
# capacity interaction curves, and adjust the load step if necessary
|
|
676
|
+
|
|
677
|
+
# Store the change in the local end force vector for this load step
|
|
678
|
+
sub_member.f_nonlin[combo_name] += Delta_f.reshape(12)
|
|
619
679
|
|
|
620
680
|
else:
|
|
621
681
|
|
|
@@ -633,6 +693,120 @@ def _pushover_step(model: FEModel3D, combo_name: str, push_combo: str, step_num:
|
|
|
633
693
|
print('- Restarting load step due to plastic load reversal')
|
|
634
694
|
|
|
635
695
|
|
|
696
|
+
def _load_step_ratio(model: FEModel3D, combo_name: str, Delta_f: NDArray[float64]) -> float:
|
|
697
|
+
"""
|
|
698
|
+
Determines the fraction of the pushover load step that should be applied.
|
|
699
|
+
|
|
700
|
+
It is desirable to ensure plastic hinges occur at the end of a load step. This method checks
|
|
701
|
+
if the current load step has caused any plastic hinges to form. If so, it calculates the
|
|
702
|
+
fraction of the load step that should be applied to ensure the hinge forms at the end of the
|
|
703
|
+
load step.
|
|
704
|
+
|
|
705
|
+
See Section 12.6 of `Matrix Structural Analysis, Second Edition` for more information.
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
# Define a local helper method for this method
|
|
709
|
+
def tal(p, my, mz, dp, dmy, dmz, Phi):
|
|
710
|
+
|
|
711
|
+
# Initial guesses
|
|
712
|
+
tal_l = 0.0 # Lower bound guess
|
|
713
|
+
tal_u = 1.0 # Upper bound guess
|
|
714
|
+
tal_r = tal_u # Assumed initial value of the root we are seeking
|
|
715
|
+
|
|
716
|
+
# Counter variable
|
|
717
|
+
num_iter = 0
|
|
718
|
+
|
|
719
|
+
while (not isclose(Phi(p + tal_r*dp, my + tal_r*dmy, mz + tal_r*dmz) - 1.0, 0.0)
|
|
720
|
+
and num_iter < 100):
|
|
721
|
+
|
|
722
|
+
# Increment the iteration counter
|
|
723
|
+
num_iter += 1
|
|
724
|
+
|
|
725
|
+
# Calculate `Phi` (the utilization ratio) for the lower and upper guesses
|
|
726
|
+
Phi_tal_l = Phi(p + tal_l*dp, my + tal_l*dmy, mz + tal_l*dmz)
|
|
727
|
+
Phi_tal_u = Phi(p + tal_u*dp, my + tal_u*dmy, mz + tal_u*dmz)
|
|
728
|
+
|
|
729
|
+
# Estimate the root and its `Phi` value
|
|
730
|
+
tal_r = 1 - (Phi_tal_u - 1.0)*(tal_l - tal_u)/(Phi_tal_l - Phi_tal_u)
|
|
731
|
+
Phi_tal_r = Phi(p + tal_r*dp, my + tal_r*dmy, mz + tal_r*dmz)
|
|
732
|
+
|
|
733
|
+
# Replace the lower or upper guess with the new estimate depending on which (Phi - 1)
|
|
734
|
+
# one has the same sign as (Phi - 1) for the new root estimate.
|
|
735
|
+
if (Phi_tal_l - 1.0)*(Phi_tal_r - 1.0) < 0:
|
|
736
|
+
tal_u = tal_r
|
|
737
|
+
elif (Phi_tal_u - 1.0)*(Phi_tal_r - 1.0) < 0:
|
|
738
|
+
tal_l = tal_r
|
|
739
|
+
|
|
740
|
+
# Cap the number of iterations to avoid infinite loops
|
|
741
|
+
if num_iter >= 100:
|
|
742
|
+
raise Exception('Unable to determine the fraction of the pushover load step that should be applied.')
|
|
743
|
+
|
|
744
|
+
# Return the fraction of the pushover load step that should be applied
|
|
745
|
+
return tal_r
|
|
746
|
+
|
|
747
|
+
# Initilize the lowest value for the fraction of the pushover load step that should be applied
|
|
748
|
+
tal_min = 1.0
|
|
749
|
+
|
|
750
|
+
# Step through each physical member in the model
|
|
751
|
+
for phys_member in model.members.values():
|
|
752
|
+
|
|
753
|
+
# Step through each sub-member of the physical member
|
|
754
|
+
for sub_member in phys_member.sub_members.values():
|
|
755
|
+
|
|
756
|
+
# Obtain the member's plastic load capacities
|
|
757
|
+
section = sub_member.section
|
|
758
|
+
Py = section.material.fy*section.A
|
|
759
|
+
Mpy = section.material.fy*section.Zy
|
|
760
|
+
Mpz = section.material.fy*section.Zz
|
|
761
|
+
|
|
762
|
+
# Obtain member end forces for the latest load step
|
|
763
|
+
fxi = sub_member.f_nonlin[combo_name][0]
|
|
764
|
+
myi = sub_member.f_nonlin[combo_name][4]
|
|
765
|
+
mzi = sub_member.f_nonlin[combo_name][5]
|
|
766
|
+
fxj = sub_member.f_nonlin[combo_name][6]
|
|
767
|
+
myj = sub_member.f_nonlin[combo_name][10]
|
|
768
|
+
mzj = sub_member.f_nonlin[combo_name][11]
|
|
769
|
+
|
|
770
|
+
# Check if the yield surface has been exceeded at the i-end of the member
|
|
771
|
+
if sub_member.section.Phi(fxi, myi, mzi) > 1.0:
|
|
772
|
+
|
|
773
|
+
# Convert the member end forces to nondiminensionalized values
|
|
774
|
+
p = abs(fxi/Py)
|
|
775
|
+
m_y = abs(myi/Mpy)
|
|
776
|
+
m_z = abs(mzi/Mpz)
|
|
777
|
+
|
|
778
|
+
dp = abs(Delta_f[0, 0]/Py)
|
|
779
|
+
dmy = abs(Delta_f[4, 0]/Mpy)
|
|
780
|
+
dmz = abs(Delta_f[5, 0]/Mpz)
|
|
781
|
+
|
|
782
|
+
tal_i = tal(p, m_y, m_z, dp, dmy, dmz, sub_member.section.Phi)
|
|
783
|
+
|
|
784
|
+
else: tal_i = 1.0
|
|
785
|
+
|
|
786
|
+
# Check if the yield surface has been exceeded at the j-end of the member
|
|
787
|
+
if sub_member.section.Phi(fxj, myj, mzj) > 1.0:
|
|
788
|
+
|
|
789
|
+
# Convert the member end forces to nondiminensionalized values
|
|
790
|
+
p = abs(fxj/Py)
|
|
791
|
+
m_y = abs(myj/Mpy)
|
|
792
|
+
m_z = abs(mzj/Mpz)
|
|
793
|
+
|
|
794
|
+
dp = abs(Delta_f[6, 0]/Py)
|
|
795
|
+
dmy = abs(Delta_f[10, 0]/Mpy)
|
|
796
|
+
dmz = abs(Delta_f[11, 0]/Mpz)
|
|
797
|
+
|
|
798
|
+
tal_j = tal(p, m_y, m_z, dp, dmy, dmz, sub_member.section.Phi)
|
|
799
|
+
|
|
800
|
+
else: tal_j = 1.0
|
|
801
|
+
|
|
802
|
+
if tal_i < tal_min:
|
|
803
|
+
tal_min = tal_i
|
|
804
|
+
if tal_j < tal_min:
|
|
805
|
+
tal_min = tal_j
|
|
806
|
+
|
|
807
|
+
# Return the fraction of the pushover load step that should be applied
|
|
808
|
+
return tal_min
|
|
809
|
+
|
|
636
810
|
def _unpartition(model: FEModel3D, V1: NDArray[float64], V2: NDArray[float64], V1_indices: List[int], V2_indices: List[int]) -> NDArray[float64]:
|
|
637
811
|
"""Unpartitions a vector and returns it as a global vector including all dofs.
|
|
638
812
|
|