structsolve 0.9.2__tar.gz → 0.9.4__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 (41) hide show
  1. {structsolve-0.9.2/structsolve.egg-info → structsolve-0.9.4}/PKG-INFO +8 -4
  2. {structsolve-0.9.2 → structsolve-0.9.4}/README.md +7 -3
  3. {structsolve-0.9.2 → structsolve-0.9.4}/pyproject.toml +1 -1
  4. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/analysis.py +34 -30
  5. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/arc_length.py +45 -41
  6. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/arpackutils.py +20 -23
  7. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/callbacks.py +21 -19
  8. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/convergence.py +163 -32
  9. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/freq.py +73 -31
  10. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/linear_buckling.py +228 -119
  11. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/newton_raphson.py +78 -45
  12. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/static.py +1 -1
  13. {structsolve-0.9.2 → structsolve-0.9.4/structsolve.egg-info}/PKG-INFO +8 -4
  14. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve.egg-info/SOURCES.txt +3 -1
  15. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_analysis.py +55 -5
  16. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_arc_length.py +1 -1
  17. structsolve-0.9.4/tests/test_arc_length_fung_kaplan.py +261 -0
  18. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_follower_freq_static.py +2 -2
  19. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_follower_loads.py +11 -9
  20. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_freq.py +100 -0
  21. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_linear_buckling.py +32 -32
  22. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_load_factor_dependent_load.py +37 -31
  23. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_newton_raphson.py +6 -3
  24. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_reuse.py +3 -3
  25. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_static.py +6 -6
  26. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_static_deflection.py +19 -16
  27. structsolve-0.9.4/tests/test_warnings_registry.py +159 -0
  28. {structsolve-0.9.2 → structsolve-0.9.4}/LICENSE +0 -0
  29. {structsolve-0.9.2 → structsolve-0.9.4}/setup.cfg +0 -0
  30. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/__init__.py +0 -0
  31. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/arc_length_crisfield.py +0 -0
  32. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/arc_length_riks.py +0 -0
  33. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/logger.py +0 -0
  34. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve/sparseutils.py +0 -0
  35. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve.egg-info/dependency_links.txt +0 -0
  36. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve.egg-info/requires.txt +0 -0
  37. {structsolve-0.9.2 → structsolve-0.9.4}/structsolve.egg-info/top_level.txt +0 -0
  38. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_arpackutils.py +0 -0
  39. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_linear_buckling_unsymmetric.py +0 -0
  40. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_sparse_first.py +0 -0
  41. {structsolve-0.9.2 → structsolve-0.9.4}/tests/test_sparseutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structsolve
3
- Version: 0.9.2
3
+ Version: 0.9.4
4
4
  Summary: Structural analysis solvers
5
5
  Author-email: "Saullo G. P. Castro" <castrosaullo@gmail.com>
6
6
  License-Expression: BSD-3-Clause
@@ -63,7 +63,7 @@ Currently these solvers are pretty much compatible with my other repository
63
63
  Citing this library
64
64
  ===================
65
65
 
66
- Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.9.2). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
66
+ Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.9.4). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
67
67
 
68
68
 
69
69
  Documentation
@@ -78,12 +78,16 @@ History
78
78
 
79
79
  See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
80
80
 
81
- * version 0.9.2 (2026-10-24), including 0.9.0 and 0.9.1
81
+ * version 0.9.4 (2026-10-28)
82
82
  - 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
+ - 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
83
84
  - `lb` returns the negative load multipliers closest to zero after the positive ones, and a tensile reference load no longer needs the dense fallback
84
- - `freq(sort=True)` sorts and filters `lambda2` together with the eigenvectors, and rigid-body modes no longer fail the verification of the residuals
85
+ - `freq(sort=True)` sorts and filters `lambda2` together with the eigenvectors, and neither rigid-body modes nor eigenpairs with an eigenvalue close to zero whose backward error is at the round-off, e.g. pre-stressed close to buckling, fail the verification of the residuals any more
85
86
  - arc-length solvers: a converged step that points back along the path is repeated with half the arc length, instead of being accepted
86
87
  - documentation published again: pandoc installed in the documentation workflow
88
+ - `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
+ - 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
90
+ - `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
87
91
  * version 0.8.0 (2026-10-14)
88
92
  - 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
89
93
  * version 0.7.0 (2026-10-13)
@@ -28,7 +28,7 @@ Currently these solvers are pretty much compatible with my other repository
28
28
  Citing this library
29
29
  ===================
30
30
 
31
- Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.9.2). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
31
+ Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.9.4). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
32
32
 
33
33
 
34
34
  Documentation
@@ -43,12 +43,16 @@ History
43
43
 
44
44
  See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
45
45
 
46
- * version 0.9.2 (2026-10-24), including 0.9.0 and 0.9.1
46
+ * version 0.9.4 (2026-10-28)
47
47
  - 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
+ - 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
48
49
  - `lb` returns the negative load multipliers closest to zero after the positive ones, and a tensile reference load no longer needs the dense fallback
