weac 2.5.2__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,12 +8,15 @@ authors:
8
8
  - family-names: "Weissgraeber"
9
9
  given-names: "Philipp"
10
10
  orcid: "https://orcid.org/0000-0001-8320-8672"
11
- version: 2.5.2
11
+ version: 2.6.0
12
12
  date-released: 2021-12-30
13
13
  identifiers:
14
14
  - description: Collection of archived snapshots of all versions of WEAC
15
15
  type: doi
16
16
  value: 10.5281/zenodo.5773113
17
+ - description: Release v2.5 with the implementation of slab touchdown in PST experiments
18
+ type: doi
19
+ value: 10.5281/zenodo.11121171
17
20
  - description: Release v2.4 for the analysis of slope-normal and vertical PST boundary conditions
18
21
  type: doi
19
22
  value: 10.5281/zenodo.10555144
weac-2.6.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
2
+
3
+ WEAC (c) 2024 is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
4
+
5
+ You are free to:
6
+
7
+ - Share — copy and redistribute the material in any medium or format
8
+ - Adapt — remix, transform, and build upon the material.
9
+
10
+ Under the following terms:
11
+
12
+ - Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
13
+
14
+ - NonCommercial — You may not use the material for commercial purposes.
15
+
16
+ - ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
17
+
18
+ No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
19
+
20
+ Notices:
21
+
22
+ You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
23
+
24
+ No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: weac
3
- Version: 2.5.2
3
+ Version: 2.6.0
4
4
  Summary: Weak layer anticrack nucleation model
5
5
  Home-page: https://github.com/2phi/weac
6
6
  Author: 2phi GbR
@@ -144,12 +144,11 @@ git clone https://github.com/2phi/weac
144
144
  ```
145
145
  for local use.
146
146
 
147
- Needs
147
+ Needs (see also [requirements.txt](https://github.com/2phi/weac/blob/main/weac/requirements.txt)):
148
148
  - [Python](https://www.python.org/downloads/release/python-3100/) ≥ 3.10
149
- - [Numpy](https://numpy.org/) for matrix operations
150
- - [Scipy](https://www.scipy.org/) for solving optimization problems
151
- - [Pandas](https://pandas.pydata.org/) for data handling
152
- - [Matplotlib](https://matplotlib.org/) for plotting
149
+ - [Numpy](https://numpy.org/) ≥ 2.0.1
150
+ - [Scipy](https://www.scipy.org/) ≥ 1.14.0
151
+ - [Matplotlib](https://matplotlib.org/) ≥ 3.9.1
153
152
 
154
153
  <!-- USAGE EXAMPLES -->
155
154
  ## Usage
@@ -284,18 +283,31 @@ See the [open issues](https://github.com/2phi/weac/issues) for a list of propose
284
283
  5. Open a pull request
285
284
 
286
285
 
287
- <!-- LICENSE -->
286
+ <!-- WORKFLOWS -->
288
287
  ## Workflows
289
288
  [![Publish Python 🐍 releases 📦 to PyPI ](https://github.com/2phi/weac/actions/workflows/release.yml/badge.svg)](https://github.com/2phi/weac/actions/workflows/release.yml)<br>
290
289
  [![Build and publish Sphinx 🪬 documentation ](https://github.com/2phi/weac/actions/workflows/docs.yml/badge.svg)](https://github.com/2phi/weac/actions/workflows/docs.yml)
291
290
 
292
291
 
292
+
293
293
  <!-- LICENSE -->
294
294
  ## License
295
295
 
296
- Copyright 2phi GbR, 2020-2024.
296
+ <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/">WEAC is licensed under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY-NC-SA 4.0 <img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" alt=""></a></p>
297
+
298
+ You are free to:
299
+
300
+ - **Share** — copy and redistribute the material in any medium or format
301
+ - **Adapt** — remix, transform, and build upon the material for any purpose, even commercially.
302
+
303
+ Under the following terms:
304
+
305
+ - **Attribution** — You must give [appropriate credit](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-appropriate-credit), provide a link to the license, and [indicate if changes were made](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-indicate-changes). You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
306
+
307
+ - **NonCommercial** — You may not use the material for [commercial purposes](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-commercial-purposes).
308
+
309
+ - **ShareAlike** — If you remix, transform, or build upon the material, you must distribute your contributions under the [same license](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-same-license) as the original.
297
310
 
298
- We currently do not offer an open-source license. Please contact us for private licensing options.
299
311
 
300
312
 
301
313
  <!-- CONTACT -->
@@ -120,12 +120,11 @@ git clone https://github.com/2phi/weac
120
120
  ```
