weac 2.6.1__tar.gz → 2.6.3__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.
- {weac-2.6.1 → weac-2.6.3}/CITATION.cff +1 -1
- {weac-2.6.1/weac.egg-info → weac-2.6.3}/PKG-INFO +13 -2
- {weac-2.6.1 → weac-2.6.3}/pyproject.toml +36 -7
- weac-2.6.3/tests/test_eigensystem.py +467 -0
- {weac-2.6.1 → weac-2.6.3}/tests/test_mixins.py +63 -0
- {weac-2.6.1 → weac-2.6.3}/tests/test_plot.py +3 -0
- {weac-2.6.1 → weac-2.6.3}/weac/__init__.py +4 -8
- {weac-2.6.1 → weac-2.6.3}/weac/eigensystem.py +180 -139
- {weac-2.6.1 → weac-2.6.3}/weac/inverse.py +5 -8
- {weac-2.6.1 → weac-2.6.3}/weac/layered.py +24 -9
- {weac-2.6.1 → weac-2.6.3}/weac/mixins.py +8 -10
- {weac-2.6.1 → weac-2.6.3}/weac/plot.py +244 -196
- {weac-2.6.1 → weac-2.6.3}/weac/tools.py +6 -5
- {weac-2.6.1 → weac-2.6.3/weac.egg-info}/PKG-INFO +13 -2
- weac-2.6.3/weac.egg-info/requires.txt +23 -0
- weac-2.6.1/tests/test_eigensystem.py +0 -104
- weac-2.6.1/weac.egg-info/requires.txt +0 -11
- {weac-2.6.1 → weac-2.6.3}/LICENSE +0 -0
- {weac-2.6.1 → weac-2.6.3}/MANIFEST.in +0 -0
- {weac-2.6.1 → weac-2.6.3}/README.md +0 -0
- {weac-2.6.1 → weac-2.6.3}/img/bc.png +0 -0
- {weac-2.6.1 → weac-2.6.3}/img/layering.png +0 -0
- {weac-2.6.1 → weac-2.6.3}/img/logo.png +0 -0
- {weac-2.6.1 → weac-2.6.3}/img/model.png +0 -0
- {weac-2.6.1 → weac-2.6.3}/img/profiles.png +0 -0
- {weac-2.6.1 → weac-2.6.3}/img/systems.png +0 -0
- {weac-2.6.1 → weac-2.6.3}/setup.cfg +0 -0
- {weac-2.6.1 → weac-2.6.3}/tests/test_layered.py +0 -0
- {weac-2.6.1 → weac-2.6.3}/tests/test_tools.py +0 -0
- {weac-2.6.1 → weac-2.6.3}/weac.egg-info/SOURCES.txt +0 -0
- {weac-2.6.1 → weac-2.6.3}/weac.egg-info/dependency_links.txt +0 -0
- {weac-2.6.1 → weac-2.6.3}/weac.egg-info/top_level.txt +0 -0
|
@@ -8,7 +8,7 @@ authors:
|
|
|
8
8
|
- family-names: "Weissgraeber"
|
|
9
9
|
given-names: "Philipp"
|
|
10
10
|
orcid: "https://orcid.org/0000-0001-8320-8672"
|
|
11
|
-
version: 2.6.
|
|
11
|
+
version: 2.6.3
|
|
12
12
|
date-released: 2021-12-30
|
|
13
13
|
identifiers:
|
|
14
14
|
- description: Collection of archived snapshots of all versions of WEAC
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: weac
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.3
|
|
4
4
|
Summary: Weak layer anticrack nucleation model
|
|
5
5
|
Author-email: 2phi GbR <mail@2phi.de>
|
|
6
6
|
License: Proprietary
|
|
@@ -21,9 +21,20 @@ Requires-Dist: scipy>=1.14.0
|
|
|
21
21
|
Provides-Extra: interactive
|
|
22
22
|
Requires-Dist: jupyter; extra == "interactive"
|
|
23
23
|
Requires-Dist: ipython>=8.12.3; extra == "interactive"
|
|
24
|
+
Requires-Dist: notebook>=7.0.0; extra == "interactive"
|
|
25
|
+
Requires-Dist: ipywidgets>=8.0.0; extra == "interactive"
|
|
24
26
|
Provides-Extra: docs
|
|
25
27
|
Requires-Dist: sphinx; extra == "docs"
|
|
26
28
|
Requires-Dist: sphinxawesome-theme; extra == "docs"
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
27
38
|
|
|
28
39
|
<!-- LOGO AND TITLE-->
|
|
29
40
|
<!-- <p align="right"><img src="https://github.com/2phi/weac/raw/main/img/logo.png" alt="Logo" width="80" height="80"></p> -->
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weac"
|
|
7
|
-
version = "2.6.
|
|
7
|
+
version = "2.6.3"
|
|
8
8
|
authors = [
|
|
9
9
|
{name = "2phi GbR", email = "mail@2phi.de"},
|
|
10
10
|
]
|
|
@@ -31,24 +31,53 @@ Documentation = "https://2phi.github.io/weac"
|
|
|
31
31
|
"Issues and feature requests" = "https://github.com/2phi/weac/issues"
|
|
32
32
|
|
|
33
33
|
[project.optional-dependencies]
|
|
34
|
-
interactive = [
|
|
34
|
+
interactive = [
|
|
35
|
+
"jupyter",
|
|
36
|
+
"ipython>=8.12.3",
|
|
37
|
+
"notebook>=7.0.0",
|
|
38
|
+
"ipywidgets>=8.0.0"
|
|
39
|
+
]
|
|
35
40
|
docs = ["sphinx", "sphinxawesome-theme"]
|
|
41
|
+
test = [
|
|
42
|
+
"pytest>=7.0.0",
|
|
43
|
+
"pytest-cov>=4.0.0"
|
|
44
|
+
]
|
|
45
|
+
dev = [
|
|
46
|
+
"black>=23.0.0",
|
|
47
|
+
"mypy>=1.0.0",
|
|
48
|
+
"pytest>=7.0.0",
|
|
49
|
+
"pytest-cov>=4.0.0"
|
|
50
|
+
]
|
|
36
51
|
|
|
37
52
|
[tool.setuptools]
|
|
38
53
|
packages = ["weac"]
|
|
39
54
|
package-data = {"*" = ["CITATION.cff"], "img" = ["*.png"]}
|
|
40
55
|
|
|
41
56
|
[tool.ruff]
|
|
57
|
+
line-length = 89
|
|
58
|
+
target-version = "py312"
|
|
59
|
+
|
|
60
|
+
[tool.ruff.lint]
|
|
42
61
|
ignore = ["E741"]
|
|
43
62
|
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"**/*.ipynb" = ["F403", "F405", "F401"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.format]
|
|
67
|
+
line-ending = "lf"
|
|
68
|
+
|
|
44
69
|
[tool.pylint.typecheck]
|
|
45
70
|
generated-members = "matplotlib.cm.*"
|
|
46
71
|
|
|
72
|
+
[tool.pylint.main]
|
|
73
|
+
# Ignore notebook files for pylint to avoid false positives and unused-import in exploratory cells
|
|
74
|
+
ignore-patterns = [".*\\.ipynb$"]
|
|
75
|
+
|
|
47
76
|
[tool.pycodestyle]
|
|
48
77
|
ignore = ["E121", "E123", "E126", "E211", "E226", "E24", "E704", "W503", "W504", "E741"]
|
|
49
78
|
|
|
50
79
|
[tool.bumpversion]
|
|
51
|
-
current_version = "2.6.
|
|
80
|
+
current_version = "2.6.3"
|
|
52
81
|
tag = true
|
|
53
82
|
commit = true
|
|
54
83
|
|
|
@@ -60,13 +89,13 @@ filename = "CITATION.cff"
|
|
|
60
89
|
|
|
61
90
|
[[tool.bumpversion.files]]
|
|
62
91
|
filename = "weac/__init__.py"
|
|
63
|
-
search = "__version__ =
|
|
64
|
-
replace = "__version__ =
|
|
92
|
+
search = "__version__ = \"{current_version}\""
|
|
93
|
+
replace = "__version__ = \"{new_version}\""
|
|
65
94
|
|
|
66
95
|
[[tool.bumpversion.files]]
|
|
67
96
|
filename = "demo/demo.ipynb"
|
|
68
97
|
|
|
69
98
|
[[tool.bumpversion.files]]
|
|
70
99
|
filename = "docs/sphinx/conf.py"
|
|
71
|
-
search = "release =
|
|
72
|
-
replace = "release =
|
|
100
|
+
search = "release = \"{current_version}\""
|
|
101
|
+
replace = "release = \"{new_version}\""
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the Eigensystem class in the WEAC package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from weac.eigensystem import Eigensystem
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestEigensystem(unittest.TestCase):
|
|
14
|
+
"""Test cases for the Eigensystem class."""
|
|
15
|
+
|
|
16
|
+
def setUp(self):
|
|
17
|
+
"""Set up test fixtures."""
|
|
18
|
+
# Create an Eigensystem instance for testing
|
|
19
|
+
self.eigen = Eigensystem(system="pst-")
|
|
20
|
+
|
|
21
|
+
# Set up properties needed for tests
|
|
22
|
+
self.eigen.set_beam_properties(layers="A")
|
|
23
|
+
self.eigen.set_foundation_properties()
|
|
24
|
+
|
|
25
|
+
def test_initialization(self):
|
|
26
|
+
"""Test that Eigensystem initializes with correct default values."""
|
|
27
|
+
# Test default initialization
|
|
28
|
+
self.assertEqual(self.eigen.system, "pst-")
|
|
29
|
+
self.assertFalse(self.eigen.touchdown)
|
|
30
|
+
self.assertAlmostEqual(self.eigen.g, 9810.0) # Gravitational constant
|
|
31
|
+
|
|
32
|
+
def test_all_system_types(self):
|
|
33
|
+
"""Test initialization with all possible system types."""
|
|
34
|
+
system_types = ["pst-", "-pst", "vpst-", "-vpst", "skier", "skiers"]
|
|
35
|
+
|
|
36
|
+
for system in system_types:
|
|
37
|
+
with self.subTest(system=system):
|
|
38
|
+
eigen = Eigensystem(system=system)
|
|
39
|
+
self.assertEqual(eigen.system, system)
|
|
40
|
+
eigen.set_beam_properties(layers="A")
|
|
41
|
+
eigen.set_foundation_properties()
|
|
42
|
+
eigen.calc_fundamental_system()
|
|
43
|
+
|
|
44
|
+
# Basic functionality checks
|
|
45
|
+
self.assertIsNotNone(eigen.kn)
|
|
46
|
+
self.assertIsNotNone(eigen.kt)
|
|
47
|
+
self.assertIsNotNone(eigen.A11)
|
|
48
|
+
self.assertIsNotNone(eigen.ewC)
|
|
49
|
+
self.assertIsNotNone(eigen.ewR)
|
|
50
|
+
|
|
51
|
+
def test_system_type_eigenvalue_comparison(self):
|
|
52
|
+
"""Test and compare eigenvalues between different system types."""
|
|
53
|
+
# Define consistent properties for all systems
|
|
54
|
+
beam_props = [[300, 200]]
|
|
55
|
+
foundation_props = {"t": 30.0, "E": 0.25, "nu": 0.25}
|
|
56
|
+
|
|
57
|
+
# Create eigensystems with different system types
|
|
58
|
+
systems = {}
|
|
59
|
+
for system_type in ["pst-", "-pst", "vpst-", "-vpst", "skier", "skiers"]:
|
|
60
|
+
eigen = Eigensystem(system=system_type)
|
|
61
|
+
eigen.set_beam_properties(beam_props)
|
|
62
|
+
eigen.set_foundation_properties(**foundation_props)
|
|
63
|
+
eigen.calc_fundamental_system()
|
|
64
|
+
systems[system_type] = eigen
|
|
65
|
+
|
|
66
|
+
# Check that eigenvalues are produced for all systems
|
|
67
|
+
for system_type, eigen in systems.items():
|
|
68
|
+
with self.subTest(system=system_type):
|
|
69
|
+
# Check that eigenvalues are calculated
|
|
70
|
+
self.assertIsNotNone(eigen.ewC)
|
|
71
|
+
self.assertIsNotNone(eigen.ewR)
|
|
72
|
+
|
|
73
|
+
# Check that the number of eigenvalues is consistent with expectations
|
|
74
|
+
# For this beam on elastic foundation problem, we expect 2 complex eigenvalues
|
|
75
|
+
self.assertEqual(
|
|
76
|
+
len(eigen.ewC),
|
|
77
|
+
2,
|
|
78
|
+
f"System {system_type} should have 2 complex eigenvalues",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# For PST-type systems, expect 2 real eigenvalues
|
|
82
|
+
if system_type in ["pst-", "-pst", "vpst-", "-vpst"]:
|
|
83
|
+
self.assertEqual(
|
|
84
|
+
len(eigen.ewR),
|
|
85
|
+
2,
|
|
86
|
+
f"PST-type system {system_type} should have 2 real eigenvalues",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Compare eigenvalues between different PST variants
|
|
90
|
+
# Corresponding eigenvalues may differ in sign but should have similar magnitudes
|
|
91
|
+
for i, _ in enumerate(systems["pst-"].ewC):
|
|
92
|
+
# Compare magnitudes of complex eigenvalues between PST variants
|
|
93
|
+
mag_pst_right = abs(systems["pst-"].ewC[i])
|
|
94
|
+
mag_pst_left = abs(systems["-pst"].ewC[i])
|
|
95
|
+
self.assertAlmostEqual(
|
|
96
|
+
mag_pst_right,
|
|
97
|
+
mag_pst_left,
|
|
98
|
+
places=2,
|
|
99
|
+
msg=f"Complex eigenvalue {i} should have similar magnitude in PST variants",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
mag_vpst_right = abs(systems["vpst-"].ewC[i])
|
|
103
|
+
mag_vpst_left = abs(systems["-vpst"].ewC[i])
|
|
104
|
+
self.assertAlmostEqual(
|
|
105
|
+
mag_vpst_right,
|
|
106
|
+
mag_vpst_left,
|
|
107
|
+
places=2,
|
|
108
|
+
msg=f"Complex eigenvalue {i} should have similar magnitude in VPST variants",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def test_system_response_for_different_boundary_conditions(self):
|
|
112
|
+
"""Test system response for different boundary conditions."""
|
|
113
|
+
# Create test systems with different boundary conditions
|
|
114
|
+
systems = {
|
|
115
|
+
"pst-": Eigensystem(system="pst-"),
|
|
116
|
+
"skier": Eigensystem(system="skier"),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Set common properties
|
|
120
|
+
for system_type, eigen in systems.items():
|
|
121
|
+
eigen.set_beam_properties(layers="A")
|
|
122
|
+
eigen.set_foundation_properties()
|
|
123
|
+
eigen.calc_fundamental_system()
|
|
124
|
+
|
|
125
|
+
# Test coordinates for evaluation
|
|
126
|
+
x_values = np.linspace(0, 1000, 5) # 5 points from 0 to 1000 mm
|
|
127
|
+
|
|
128
|
+
# Test constants for the solution
|
|
129
|
+
C = np.zeros((6, 1)) # Zero constants for simplicity
|
|
130
|
+
|
|
131
|
+
# Set an incline angle
|
|
132
|
+
phi = 30 # degrees
|
|
133
|
+
|
|
134
|
+
# Test that solutions can be computed for both systems
|
|
135
|
+
for system_type, eigen in systems.items():
|
|
136
|
+
with self.subTest(system=system_type):
|
|
137
|
+
# Test bedded solution
|
|
138
|
+
z_bedded = eigen.z(x_values, C, 0, phi, bed=True)
|
|
139
|
+
|
|
140
|
+
# Check solution dimensions
|
|
141
|
+
self.assertEqual(z_bedded.shape[0], 6) # 6 solution components
|
|
142
|
+
self.assertEqual(
|
|
143
|
+
z_bedded.shape[1], len(x_values)
|
|
144
|
+
) # One column per x value
|
|
145
|
+
|
|
146
|
+
# Test unbedded solution
|
|
147
|
+
z_unbedded = eigen.z(x_values, C, 0, phi, bed=False)
|
|
148
|
+
|
|
149
|
+
# Check solution dimensions
|
|
150
|
+
self.assertEqual(z_unbedded.shape[0], 6)
|
|
151
|
+
self.assertEqual(z_unbedded.shape[1], len(x_values))
|
|
152
|
+
|
|
153
|
+
# Test that bedded and unbedded solutions are different
|
|
154
|
+
self.assertFalse(np.allclose(z_bedded, z_unbedded))
|
|
155
|
+
|
|
156
|
+
# Test with a single x value to cover line 656
|
|
157
|
+
z_single = eigen.z(x_values[0], C, 0, phi, bed=True)
|
|
158
|
+
self.assertEqual(z_single.shape[0], 6)
|
|
159
|
+
self.assertEqual(z_single.shape[1], 1)
|
|
160
|
+
|
|
161
|
+
# Test skier load
|
|
162
|
+
skier_system = systems["skier"]
|
|
163
|
+
skier_mass = 80 # kg
|
|
164
|
+
Fn, Ft = skier_system.get_skier_load(skier_mass, phi)
|
|
165
|
+
|
|
166
|
+
# Check skier load values are reasonable
|
|
167
|
+
self.assertGreater(Fn, 0)
|
|
168
|
+
self.assertLess(Ft, 0) # Downslope component should be negative
|
|
169
|
+
|
|
170
|
+
def test_set_beam_properties(self):
|
|
171
|
+
"""Test setting beam properties with different layer configurations."""
|
|
172
|
+
# Create a new instance to test from scratch
|
|
173
|
+
eigen = Eigensystem(system="pst-")
|
|
174
|
+
|
|
175
|
+
# Test with a single layer
|
|
176
|
+
eigen.set_beam_properties(layers="A")
|
|
177
|
+
|
|
178
|
+
# Check that slab property is set
|
|
179
|
+
self.assertIsNotNone(eigen.slab)
|
|
180
|
+
# The actual shape might be different from what we expected
|
|
181
|
+
# Let's just check that it's a 2D array with at least one row
|
|
182
|
+
self.assertGreaterEqual(eigen.slab.shape[0], 1)
|
|
183
|
+
|
|
184
|
+
# Test with multiple layers
|
|
185
|
+
eigen.set_beam_properties(
|
|
186
|
+
[
|
|
187
|
+
[200, 100], # [density (kg/m^3), thickness (mm)]
|
|
188
|
+
[300, 150],
|
|
189
|
+
[400, 50],
|
|
190
|
+
]
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Check that slab property is updated
|
|
194
|
+
self.assertIsNotNone(eigen.slab)
|
|
195
|
+
# Check that we have the right number of layers
|
|
196
|
+
self.assertEqual(eigen.slab.shape[0], 3)
|
|
197
|
+
|
|
198
|
+
def test_set_beam_properties_with_update(self):
|
|
199
|
+
"""Test setting beam properties with update flag."""
|
|
200
|
+
# Create a new instance
|
|
201
|
+
eigen = Eigensystem(system="pst-")
|
|
202
|
+
|
|
203
|
+
# Set up a foundation to make calc_fundamental_system work
|
|
204
|
+
eigen.set_foundation_properties()
|
|
205
|
+
|
|
206
|
+
# Test with update=True, which should trigger calc_fundamental_system
|
|
207
|
+
with mock.patch.object(eigen, "calc_fundamental_system") as mock_calc:
|
|
208
|
+
eigen.set_beam_properties([[300, 200]], update=True)
|
|
209
|
+
mock_calc.assert_called_once()
|
|
210
|
+
|
|
211
|
+
@mock.patch("weac.eigensystem.load_dummy_profile")
|
|
212
|
+
def test_set_beam_properties_with_string(self, mock_load_profile):
|
|
213
|
+
"""Test setting beam properties using a string input."""
|
|
214
|
+
# Mock the load_dummy_profile function
|
|
215
|
+
mock_layers = np.array([[300, 200]])
|
|
216
|
+
mock_E = np.array([10.0])
|
|
217
|
+
mock_load_profile.return_value = (mock_layers, mock_E)
|
|
218
|
+
|
|
219
|
+
# Create a new instance
|
|
220
|
+
eigen = Eigensystem(system="pst-")
|
|
221
|
+
|
|
222
|
+
# Call set_beam_properties with a string
|
|
223
|
+
eigen.set_beam_properties(layers="B")
|
|
224
|
+
|
|
225
|
+
# Verify the mock was called with the right parameter
|
|
226
|
+
mock_load_profile.assert_called_once_with("B")
|
|
227
|
+
|
|
228
|
+
# Check that the properties were set correctly
|
|
229
|
+
self.assertIsNotNone(eigen.slab)
|
|
230
|
+
self.assertGreaterEqual(eigen.slab.shape[0], 1)
|
|
231
|
+
|
|
232
|
+
def test_set_foundation_properties(self):
|
|
233
|
+
"""Test setting foundation properties."""
|
|
234
|
+
# Create a new instance to test from scratch
|
|
235
|
+
eigen = Eigensystem(system="pst-")
|
|
236
|
+
|
|
237
|
+
# Test with default parameters
|
|
238
|
+
eigen.set_foundation_properties()
|
|
239
|
+
|
|
240
|
+
# Check that weak layer properties are set
|
|
241
|
+
self.assertIsNotNone(eigen.weak)
|
|
242
|
+
self.assertIn("E", eigen.weak)
|
|
243
|
+
self.assertIn("nu", eigen.weak)
|
|
244
|
+
|
|
245
|
+
# Test with custom parameters
|
|
246
|
+
eigen.set_foundation_properties(
|
|
247
|
+
t=50.0, # Weak layer thickness (mm)
|
|
248
|
+
E=0.5, # Young's modulus (MPa)
|
|
249
|
+
nu=0.3, # Poisson's ratio
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Check that weak layer properties are updated
|
|
253
|
+
self.assertIsNotNone(eigen.weak)
|
|
254
|
+
self.assertEqual(eigen.weak["E"], 0.5)
|
|
255
|
+
self.assertEqual(eigen.weak["nu"], 0.3)
|
|
256
|
+
self.assertEqual(eigen.t, 50.0)
|
|
257
|
+
|
|
258
|
+
def test_set_foundation_properties_with_update(self):
|
|
259
|
+
"""Test setting foundation properties with update flag."""
|
|
260
|
+
# Create a new instance
|
|
261
|
+
eigen = Eigensystem(system="pst-")
|
|
262
|
+
|
|
263
|
+
# Set beam properties to make calc_fundamental_system work
|
|
264
|
+
eigen.set_beam_properties(layers="A")
|
|
265
|
+
|
|
266
|
+
# Test with update=True, which should trigger calc_fundamental_system
|
|
267
|
+
with mock.patch.object(eigen, "calc_fundamental_system") as mock_calc:
|
|
268
|
+
eigen.set_foundation_properties(update=True)
|
|
269
|
+
mock_calc.assert_called_once()
|
|
270
|
+
|
|
271
|
+
def test_calc_fundamental_system(self):
|
|
272
|
+
"""Test calculation of the fundamental system."""
|
|
273
|
+
# Calculate the fundamental system
|
|
274
|
+
self.eigen.calc_fundamental_system()
|
|
275
|
+
|
|
276
|
+
# Check that the system has been initialized
|
|
277
|
+
self.assertIsNotNone(
|
|
278
|
+
getattr(self.eigen, "kn", None)
|
|
279
|
+
) # Foundation normal stiffness
|
|
280
|
+
self.assertIsNotNone(
|
|
281
|
+
getattr(self.eigen, "kt", None)
|
|
282
|
+
) # Foundation shear stiffness
|
|
283
|
+
self.assertIsNotNone(getattr(self.eigen, "A11", None)) # Extensional stiffness
|
|
284
|
+
self.assertIsNotNone(
|
|
285
|
+
getattr(self.eigen, "B11", None)
|
|
286
|
+
) # Bending-extension coupling stiffness
|
|
287
|
+
self.assertIsNotNone(getattr(self.eigen, "D11", None)) # Bending stiffness
|
|
288
|
+
self.assertIsNotNone(getattr(self.eigen, "kA55", None)) # Shear stiffness
|
|
289
|
+
|
|
290
|
+
def test_set_surface_load(self):
|
|
291
|
+
"""Test setting surface load."""
|
|
292
|
+
# Create a new instance
|
|
293
|
+
eigen = Eigensystem()
|
|
294
|
+
|
|
295
|
+
# Initial value should be 0
|
|
296
|
+
self.assertEqual(eigen.p, 0)
|
|
297
|
+
|
|
298
|
+
# Set a surface load
|
|
299
|
+
eigen.set_surface_load(100.0)
|
|
300
|
+
self.assertEqual(eigen.p, 100.0)
|
|
301
|
+
|
|
302
|
+
def test_get_load_vector(self):
|
|
303
|
+
"""Test getting the load vector."""
|
|
304
|
+
# Initialize with beam and foundation properties
|
|
305
|
+
eigen = Eigensystem(system="pst-")
|
|
306
|
+
eigen.set_beam_properties(layers="A")
|
|
307
|
+
eigen.set_foundation_properties()
|
|
308
|
+
eigen.calc_fundamental_system()
|
|
309
|
+
|
|
310
|
+
# Test the load vector calculation
|
|
311
|
+
phi = 30 # degrees
|
|
312
|
+
load_vector = eigen.get_load_vector(phi)
|
|
313
|
+
|
|
314
|
+
# Check expected dimensions and structure
|
|
315
|
+
self.assertEqual(load_vector.shape, (6, 1))
|
|
316
|
+
self.assertEqual(load_vector[0, 0], 0) # First component should be 0
|
|
317
|
+
self.assertEqual(load_vector[2, 0], 0) # Third component should be 0
|
|
318
|
+
self.assertEqual(load_vector[4, 0], 0) # Fifth component should be 0
|
|
319
|
+
|
|
320
|
+
# Test with surface load to ensure all branches are covered
|
|
321
|
+
eigen.set_surface_load(100.0)
|
|
322
|
+
load_vector_with_surface = eigen.get_load_vector(phi)
|
|
323
|
+
|
|
324
|
+
# The resulting load vector should be different
|
|
325
|
+
self.assertFalse(np.array_equal(load_vector, load_vector_with_surface))
|
|
326
|
+
|
|
327
|
+
def test_pst_specific_behavior(self):
|
|
328
|
+
"""Test PST (Propagation Saw Test) specific behavior."""
|
|
329
|
+
# Test pst- (cut from right)
|
|
330
|
+
eigen_pst_right = Eigensystem(system="pst-")
|
|
331
|
+
eigen_pst_right.set_beam_properties(layers="A")
|
|
332
|
+
eigen_pst_right.set_foundation_properties()
|
|
333
|
+
eigen_pst_right.calc_fundamental_system()
|
|
334
|
+
|
|
335
|
+
# Test -pst (cut from left)
|
|
336
|
+
eigen_pst_left = Eigensystem(system="-pst")
|
|
337
|
+
eigen_pst_left.set_beam_properties(layers="A")
|
|
338
|
+
eigen_pst_left.set_foundation_properties()
|
|
339
|
+
eigen_pst_left.calc_fundamental_system()
|
|
340
|
+
|
|
341
|
+
# Both should have valid eigensystems but potentially different values
|
|
342
|
+
self.assertTrue(eigen_pst_right.ewC is not False)
|
|
343
|
+
self.assertTrue(eigen_pst_left.ewC is not False)
|
|
344
|
+
|
|
345
|
+
def test_vpst_specific_behavior(self):
|
|
346
|
+
"""Test vertical PST specific behavior."""
|
|
347
|
+
# Test vpst- (vertical cut from right)
|
|
348
|
+
eigen_vpst_right = Eigensystem(system="vpst-")
|
|
349
|
+
eigen_vpst_right.set_beam_properties(layers="A")
|
|
350
|
+
eigen_vpst_right.set_foundation_properties()
|
|
351
|
+
eigen_vpst_right.calc_fundamental_system()
|
|
352
|
+
|
|
353
|
+
# Test -vpst (vertical cut from left)
|
|
354
|
+
eigen_vpst_left = Eigensystem(system="-vpst")
|
|
355
|
+
eigen_vpst_left.set_beam_properties(layers="A")
|
|
356
|
+
eigen_vpst_left.set_foundation_properties()
|
|
357
|
+
eigen_vpst_left.calc_fundamental_system()
|
|
358
|
+
|
|
359
|
+
# Both should have valid eigensystems
|
|
360
|
+
self.assertTrue(eigen_vpst_right.ewC is not False)
|
|
361
|
+
self.assertTrue(eigen_vpst_left.ewC is not False)
|
|
362
|
+
|
|
363
|
+
def test_skier_specific_behavior(self):
|
|
364
|
+
"""Test skier-specific behavior."""
|
|
365
|
+
# Test single skier
|
|
366
|
+
eigen_skier = Eigensystem(system="skier")
|
|
367
|
+
eigen_skier.set_beam_properties(layers="A")
|
|
368
|
+
eigen_skier.set_foundation_properties()
|
|
369
|
+
eigen_skier.calc_fundamental_system()
|
|
370
|
+
|
|
371
|
+
# Test skier load calculation
|
|
372
|
+
skier_mass = 80 # kg
|
|
373
|
+
slope_angle = 30 # degrees
|
|
374
|
+
Fn, Ft = eigen_skier.get_skier_load(skier_mass, slope_angle)
|
|
375
|
+
|
|
376
|
+
# Check that load values are calculated and reasonable
|
|
377
|
+
self.assertGreater(Fn, 0) # Normal force should be positive
|
|
378
|
+
self.assertLess(Ft, 0) # Tangential force should be negative on a slope
|
|
379
|
+
|
|
380
|
+
# Test multiple skiers
|
|
381
|
+
eigen_skiers = Eigensystem(system="skiers")
|
|
382
|
+
eigen_skiers.set_beam_properties(layers="A")
|
|
383
|
+
eigen_skiers.set_foundation_properties()
|
|
384
|
+
eigen_skiers.calc_fundamental_system()
|
|
385
|
+
|
|
386
|
+
# Both should have valid eigensystems
|
|
387
|
+
self.assertTrue(eigen_skier.ewC is not False)
|
|
388
|
+
self.assertTrue(eigen_skiers.ewC is not False)
|
|
389
|
+
|
|
390
|
+
def test_touchdown_parameter(self):
|
|
391
|
+
"""Test the touchdown parameter behavior."""
|
|
392
|
+
# Test with touchdown=True
|
|
393
|
+
eigen_touchdown = Eigensystem(system="pst-", touchdown=True)
|
|
394
|
+
self.assertTrue(eigen_touchdown.touchdown)
|
|
395
|
+
|
|
396
|
+
# Test with touchdown=False (default)
|
|
397
|
+
eigen_no_touchdown = Eigensystem(system="pst-")
|
|
398
|
+
self.assertFalse(eigen_no_touchdown.touchdown)
|
|
399
|
+
|
|
400
|
+
def test_touchdown_with_all_system_types(self):
|
|
401
|
+
"""Test the touchdown flag behavior with all possible system types."""
|
|
402
|
+
system_types = ["pst-", "-pst", "vpst-", "-vpst", "skier", "skiers"]
|
|
403
|
+
|
|
404
|
+
# Test with touchdown=True for all system types
|
|
405
|
+
for system in system_types:
|
|
406
|
+
with self.subTest(system=system, touchdown=True):
|
|
407
|
+
eigen = Eigensystem(system=system, touchdown=True)
|
|
408
|
+
self.assertTrue(eigen.touchdown)
|
|
409
|
+
eigen.set_beam_properties(layers="A")
|
|
410
|
+
eigen.set_foundation_properties()
|
|
411
|
+
eigen.calc_fundamental_system()
|
|
412
|
+
|
|
413
|
+
# Basic functionality checks
|
|
414
|
+
self.assertIsNotNone(eigen.kn)
|
|
415
|
+
self.assertIsNotNone(eigen.kt)
|
|
416
|
+
self.assertIsNotNone(eigen.A11)
|
|
417
|
+
self.assertIsNotNone(eigen.ewC)
|
|
418
|
+
self.assertIsNotNone(eigen.ewR)
|
|
419
|
+
|
|
420
|
+
# Test with touchdown=False for all system types
|
|
421
|
+
for system in system_types:
|
|
422
|
+
with self.subTest(system=system, touchdown=False):
|
|
423
|
+
eigen = Eigensystem(system=system, touchdown=False)
|
|
424
|
+
self.assertFalse(eigen.touchdown)
|
|
425
|
+
eigen.set_beam_properties(layers="A")
|
|
426
|
+
eigen.set_foundation_properties()
|
|
427
|
+
eigen.calc_fundamental_system()
|
|
428
|
+
|
|
429
|
+
# Basic functionality checks
|
|
430
|
+
self.assertIsNotNone(eigen.kn)
|
|
431
|
+
self.assertIsNotNone(eigen.kt)
|
|
432
|
+
self.assertIsNotNone(eigen.A11)
|
|
433
|
+
self.assertIsNotNone(eigen.ewC)
|
|
434
|
+
self.assertIsNotNone(eigen.ewR)
|
|
435
|
+
|
|
436
|
+
def test_weight_and_surface_loads(self):
|
|
437
|
+
"""Test the calculation of weight and surface loads."""
|
|
438
|
+
eigen = Eigensystem()
|
|
439
|
+
eigen.set_beam_properties(layers="A")
|
|
440
|
+
|
|
441
|
+
# Test weight load at different angles
|
|
442
|
+
# At 0 degrees (flat)
|
|
443
|
+
qn, qt = eigen.get_weight_load(0)
|
|
444
|
+
self.assertGreater(qn, 0) # Normal load is positive
|
|
445
|
+
self.assertAlmostEqual(qt, 0, places=5) # Tangential load is zero
|
|
446
|
+
|
|
447
|
+
# At 30 degrees
|
|
448
|
+
qn, qt = eigen.get_weight_load(30)
|
|
449
|
+
self.assertGreater(qn, 0) # Normal load is positive
|
|
450
|
+
self.assertLess(qt, 0) # Tangential load is negative (downslope)
|
|
451
|
+
|
|
452
|
+
# Set surface load
|
|
453
|
+
eigen.set_surface_load(100.0)
|
|
454
|
+
|
|
455
|
+
# Test surface load at different angles
|
|
456
|
+
pn, pt = eigen.get_surface_load(0)
|
|
457
|
+
self.assertAlmostEqual(pn, 100.0) # Normal load equals input at 0 degrees
|
|
458
|
+
self.assertAlmostEqual(pt, 0, places=5) # Tangential load is zero
|
|
459
|
+
|
|
460
|
+
# At 30 degrees
|
|
461
|
+
pn, pt = eigen.get_surface_load(30)
|
|
462
|
+
self.assertGreater(pn, 0) # Normal load is positive
|
|
463
|
+
self.assertLess(pt, 0) # Tangential load is negative (downslope)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
if __name__ == "__main__":
|
|
467
|
+
unittest.main()
|
|
@@ -46,6 +46,69 @@ class TestClass(FieldQuantitiesMixin, SolutionMixin, SlabContactMixin, Eigensyst
|
|
|
46
46
|
self.p = 0 # surface line load
|
|
47
47
|
self.phi = 0 # inclination angle
|
|
48
48
|
|
|
49
|
+
self.z_coord = np.array([0.0, 1.0])
|
|
50
|
+
|
|
51
|
+
def test_w(self):
|
|
52
|
+
"""Test calculation of deflection."""
|
|
53
|
+
# Test with default parameters
|
|
54
|
+
result = self.w(self.Z)
|
|
55
|
+
self.assertIsInstance(result, np.ndarray)
|
|
56
|
+
self.assertEqual(result.shape, (5,)) # Should match number of positions
|
|
57
|
+
self.assertTrue(np.allclose(result, 3)) # Third row of Z
|
|
58
|
+
|
|
59
|
+
# Test with different units
|
|
60
|
+
result_mm = self.w(self.Z, unit="mm")
|
|
61
|
+
result_cm = self.w(self.Z, unit="cm")
|
|
62
|
+
self.assertTrue(np.allclose(result_mm, result_cm * 10))
|
|
63
|
+
|
|
64
|
+
def test_dw_dx(self):
|
|
65
|
+
"""Test calculation of deflection derivative."""
|
|
66
|
+
# Test with default parameters
|
|
67
|
+
result = self.dw_dx(self.Z)
|
|
68
|
+
self.assertIsInstance(result, np.ndarray)
|
|
69
|
+
self.assertEqual(result.shape, (5,)) # Should match number of positions
|
|
70
|
+
self.assertTrue(np.allclose(result, 4)) # Fourth row of Z
|
|
71
|
+
|
|
72
|
+
def test_psi(self):
|
|
73
|
+
"""Test calculation of rotation."""
|
|
74
|
+
# Test with default parameters
|
|
75
|
+
result = self.psi(self.Z)
|
|
76
|
+
self.assertIsInstance(result, np.ndarray)
|
|
77
|
+
self.assertEqual(result.shape, (5,)) # Should match number of positions
|
|
78
|
+
self.assertTrue(np.allclose(result, 5)) # Fifth row of Z
|
|
79
|
+
|
|
80
|
+
# Test with different units
|
|
81
|
+
result_rad = self.psi(self.Z, unit="rad")
|
|
82
|
+
result_deg = self.psi(self.Z, unit="degrees")
|
|
83
|
+
self.assertTrue(np.allclose(result_rad, np.deg2rad(result_deg)))
|
|
84
|
+
|
|
85
|
+
def test_calc_segments(self):
|
|
86
|
+
"""Test calculation of segments."""
|
|
87
|
+
# Test with default parameters
|
|
88
|
+
crack_segments = self.calc_segments(L=1000, a=300)
|
|
89
|
+
|
|
90
|
+
# Check that the segments dictionary contains expected keys
|
|
91
|
+
self.assertIn("crack", crack_segments)
|
|
92
|
+
self.assertIn("li", crack_segments["crack"])
|
|
93
|
+
self.assertIn("ki", crack_segments["crack"])
|
|
94
|
+
self.assertIn("mi", crack_segments["crack"])
|
|
95
|
+
|
|
96
|
+
# Check segment lengths
|
|
97
|
+
self.assertEqual(
|
|
98
|
+
len(crack_segments["crack"]["li"]), 2
|
|
99
|
+
) # Should have 2 segments for pst-
|
|
100
|
+
self.assertEqual(crack_segments["crack"]["li"][0], 700) # First segment length
|
|
101
|
+
self.assertEqual(
|
|
102
|
+
crack_segments["crack"]["li"][1], 300
|
|
103
|
+
) # Second segment length (crack length)
|
|
104
|
+
|
|
105
|
+
def test_energy_release_rate_ratio(self):
|
|
106
|
+
"""Test calculation of energy release rate ratio."""
|
|
107
|
+
# Test with default parameters
|
|
108
|
+
result = self.layered.energy_release_rate_ratio(self.stress, self.stress_slope)
|
|
109
|
+
self.assertIsInstance(result, float)
|
|
110
|
+
self.assertTrue(np.allclose(result, self.expected_ratio))
|
|
111
|
+
|
|
49
112
|
|
|
50
113
|
class TestFieldQuantitiesMixin(unittest.TestCase):
|
|
51
114
|
"""Test cases for FieldQuantitiesMixin."""
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import unittest
|
|
7
7
|
|
|
8
8
|
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
9
10
|
|
|
10
11
|
import weac.plot
|
|
11
12
|
from weac.layered import Layered
|
|
@@ -39,6 +40,8 @@ class TestPlot(unittest.TestCase):
|
|
|
39
40
|
if not os.path.exists("plots"):
|
|
40
41
|
os.makedirs("plots")
|
|
41
42
|
|
|
43
|
+
self.compliance = np.array([[1.0, 0.0], [0.0, 1.0]])
|
|
44
|
+
|
|
42
45
|
def tearDown(self):
|
|
43
46
|
"""Clean up after tests."""
|
|
44
47
|
# Close all matplotlib figures to avoid memory leaks
|