49
- - `freq(sort=True)` sorts and filters `lambda2` together with the eigenvectors, and rigid-body modes no longer fail the verification of the residuals
50
+ - `freq(sort=True)` sorts and filters `lambda2` together with the eigenvectors, and neither rigid-body modes nor eigenpairs with an eigenvalue close to zero whose backward error is at the round-off, e.g. pre-stressed close to buckling, fail the verification of the residuals any more
50
51
  - arc-length solvers: a converged step that points back along the path is repeated with half the arc length, instead of being accepted
51
52
  - documentation published again: pandoc installed in the documentation workflow
53
+ - `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
+ - 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
55
+ - `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
52
56
  * version 0.8.0 (2026-10-14)
53
57
  - 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
54
58
  * 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.2"
7
+ version = "0.9.4"
8
8
  description = "Structural analysis solvers"
9
9
  readme = "README.md"
10
10
  license = "BSD-3-Clause"
@@ -15,7 +15,7 @@ import numpy as np
15
15
  from .static import solve, _factorize
16
16
  from .logger import msg, warn
17
17
  from . import callbacks
18
- from .newton_raphson import _check_convergence, _weights
18
+ from .newton_raphson import _check_convergence, _weights, _abs_matrix
19
19
  from .newton_raphson import _solver_NR
20
20
  from .arc_length_riks import _solver_arc_length_riks
21
21
  from .arc_length_crisfield import _solver_arc_length_crisfield
@@ -172,19 +172,20 @@ class Analysis(object):
172
172
  Notes
173
173
  -----
174
174
  For non-linear analyses, the internal force vector
175
- `\{F_{int}(c)\}` must be in equilibrium with the external force
175
+ `\boldsymbol{F}_{int}(\boldsymbol{c})` must be in equilibrium with the external force
176
176
  vector scaled by the load factor `\lambda`:
177
177
 
178
178
  .. math::
179
179
 
180
- \{R\} = \lambda \{F_{ext}\} - \{F_{int}(c)\} = \{0\}
180
+ \boldsymbol{R} = \boldsymbol{F}_{int}(\boldsymbol{c}) - \lambda \boldsymbol{F}_{ext} = \boldsymbol{0}
181
181
 
182
- and the tangent stiffness matrix `[K_T] = [K_C] + [K_G]`, given by the
182
+ and the tangent stiffness matrix `\boldsymbol{K}_T = \boldsymbol{K}_C + \boldsymbol{K}_G`, given by the
183
183
  callables ``calc_kC`` and ``calc_kG``, should be the exact derivative of
184
- `\{F_{int}\}` with respect to `\{c\}`, such that the Newton-Raphson
185
- iterations converge quadratically. The convergence is checked by
186
- default with Crisfield's criterion, the residual and the reference
187
- force vectors scaled with the diagonal of the linear stiffness matrix
184
+ `\boldsymbol{F}_{int}` with respect to `\boldsymbol{c}`, `\boldsymbol{K}_T = \partial
185
+ \boldsymbol{R}/\partial \boldsymbol{c}`, such that the Newton-Raphson
186
+ iterations, `\boldsymbol{K}_T \delta \boldsymbol{c} = -\boldsymbol{R}`, converge
187
+ quadratically. The convergence is checked by default with Crisfield's
188
+ criterion, the residual and the reference force vectors scaled with the diagonal of the linear stiffness matrix
188
189
  ``calc_kC()`` over the free degrees of freedom, such that it does not
189
190
  depend on the units of each degree of freedom, e.g. translations and
190
191
  rotations, see :mod:`.convergence`; ``convergence = 'relative'`` keeps
@@ -209,15 +210,15 @@ class Analysis(object):
209
210
  such that ``calc_fint`` may contain the configuration-dependent part of
210
211
  the load, scaled by ``inc``, and ``calc_kC + calc_kG`` its derivative.
211
212
  - ``calc_fext(inc=1., c=c)`` must return the load vector of the
212
- configuration ``c`` for a unit load factor, the derivative of the
213
- residual with respect to the load factor, used by the arc-length
214
- solvers.
213
+ configuration ``c`` for a unit load factor, minus the derivative of
214
+ the residual with respect to the load factor, `-\partial
215
+ \boldsymbol{R}/\partial \lambda`, used by the arc-length solvers.
215
216
  - ``calc_fext(inc=1., c=c, lbd=lbd)`` also receives the current load
216
217
  factor when it declares ``lbd``, for a derivative `\partial
217
- R/\partial \lambda` that depends on the load factor, e.g. prescribed
218
+ \boldsymbol{R}/\partial \lambda` that depends on the load factor, e.g. prescribed
218
219
  displacements imposed exactly as linear constraints, where the
219
220
  reference vector ``calc_fext(inc=1.)`` may be null. The solvers then
220
- use `\partial R/\partial \lambda` for the displacement scale of the
221
+ use `-\partial \boldsymbol{R}/\partial \lambda` for the displacement scale of the
221
222
  arc-length methods, for the predictor of the first Newton-Raphson
222
223
  increment and as a reference of the convergence criterion, see
223
224
  :mod:`.callbacks`. The linear static analysis never passes
@@ -313,29 +314,29 @@ class Analysis(object):
313
314
 
314
315
  Notes
315
316
  -----
316
- The linear analysis solves `[K_C]\{c\} = \{F_{ext}\}`. When the load
317
+ The linear analysis solves `\boldsymbol{K}_C \boldsymbol{c} = \boldsymbol{F}_{ext}`. When the load
317
318
  depends on the configuration, the structure is still geometrically
318
319
  linear but the load is evaluated in the current configuration,
319
- `[K_C]\{c\} = \{F_{ext}(c)\}`, which is solved by the load
320
+ `\boldsymbol{K}_C \boldsymbol{c} = \boldsymbol{F}_{ext}(\boldsymbol{c})`, which is solved by the load
320
321
  correction iterations
321
322
 
322
323
  .. math::
323
324
 
324
- \{c_{k+1}\} = \{c_k\} + [K_C]^{-1}(\{F_{ext}(c_k)\}
325
- - [K_C]\{c_k\})
325
+ \boldsymbol{c}_{k+1} = \boldsymbol{c}_k + \boldsymbol{K}_C^{-1}(\boldsymbol{F}_{ext}(\boldsymbol{c}_k)
326
+ - \boldsymbol{K}_C \boldsymbol{c}_k)
326
327
 
327
- starting from `\{c_0\} = [K_C]^{-1}\{F_{ext}\}`, the solution of the
328
- reference load ``calc_fext()``, with `\{F_{ext}(c_k)\}` given by
328
+ starting from `\boldsymbol{c}_0 = \boldsymbol{K}_C^{-1} \boldsymbol{F}_{ext}`, the solution of the
329
+ reference load ``calc_fext()``, with `\boldsymbol{F}_{ext}(\boldsymbol{c}_k)` given by
329
330
  ``calc_fext(inc=1., c=c_k)``. This is a fixed-point iteration
330
331
  without the load stiffness in the iteration matrix. For a follower
331
- load whose force vector is affine in `c`, `\{F(c)\} = \{F_0\} -
332
- [K_f]\{c\}`, this is the solution of `([K_C] + [K_f])\{c\} =
333
- \{F_0\}`, with the convergence rate of the spectral radius of
334
- `[K_C]^{-1}[K_f]`, i.e. of the ratio of the load to the lowest
332
+ load whose force vector is affine in `c`, `\boldsymbol{F}(\boldsymbol{c}) = \boldsymbol{F}_0 -
333
+ \boldsymbol{K}_f \boldsymbol{c}`, this is the solution of `(\boldsymbol{K}_C + \boldsymbol{K}_f) \boldsymbol{c} =
334
+ \boldsymbol{F}_0`, with the convergence rate of the spectral radius of
335
+ `\boldsymbol{K}_C^{-1} \boldsymbol{K}_f`, i.e. of the ratio of the load to the lowest
335
336
  critical load of the linearized problem; beyond it the iterations
336
- diverge. The error is multiplied by `-[K_C]^{-1}[K_f]` at each
337
+ diverge. The error is multiplied by `-\boldsymbol{K}_C^{-1} \boldsymbol{K}_f` at each
337
338
  iteration, whose spectral radius is `1/\min|\lambda|` over the load
338
- multipliers `\lambda` of `([K_C] + \lambda [K_f])\{u\} = \{0\}`,
339
+ multipliers `\lambda` of `(\boldsymbol{K}_C + \lambda \boldsymbol{K}_f) \boldsymbol{u} = \boldsymbol{0}`,
339
340
  including the negative and complex ones.
340
341
 
341
342
  """
@@ -378,15 +379,18 @@ class Analysis(object):
378
379
  msg('Configuration-dependent load: load correction iterations',
379
380
  level=2, silent=silent)
380
381
  w = _weights(self, k0)
382
+ k0_abs = _abs_matrix(k0)
381
383
  # k0 is factorized once for all the corrections
382
384
  solve_k0 = None
383
385
  for iteration in range(1, self.maxNumIter + 1):
384
386
  fext = self.calc_fext(inc=1., c=c, silent=True)
385
387
  fint = k0 @ c
386
- R = fext - fint
387
- conv, Rnorm, Rrel = _check_convergence(self, R, fint, fext, w=w)
388
- msg('Iteration: %d, %s = %1.3e' % (iteration, 'zeta' if
389
- self.convergence == 'crisfield' else 'relative ||R||', Rrel),
388
+ R = fint - fext
389
+ conv, Rnorm, Rrel, roundoff = _check_convergence(self, R, fint,
390
+ fext, w=w, Kc_abs=k0_abs @ np.abs(c), full_output=True)
391
+ msg('Iteration: %d, %s = %1.3e%s' % (iteration, 'zeta' if
392
+ self.convergence == 'crisfield' else 'relative ||R||', Rrel,
393
+ ', converged at the round-off floor' if roundoff else ''),
390
394
  level=3, silent=silent)
391
395
  if conv:
392
396
  return c
@@ -397,7 +401,7 @@ class Analysis(object):
397
401
  solve_k0 = _factorize(k0)
398
402
  except RuntimeError:
399
403
  break
400
- c = c + solve_k0(R)
404
+ c = c + solve_k0(-R)
401
405
  warn('The load correction iterations did not converge, the load may '
402
406
  'exceed the critical load of the linearized problem', level=1,
403
407
  silent=silent)
@@ -14,7 +14,7 @@ from .static import solve, _factorize
14
14
  from . import callbacks
15
15
  from .newton_raphson import (_check_convergence, _check_divergence,
16
16
  _NR_iterations, _log_iteration, _weights,
17
- INC_CUT_FACTOR)
17
+ _abs_matrix, INC_CUT_FACTOR)
18
18
 
19
19
 
20
20
  #: Scaling factor of the load-factor term in the arc-length constraint
@@ -75,65 +75,67 @@ def _solver_arc_length(an, method, silent=False):
75
75
 
76
76
  Notes
77
77
  -----
78
- The equilibrium path `\{F_{int}(c)\} = \lambda \{F_{ext}\}` is traced with
78
+ The equilibrium path `\boldsymbol{F}_{int}(\boldsymbol{c}) = \lambda \boldsymbol{F}_{ext}` is traced with
79
79
  the arc-length constraint
80
80
 
81
81
  .. math::
82
82
 
83
- \frac{\{\Delta c\}^T\{\Delta c\}}{\{u_{ref}\}^T\{u_{ref}\}}
83
+ \frac{\Delta \boldsymbol{c}^\top \Delta \boldsymbol{c}}{\boldsymbol{u}_{ref}^\top \boldsymbol{u}_{ref}}
84
84
  + \psi^2 \Delta\lambda^2 = \Delta s^2
85
85
 
86
- where `\{\Delta c\}` and `\Delta\lambda` are the increments of the current
87
- step and `\{u_{ref}\}` is the linear solution for `\lambda=1`, which makes
86
+ where `\Delta \boldsymbol{c}` and `\Delta\lambda` are the increments of the current
87
+ step and `\boldsymbol{u}_{ref}` is the linear solution for `\lambda=1`, which makes
88
88
  the arc length `\Delta s` independent of the units of the model. In the
89
89
  linear regime `\Delta s \approx \sqrt{1 + \psi^2} \Delta\lambda`. It is
90
90
  the spherical arc-length constraint of [4]_ (Section 9.3.2, Eq. (9.19),
91
91
  p. 271), where the displacement term is not scaled and the load term is
92
- `\psi^2 \Delta\lambda^2 \{F_{ext}\}^T\{F_{ext}\}`. `\psi` is the
92
+ `\psi^2 \Delta\lambda^2 \boldsymbol{F}_{ext}^\top \boldsymbol{F}_{ext}`. `\psi` is the
93
93
  constant ``PSI``,
94
- and `\{u_{ref}\}^T\{u_{ref}\}` is
94
+ and `\boldsymbol{u}_{ref}^\top \boldsymbol{u}_{ref}` is
95
95
  replaced by 1 when it is null. With the inner product
96
96
 
97
97
  .. math::
98
98
 
99
99
  \langle (a, \alpha), (b, \beta) \rangle =
100
- \frac{\{a\}^T\{b\}}{\{u_{ref}\}^T\{u_{ref}\}} + \psi^2 \alpha \beta
100
+ \frac{\boldsymbol{a}^\top \boldsymbol{b}}{\boldsymbol{u}_{ref}^\top \boldsymbol{u}_{ref}} + \psi^2 \alpha \beta
101
101
 
102
102
  the constraint reads `g = \frac{1}{2}(\langle (\Delta c, \Delta\lambda),
103
103
  (\Delta c, \Delta\lambda) \rangle - \Delta s^2) = 0`.
104
104
 
105
105
  Each step starts from a tangent predictor, whose direction follows the
106
106
  previous increment, allowing limit points to be passed. With
107
- `\{\delta c_q\} = [K_T]^{-1}\{F_{ext}\}` at the last converged state,
107
+ `\delta \boldsymbol{c}_q = \boldsymbol{K}_T^{-1} \boldsymbol{F}_{ext}` at the last converged state,
108
108
  the predictor
109
109
 
110
110
  .. math::
111
111
 
112
112
  \Delta\lambda = \pm \frac{\Delta s}{\sqrt{\langle (\delta c_q, 1),
113
113
  (\delta c_q, 1) \rangle}}, \qquad
114
- \{\Delta c\} = \Delta\lambda \{\delta c_q\}
114
+ \Delta \boldsymbol{c} = \Delta\lambda \delta \boldsymbol{c}_q
115
115
 
116
116
  satisfies the constraint, with the sign of `\langle (\delta c_q, 1),
