structsolve 0.9.3__tar.gz → 0.10.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 (63) hide show
  1. {structsolve-0.9.3/structsolve.egg-info → structsolve-0.10.0}/PKG-INFO +15 -3
  2. {structsolve-0.9.3 → structsolve-0.10.0}/README.md +12 -2
  3. {structsolve-0.9.3 → structsolve-0.10.0}/pyproject.toml +4 -1
  4. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/__init__.py +2 -0
  5. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/analysis.py +257 -44
  6. structsolve-0.10.0/structsolve/arc_length.py +1190 -0
  7. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/arpackutils.py +152 -8
  8. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/convergence.py +5 -1
  9. structsolve-0.10.0/structsolve/displacement_control.py +32 -0
  10. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/freq.py +45 -13
  11. structsolve-0.10.0/structsolve/generalized_displacement_control.py +33 -0
  12. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/linear_buckling.py +153 -38
  13. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/newton_raphson.py +478 -24
  14. structsolve-0.10.0/structsolve/pardiso.py +236 -0
  15. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/sparseutils.py +25 -9
  16. structsolve-0.10.0/structsolve/static.py +970 -0
  17. structsolve-0.10.0/structsolve/stopping.py +41 -0
  18. {structsolve-0.9.3 → structsolve-0.10.0/structsolve.egg-info}/PKG-INFO +15 -3
  19. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve.egg-info/SOURCES.txt +20 -0
  20. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve.egg-info/requires.txt +3 -0
  21. structsolve-0.10.0/tests/test_arc_length_robustness.py +167 -0
  22. structsolve-0.10.0/tests/test_arc_length_weights.py +87 -0
  23. structsolve-0.10.0/tests/test_branch_switching.py +305 -0
  24. structsolve-0.10.0/tests/test_constraint_values.py +269 -0
  25. structsolve-0.10.0/tests/test_constraint_values_load_factor.py +121 -0
  26. structsolve-0.10.0/tests/test_constraints.py +192 -0
  27. structsolve-0.10.0/tests/test_displacement_control.py +159 -0
  28. structsolve-0.10.0/tests/test_eigen_pardiso.py +228 -0
  29. structsolve-0.10.0/tests/test_factorization_cache.py +291 -0
  30. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_follower_freq_static.py +18 -0
  31. structsolve-0.10.0/tests/test_generalized_displacement_control.py +90 -0
  32. structsolve-0.10.0/tests/test_inertia.py +401 -0
  33. structsolve-0.10.0/tests/test_inertia_unsymmetric.py +380 -0
  34. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_linear_buckling.py +2 -1
  35. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_newton_raphson.py +24 -0
  36. structsolve-0.10.0/tests/test_pardiso.py +139 -0
  37. structsolve-0.10.0/tests/test_restart.py +95 -0
  38. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_reuse.py +2 -1
  39. structsolve-0.10.0/tests/test_simo1990_cylinder.py +124 -0
  40. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_sparse_first.py +1 -1
  41. structsolve-0.10.0/tests/test_stopping.py +85 -0
  42. structsolve-0.9.3/structsolve/arc_length.py +0 -475
  43. structsolve-0.9.3/structsolve/static.py +0 -147
  44. {structsolve-0.9.3 → structsolve-0.10.0}/LICENSE +0 -0
  45. {structsolve-0.9.3 → structsolve-0.10.0}/setup.cfg +0 -0
  46. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/arc_length_crisfield.py +0 -0
  47. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/arc_length_riks.py +0 -0
  48. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/callbacks.py +0 -0
  49. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve/logger.py +0 -0
  50. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve.egg-info/dependency_links.txt +0 -0
  51. {structsolve-0.9.3 → structsolve-0.10.0}/structsolve.egg-info/top_level.txt +0 -0
  52. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_analysis.py +0 -0
  53. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_arc_length.py +0 -0
  54. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_arc_length_fung_kaplan.py +0 -0
  55. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_arpackutils.py +0 -0
  56. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_follower_loads.py +0 -0
  57. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_freq.py +0 -0
  58. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_linear_buckling_unsymmetric.py +0 -0
  59. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_load_factor_dependent_load.py +0 -0
  60. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_sparseutils.py +0 -0
  61. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_static.py +0 -0
  62. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_static_deflection.py +0 -0
  63. {structsolve-0.9.3 → structsolve-0.10.0}/tests/test_warnings_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structsolve
3
- Version: 0.9.3
3
+ Version: 0.10.0
4
4
  Summary: Structural analysis solvers
5
5
  Author-email: "Saullo G. P. Castro" <castrosaullo@gmail.com>
6
6
  License-Expression: BSD-3-Clause
@@ -26,6 +26,8 @@ Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Requires-Dist: numpy
28
28
  Requires-Dist: scipy
29
+ Provides-Extra: pardiso
30
+ Requires-Dist: mkl; extra == "pardiso"
29
31
  Provides-Extra: test
30
32
  Requires-Dist: pytest; extra == "test"
31
33
  Requires-Dist: pytest-cov; extra == "test"
@@ -59,11 +61,14 @@ Structural analysis solvers tailored for semi-analytical models
59
61
  Currently these solvers are pretty much compatible with my other repository
60
62
  [panels](https://github.com/saullocastro/panels).
61
63
 
64
+ The folder `notebooks/` holds validation notebooks against results of the
65
+ literature, run with models of `panels`, see `notebooks/README.md`.
66
+
62
67
 
63
68
  Citing this library
64
69
  ===================
65
70
 
66
- Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.9.3). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
71
+ Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.10.0). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
67
72
 
68
73
 
69
74
  Documentation
@@ -78,7 +83,13 @@ History
78
83
 
79
84
  See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
80
85
 
81
- * version 0.9.3 (2026-10-26)
86
+ * version 0.10.0 (2026-11-03)
87
+ - Arc-length corrections: line search and early divergence check; indirect displacement control and generalized displacement control; weights of the degrees of freedom in the arc-length norm
88
+ - Stability along the path: the number of negative eigenvalues of the tangent with PARDISO (`linear_solver='pardiso'`, `inertia=True`), increments cut when it jumps (`max_inertia_jump`) and switching to the bifurcated branch along the critical eigenvector (`branch_switching`)
89
+ - Linear constraints of the solution, `C^T c = 0`, by a border of the factorizations; stop criteria and `final_load_factor`; restart of the non-linear analyses from `path_state`
90
+ - Cheaper factorizations: the linear solutions use `linear_solver`, and the null rows and columns, the symmetry check and the scaling of PARDISO cost less
91
+
92
+ * version 0.9.4 (2026-10-28)
82
93
  - Crisfield's convergence criterion is the default of the non-linear analyses, `Analysis.convergence = 'crisfield'`, with `relTOL = 1e-9`: the residual and the reference forces are measured with the norm scaled by the diagonal of `calc_kC()` over the free degrees of freedom; the former check is kept with `convergence = 'relative'`
83
94
  - With both convergence criteria, an iteration also converges at the round-off floor of its residual, `3 eps ||(|K_C0| |c|)||`: the arc-length methods trace the snap-through of a low arch at `relTOL = 1e-9` through the null load where they stopped
84
95
  - `lb` returns the negative load multipliers closest to zero after the positive ones, and a tensile reference load no longer needs the dense fallback
@@ -87,6 +98,7 @@ See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
87
98
  - documentation published again: pandoc installed in the documentation workflow
88
99
  - `lb` and `freq` no longer reset the registry of the warnings already shown, which made a warning of the caller under the `'default'` action be shown again after each call
89
100
  - The residual is `R = Fint - lbd*Fext`, the convention of `pyfe3d.solver` and `panels`, with `K_T dc = -R` in the Newton-Raphson corrections and `dR/dlbd = -Fext`; the solutions are bitwise identical
101
+ - `structsolve.convergence`: the round-off floor cites Higham (2002), Sec. 3.5, for the componentwise bound of the rounding error of a matrix-vector product
90
102
  * version 0.8.0 (2026-10-14)
91
103
  - Fewer evaluations and factorizations, same solutions: `dR/dlbd` is evaluated together with the tangent stiffness matrix only, Newton-Raphson reuses the factorization of the tangent while it is not updated, the follower linear static analysis factorizes `K0` once, and `lb` estimates its shift and the inertia of `K` once for all its attempts
92
104
  * version 0.7.0 (2026-10-13)
@@ -24,11 +24,14 @@ Structural analysis solvers tailored for semi-analytical models
24
24
  Currently these solvers are pretty much compatible with my other repository
25
25
  [panels](https://github.com/saullocastro/panels).
26
26
 
27
+ The folder `notebooks/` holds validation notebooks against results of the
28
+ literature, run with models of `panels`, see `notebooks/README.md`.
29
+
27
30
 
28
31
  Citing this library
29
32
  ===================
30
33
 
31
- Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.9.3). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
34
+ Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.10.0). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
32
35
 
33
36
 
34
37
  Documentation
@@ -43,7 +46,13 @@ History
43
46
 
44
47
  See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
45
48
 
46
- * version 0.9.3 (2026-10-26)
49
+ * version 0.10.0 (2026-11-03)
50
+ - Arc-length corrections: line search and early divergence check; indirect displacement control and generalized displacement control; weights of the degrees of freedom in the arc-length norm
51
+ - Stability along the path: the number of negative eigenvalues of the tangent with PARDISO (`linear_solver='pardiso'`, `inertia=True`), increments cut when it jumps (`max_inertia_jump`) and switching to the bifurcated branch along the critical eigenvector (`branch_switching`)
52
+ - Linear constraints of the solution, `C^T c = 0`, by a border of the factorizations; stop criteria and `final_load_factor`; restart of the non-linear analyses from `path_state`
53
+ - Cheaper factorizations: the linear solutions use `linear_solver`, and the null rows and columns, the symmetry check and the scaling of PARDISO cost less
54
+
55
+ * version 0.9.4 (2026-10-28)
47
56
  - Crisfield's convergence criterion is the default of the non-linear analyses, `Analysis.convergence = 'crisfield'`, with `relTOL = 1e-9`: the residual and the reference forces are measured with the norm scaled by the diagonal of `calc_kC()` over the free degrees of freedom; the former check is kept with `convergence = 'relative'`
48
57
  - With both convergence criteria, an iteration also converges at the round-off floor of its residual, `3 eps ||(|K_C0| |c|)||`: the arc-length methods trace the snap-through of a low arch at `relTOL = 1e-9` through the null load where they stopped
49
58
  - `lb` returns the negative load multipliers closest to zero after the positive ones, and a tensile reference load no longer needs the dense fallback
@@ -52,6 +61,7 @@ See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
52
61
  - documentation published again: pandoc installed in the documentation workflow
53
62
  - `lb` and `freq` no longer reset the registry of the warnings already shown, which made a warning of the caller under the `'default'` action be shown again after each call
54
63
  - The residual is `R = Fint - lbd*Fext`, the convention of `pyfe3d.solver` and `panels`, with `K_T dc = -R` in the Newton-Raphson corrections and `dR/dlbd = -Fext`; the solutions are bitwise identical
64
+ - `structsolve.convergence`: the round-off floor cites Higham (2002), Sec. 3.5, for the componentwise bound of the rounding error of a matrix-vector product
55
65
  * version 0.8.0 (2026-10-14)
56
66
  - Fewer evaluations and factorizations, same solutions: `dR/dlbd` is evaluated together with the tangent stiffness matrix only, Newton-Raphson reuses the factorization of the tangent while it is not updated, the follower linear static analysis factorizes `K0` once, and `lb` estimates its shift and the inertia of `K` once for all its attempts
57
67
  * version 0.7.0 (2026-10-13)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "structsolve"
7
- version = "0.9.3"
7
+ version = "0.10.0"
8
8
  description = "Structural analysis solvers"
9
9
  readme = "README.md"
10
10
  license = "BSD-3-Clause"
@@ -38,6 +38,9 @@ dependencies = [
38
38
  ]
39
39
 
40
40
  [project.optional-dependencies]