121
121
  for local use.
122
122
 
123
- Needs
123
+ Needs (see also [requirements.txt](https://github.com/2phi/weac/blob/main/weac/requirements.txt)):
124
124
  - [Python](https://www.python.org/downloads/release/python-3100/) &ge; 3.10
125
- - [Numpy](https://numpy.org/) for matrix operations
126
- - [Scipy](https://www.scipy.org/) for solving optimization problems
127
- - [Pandas](https://pandas.pydata.org/) for data handling
128
- - [Matplotlib](https://matplotlib.org/) for plotting
125
+ - [Numpy](https://numpy.org/) &ge; 2.0.1
126
+ - [Scipy](https://www.scipy.org/) &ge; 1.14.0
127
+ - [Matplotlib](https://matplotlib.org/) &ge; 3.9.1
129
128
 
130
129
  <!-- USAGE EXAMPLES -->
131
130
  ## Usage
@@ -260,18 +259,31 @@ See the [open issues](https://github.com/2phi/weac/issues) for a list of propose
260
259
  5. Open a pull request
261
260
 
262
261
 
263
- <!-- LICENSE -->
262
+ <!-- WORKFLOWS -->
264
263
  ## Workflows
265
264
  [![Publish Python 🐍 releases 📦 to PyPI ](https://github.com/2phi/weac/actions/workflows/release.yml/badge.svg)](https://github.com/2phi/weac/actions/workflows/release.yml)<br>
266
265
  [![Build and publish Sphinx 🪬 documentation ](https://github.com/2phi/weac/actions/workflows/docs.yml/badge.svg)](https://github.com/2phi/weac/actions/workflows/docs.yml)
267
266
 
268
267
 
268
+
269
269
  <!-- LICENSE -->
270
270
  ## License
271
271
 
272
- Copyright 2phi GbR, 2020-2024.
272
+ <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/">WEAC is licensed under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY-NC-SA 4.0 <img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" alt=""></a></p>
273
+
274
+ You are free to:
275
+
276
+ - **Share** — copy and redistribute the material in any medium or format
277
+ - **Adapt** — remix, transform, and build upon the material for any purpose, even commercially.
278
+
279
+ Under the following terms:
280
+
281
+ - **Attribution** — You must give [appropriate credit](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-appropriate-credit), provide a link to the license, and [indicate if changes were made](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-indicate-changes). You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
282
+
283
+ - **NonCommercial** — You may not use the material for [commercial purposes](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-commercial-purposes).
284
+
285
+ - **ShareAlike** — If you remix, transform, or build upon the material, you must distribute your contributions under the [same license](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1#ref-same-license) as the original.
273
286
 
274
- We currently do not offer an open-source license. Please contact us for private licensing options.
275
287
 
276
288
 
277
289
  <!-- CONTACT -->
@@ -10,6 +10,11 @@ img/logo.png
10
10
  img/model.png
11
11
  img/profiles.png
12
12
  img/systems.png
13
+ tests/test_eigensystem.py
14
+ tests/test_layered.py
15
+ tests/test_mixins.py
16
+ tests/test_plot.py
17
+ tests/test_tools.py
13
18
  weac/__init__.py
14
19
  weac/eigensystem.py
15
20
  weac/inverse.py
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = weac
3
- version = 2.5.2
3
+ version = 2.6.0
4
4
  author = 2phi GbR
5
5
  author_email = mail@2phi.de
6
6
  description = Weak layer anticrack nucleation model
@@ -0,0 +1,104 @@
1
+ """
2
+ Unit tests for the Eigensystem class in the WEAC package.
3
+ """
4
+
5
+ import unittest
6
+
7
+ from weac.eigensystem import Eigensystem
8
+
9
+
10
+ class TestEigensystem(unittest.TestCase):
11
+ """Test cases for the Eigensystem class."""
12
+
13
+ def setUp(self):
14
+ """Set up test fixtures."""
15
+ # Create an Eigensystem instance for testing
16
+ self.eigen = Eigensystem(system="pst-")
17
+
18
+ # Set up properties needed for tests
19
+ self.eigen.set_beam_properties([[300, 200]])
20
+ self.eigen.set_foundation_properties()
21
+
22
+ def test_initialization(self):
23
+ """Test that Eigensystem initializes with correct default values."""
24
+ # Test default initialization
25
+ self.assertEqual(self.eigen.system, "pst-")
26
+ self.assertFalse(self.eigen.touchdown)
27
+ self.assertAlmostEqual(self.eigen.g, 9810.0) # Gravitational constant
28
+
29
+ def test_set_beam_properties(self):
30
+ """Test setting beam properties with different layer configurations."""
31
+ # Create a new instance to test from scratch
32
+ eigen = Eigensystem(system="pst-")
33
+
34
+ # Test with a single layer
35
+ eigen.set_beam_properties([[300, 200]]) # [density (kg/m^3), thickness (mm)]
36
+
37
+ # Check that slab property is set
38
+ self.assertIsNotNone(eigen.slab)
39
+ # The actual shape might be different from what we expected
40
+ # Let's just check that it's a 2D array with at least one row
41
+ self.assertGreaterEqual(eigen.slab.shape[0], 1)
42
+
43
+ # Test with multiple layers
44
+ eigen.set_beam_properties(
45
+ [
46
+ [200, 100], # [density (kg/m^3), thickness (mm)]
47
+ [300, 150],
48
+ [400, 50],
49
+ ]
50
+ )
51
+
52
+ # Check that slab property is updated
53
+ self.assertIsNotNone(eigen.slab)
54
+ # Check that we have the right number of layers
55
+ self.assertEqual(eigen.slab.shape[0], 3)
56
+
57
+ def test_set_foundation_properties(self):
58
+ """Test setting foundation properties."""
59
+ # Create a new instance to test from scratch
60
+ eigen = Eigensystem(system="pst-")
61
+
62
+ # Test with default parameters
63
+ eigen.set_foundation_properties()
64
+
65
+ # Check that weak layer properties are set
66
+ self.assertIsNotNone(eigen.weak)
67
+ self.assertIn("E", eigen.weak)
68
+ self.assertIn("nu", eigen.weak)
69
+
70
+ # Test with custom parameters
71
+ eigen.set_foundation_properties(
72
+ t=50.0, # Weak layer thickness (mm)
73
+ E=0.5, # Young's modulus (MPa)
74
+ nu=0.3, # Poisson's ratio
75
+ )
76
+
77
+ # Check that weak layer properties are updated
78
+ self.assertIsNotNone(eigen.weak)
79
+ self.assertEqual(eigen.weak["E"], 0.5)
80
+ self.assertEqual(eigen.weak["nu"], 0.3)
81
+ self.assertEqual(eigen.t, 50.0)
82
+
83
+ def test_calc_fundamental_system(self):
84
+ """Test calculation of the fundamental system."""
85
+ # Calculate the fundamental system
86
+ self.eigen.calc_fundamental_system()
87
+
88
+ # Check that the system has been initialized
89
+ self.assertIsNotNone(
90
+ getattr(self.eigen, "kn", None)
91
+ ) # Foundation normal stiffness
92
+ self.assertIsNotNone(
93
+ getattr(self.eigen, "kt", None)
94
+ ) # Foundation shear stiffness
95
+ self.assertIsNotNone(getattr(self.eigen, "A11", None)) # Extensional stiffness
96
+ self.assertIsNotNone(
97
+ getattr(self.eigen, "B11", None)
98
+ ) # Bending-extension coupling stiffness
99
+ self.assertIsNotNone(getattr(self.eigen, "D11", None)) # Bending stiffness
100
+ self.assertIsNotNone(getattr(self.eigen, "kA55", None)) # Shear stiffness
101
+
102
+
103
+ if __name__ == "__main__":
104
+ unittest.main()
@@ -0,0 +1,191 @@
1
+ """
2
+ Unit tests for the Layered class in the WEAC package.
3
+ """
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from weac.layered import Layered
10
+
11
+
12
+ class TestLayered(unittest.TestCase):
13
+ """Test cases for the Layered class."""
14
+
15
+ def setUp(self):
16
+ """Set up test fixtures."""
17
+ # Create a default Layered instance for testing
18
+ self.layered = Layered(system="pst-")
19
+
20
+ # Create a Layered instance with custom parameters
21
+ self.custom_layered = Layered(
22
+ system="skier",
23
+ layers=[[240, 200]], # [density (kg/m^3), thickness (mm)]
24
+ touchdown=True,
25
+ )
26
+
27
+ def test_initialization(self):
28
+ """Test that Layered initializes with correct default values."""
29
+ # Test default initialization
30
+ self.assertEqual(self.layered.system, "pst-")
31
+ self.assertFalse(self.layered.touchdown)
32
+
33
+ # Test custom initialization
34
+ self.assertEqual(self.custom_layered.system, "skier")
35
+ self.assertTrue(self.custom_layered.touchdown)
36
+ self.assertEqual(len(self.custom_layered.slab), 1)
37
+ self.assertAlmostEqual(self.custom_layered.slab[0, 0], 240.0) # Density
38
+ self.assertAlmostEqual(self.custom_layered.slab[0, 1], 200.0) # Thickness
39
+
40
+ def test_calc_segments(self):
41
+ """Test calculation of segments for different systems."""
42
+ # Test for PST cut from right
43
+ self.layered.system = "pst-"
44
+ segments = self.layered.calc_segments(L=1000, a=300)
45
+
46
+ # Check that segments dictionary contains expected keys
47
+ self.assertIn("crack", segments)
48
+ self.assertIn("nocrack", segments)
49
+ self.assertIn("both", segments)
50
+
51
+ # Check segment lengths for crack configuration
52
+ crack_segments = segments["crack"]
53
+ self.assertIn("li", crack_segments)
54
+ self.assertEqual(len(crack_segments["li"]), 2) # Two segments for PST-
55
+ self.assertAlmostEqual(crack_segments["li"][0], 700.0) # First segment length
56
+ self.assertAlmostEqual(crack_segments["li"][1], 300.0) # Second segment length
57
+
58
+ # Test for skier system
59
+ self.layered.system = "skier"
60
+ segments = self.layered.calc_segments()
61
+
62
+ # Check that segments dictionary contains expected keys
63
+ self.assertIn("crack", segments)
64
+
65
+ # Check segment lengths for skier configuration
66
+ skier_segments = segments["crack"]
67
+ self.assertIn("li", skier_segments)
68
+ # Note: The actual implementation returns 4 segments for skier, not 2
69
+ self.assertEqual(len(skier_segments["li"]), 4) # Four segments for skier
70
+
71
+ # Test for multiple skiers
72
+ self.layered.system = "skiers"
73
+ segments = self.layered.calc_segments(
74
+ li=[500, 100, 250, 30, 30, 500],
75
+ ki=[True, True, True, False, False, True],
76
+ mi=[80, 80, 0, 0, 0],
77
+ )
78
+
79
+ # Check that segments dictionary contains expected keys
80
+ self.assertIn("crack", segments)
81
+
82
+ # Check segment lengths for multiple skiers configuration
83
+ skiers_segments = segments["crack"]
84
+ self.assertIn("li", skiers_segments)
85
+ self.assertEqual(len(skiers_segments["li"]), 6) # Six segments as specified
86
+ self.assertAlmostEqual(skiers_segments["li"][0], 500.0)
87
+ self.assertAlmostEqual(skiers_segments["li"][1], 100.0)
88
+ self.assertAlmostEqual(skiers_segments["li"][2], 250.0)
89
+ self.assertAlmostEqual(skiers_segments["li"][3], 30.0)
90
+ self.assertAlmostEqual(skiers_segments["li"][4], 30.0)
91
+ self.assertAlmostEqual(skiers_segments["li"][5], 500.0)
92
+
93
+ def test_assemble_and_solve(self):
94
+ """Test assembly and solution of the system."""
95
+ # Set up a simple configuration
96
+ self.layered.set_beam_properties([[240, 200]])
97
+ self.layered.set_foundation_properties()
98
+ self.layered.calc_fundamental_system()
99
+
100
+ # Calculate segments
101
+ segments = self.layered.calc_segments(L=1000, a=300)
102
+
103
+ # Assemble and solve the system
104
+ C = self.layered.assemble_and_solve(phi=0, **segments["crack"])
105
+
106
+ # Check that solution vector has correct shape
107
+ self.assertIsNotNone(C)
108
+ self.assertEqual(C.shape, (6, 2)) # 6 state variables, 2 segments
109
+
110
+ # Test with non-zero slope angle
111
+ C_slope = self.layered.assemble_and_solve(phi=30, **segments["crack"])
112
+ self.assertIsNotNone(C_slope)
113
+ self.assertEqual(C_slope.shape, (6, 2))
114
+
115
+ def test_rasterize_solution(self):
116
+ """Test rasterization of the solution."""
117
+ # Set up a simple configuration
118
+ self.layered.set_beam_properties([[240, 200]])
119
+ self.layered.set_foundation_properties()
120
+ self.layered.calc_fundamental_system()
121
+
122
+ # Calculate segments
123
+ segments = self.layered.calc_segments(L=1000, a=300)
124
+
125
+ # Assemble and solve the system
126
+ C = self.layered.assemble_and_solve(phi=0, **segments["crack"])
127
+
128
+ # Rasterize the solution
129
+ xsl, z, xwl = self.layered.rasterize_solution(C=C, phi=0, **segments["crack"])
130
+
131
+ # Check that output arrays have correct shapes
132
+ self.assertIsNotNone(xsl)
133
+ self.assertIsNotNone(z)
134
+ self.assertIsNotNone(xwl)
135
+ self.assertEqual(z.shape[0], 6) # 6 state variables
136
+ self.assertEqual(xsl.shape, z.shape[1:]) # Same length as state variables
137
+
138
+ # Check that x coordinates are within expected range
139
+ self.assertGreaterEqual(np.min(xsl), 0)
140
+ self.assertLessEqual(np.max(xsl), 1000)
141
+
142
+ def test_gdif(self):
143
+ """Test calculation of differential energy release rate."""
144
+ # Set up a simple configuration
145
+ self.layered.set_beam_properties([[240, 200]])
146
+ self.layered.set_foundation_properties()
147
+ self.layered.calc_fundamental_system()
148
+
149
+ # Calculate segments
150
+ segments = self.layered.calc_segments(L=1000, a=300)
151
+
152
+ # Assemble and solve the system
153
+ C = self.layered.assemble_and_solve(phi=0, **segments["crack"])
154
+
155
+ # Calculate differential energy release rate
156
+ G = self.layered.gdif(C, phi=0, **segments["crack"])
157
+
158
+ # Check that energy release rate is non-negative
159
+ self.assertIsNotNone(G)
160
+ self.assertEqual(len(G), 3) # Three components: mode I, mode II, and total
161
+ self.assertGreaterEqual(
162
+ G[2], 0
163
+ ) # Total energy release rate should be non-negative
164
+
165
+ def test_ginc(self):
166
+ """Test calculation of incremental energy release rate."""
167
+ # Set up a simple configuration
168
+ self.layered.set_beam_properties([[240, 200]])
169
+ self.layered.set_foundation_properties()
170
+ self.layered.calc_fundamental_system()
171
+
172
+ # Calculate segments for both configurations
173
+ segments = self.layered.calc_segments(L=1000, a=300)
174
+
175
+ # Assemble and solve the system for both configurations
176
+ C0 = self.layered.assemble_and_solve(phi=0, **segments["nocrack"])
177
+ C1 = self.layered.assemble_and_solve(phi=0, **segments["crack"])
178
+
179
+ # Calculate incremental energy release rate
180
+ G = self.layered.ginc(C0, C1, phi=0, **segments["both"])
181
+
182
+ # Check that energy release rate is non-negative
183
+ self.assertIsNotNone(G)
184
+ self.assertEqual(len(G), 3) # Three components: mode I, mode II, and total
185
+ self.assertGreaterEqual(
186
+ G[2], 0
187
+ ) # Total energy release rate should be non-negative
188
+
189
+
190
+ if __name__ == "__main__":
191
+ unittest.main()
@@ -0,0 +1,121 @@
1
+ """
2
+ Unit tests for the mixins module in the WEAC package.
3
+ """
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from weac.eigensystem import Eigensystem
10
+ from weac.mixins import FieldQuantitiesMixin, SlabContactMixin, SolutionMixin
11
+
12
+
13
+ class TestClass(FieldQuantitiesMixin, SolutionMixin, SlabContactMixin, Eigensystem):
14
+ """Test class for mixin testing."""
15
+
16
+ def __init__(self):
17
+ """Initialize test class."""
18
+ # Initialize parent class
19
+ super().__init__(system="pst-", touchdown=False)
20
+
21
+ # Create a 2D array for Z where the first index is the state variable
22
+ # and the second index is the position
23
+ self.Z = np.zeros((6, 5)) # 6 state variables, 5 positions
24
+ for i in range(6):
25
+ self.Z[i, :] = i + 1 # Each row has values [1,1,1,1,1], [2,2,2,2,2], etc.
26
+
27
+ # Set required attributes for the mixins
28
+ self.h = 200 # slab thickness in mm
29
+ self.td = 0 # touchdown length
30
+ self.t = 1 # weak layer thickness
31
+ self.A11 = 1e6 # axial stiffness
32
+ self.B11 = 1e4 # coupling stiffness
33
+ self.D11 = 1e2 # bending stiffness
34
+ self.kA55 = 1e5 # shear stiffness
35
+ self.kn = 1e3 # normal foundation stiffness
36
+ self.kt = 1e3 # tangential foundation stiffness
37
+ self.system = "pst-" # system type
38
+ self.touchdown = False # touchdown flag
39
+ self.g = 9810 # gravity constant
40
+ self.mode = "A" # touchdown mode
41
+
42
+ # Create slab properties array with columns:
43
+ # density (kg/m^3), thickness (mm), Young's modulus (MPa), shear modulus (MPa), Poisson's ratio
44
+ self.slab = np.array([[300, 200, 1e3, 4e2, 0.25]])
45
+
46
+ self.p = 0 # surface line load
47
+ self.phi = 0 # inclination angle
48
+
49
+
50
+ class TestFieldQuantitiesMixin(unittest.TestCase):
51
+ """Test cases for FieldQuantitiesMixin."""
52
+
53
+ def setUp(self):
54
+ """Set up test fixtures."""
55
+ self.test_obj = TestClass()
56
+
57
+ def test_w(self):
58
+ """Test calculation of deflection."""
59
+ # Test with default parameters
60
+ result = self.test_obj.w(self.test_obj.Z)
61
+ self.assertIsInstance(result, np.ndarray)
62
+ self.assertEqual(result.shape, (5,)) # Should match number of positions
63
+ self.assertTrue(np.allclose(result, 3)) # Third row of Z
64
+
65
+ # Test with different units
66
+ result_mm = self.test_obj.w(self.test_obj.Z, unit="mm")
67
+ result_cm = self.test_obj.w(self.test_obj.Z, unit="cm")
68
+ self.assertTrue(np.allclose(result_mm, result_cm * 10))
69
+
70
+ def test_dw_dx(self):
71
+ """Test calculation of deflection derivative."""
72
+ # Test with default parameters
73
+ result = self.test_obj.dw_dx(self.test_obj.Z)
74
+ self.assertIsInstance(result, np.ndarray)
75
+ self.assertEqual(result.shape, (5,)) # Should match number of positions
76
+ self.assertTrue(np.allclose(result, 4)) # Fourth row of Z
77
+
78
+ def test_psi(self):
79
+ """Test calculation of rotation."""
80
+ # Test with default parameters
81
+ result = self.test_obj.psi(self.test_obj.Z)
82
+ self.assertIsInstance(result, np.ndarray)
83
+ self.assertEqual(result.shape, (5,)) # Should match number of positions
84
+ self.assertTrue(np.allclose(result, 5)) # Fifth row of Z
85
+
86
+ # Test with different units
87
+ result_rad = self.test_obj.psi(self.test_obj.Z, unit="rad")
88
+ result_deg = self.test_obj.psi(self.test_obj.Z, unit="degrees")
89
+ self.assertTrue(np.allclose(result_rad, np.deg2rad(result_deg)))
90
+
91
+
92
+ class TestSolutionMixin(unittest.TestCase):
93
+ """Test cases for SolutionMixin."""
94
+
95
+ def setUp(self):
96
+ """Set up test fixtures."""
97
+ self.test_obj = TestClass()
98
+
99
+ def test_calc_segments(self):
100
+ """Test calculation of segments."""
101
+ # Test with default parameters
102
+ crack_segments = self.test_obj.calc_segments(L=1000, a=300)
103
+
104
+ # Check that the segments dictionary contains expected keys
105
+ self.assertIn("crack", crack_segments)
106
+ self.assertIn("li", crack_segments["crack"])
107
+ self.assertIn("ki", crack_segments["crack"])
108
+ self.assertIn("mi", crack_segments["crack"])
109
+
110
+ # Check segment lengths
111
+ self.assertEqual(
112
+ len(crack_segments["crack"]["li"]), 2
113
+ ) # Should have 2 segments for pst-
114
+ self.assertEqual(crack_segments["crack"]["li"][0], 700) # First segment length
115
+ self.assertEqual(
116
+ crack_segments["crack"]["li"][1], 300
117
+ ) # Second segment length (crack length)
118
+
119
+
120
+ if __name__ == "__main__":
121
+ unittest.main()
@@ -0,0 +1,121 @@
1
+ """
2
+ Unit tests for the plot module in the WEAC package.
3
+ """
4
+
5
+ import os
6
+ import unittest
7
+
8
+ import matplotlib.pyplot as plt
9
+
10
+ import weac.plot
11
+ from weac.layered import Layered
12
+
13
+
14
+ class TestPlot(unittest.TestCase):
15
+ """Test cases for visualization functions in the plot module."""
16
+
17
+ def setUp(self):
18
+ """Set up test fixtures."""
19
+ # Create a Layered instance for testing
20
+ self.layered = Layered(system="pst-")
21
+ self.layered.set_beam_properties(
22
+ [[300, 200]]
23
+ ) # [density (kg/m^3), thickness (mm)]
24
+ self.layered.set_foundation_properties()
25
+ self.layered.calc_fundamental_system()
26
+
27
+ # Calculate segments
28
+ self.segments = self.layered.calc_segments(L=1000, a=300)
29
+
30
+ # Assemble and solve the system
31
+ self.C = self.layered.assemble_and_solve(phi=0, **self.segments["crack"])
32
+
33
+ # Rasterize the solution
34
+ self.xsl, self.z, self.xwl = self.layered.rasterize_solution(
35
+ C=self.C, phi=0, **self.segments["crack"]
36
+ )
37
+
38
+ # Create plots directory if it doesn't exist
39
+ if not os.path.exists("plots"):
40
+ os.makedirs("plots")
41
+
42
+ def tearDown(self):
43
+ """Clean up after tests."""
44
+ # Close all matplotlib figures to avoid memory leaks
45
+ plt.close("all")
46
+
47
+ # Clean up plot files
48
+ plot_files = [
49
+ "plots/profile.png",
50
+ "plots/cont.png",
51
+ "plots/disp.png",
52
+ "plots/stress.png",
53
+ ]
54
+ for file in plot_files:
55
+ if os.path.exists(file):
56
+ os.remove(file)
57
+
58
+ def test_slab_profile(self):
59
+ """Test plotting of slab profile."""
60
+ # Test with default parameters
61
+ weac.plot.slab_profile(self.layered)
62
+
63
+ # Check that the plot file was created
64
+ self.assertTrue(os.path.exists("plots/profile.png"))
65
+
66
+ def test_deformed(self):
67
+ """Test plotting of deformed slab."""
68
+ # Test with default parameters
69
+ weac.plot.deformed(self.layered, xsl=self.xsl, xwl=self.xwl, z=self.z, phi=0)
70
+
71
+ # Check that the plot file was created
72
+ self.assertTrue(os.path.exists("plots/cont.png"))
73
+
74
+ # Test with custom parameters
75
+ weac.plot.deformed(
76
+ self.layered,
77
+ xsl=self.xsl,
78
+ xwl=self.xwl,
79
+ z=self.z,
80
+ phi=0,
81
+ scale=2.0,
82
+ field="w",
83
+ normalize=False,
84
+ )
85
+
86
+ # Check that the plot file was created
87
+ self.assertTrue(os.path.exists("plots/cont.png"))
88
+
89
+ def test_displacements(self):
90
+ """Test plotting of displacements."""
91
+ # Test with default parameters
92
+ weac.plot.displacements(
93
+ self.layered,
94
+ x=self.xsl,
95
+ z=self.z,
96
+ li=self.segments["crack"]["li"],
97
+ ki=self.segments["crack"]["ki"],
98
+ mi=self.segments["crack"]["mi"], # Add mi parameter
99
+ )
100
+
101
+ # Check that the plot file was created
102
+ self.assertTrue(os.path.exists("plots/disp.png"))
103
+
104
+ def test_stresses(self):
105
+ """Test plotting of stresses."""
106
+ # Test with default parameters
107
+ weac.plot.stresses(
108
+ self.layered,
109
+ x=self.xwl,
110
+ z=self.z,
111
+ li=self.segments["crack"]["li"],
112
+ ki=self.segments["crack"]["ki"],
113
+ mi=self.segments["crack"]["mi"], # Add mi parameter
114
+ )
115
+
116
+ # Check that the plot file was created
117
+ self.assertTrue(os.path.exists("plots/stress.png"))
118
+
119
+
120
+ if __name__ == "__main__":
121
+ unittest.main()
@@ -0,0 +1,41 @@
1
+ """
2
+ Unit tests for the tools module in the WEAC package.
3
+ """
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from weac.tools import bergfeld
10
+
11
+
12
+ class TestTools(unittest.TestCase):
13
+ """Test cases for utility functions in the tools module."""
14
+
15
+ def test_bergfeld(self):
16
+ """Test the Bergfeld function for calculating Young's modulus from density."""
17
+ # Test with a typical snow density
18
+ density = 300 # kg/m^3
19
+ young = bergfeld(density)
20
+
21
+ # Check that the result is positive and within expected range
22
+ self.assertGreater(young, 0)
23
+
24
+ # Test with an array of densities
25
+ densities = np.array([200, 300, 400])
26
+ youngs = bergfeld(densities)
27
+
28
+ # Check that the result is an array of the same shape
29
+ self.assertEqual(youngs.shape, densities.shape)
30
+
31
+ # Check that all values are positive and increasing with density
32
+ self.assertTrue(np.all(youngs > 0))
33
+ self.assertTrue(np.all(np.diff(youngs) > 0))
34
+
35
+ # Test with zero density (should handle gracefully)
36
+ zero_young = bergfeld(0)
37
+ self.assertGreaterEqual(zero_young, 0)
38
+
39
+
40
+ if __name__ == "__main__":
41
+ unittest.main()
@@ -11,7 +11,7 @@ from weac.inverse import Inverse
11
11
  from weac import plot
12
12
 
13
13
  # Version
14
- __version__ = '2.5.2'
14
+ __version__ = '2.6.0'
15
15
 
16
16
  # Public names
17
17
  __all__ = [
weac-2.5.2/LICENSE DELETED
@@ -1,3 +0,0 @@
1
- Copyright (c) 2021 2phi GbR.
2
-
3
- We currently do not offer an open source license. Please contact us for private licensing options.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes