weac 2.6.0__tar.gz → 2.6.2__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 (33) hide show
  1. {weac-2.6.0 → weac-2.6.2}/CITATION.cff +1 -1
  2. {weac-2.6.0 → weac-2.6.2}/PKG-INFO +22 -8
  3. weac-2.6.2/pyproject.toml +101 -0
  4. weac-2.6.2/setup.cfg +4 -0
  5. weac-2.6.2/tests/test_eigensystem.py +467 -0
  6. {weac-2.6.0 → weac-2.6.2}/tests/test_mixins.py +63 -0
  7. {weac-2.6.0 → weac-2.6.2}/tests/test_plot.py +3 -0
  8. {weac-2.6.0 → weac-2.6.2}/weac/__init__.py +4 -8
  9. {weac-2.6.0 → weac-2.6.2}/weac/eigensystem.py +180 -139
  10. {weac-2.6.0 → weac-2.6.2}/weac/inverse.py +5 -8
  11. {weac-2.6.0 → weac-2.6.2}/weac/layered.py +24 -9
  12. {weac-2.6.0 → weac-2.6.2}/weac/mixins.py +423 -426
  13. {weac-2.6.0 → weac-2.6.2}/weac/plot.py +244 -196
  14. {weac-2.6.0 → weac-2.6.2}/weac/tools.py +84 -69
  15. weac-2.6.2/weac.egg-info/PKG-INFO +364 -0
  16. {weac-2.6.0/build → weac-2.6.2}/weac.egg-info/SOURCES.txt +6 -2
  17. weac-2.6.2/weac.egg-info/dependency_links.txt +1 -0
  18. weac-2.6.2/weac.egg-info/requires.txt +23 -0
  19. weac-2.6.2/weac.egg-info/top_level.txt +1 -0
  20. weac-2.6.0/pyproject.toml +0 -9
  21. weac-2.6.0/setup.cfg +0 -45
  22. weac-2.6.0/tests/test_eigensystem.py +0 -104
  23. {weac-2.6.0 → weac-2.6.2}/LICENSE +0 -0
  24. {weac-2.6.0 → weac-2.6.2}/MANIFEST.in +0 -0
  25. {weac-2.6.0 → weac-2.6.2}/README.md +0 -0
  26. {weac-2.6.0 → weac-2.6.2}/img/bc.png +0 -0
  27. {weac-2.6.0 → weac-2.6.2}/img/layering.png +0 -0
  28. {weac-2.6.0 → weac-2.6.2}/img/logo.png +0 -0
  29. {weac-2.6.0 → weac-2.6.2}/img/model.png +0 -0
  30. {weac-2.6.0 → weac-2.6.2}/img/profiles.png +0 -0
  31. {weac-2.6.0 → weac-2.6.2}/img/systems.png +0 -0
  32. {weac-2.6.0 → weac-2.6.2}/tests/test_layered.py +0 -0
  33. {weac-2.6.0 → weac-2.6.2}/tests/test_tools.py +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.0
11
+ version: 2.6.2
12
12
  date-released: 2021-12-30
13
13
  identifiers:
14
14
  - description: Collection of archived snapshots of all versions of WEAC
@@ -1,11 +1,10 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: weac
3
- Version: 2.6.0
3
+ Version: 2.6.2
4
4
  Summary: Weak layer anticrack nucleation model
5
- Home-page: https://github.com/2phi/weac
6
- Author: 2phi GbR
7
- Author-email: mail@2phi.de
5
+ Author-email: 2phi GbR <mail@2phi.de>
8
6
  License: Proprietary
7
+ Project-URL: Homepage, https://github.com/2phi/weac
9
8
  Project-URL: Demo, https://github.com/2phi/weac/blob/main/demo/demo.ipynb
10
9
  Project-URL: Documentation, https://2phi.github.io/weac
11
10
  Project-URL: Issues and feature requests, https://github.com/2phi/weac/issues