117
117
  (\Delta c_{prev}, \Delta\lambda_{prev}) \rangle`, where
118
118
  `(\Delta c_{prev}, \Delta\lambda_{prev})` is the increment of the
119
119
  previous converged step, and a positive sign at the first step. The
120
- initial arc length, `\Delta s_0 = initialInc \sqrt{\langle (\delta c_q,
121
- 1), (\delta c_q, 1) \rangle}` at the origin, corresponds to a load factor
120
+ initial arc length, `\Delta s_0 = initialInc \sqrt{\langle (\delta \boldsymbol{c}_q,
121
+ 1), (\delta \boldsymbol{c}_q, 1) \rangle}` at the origin, corresponds to a load factor
122
122
  increment of ``an.initialInc`` along the initial tangent.
123
123
 
124
- At each iteration, the residual is `\{R\} = (\lambda + \Delta\lambda)
125
- \{F_{ext}\} - \{F_{int}(c + \Delta c)\}`, and the bordered system is
124
+ At each iteration, the residual is `\boldsymbol{R} = \boldsymbol{F}_{int}(\boldsymbol{c} +
125
+ \Delta \boldsymbol{c}) - (\lambda + \Delta\lambda) \boldsymbol{F}_{ext}`, with `\partial
126
+ \boldsymbol{R}/\partial \lambda = -\boldsymbol{F}_{ext}`, and the bordered system
127
+ `\boldsymbol{K}_T \delta \boldsymbol{c} - \delta\lambda \, \boldsymbol{F}_{ext} = -\boldsymbol{R}` is
126
128
  solved with two solutions of the tangent stiffness matrix,
127
- `\{\delta c_R\} = [K_T]^{-1}\{R\}` and `\{\delta c_q\} =
128
- [K_T]^{-1}\{F_{ext}\}`; the increment is updated to `(\Delta c +
129
- \delta c_R + \delta\lambda \, \delta c_q, \Delta\lambda +
129
+ `\delta \boldsymbol{c}_R = -\boldsymbol{K}_T^{-1} \boldsymbol{R}` and `\delta \boldsymbol{c}_q =
130
+ \boldsymbol{K}_T^{-1} \boldsymbol{F}_{ext}`; the increment is updated to `(\Delta \boldsymbol{c} +
131
+ \delta \boldsymbol{c}_R + \delta\lambda \, \delta \boldsymbol{c}_q, \Delta\lambda +
130
132
  \delta\lambda)`, where the correction of the load factor
131
133
  `\delta\lambda` comes from the constraint:
132
134
 
133
135
  - ``method='riks'``: the constraint is linearized at the current
134
136
  increment (updated normal plane), which is Newton-Raphson applied to
135
137
  the augmented system of the equilibrium equations and the constraint
136
- ([4]_, Section 9.3.2, p. 272), with quadratic convergence when `[K_T]`
138
+ ([4]_, Section 9.3.2, p. 272), with quadratic convergence when `\boldsymbol{K}_T`
137
139
  is updated at every iteration and is the exact Jacobian. It follows
138
140
  Riks [1]_, who applies Newton's method to the equilibrium equations
139
141
  augmented with an equation that controls the arc length, and states
@@ -158,7 +160,7 @@ def _solver_arc_length(an, method, silent=False):
158
160
  - ``method='crisfield'``: the quadratic (spherical) constraint is solved
159
161
  exactly, choosing the root with the smallest angle to the current
160
162
  increment. When the roots are complex, the linearized constraint is
161
- used, see [2]_. With `\{u\} = \{\Delta c\} + \{\delta c_R\}`, the
163
+ used, see [2]_. With `\boldsymbol{u} = \Delta \boldsymbol{c} + \delta \boldsymbol{c}_R`, the
162
164
  constraint
163
165
  `\langle (u + \delta\lambda \, \delta c_q, \Delta\lambda +
164
166
  \delta\lambda), (u + \delta\lambda \, \delta c_q, \Delta\lambda +
@@ -185,7 +187,7 @@ def _solver_arc_length(an, method, silent=False):
185
187
  tangent predictor of the hyperplane normal to it, in the inner product
186
188
  above, through the last converged point, as the points of the plane of
187
189
  Riks [1]_, normal to the tangent at the distance `\Delta s` from the
188
- last converged point. With full Newton-Raphson, `[K_T]` is updated at
190
+ last converged point. With full Newton-Raphson, `\boldsymbol{K}_T` is updated at
189
191
  every iteration at the current iterate `(c + \Delta c, \lambda +
190
192
  \Delta\lambda)`; with
191
193
  ``an.modified_NR = True`` the factorization at the last converged state
@@ -210,27 +212,27 @@ def _solver_arc_length(an, method, silent=False):
210
212
  `\lambda < 1` to `\lambda + \Delta\lambda > 1`, the step is replaced by
211
213
  Newton-Raphson iterations at exactly `\lambda = 1`, see
212
214
  :func:`.newton_raphson._NR_iterations`, from the predictor
213
- `t \{\Delta c\}` with `t = (1 - \lambda)/\Delta\lambda`. When these
215
+ `t \Delta \boldsymbol{c}` with `t = (1 - \lambda)/\Delta\lambda`. When these
214
216
  iterations do not converge, the step is repeated with a shorter arc
215
217
  length.
216
218
 
217
219
  When the load depends on the configuration, i.e. ``calc_fext`` accepts
218
- the keyword argument ``c``, `\{F_{ext}\}` in `\{\delta c_q\}` and in the
220
+ the keyword argument ``c``, `\boldsymbol{F}_{ext}` in `\delta \boldsymbol{c}_q` and in the
219
221
  tangent predictor is the load vector of the current configuration
220
- ``calc_fext(inc=1., c=c)``, the derivative `\partial R/\partial
221
- \lambda` of the residual, whereas `\{u_{ref}\}` remains the linear
222
+ ``calc_fext(inc=1., c=c)``, minus the derivative `\partial \boldsymbol{R}/\partial
223
+ \lambda` of the residual, whereas `\boldsymbol{u}_{ref}` remains the linear
222
224
  solution of the reference vector. The callables that accept ``inc``
223
225
  receive the current load factor, see :mod:`.callbacks`.
224
226
 
225
227
  When ``calc_fext`` also accepts the keyword argument ``lbd``, e.g. for
226
228
  prescribed displacements imposed exactly, it receives the load factor
227
- together with the configuration, `\{F_{ext}\}` above being then
228
- `\partial R/\partial \lambda` at the current `c` and `\lambda`;
229
- `\{u_{ref}\}` is the linear solution of `\partial R/\partial \lambda`
229
+ together with the configuration, `\boldsymbol{F}_{ext}` above being then
230
+ `-\partial \boldsymbol{R}/\partial \lambda` at the current `c` and `\lambda`;
231
+ `\boldsymbol{u}_{ref}` is the linear solution of `-\partial \boldsymbol{R}/\partial \lambda`
230
232
  at `c = 0`, `\lambda = 0`, since the reference vector may be null, e.g.
231
- under a prescribed displacement alone; and `\lambda \, \partial
232
- R/\partial \lambda` is an additional reference of the relative
233
- convergence criterion. `\partial R/\partial \lambda` is evaluated
233
+ under a prescribed displacement alone; and `-\lambda \, \partial
234
+ \boldsymbol{R}/\partial \lambda` is an additional reference of the relative
235
+ convergence criterion. `-\partial \boldsymbol{R}/\partial \lambda` is evaluated
234
236
  together with the tangent stiffness matrix, at the same iterate, i.e.
235
237
  once per update of the tangent stiffness matrix, and used in the
236
238
  bordered solution; the last one evaluated is the reference of the next
@@ -261,9 +263,10 @@ def _solver_arc_length(an, method, silent=False):
261
263
  fext = an.calc_fext(inc=1., silent=True)
262
264
  kC0 = an.calc_kC(silent=silent)
263
265
  w = _weights(an, kC0)
266
+ K0_abs = _abs_matrix(kC0)
264
267
  lbd_dependent = callbacks.is_load_factor_dependent(an)
265
268
  if lbd_dependent:
266
- # dR/dlbd at c = 0 and lbd = 0
269
+ # -dR/dlbd at c = 0 and lbd = 0
267
270
  q0 = callbacks.calc_load_vector(an, np.zeros_like(fext), fext, 0.)
268
271
  u_ref = solve(kC0, q0, silent=True)
269
272
  else:
@@ -305,7 +308,7 @@ def _solver_arc_length(an, method, silent=False):
305
308
  warn('Singular tangent stiffness matrix at the last converged state! Analysis stopped',
306
309
  level=1, silent=silent)
307
310
  break
308
- # load vector of the converged configuration, dR/dlbd, kept with
311
+ # load vector of the converged configuration, -dR/dlbd, kept with
309
312
  # the tangent stiffness matrix when the step is repeated
310
313
  if step_num == 1 and lbd_dependent:
311
314
  q_conv = q0
@@ -336,7 +339,7 @@ def _solver_arc_length(an, method, silent=False):
336
339
  Rnorms = []
337
340
  solve_kT = solve_kT_conv
338
341
  dc_q = dc_q_conv
339
- # the last dR/dlbd evaluated, with the tangent stiffness matrix
342
+ # the last -dR/dlbd evaluated, with the tangent stiffness matrix
340
343
  q_iter = q_conv
341
344
  iter_kT = 0
342
345
  iteration = 0
@@ -344,14 +347,15 @@ def _solver_arc_length(an, method, silent=False):
344
347
  iteration += 1
345
348
  fint = callbacks.calc_fint(an, c + dc, lbd + dlbd)
346
349
  fext_total = (lbd + dlbd)*fext
347
- R = fext_total - fint
350
+ R = fint - fext_total
348
351
  q_ref = None
349
352
  if lbd_dependent:
350
353
  q_ref = (lbd + dlbd)*q_iter
351
- conv, Rnorm, Rrel = _check_convergence(an, R, fint, fext_total,
352
- fext_ref=dlbd*fext,
353
- q_ref=q_ref, w=w)
354
- _log_iteration(an, iteration, R, Rrel, silent, lbd=lbd + dlbd)
354
+ conv, Rnorm, Rrel, roundoff = _check_convergence(an, R, fint,
355
+ fext_total, fext_ref=dlbd*fext, q_ref=q_ref, w=w,
356
+ Kc_abs=K0_abs @ np.abs(c + dc), full_output=True)
357
+ _log_iteration(an, iteration, R, Rrel, silent, lbd=lbd + dlbd,
358
+ roundoff=roundoff)
355
359
  if conv:
356
360
  converged = True
357
361
  break
@@ -370,7 +374,7 @@ def _solver_arc_length(an, method, silent=False):
370
374
  warn('Singular tangent stiffness matrix', level=2, silent=silent)
371
375
  break
372
376
  if lbd_dependent:
373
- # dR/dlbd at the iterate of the tangent stiffness matrix
377
+ # -dR/dlbd at the iterate of the tangent stiffness matrix
374
378
  q_iter = callbacks.calc_load_vector(an, c + dc, fext,
375
379
  lbd + dlbd)
376
380
  dc_q = solve_kT(q_iter)
@@ -378,7 +382,7 @@ def _solver_arc_length(an, method, silent=False):
378
382
  dc_q = solve_kT(callbacks.calc_load_vector(an, c + dc,
379
383
  fext))
380
384
  iter_kT = 1
381
- dc_R = solve_kT(R)
385
+ dc_R = solve_kT(-R)
382
386
 
383
387
  # linearized constraint
384
388
  den = inner(dc, dlbd, dc_q, 1.)
@@ -421,7 +425,7 @@ def _solver_arc_length(an, method, silent=False):
421
425
  t = (1. - lbd)/dlbd
422
426
  converged, dc1, _ = _NR_iterations(an, c, t*dc, 1., fext,
423
427
  kT0=None, silent=silent, lbd_dependent=lbd_dependent,
424
- q=q_iter, w=w)
428
+ q=q_iter, w=w, K0_abs=K0_abs)
425
429
  if converged:
426
430
  msg('Converged at load factor 1.00000', level=2, silent=silent)
427
431
  an.cs.append((c + dc1).copy())
@@ -37,7 +37,6 @@ import io
37
37
  import os
38
38
  import re
39
39
  import sys
40
- import warnings
41
40
 
42
41
  import numpy as np
43
42
  from scipy.linalg import cholesky, eigh, solve_triangular
@@ -407,27 +406,27 @@ def _rayleigh_ritz(A, M, V):
407
406
  Notes
408
407
  -----
409
408
  The eigenvectors of different passes of :func:`capped_eigsh` are only
410
- `[M]`-orthogonal to the accuracy of ARPACK, e.g. `10^{-6}` between
411
- close eigenvalues of different passes. `[V]` is `[M]`-orthonormalized
412
- with the Cholesky factor `[L]` of `[V]^T[M][V]`, and the projected
413
- problem `[W]^T[A][W]\{y\} = w\{y\}`, with `[W] = [V][L]^{-T}`, is
409
+ `\boldsymbol{M}`-orthogonal to the accuracy of ARPACK, e.g. `10^{-6}` between
410
+ close eigenvalues of different passes. `\boldsymbol{V}` is `\boldsymbol{M}`-orthonormalized
411
+ with the Cholesky factor `\boldsymbol{L}` of `\boldsymbol{V}^\top \boldsymbol{M} \boldsymbol{V}`, and the projected
412
+ problem `\boldsymbol{W}^\top \boldsymbol{A} \boldsymbol{W} \boldsymbol{y} = w \boldsymbol{y}`, with `\boldsymbol{W} = \boldsymbol{V} \boldsymbol{L}^{-\top}`, is
414
413
  solved with the LAPACK driver ``dsyevr`` (``driver='evr'``), which does
415
414
  not use the ``dsteqr`` of Intel MKL broken above 32 x 32 matrices, see
416
- :func:`dsteqr_is_faulty`. The returned eigenvectors `[W]\{y\}` are
417
- `[M]`-orthonormal to the rounding errors, and the Ritz values are at
418
- least as accurate as the eigenvalues of the passes. `[V]^T[M][V]` and
419
- `[W]^T[A][W]` are symmetrized, `\frac{1}{2}([B] + [B]^T)`, before the
415
+ :func:`dsteqr_is_faulty`. The returned eigenvectors `\boldsymbol{W} \boldsymbol{y}` are
416
+ `\boldsymbol{M}`-orthonormal to the rounding errors, and the Ritz values are at
417
+ least as accurate as the eigenvalues of the passes. `\boldsymbol{V}^\top \boldsymbol{M} \boldsymbol{V}` and
418
+ `\boldsymbol{W}^\top \boldsymbol{A} \boldsymbol{W}` are symmetrized, `\frac{1}{2}(\boldsymbol{B} + \boldsymbol{B}^\top)`, before the
420
419
  Cholesky factorization and the eigenvalue solution.
421
420
 
422
421
  The Ritz values are the optimal approximations of the eigenvalues that
423
- can be obtained from the subspace spanned by the columns of `[V]`
422
+ can be obtained from the subspace spanned by the columns of `\boldsymbol{V}`
424
423
  ([1]_, Chapter 11, "Approximations from a Subspace", Section 11.4,
425
424
  "Optimality", Eq. (11.5) and Theorem 11.4.1, p. 236): the `j`-th
426
425
  Ritz value is the minimum over the `j`-dimensional subspaces of the span
427
426
  of the maximum of the Rayleigh quotient, so it can only improve when the
428
427
  span grows, and the span contains the eigenvectors of every pass. The
429
428
  theorem is stated for the standard problem; it holds for the pencil in
430
- the `[M]` inner product, through the Cholesky factor of `[V]^T[M][V]`.
429
+ the `\boldsymbol{M}` inner product, through the Cholesky factor of `\boldsymbol{V}^\top \boldsymbol{M} \boldsymbol{V}`.
431
430
 
432
431
  References
433
432
  ----------
@@ -446,7 +445,7 @@ def _rayleigh_ritz(A, M, V):
446
445
  def capped_eigsh(A, k, M, sigma, which, mode='normal', tol=0):
447
446
  r""":func:`scipy.sparse.linalg.eigsh` with ``ncv`` from :func:`arpack_ncv`
448
447
 
449
- Solves `[A]\{x\} = w [M]\{x\}` with a positive definite ``M`` about the
448
+ Solves `\boldsymbol{A} \boldsymbol{x} = w \boldsymbol{M} \boldsymbol{x}` with a positive definite ``M`` about the
450
449
  shift ``sigma``, in the shift-invert (``mode='normal'``) or Cayley
451
450
  (``mode='cayley'``) mode, selecting the eigenvalues by increasing `w`,
452
451
  e.g. the Cayley mode with ``which='SM'`` and ``sigma`` larger than all
@@ -490,7 +489,7 @@ def capped_eigsh(A, k, M, sigma, which, mode='normal', tol=0):
490
489
  Returns
491
490
  -------
492
491
  w, x : ndarray
493
- The eigenvalues and the `[M]`-orthonormal eigenvectors, by
492
+ The eigenvalues and the `\boldsymbol{M}`-orthonormal eigenvectors, by
494
493
  increasing `w` when computed in passes. ``w`` is a 1-D array of size
495
494
  ``k`` and ``x`` an array of shape ``(n, k)``.
496
495
 
@@ -502,7 +501,7 @@ def capped_eigsh(A, k, M, sigma, which, mode='normal', tol=0):
502
501
  -----
503
502
  In the shift-invert mode ARPACK computes the eigenvalues
504
503
  `\nu = 1/(w - \sigma)` of largest modulus of the operator
505
- `([A] - \sigma [M])^{-1}[M]`, i.e. the `w` nearest `\sigma` ([1]_,
504
+ `(\boldsymbol{A} - \sigma \boldsymbol{M})^{-1} \boldsymbol{M}`, i.e. the `w` nearest `\sigma` ([1]_,
506
505
  Chapter 3, Section 3.2, Eq. (3.2.2)).
507
506
  The passes keep ``k <= (max_ncv - 1)//2``, i.e. ``ncv >= 2 k + 1``.
508
507
  With ``k`` close to ``ncv`` ARPACK converges very slowly, not at all, or
@@ -510,22 +509,22 @@ def capped_eigsh(A, k, M, sigma, which, mode='normal', tol=0):
510
509
 
511
510
  The passes after the first one use the shift-invert mode about
512
511
  `\sigma_d`, the middle of the largest gap between the last five
513
- eigenvalues found, with the eigenvectors `[V]` of the previous passes
512
+ eigenvalues found, with the eigenvectors `\boldsymbol{V}` of the previous passes
514
513
  deflated from the operator:
515
514
 
516
515
  .. math::
517
516
 
518
- \{y\} = ([I] - [V][V]^T[M])([A] - \sigma_d [M])^{-1}\{x\}
517
+ \boldsymbol{y} = (\boldsymbol{I} - \boldsymbol{V} \boldsymbol{V}^\top \boldsymbol{M})(\boldsymbol{A} - \sigma_d \boldsymbol{M})^{-1} \boldsymbol{x}
519
518
 
520
- such that the eigenvalues of `[V]`, already found, become null and are
519
+ such that the eigenvalues of `\boldsymbol{V}`, already found, become null and are
521
520
  not found again. Each pass therefore returns the eigenvalues nearest
522
521
  `\sigma_d` among those not found yet, i.e. the next ones by increasing
523
522
  `w`. A shift close to them, instead of ``sigma``, separates them better:
524
523
  in the study of the panels cylinders it was 30 to 45% faster and gave
525
524
  eigenvalues 5 to 10 times more accurate. ARPACK applies the operator
526
- to `[M]\{x\}`; for an eigenvector `\{v_i\}` of `[V]`,
527
- `([A] - \sigma_d [M])^{-1}[M]\{v_i\} = \{v_i\}/(w_i - \sigma_d)` and,
528
- `[V]` being `[M]`-orthonormal, `([I] - [V][V]^T[M])\{v_i\} = \{0\}`.
525
+ to `\boldsymbol{M} \boldsymbol{x}`; for an eigenvector `\boldsymbol{v}_i` of `\boldsymbol{V}`,
526
+ `(\boldsymbol{A} - \sigma_d \boldsymbol{M})^{-1} \boldsymbol{M} \boldsymbol{v}_i = \boldsymbol{v}_i/(w_i - \sigma_d)` and,
527
+ `\boldsymbol{V}` being `\boldsymbol{M}`-orthonormal, `(\boldsymbol{I} - \boldsymbol{V} \boldsymbol{V}^\top \boldsymbol{M}) \boldsymbol{v}_i = \boldsymbol{0}`.
529
528
 
530
529
  References
531
530
  ----------
@@ -557,9 +556,7 @@ def capped_eigsh(A, k, M, sigma, which, mode='normal', tol=0):
557
556
  order = np.argsort(w, kind='stable')
558
557
  w, V = w[order], V[:, order]
559
558
  sigma_d = _deflation_shift(w, far_shift)
560
- with warnings.catch_warnings():
561
- warnings.simplefilter("ignore")
562
- lu = splu(csc_matrix(A - sigma_d*M))
559
+ lu = splu(csc_matrix(A - sigma_d*M))
563
560
  MV = M @ V
564
561
 
565
562
  def matvec(x, V=V, MV=MV, lu=lu):