41
+ pardiso = [
42
+ "mkl",
43
+ ]
41
44
  test = [
42
45
  "pytest",
43
46
  "pytest-cov",
@@ -17,6 +17,8 @@ directly from the ``structsolve`` namespace:
17
17
  - :class:`.DenseFallbackWarning`: warning of :func:`.lb` and :func:`.freq`
18
18
  when a dense solver was used as a last resort
19
19
 
20
+ and the criteria to stop a non-linear analysis in :mod:`structsolve.stopping`.
21
+
20
22
  """
21
23
  from __future__ import absolute_import
22
24
 
@@ -12,13 +12,18 @@ from __future__ import absolute_import
12
12
 
13
13
  import numpy as np
14
14
 
15
- from .static import solve, _factorize
15
+ from .static import solve, _factorize, _FactorizationCache
16
16
  from .logger import msg, warn
17
17
  from . import callbacks
18
- from .newton_raphson import _check_convergence, _weights, _abs_matrix
18
+ from .newton_raphson import (_check_convergence, _weights, _abs_matrix,
19
+ _constraint_matrix, _constraint_values,
20
+ _residual_projector)
19
21
  from .newton_raphson import _solver_NR
20
22
  from .arc_length_riks import _solver_arc_length_riks
21
23
  from .arc_length_crisfield import _solver_arc_length_crisfield
24
+ from .displacement_control import _solver_displacement_control
25
+ from .generalized_displacement_control import (
26
+ _solver_generalized_displacement_control)
22
27
 
23
28
 
24
29
  class Analysis(object):
@@ -38,37 +43,158 @@ class Analysis(object):
38
43
  The analysis parameters are attributes of this class, described in the
39
44
  following tables together with their default values:
40
45
 
41
- ======================== ==================================================
42
- Non-Linear Algorithm Description
43
- ======================== ==================================================
44
- ``NL_method`` ``str``, ``'NR'`` (default) for the
45
- Newton-Raphson method, see
46
- :func:`.newton_raphson._solver_NR`, or
47
- ``'arc_length_riks'`` and
48
- ``'arc_length_crisfield'`` for the arc-length
49
- methods, see
50
- :func:`.arc_length._solver_arc_length`
51
- ``line_search`` ``bool``, activates a safeguarding line-search,
52
- for the Newton-Raphson method only, see
53
- :func:`.newton_raphson._NR_iterations`. Default
54
- is ``False``
55
- ``max_iter_line_search`` ``int``, maximum number of iterations of the
56
- line-search. Default is ``20``
57
- ``modified_NR`` ``bool``, activates the modified Newton-Raphson
58
- method, where the tangent stiffness matrix is
59
- not updated at every iteration. Default is
60
- ``False``, i.e. full Newton-Raphson
61
- ``compute_every_n`` ``int``, if ``modified_NR=True``, the tangent
62
- stiffness matrix is updated at every `n`
63
- iterations. Default is ``6``
64
- ``kT_initial_state`` ``bool``, if ``modified_NR=True``, tells if the
65
- tangent stiffness matrix should be calculated
66
- already at the first iteration of the analysis,
67
- which is required for example when initial
68
- imperfections take place. Otherwise the linear
69
- constitutive stiffness matrix is used. Default
70
- is ``True``
71
- ======================== ==================================================
46
+ ============================ =============================================
47
+ Non-Linear Algorithm Description
48
+ ============================ =============================================
49
+ ``NL_method`` ``str``, ``'NR'`` (default) for the
50
+ Newton-Raphson method, see
51
+ :func:`.newton_raphson._solver_NR`, or
52
+ ``'arc_length_riks'`` and
53
+ ``'arc_length_crisfield'`` for the arc-length
54
+ methods, or ``'displacement_control'`` for
55
+ the indirect displacement control, or
56
+ ``'generalized_displacement_control'``,
57
+ see
58
+ :func:`.arc_length._solver_arc_length`
59
+ ``control_vector`` ``array``, the vector
60
+ `\boldsymbol{\psi}` of the controlled
61
+ quantity `\boldsymbol{\psi}^\top
62
+ \boldsymbol{c}` of
63
+ ``NL_method='displacement_control'``, e.g.
64
+ a unit vector that picks one degree of
65
+ freedom, or a buckling mode. Default is
66
+ ``None``
67
+ ``control_scale`` ``float``, the reference value of the
68
+ controlled quantity, which makes the step
69
+ dimensionless. Default is ``None``, i.e.
70
+ `|\boldsymbol{\psi}^\top
71
+ \boldsymbol{u}_{ref}|`, its value in the linear solution for a
72
+ load factor of 1
73
+ ``line_search`` ``bool``, activates a safeguarding
74
+ line-search, for the Newton-Raphson method
75
+ only, see
76
+ :func:`.newton_raphson._NR_iterations`.
77
+ Default is ``False``
78
+ ``max_iter_line_search`` ``int``, maximum number of iterations of the
79
+ line-search. Default is ``20``
80
+ ``arc_length_line_search`` ``bool``, activates a backtracking line
81
+ search on the corrections of the arc-length
82
+ methods, see
83
+ :func:`.arc_length._solver_arc_length`.
84
+ Default is ``False``
85
+ ``branch_switching`` ``int``, number of bifurcation points at
86
+ which the arc-length and the displacement
87
+ control methods switch to the bifurcated
88
+ branch, along the critical eigenvector,
89
+ where the number of negative eigenvalues
90
+ increases or decreases, which requires
91
+ ``inertia``, see
92
+ :func:`.arc_length._solver_arc_length`.
93
+ Default is ``0``
94
+ ``arc_length_weights`` ``array``, non-negative weights of the
95
+ degrees of freedom in the norm of the
96
+ arc-length constraint, e.g. 1 for the
97
+ translations and 0 for their derivatives
98
+ or the rotations, see
99
+ :func:`.arc_length._solver_arc_length`.
100
+ Default is ``None``, all equal to 1
101
+ ``early_divergence`` ``bool``, stops a diverging step earlier,
102
+ such that its increment is cut before the
103
+ iterations are spent, see
104
+ :func:`.newton_raphson._check_divergence`.
105
+ Default is ``False``
106
+ ``modified_NR`` ``bool``, activates the modified
107
+ Newton-Raphson method, where the tangent
108
+ stiffness matrix is not updated at every
109
+ iteration. Default is ``False``, i.e. full
110
+ Newton-Raphson
111
+ ``compute_every_n`` ``int``, if ``modified_NR=True``, the tangent
112
+ stiffness matrix is updated at every `n`
113
+ iterations. Default is ``6``
114
+ ``kT_initial_state`` ``bool``, if ``modified_NR=True``, tells if
115
+ the tangent stiffness matrix should be
116
+ calculated already at the first iteration of
117
+ the analysis, which is required for example
118
+ when initial imperfections take place.
119
+ Otherwise the linear constitutive stiffness
120
+ matrix is used. Default is ``True``
121
+ ``linear_solver`` ``str``, factorization of the tangent
122
+ stiffness matrix of the non-linear solvers,
123
+ ``'splu'`` (default), the sparse LU of
124
+ SuperLU, or ``'pardiso'``, the symmetric
125
+ indefinite factorization of PARDISO, which
126
+ requires oneMKL, see :mod:`.pardiso`, and
127
+ gives the number
128
+ of negative eigenvalues, see
129
+ :func:`.static._factorize`
130
+ ``inertia`` ``bool``, the tangent stiffness matrix is
131
+ factorized at every converged increment of
132
+ the non-linear analyses, and its number of
133
+ negative eigenvalues (``'pardiso'``) and the
134
+ sign of its determinant are recorded, such
135
+ that limit points, bifurcations crossed and
136
+ unstable branches are visible. Default is
137
+ ``False``
138
+ ``inertia_num_eigvals`` ``int`` `k`, with ``inertia``: when the
139
+ count of PARDISO is not available, e.g.
140
+ for an unsymmetric tangent stiffness
141
+ matrix, the number of its negative real
142
+ eigenvalues among the `k` of smallest
143
+ magnitude, scaled by the diagonal of the
144
+ linear stiffness matrix, by shift-invert
145
+ with ARPACK and the LU factorization of
146
+ the converged state, see
147
+ :func:`.newton_raphson._eigvals_inertia`.
148
+ Default is ``None``, not computed
149
+ ``max_inertia_jump`` ``int``, a converged increment whose number
150
+ of negative eigenvalues changed by more
151
+ than this value is repeated with a smaller
152
+ increment, which requires ``inertia`` and
153
+ ``linear_solver='pardiso'`` or
154
+ ``inertia_num_eigvals``. Default is
155
+ ``None``
156
+ ``constraints`` ``array`` or sparse matrix `\boldsymbol{C}`,
157
+ of shape ``(n, m)``, a 1-D array for one
158
+ constraint: the linear constraints
159
+ `\boldsymbol{C}^\top \boldsymbol{c} =
160
+ \boldsymbol{0}` of the solution, e.g. an
161
+ inertia relief or multipoint constraints,
162
+ imposed by bordering every factorization
163
+ of the stiffness matrices, see
164
+ :func:`.static._factorize`, in the linear
165
+ and the non-linear analyses. Default is
166
+ ``None``
167
+ ``constraint_values`` ``array`` `\boldsymbol{d}`, of shape
168
+ ``(m,)``, the values of the constraints,
169
+ `\boldsymbol{C}^\top \boldsymbol{c} =
170
+ \lambda \boldsymbol{d}` along the
171
+ non-linear path and `\boldsymbol{C}^\top
172
+ \boldsymbol{c} = \boldsymbol{d}` in the
173
+ linear analysis, e.g. a displacement or a
174
+ rotation prescribed in proportion to the
175
+ load factor, with or without external
176
+ load, which requires ``constraints``.
177
+ Default is ``None``, all null
178
+ ``stop_criterion`` ``callable``, ``stop_criterion(an)`` is
179
+ called after every converged increment of
180
+ the non-linear analyses, which stop when it
181
+ returns ``True``, e.g. a number of
182
+ increments beyond the first limit point or
183
+ a displacement measure, see
184
+ :mod:`.stopping`. Default is ``None``
185
+ ``final_load_factor`` ``float``, the load factor at which the
186
+ non-linear analyses finish, exactly, or,
187
+ for the arc-length and displacement control
188
+ methods, ``None``: the path is traced
189
+ beyond any load factor until another stop,
190
+ e.g. ``stop_criterion`` or
191
+ ``maxArcLength``. Default is ``1.``
192
+ ``restart_state`` ``dict``, the ``path_state`` of a previous
193
+ analysis with the same ``NL_method``, from
194
+ whose last converged increment the
195
+ non-linear analysis continues. Default is
196
+ ``None``
197
+ ============================ =============================================
72
198
 
73
199
  ================ =================================================
74
200
  Incrementation Description
@@ -166,6 +292,33 @@ class Analysis(object):
166
292
  cs : list
167
293
  Solution vectors of the converged increments, filled by
168
294
  :meth:`.static`.
295
+ num_negative_eigvals : list
296
+ With ``inertia=True``, for each converged increment of a non-linear
297
+ analysis, the number of negative eigenvalues of the tangent
298
+ stiffness matrix on the degrees of freedom whose row or column is
299
+ not null, ``None`` with ``linear_solver='splu'``, when PARDISO fell
300
+ back to SuperLU for an unsymmetric matrix, or for a singular matrix,
301
+ unless ``inertia_num_eigvals`` is given: the number of negative real
302
+ eigenvalues among the ``inertia_num_eigvals`` of smallest magnitude
303
+ is then recorded for the SuperLU factorizations. Empty otherwise.
304
+ det_signs : list
305
+ With ``inertia=True``, for each converged increment of a non-linear
306
+ analysis, the sign of the determinant of the same matrix, ``1`` or
307
+ ``-1``, ``0`` when it is singular. Empty otherwise.
308
+ num_perturbed_pivots : list
309
+ With ``inertia=True`` and ``linear_solver='pardiso'``, the number of
310
+ pivots perturbed by PARDISO at each converged increment, which makes
311
+ the count of negative eigenvalues uncertain when not zero; ``None``
312
+ otherwise.
313
+ branch_points : list
314
+ With ``branch_switching``, the indices in ``increments`` of the
315
+ converged increments from which the path was switched to a
316
+ bifurcated branch.
317
+ path_state : dict or None
318
+ After each converged increment of a non-linear analysis, the state
319
+ from which it can be continued, see ``restart_state``: the solution,
320
+ the load factor, the next increment and the directions of the
321
+ predictors.
169
322
  last_analysis : str
170
323
  Type of the last analysis run, ``'static'`` after :meth:`.static`.
171
324
 
@@ -229,12 +382,21 @@ class Analysis(object):
229
382
 
230
383
  """
231
384
  __slots__ = ['NL_method', 'line_search', 'max_iter_line_search',
385
+ 'arc_length_line_search',
386
+ 'early_divergence', 'arc_length_weights', 'control_vector',
387
+ 'control_scale', 'linear_solver', 'inertia',
388
+ 'constraints', 'constraint_values', 'stop_criterion',
389
+ 'final_load_factor',
390
+ 'branch_switching', 'max_inertia_jump', 'inertia_num_eigvals',
391
+ 'restart_state', 'path_state', 'branch_points',
392
+ 'num_negative_eigvals', 'det_signs', 'num_perturbed_pivots',
232
393
  'modified_NR', 'compute_every_n',
233
394
  'kT_initial_state', 'initialInc', 'minInc', 'maxInc',
234
395
  'maxArcLength', 'convergence', 'absTOL', 'relTOL', 'maxNumIter',
235
396
  'too_slow_TOL',
236
397
  'increments', 'cs', 'last_analysis', 'calc_fext', 'calc_kC',
237
- 'calc_fint', 'calc_kG']
398
+ 'calc_fint', 'calc_kG', '_factorization_cache',
399
+ '_inertia_scaling']
238
400
 