@@ -16,11 +15,26 @@ Classifier: Topic :: Scientific/Engineering
16
15
  Requires-Python: >=3.10
17
16
  Description-Content-Type: text/markdown
18
17
  License-File: LICENSE
19
- Requires-Dist: matplotlib
20
- Requires-Dist: numpy
21
- Requires-Dist: scipy
18
+ Requires-Dist: matplotlib>=3.9.1
19
+ Requires-Dist: numpy>=2.0.1
20
+ Requires-Dist: scipy>=1.14.0
22
21
  Provides-Extra: interactive
23
22
  Requires-Dist: jupyter; extra == "interactive"
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"
26
+ Provides-Extra: docs
27
+ Requires-Dist: sphinx; extra == "docs"
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
24
38
 
25
39
  <!-- LOGO AND TITLE-->
26
40
  <!-- <p align="right"><img src="https://github.com/2phi/weac/raw/main/img/logo.png" alt="Logo" width="80" height="80"></p> -->
@@ -0,0 +1,101 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "weac"
7
+ version = "2.6.2"
8
+ authors = [
9
+ {name = "2phi GbR", email = "mail@2phi.de"},
10
+ ]
11
+ description = "Weak layer anticrack nucleation model"
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ license = {text = "Proprietary"}
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: Other/Proprietary License",
18
+ "Operating System :: OS Independent",
19
+ "Topic :: Scientific/Engineering",
20
+ ]
21
+ dependencies = [
22
+ "matplotlib>=3.9.1",
23
+ "numpy>=2.0.1",
24
+ "scipy>=1.14.0",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/2phi/weac"
29
+ Demo = "https://github.com/2phi/weac/blob/main/demo/demo.ipynb"
30
+ Documentation = "https://2phi.github.io/weac"
31
+ "Issues and feature requests" = "https://github.com/2phi/weac/issues"
32
+
33
+ [project.optional-dependencies]
34
+ interactive = [
35
+ "jupyter",
36
+ "ipython>=8.12.3",
37
+ "notebook>=7.0.0",
38
+ "ipywidgets>=8.0.0"
39
+ ]
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
+ ]
51
+
52
+ [tool.setuptools]
53
+ packages = ["weac"]
54
+ package-data = {"*" = ["CITATION.cff"], "img" = ["*.png"]}
55
+
56
+ [tool.ruff]
57
+ line-length = 89
58
+ target-version = "py312"
59
+
60
+ [tool.ruff.lint]
61
+ ignore = ["E741"]
62
+
63
+ [tool.ruff.lint.per-file-ignores]
64
+ "**/*.ipynb" = ["F403", "F405", "F401"]
65
+
66
+ [tool.ruff.format]
67
+ line-ending = "lf"
68
+
69
+ [tool.pylint.typecheck]
70
+ generated-members = "matplotlib.cm.*"
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
+
76
+ [tool.pycodestyle]
77
+ ignore = ["E121", "E123", "E126", "E211", "E226", "E24", "E704", "W503", "W504", "E741"]
78
+
79
+ [tool.bumpversion]
80
+ current_version = "2.6.2"
81
+ tag = true
82
+ commit = true
83
+
84
+ [[tool.bumpversion.files]]
85
+ filename = "pyproject.toml"
86
+
87
+ [[tool.bumpversion.files]]
88
+ filename = "CITATION.cff"
89
+
90
+ [[tool.bumpversion.files]]
91
+ filename = "weac/__init__.py"
92
+ search = "__version__ = '{current_version}'"
93
+ replace = "__version__ = '{new_version}'"
94
+
95
+ [[tool.bumpversion.files]]
96
+ filename = "demo/demo.ipynb"
97
+
98
+ [[tool.bumpversion.files]]
99
+ filename = "docs/sphinx/conf.py"
100
+ search = "release = '{current_version}'"
101
+ replace = "release = '{new_version}'"
weac-2.6.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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()