structsolve 0.4.0__tar.gz → 0.4.1__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 (29) hide show
  1. {structsolve-0.4.0/structsolve.egg-info → structsolve-0.4.1}/PKG-INFO +5 -5
  2. {structsolve-0.4.0 → structsolve-0.4.1}/README.md +4 -4
  3. {structsolve-0.4.0 → structsolve-0.4.1}/pyproject.toml +1 -1
  4. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/linear_buckling.py +47 -16
  5. {structsolve-0.4.0 → structsolve-0.4.1/structsolve.egg-info}/PKG-INFO +5 -5
  6. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_linear_buckling.py +40 -0
  7. {structsolve-0.4.0 → structsolve-0.4.1}/LICENSE +0 -0
  8. {structsolve-0.4.0 → structsolve-0.4.1}/setup.cfg +0 -0
  9. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/__init__.py +0 -0
  10. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/analysis.py +0 -0
  11. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/arc_length.py +0 -0
  12. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/arc_length_crisfield.py +0 -0
  13. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/arc_length_riks.py +0 -0
  14. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/freq.py +0 -0
  15. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/logger.py +0 -0
  16. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/newton_raphson.py +0 -0
  17. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/sparseutils.py +0 -0
  18. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve/static.py +0 -0
  19. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve.egg-info/SOURCES.txt +0 -0
  20. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve.egg-info/dependency_links.txt +0 -0
  21. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve.egg-info/requires.txt +0 -0
  22. {structsolve-0.4.0 → structsolve-0.4.1}/structsolve.egg-info/top_level.txt +0 -0
  23. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_analysis.py +0 -0
  24. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_arc_length.py +0 -0
  25. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_freq.py +0 -0
  26. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_newton_raphson.py +0 -0
  27. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_sparseutils.py +0 -0
  28. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_static.py +0 -0
  29. {structsolve-0.4.0 → structsolve-0.4.1}/tests/test_static_deflection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structsolve
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Structural analysis solvers
5
5
  Author-email: "Saullo G. P. Castro" <castrosaullo@gmail.com>
6
6
  License: BSD-2-Clause
@@ -41,8 +41,7 @@ Github Actions status:
41
41
 
42
42
  Coverage status:
43
43
  [![codecov](https://github.com/saullocastro/structsolve/actions/workflows/coverage.yml/badge.svg)](https://github.com/saullocastro/structsolve/actions/workflows/coverage.yml)
44
- [![Codecov Status](https://codecov.io/gh/saullocastro/structsolve/branch/master/graph/badge.svg)](https://codecov.io/gh/saullocastro/structsolve)
45
-
44
+ [![Codecov Status](https://codecov.io/gh/saullocastro/structsolve/branch/main/graph/badge.svg?token=NNHK0SFZNH)](https://codecov.io/gh/saullocastro/structsolve)
46
45
 
47
46
  Structural analysis solvers tailored for semi-analytical models
48
47
  ===============================================================
@@ -61,7 +60,7 @@ Currently these solvers are pretty much compatible with my other repositories
61
60
  Citing this library
62
61
  ===================
63
62
 
64
- Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.4.0). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
63
+ Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.4.1). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
65
64
 
66
65
 
67
66
  Documentation
@@ -76,7 +75,8 @@ History
76
75
 
77
76
  See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
78
77
 
79
- * version 0.4.0 (2026-09-17)
78
+ * version 0.4.1 (2026-09-17)
79
+ - Fixed the eigenvalue shift of the sparse solver of `lb`, which could return buckling loads higher than the critical ones
80
80
  - Newton-Raphson with full Newton iterations and a relative convergence
81
81
  criterion by default, reaching quadratic convergence with exact tangent
82
82
  stiffness matrices
@@ -4,8 +4,7 @@ Github Actions status:
4
4
 
5
5
  Coverage status:
6
6
  [![codecov](https://github.com/saullocastro/structsolve/actions/workflows/coverage.yml/badge.svg)](https://github.com/saullocastro/structsolve/actions/workflows/coverage.yml)
7
- [![Codecov Status](https://codecov.io/gh/saullocastro/structsolve/branch/master/graph/badge.svg)](https://codecov.io/gh/saullocastro/structsolve)
8
-
7
+ [![Codecov Status](https://codecov.io/gh/saullocastro/structsolve/branch/main/graph/badge.svg?token=NNHK0SFZNH)](https://codecov.io/gh/saullocastro/structsolve)
9
8
 
10
9
  Structural analysis solvers tailored for semi-analytical models
11
10
  ===============================================================
@@ -24,7 +23,7 @@ Currently these solvers are pretty much compatible with my other repositories
24
23
  Citing this library
25
24
  ===================
26
25
 
27
- Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.4.0). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
26
+ Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.4.1). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
28
27
 
29
28
 
30
29
  Documentation
@@ -39,7 +38,8 @@ History
39
38
 
40
39
  See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
41
40
 
42
- * version 0.4.0 (2026-09-17)
41
+ * version 0.4.1 (2026-09-17)
42
+ - Fixed the eigenvalue shift of the sparse solver of `lb`, which could return buckling loads higher than the critical ones
43
43
  - Newton-Raphson with full Newton iterations and a relative convergence
44
44
  criterion by default, reaching quadratic convergence with exact tangent
45
45
  stiffness matrices
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "structsolve"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Structural analysis solvers"
9
9
  readme = "README.md"
10
10
  license = {text = "BSD-2-Clause"}
@@ -1,29 +1,60 @@
1
1
  import warnings
2
2
 
3
3
  import numpy as np
4
- from scipy.sparse.linalg import eigsh, spsolve
4
+ from scipy.sparse import csc_matrix
5
+ from scipy.sparse.linalg import eigsh, splu
5
6
  from scipy.linalg import eigh
6
7
 
7
8
  from .logger import msg, warn
8
9
  from .sparseutils import remove_null_cols
9
10
 
10
11
 
11
- def _estimate_sigma(K, KG):
12
+ def _estimate_sigma(K, KG, safety=10., max_iter=50, rel_tol=1e-3):
13
+ r"""Shift of the Cayley mode used by :func:`lb`
14
+
15
+ With ``sigma > 0``, ``eigsh(A=KG, M=K, sigma=sigma, mode='cayley',
16
+ which='SM')`` returns the eigenvalues `\mu` of ``KG u = mu K u`` with the
17
+ smallest `|(\mu + \sigma)/(\mu - \sigma)|`. This ordering selects the most
18
+ negative `\mu`, i.e. the lowest positive load multipliers `-1/\mu`, only
19
+ when `\sigma` is larger than `|\mu|` of these critical eigenvalues.
20
+ Otherwise the eigenvalues closest to `-\sigma` are returned.
21
+
22
+ The largest `|\mu|` is estimated with power iterations on
23
+ `[K]^{-1}[K_G]`, whose norm ratio in the `[K]`-norm increases towards the
24
+ largest `|\mu|`, and multiplied by ``safety``. The default shift ``1.`` is
25
+ returned when ``K`` is singular, not positive definite, or the linear
26
+ solution is inaccurate.
27
+
28
+ """
12
29
  try:
13
- rhs = KG @ np.random.RandomState(42).randn(K.shape[0])
14
- with warnings.catch_warnings(record=True) as caught:
15
- warnings.simplefilter("always")
16
- y = spsolve(K, rhs)
17
- if any(issubclass(w.category, (RuntimeWarning, Warning))
18
- and "singular" in str(w.message).lower() for w in caught):
19
- return 1.
20
- residual = np.linalg.norm(K @ y - rhs)
21
- if residual > 1e-6 * np.linalg.norm(rhs):
22
- return 1.
23
- sigma = abs((y @ KG @ y) / (y @ K @ y))
24
- if not np.isfinite(sigma) or sigma == 0:
25
- return 1.
26
- return sigma
30
+ with warnings.catch_warnings():
31
+ warnings.simplefilter("ignore")
32
+ lu = splu(csc_matrix(K))
33
+ x = np.random.RandomState(42).randn(K.shape[0])
34
+ Kx = K @ x
35
+ mu_max = 0.
36
+ for i in range(max_iter):
37
+ xKx = x @ Kx
38
+ if not np.isfinite(xKx) or xKx <= 0:
39
+ return 1.
40
+ x = x / np.sqrt(xKx)
41
+ rhs = KG @ x
42
+ y = lu.solve(rhs)
43
+ Ky = K @ y
44
+ if i == 0:
45
+ residual = np.linalg.norm(Ky - rhs)
46
+ if not residual <= 1e-6 * np.linalg.norm(rhs):
47
+ return 1.
48
+ # ||y||_K / ||x||_K, with ||x||_K = 1
49
+ ratio = np.sqrt(abs(y @ Ky))
50
+ if not np.isfinite(ratio) or ratio == 0:
51
+ return 1.
52
+ converged = i > 0 and ratio - mu_max <= rel_tol * ratio
53
+ mu_max = max(mu_max, ratio)
54
+ if converged:
55
+ break
56
+ x, Kx = y, Ky
57
+ return safety * mu_max
27
58
  except Exception:
28
59
  return 1.
29
60
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structsolve
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Structural analysis solvers
5
5
  Author-email: "Saullo G. P. Castro" <castrosaullo@gmail.com>
6
6
  License: BSD-2-Clause
@@ -41,8 +41,7 @@ Github Actions status:
41
41
 
42
42
  Coverage status:
43
43
  [![codecov](https://github.com/saullocastro/structsolve/actions/workflows/coverage.yml/badge.svg)](https://github.com/saullocastro/structsolve/actions/workflows/coverage.yml)
44
- [![Codecov Status](https://codecov.io/gh/saullocastro/structsolve/branch/master/graph/badge.svg)](https://codecov.io/gh/saullocastro/structsolve)
45
-
44
+ [![Codecov Status](https://codecov.io/gh/saullocastro/structsolve/branch/main/graph/badge.svg?token=NNHK0SFZNH)](https://codecov.io/gh/saullocastro/structsolve)
46
45
 
47
46
  Structural analysis solvers tailored for semi-analytical models
48
47
  ===============================================================
@@ -61,7 +60,7 @@ Currently these solvers are pretty much compatible with my other repositories
61
60
  Citing this library
62
61
  ===================
63
62
 
64
- Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.4.0). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
63
+ Saullo G. P. Castro (2026). Structural analysis solvers tailored for semi-analytical models (Version 0.4.1). Zenodo. DOI: https://doi.org/10.5281/zenodo.2581212.
65
64
 
66
65
 
67
66
  Documentation
@@ -76,7 +75,8 @@ History
76
75
 
77
76
  See [CHANGELOG.md](CHANGELOG.md) for the details of each version.
78
77
 
79
- * version 0.4.0 (2026-09-17)
78
+ * version 0.4.1 (2026-09-17)
79
+ - Fixed the eigenvalue shift of the sparse solver of `lb`, which could return buckling loads higher than the critical ones
80
80
  - Newton-Raphson with full Newton iterations and a relative convergence
81
81
  criterion by default, reaching quadratic convergence with exact tangent
82
82
  stiffness matrices
@@ -41,6 +41,46 @@ def test_lb_dense_solver():
41
41
  np.testing.assert_allclose(eigvals[:5], -2.0, rtol=1e-6)
42
42
 
43
43
 
44
+ def test_lb_sparse_shift_mixed_spectrum():
45
+ """Sparse and dense lb agree when the load multipliers have mixed signs
46
+
47
+ Regression test: the shift of the sparse solver was a single Rayleigh
48
+ quotient, in which the positive and negative eigenvalues of
49
+ ``KG u = mu K u`` cancel. The shift landed below the critical ``|mu|``
50
+ and eigsh returned the load multipliers near ``1/sigma`` instead of the
51
+ lowest ones.
52
+ """
53
+ from scipy.sparse import diags
54
+ from structsolve.linear_buckling import _estimate_sigma
55
+
56
+ n = 80
57
+ k = np.linspace(1e6, 5e6, n)
58
+ lambdas = np.concatenate([[1.0, 1.0, 1.1, 1.2],
59
+ np.linspace(1.5, 4., 36),
60
+ -np.linspace(1.5, 4., 40)])
61
+ mu = -1. / lambdas
62
+ K = diags(k).tocsc()
63
+ KG = diags(mu * k).tocsc()
64
+
65
+ # the shift must bound the largest |mu| of the critical eigenvalues
66
+ assert _estimate_sigma(K, KG) >= np.abs(mu).max()
67
+
68
+ eig_sparse, _ = lb(K, KG, silent=True, num_eigvalues=10)
69
+ eig_dense, _ = lb(K, KG, silent=True, sparse_solver=False)
70
+ np.testing.assert_allclose(eig_sparse[:4], [1.0, 1.0, 1.1, 1.2],
71
+ rtol=1e-8)
72
+ np.testing.assert_allclose(eig_sparse[:4], eig_dense[:4], rtol=1e-8)
73
+
74
+
75
+ def test_lb_sparse_singular_K():
76
+ """The shift falls back to 1 when K is singular"""
77
+ from structsolve.linear_buckling import _estimate_sigma
78
+
79
+ K = csc_matrix(np.diag([1., 2., 0., 4., 5.]))
80
+ KG = csc_matrix(np.eye(5))
81
+ assert _estimate_sigma(K, KG) == 1.
82
+
83
+
44
84
  def test_lb_plate_buckling_fsdt():
45
85
  """Test plate buckling (FSDT) based on semi-analytical Ritz method.
46
86
 
File without changes
File without changes