239
401
 
240
402
  def __init__(self, calc_fext=None, calc_fint=None, calc_kC=None,
@@ -243,6 +405,21 @@ class Analysis(object):
243
405
  self.NL_method = 'NR'
244
406
  self.line_search = False
245
407
  self.max_iter_line_search = 20
408
+ self.arc_length_line_search = False
409
+ self.early_divergence = False
410
+ self.arc_length_weights = None
411
+ self.control_vector = None
412
+ self.control_scale = None
413
+ self.linear_solver = 'splu'
414
+ self.inertia = False
415
+ self.constraints = None
416
+ self.constraint_values = None
417
+ self.stop_criterion = None
418
+ self.final_load_factor = 1.
419
+ self.branch_switching = 0
420
+ self.max_inertia_jump = None
421
+ self.inertia_num_eigvals = None
422
+ self.restart_state = None
246
423
  self.modified_NR = False
247
424
  self.compute_every_n = 6
248
425
  self.kT_initial_state = True
@@ -267,9 +444,18 @@ class Analysis(object):
267
444
  # outputs to be filled
268
445
  self.increments = None
269
446
  self.cs = None
447
+ self.num_negative_eigvals = None
448
+ self.det_signs = None
449
+ self.num_perturbed_pivots = None
450
+ self.branch_points = None
451
+ self.path_state = None
270
452
 
271
453
  # flag telling the last analysis
272
454
  self.last_analysis = ''
455
+ # pattern-dependent work of the factorizations of a running analysis
456
+ self._factorization_cache = None
457
+ # scaling of the counts of inertia_num_eigvals of a running analysis
458
+ self._inertia_scaling = None
273
459
 
274
460
 
275
461
  def static(self, NLgeom=False, silent=False):
@@ -310,7 +496,9 @@ class Analysis(object):
310
496
  ------
311
497
  ValueError
312
498
  When ``NLgeom=True`` and the ``NL_method`` attribute is not
313
- ``'NR'``, ``'arc_length_riks'`` or ``'arc_length_crisfield'``.
499
+ ``'NR'``, ``'arc_length_riks'``, ``'arc_length_crisfield'``,
500
+ ``'displacement_control'`` or
501
+ ``'generalized_displacement_control'``.
314
502
 
315
503
  Notes
316
504
  -----
@@ -342,25 +530,46 @@ class Analysis(object):
342
530
  """
343
531
  self.increments = []
344
532
  self.cs = []
533
+ self.num_negative_eigvals = []
534
+ self.det_signs = []
535
+ self.num_perturbed_pivots = []
536
+ self.branch_points = []
537
+ self.path_state = None
345
538
 
346
539
  if NLgeom:
347
540
  self.maxInc = max(self.initialInc, self.maxInc)
348
541
  msg('Started Non-Linear Static Analysis', silent=silent)
349
- if self.NL_method == 'NR':
350
- _solver_NR(self, silent=silent)
351
- elif self.NL_method == 'arc_length_riks':
352
- _solver_arc_length_riks(self, silent=silent)
353
- elif self.NL_method == 'arc_length_crisfield':
354
- _solver_arc_length_crisfield(self, silent=silent)
355
- else:
542
+ solvers = {
543
+ 'NR': _solver_NR,
544
+ 'arc_length_riks': _solver_arc_length_riks,
545
+ 'arc_length_crisfield': _solver_arc_length_crisfield,
546
+ 'displacement_control': _solver_displacement_control,
547
+ 'generalized_displacement_control':
548
+ _solver_generalized_displacement_control}
549
+ if self.NL_method not in solvers:
356
550
  raise ValueError('{0} is an invalid NL_method'.format(self.NL_method))
551
+ # the work of the factorizations that depends only on the
552
+ # pattern of the tangent stiffness matrix is reused
553
+ self._factorization_cache = _FactorizationCache()
554
+ self._inertia_scaling = None
555
+ try:
556
+ solvers[self.NL_method](self, silent=silent)
557
+ finally:
558
+ self._factorization_cache.close()
559
+ self._factorization_cache = None
560
+ self._inertia_scaling = None
357
561
 
358
562
  else:
359
563
  msg('Started Linear Static Analysis', silent=silent)
360
564
  fext = self.calc_fext(silent=silent)
361
565
  k0 = self.calc_kC(silent=silent)
362
566
 
363
- c = solve(k0, fext, silent=silent)
567
+ d = _constraint_values(self)
568
+ if self.constraints is None:
569
+ c = solve(k0, fext, silent=silent)
570
+ else:
571
+ c = _factorize(k0, constraints=_constraint_matrix(self))(fext,
572
+ d)
364
573
  if callbacks.is_load_configuration_dependent(self):
365
574
  c = self._linear_static_follower(k0, c, silent)
366
575
 
@@ -380,12 +589,15 @@ class Analysis(object):
380
589
  level=2, silent=silent)
381
590
  w = _weights(self, k0)
382
591
  k0_abs = _abs_matrix(k0)
592
+ proj = _residual_projector(self, w)
383
593
  # k0 is factorized once for all the corrections
384
594
  solve_k0 = None
385
595
  for iteration in range(1, self.maxNumIter + 1):
386
596
  fext = self.calc_fext(inc=1., c=c, silent=True)
387
597
  fint = k0 @ c
388
598
  R = fint - fext
599
+ if proj is not None:
600
+ R = proj(R)
389
601
  conv, Rnorm, Rrel, roundoff = _check_convergence(self, R, fint,
390
602
  fext, w=w, Kc_abs=k0_abs @ np.abs(c), full_output=True)
391
603
  msg('Iteration: %d, %s = %1.3e%s' % (iteration, 'zeta' if
@@ -398,7 +610,8 @@ class Analysis(object):
398
610
  break
399
611
  if solve_k0 is None:
400
612
  try:
401
- solve_k0 = _factorize(k0)
613
+ solve_k0 = _factorize(k0,
614
+ constraints=_constraint_matrix(self))
402
615
  except RuntimeError:
403
616
  break
404
617
  c = c + solve_k0(-R)