weac 3.0.2__tar.gz → 3.1.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.
- {weac-3.0.2 → weac-3.1.0}/CITATION.cff +1 -1
- {weac-3.0.2/src/weac.egg-info → weac-3.1.0}/PKG-INFO +1 -1
- {weac-3.0.2 → weac-3.1.0}/pyproject.toml +2 -2
- {weac-3.0.2 → weac-3.1.0}/src/weac/__init__.py +1 -1
- {weac-3.0.2 → weac-3.1.0}/src/weac/analysis/__init__.py +2 -2
- {weac-3.0.2 → weac-3.1.0}/src/weac/analysis/analyzer.py +57 -19
- {weac-3.0.2 → weac-3.1.0}/src/weac/analysis/criteria_evaluator.py +102 -12
- {weac-3.0.2 → weac-3.1.0}/src/weac/analysis/plotter.py +376 -26
- {weac-3.0.2 → weac-3.1.0/src/weac.egg-info}/PKG-INFO +1 -1
- {weac-3.0.2 → weac-3.1.0}/tests/analysis/test_analyzer.py +36 -0
- {weac-3.0.2 → weac-3.1.0}/tests/analysis/test_criteria_evaluator.py +11 -5
- {weac-3.0.2 → weac-3.1.0}/tests/test_regression_simulation.py +3 -3
- {weac-3.0.2 → weac-3.1.0}/LICENSE +0 -0
- {weac-3.0.2 → weac-3.1.0}/MANIFEST.in +0 -0
- {weac-3.0.2 → weac-3.1.0}/README.md +0 -0
- {weac-3.0.2 → weac-3.1.0}/img/bc.png +0 -0
- {weac-3.0.2 → weac-3.1.0}/img/layering.png +0 -0
- {weac-3.0.2 → weac-3.1.0}/img/logo.png +0 -0
- {weac-3.0.2 → weac-3.1.0}/img/model.png +0 -0
- {weac-3.0.2 → weac-3.1.0}/img/profiles.png +0 -0
- {weac-3.0.2 → weac-3.1.0}/img/systems.png +0 -0
- {weac-3.0.2 → weac-3.1.0}/setup.cfg +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/components/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/components/config.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/components/criteria_config.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/components/layer.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/components/model_input.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/components/scenario_config.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/components/segment.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/constants.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/eigensystem.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/field_quantities.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/scenario.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/slab.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/slab_touchdown.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/system_model.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/core/unknown_constants_solver.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/logging_config.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/utils/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/utils/geldsetzer.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/utils/misc.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/utils/snow_types.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac/utils/snowpilot_parser.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac.egg-info/SOURCES.txt +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac.egg-info/dependency_links.txt +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac.egg-info/requires.txt +0 -0
- {weac-3.0.2 → weac-3.1.0}/src/weac.egg-info/top_level.txt +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/analysis/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/components/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/components/test_configs.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/components/test_layer.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/core/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/core/test_eigensystem.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/core/test_field_quantities.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/core/test_scenario.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/core/test_slab.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/core/test_slab_touchdown.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/core/test_system_model.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/run_tests.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/test_comparison_results.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/utils/__init__.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/utils/json_helpers.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/utils/test_json_helpers.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/utils/test_misc.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/utils/test_snowpilot_parser.py +0 -0
- {weac-3.0.2 → weac-3.1.0}/tests/utils/weac_reference_runner.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: 3.0
|
|
11
|
+
version: 3.1.0
|
|
12
12
|
date-released: 2021-12-30
|
|
13
13
|
identifiers:
|
|
14
14
|
- description: Collection of archived snapshots of all versions of WEAC
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weac"
|
|
7
|
-
version = "3.0
|
|
7
|
+
version = "3.1.0"
|
|
8
8
|
authors = [{ name = "2phi GbR", email = "mail@2phi.de" }]
|
|
9
9
|
description = "Weak layer anticrack nucleation model"
|
|
10
10
|
readme = "README.md"
|
|
@@ -123,7 +123,7 @@ ignore = [
|
|
|
123
123
|
]
|
|
124
124
|
|
|
125
125
|
[tool.bumpversion]
|
|
126
|
-
current_version = "3.0
|
|
126
|
+
current_version = "3.1.0"
|
|
127
127
|
|
|
128
128
|
[[tool.bumpversion.files]]
|
|
129
129
|
filename = "pyproject.toml"
|
|
@@ -8,7 +8,7 @@ from .criteria_evaluator import (
|
|
|
8
8
|
CoupledCriterionResult,
|
|
9
9
|
CriteriaEvaluator,
|
|
10
10
|
FindMinimumForceResult,
|
|
11
|
-
|
|
11
|
+
SteadyStateResult,
|
|
12
12
|
)
|
|
13
13
|
from .plotter import Plotter
|
|
14
14
|
|
|
@@ -18,6 +18,6 @@ __all__ = [
|
|
|
18
18
|
"CoupledCriterionHistory",
|
|
19
19
|
"CoupledCriterionResult",
|
|
20
20
|
"FindMinimumForceResult",
|
|
21
|
-
"
|
|
21
|
+
"SteadyStateResult",
|
|
22
22
|
"Plotter",
|
|
23
23
|
]
|
|
@@ -214,7 +214,7 @@ class Analyzer:
|
|
|
214
214
|
], # Convert to t/mm^3
|
|
215
215
|
"tensile_strength": [
|
|
216
216
|
layer.tensile_strength for layer in self.sm.slab.layers
|
|
217
|
-
],
|
|
217
|
+
], # in kPa
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
# Repeat properties for each grid point in the layer
|
|
@@ -225,7 +225,7 @@ class Analyzer:
|
|
|
225
225
|
return si
|
|
226
226
|
|
|
227
227
|
@track_analyzer_call
|
|
228
|
-
def Sxx(self, Z, phi, dz=2, unit="kPa"):
|
|
228
|
+
def Sxx(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
|
|
229
229
|
"""
|
|
230
230
|
Compute axial normal stress in slab layers.
|
|
231
231
|
|
|
@@ -239,6 +239,10 @@ class Analyzer:
|
|
|
239
239
|
Element size along z-axis (mm). Default is 2 mm.
|
|
240
240
|
unit : {'kPa', 'MPa'}, optional
|
|
241
241
|
Desired output unit. Default is 'kPa'.
|
|
242
|
+
normalize : bool, optional
|
|
243
|
+
Toggle normalization. If True, normalize stress values to the tensile strength of each layer (dimensionless).
|
|
244
|
+
When normalized, the `unit` parameter is ignored and values are returned as ratios.
|
|
245
|
+
Default is False.
|
|
242
246
|
|
|
243
247
|
Returns
|
|
244
248
|
-------
|
|
@@ -258,13 +262,13 @@ class Analyzer:
|
|
|
258
262
|
m = Z.shape[1]
|
|
259
263
|
|
|
260
264
|
# Initialize axial normal stress Sxx
|
|
261
|
-
|
|
265
|
+
Sxx_MPa = np.zeros(shape=[n, m])
|
|
262
266
|
|
|
263
267
|
# Compute axial normal stress Sxx at grid points in MPa
|
|
264
268
|
for i, z in enumerate(zi):
|
|
265
|
-
|
|
269
|
+
E_MPa = zmesh["E"][i]
|
|
266
270
|
nu = zmesh["nu"][i]
|
|
267
|
-
|
|
271
|
+
Sxx_MPa[i, :] = E_MPa / (1 - nu**2) * self.sm.fq.du_dx(Z, z)
|
|
268
272
|
|
|
269
273
|
# Calculate weight load at grid points and superimpose on stress field
|
|
270
274
|
qt = -rho * G_MM_S2 * np.sin(np.deg2rad(phi))
|
|
@@ -274,14 +278,22 @@ class Analyzer:
|
|
|
274
278
|
# Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
|
|
275
279
|
# New Implementation: Changed for numerical stability
|
|
276
280
|
dz = np.diff(zi)
|
|
277
|
-
|
|
278
|
-
|
|
281
|
+
Sxx_MPa[:-1, :] += qt[:-1, np.newaxis] * dz[:, np.newaxis]
|
|
282
|
+
Sxx_MPa[-1, :] += qt[-1] * dz[-1]
|
|
283
|
+
|
|
284
|
+
# Normalize tensile stresses to tensile strength
|
|
285
|
+
if normalize:
|
|
286
|
+
tensile_strength_kPa = zmesh["tensile_strength"]
|
|
287
|
+
tensile_strength_MPa = tensile_strength_kPa / 1e3
|
|
288
|
+
# Normalize axial normal stress to layers' tensile strength
|
|
289
|
+
normalized_Sxx = Sxx_MPa / tensile_strength_MPa[:, None]
|
|
290
|
+
return normalized_Sxx
|
|
279
291
|
|
|
280
292
|
# Return axial normal stress in specified unit
|
|
281
|
-
return convert[unit] *
|
|
293
|
+
return convert[unit] * Sxx_MPa
|
|
282
294
|
|
|
283
295
|
@track_analyzer_call
|
|
284
|
-
def Txz(self, Z, phi, dz=2, unit="kPa"):
|
|
296
|
+
def Txz(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
|
|
285
297
|
"""
|
|
286
298
|
Compute shear stress in slab layers.
|
|
287
299
|
|
|
@@ -295,6 +307,9 @@ class Analyzer:
|
|
|
295
307
|
Element size along z-axis (mm). Default is 2 mm.
|
|
296
308
|
unit : {'kPa', 'MPa'}, optional
|
|
297
309
|
Desired output unit. Default is 'kPa'.
|
|
310
|
+
normalize : bool, optional
|
|
311
|
+
Toggle normalization. If True, normalize shear stress values to the tensile strength of each layer (dimensionless).
|
|
312
|
+
When normalized, the `unit` parameter is ignored and values are returned as ratios. Default is False.
|
|
298
313
|
|
|
299
314
|
Returns
|
|
300
315
|
-------
|
|
@@ -332,14 +347,22 @@ class Analyzer:
|
|
|
332
347
|
|
|
333
348
|
# Integrate -dsxx_dx along z and add cumulative weight load
|
|
334
349
|
# to obtain shear stress Txz in MPa
|
|
335
|
-
|
|
336
|
-
|
|
350
|
+
Txz_MPa = cumulative_trapezoid(dsxx_dx, zi, axis=0, initial=0)
|
|
351
|
+
Txz_MPa += cumulative_trapezoid(qt, zi, initial=0)[:, None]
|
|
352
|
+
|
|
353
|
+
# Normalize shear stresses to tensile strength
|
|
354
|
+
if normalize:
|
|
355
|
+
tensile_strength_kPa = zmesh["tensile_strength"]
|
|
356
|
+
tensile_strength_MPa = tensile_strength_kPa / 1e3
|
|
357
|
+
# Normalize shear stress to layers' tensile strength
|
|
358
|
+
normalized_Txz = Txz_MPa / tensile_strength_MPa[:, None]
|
|
359
|
+
return normalized_Txz
|
|
337
360
|
|
|
338
361
|
# Return shear stress Txz in specified unit
|
|
339
|
-
return convert[unit] *
|
|
362
|
+
return convert[unit] * Txz_MPa
|
|
340
363
|
|
|
341
364
|
@track_analyzer_call
|
|
342
|
-
def Szz(self, Z, phi, dz=2, unit="kPa"):
|
|
365
|
+
def Szz(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
|
|
343
366
|
"""
|
|
344
367
|
Compute transverse normal stress in slab layers.
|
|
345
368
|
|
|
@@ -353,6 +376,10 @@ class Analyzer:
|
|
|
353
376
|
Element size along z-axis (mm). Default is 2 mm.
|
|
354
377
|
unit : {'kPa', 'MPa'}, optional
|
|
355
378
|
Desired output unit. Default is 'kPa'.
|
|
379
|
+
normalize : bool, optional
|
|
380
|
+
Toggle normalization. If True, normalize stress values to the tensile strength of each layer (dimensionless).
|
|
381
|
+
When normalized, the `unit` parameter is ignored and values are returned as ratios.
|
|
382
|
+
Default is False.
|
|
356
383
|
|
|
357
384
|
Returns
|
|
358
385
|
-------
|
|
@@ -392,11 +419,19 @@ class Analyzer:
|
|
|
392
419
|
# Integrate dsxx_dxdx twice along z to obtain transverse
|
|
393
420
|
# normal stress Szz in MPa
|
|
394
421
|
integrand = cumulative_trapezoid(dsxx_dxdx, zi, axis=0, initial=0)
|
|
395
|
-
|
|
396
|
-
|
|
422
|
+
Szz_MPa = cumulative_trapezoid(integrand, zi, axis=0, initial=0)
|
|
423
|
+
Szz_MPa += cumulative_trapezoid(-qn, zi, initial=0)[:, None]
|
|
397
424
|
|
|
398
|
-
#
|
|
399
|
-
|
|
425
|
+
# Normalize tensile stresses to tensile strength
|
|
426
|
+
if normalize:
|
|
427
|
+
tensile_strength_kPa = zmesh["tensile_strength"]
|
|
428
|
+
tensile_strength_MPa = tensile_strength_kPa / 1e3
|
|
429
|
+
# Normalize transverse normal stress to layers' tensile strength
|
|
430
|
+
normalized_Szz = Szz_MPa / tensile_strength_MPa[:, None]
|
|
431
|
+
return normalized_Szz
|
|
432
|
+
|
|
433
|
+
# Return transverse normal stress Szz in specified unit
|
|
434
|
+
return convert[unit] * Szz_MPa
|
|
400
435
|
|
|
401
436
|
@track_analyzer_call
|
|
402
437
|
def principal_stress_slab(
|
|
@@ -438,6 +473,8 @@ class Analyzer:
|
|
|
438
473
|
'min', or if normalization of compressive principal stress
|
|
439
474
|
is requested.
|
|
440
475
|
"""
|
|
476
|
+
convert = {"kPa": 1e3, "MPa": 1}
|
|
477
|
+
|
|
441
478
|
# Raise error if specified component is not available
|
|
442
479
|
if val not in ["min", "max"]:
|
|
443
480
|
raise ValueError(f"Component {val} not defined.")
|
|
@@ -460,9 +497,10 @@ class Analyzer:
|
|
|
460
497
|
# Normalize tensile stresses to tensile strength
|
|
461
498
|
if normalize and val == "max":
|
|
462
499
|
zmesh = self.get_zmesh(dz=dz)
|
|
463
|
-
|
|
500
|
+
tensile_strength_kPa = zmesh["tensile_strength"]
|
|
501
|
+
tensile_strength_converted = tensile_strength_kPa / 1e3 * convert[unit]
|
|
464
502
|
# Normalize maximum principal stress to layers' tensile strength
|
|
465
|
-
normalized_Ps = Ps /
|
|
503
|
+
normalized_Ps = Ps / tensile_strength_converted[:, None]
|
|
466
504
|
return normalized_Ps
|
|
467
505
|
|
|
468
506
|
# Return absolute principal stresses
|
|
@@ -95,9 +95,38 @@ class CoupledCriterionResult:
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@dataclass
|
|
98
|
-
class
|
|
98
|
+
class MaximalStressResult:
|
|
99
99
|
"""
|
|
100
|
-
Holds the results of the
|
|
100
|
+
Holds the results of the maximal stress evaluation.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
-----------
|
|
104
|
+
principal_stress_kPa: np.ndarray
|
|
105
|
+
The principal stress in kPa.
|
|
106
|
+
Sxx_kPa: np.ndarray
|
|
107
|
+
The axial normal stress in kPa.
|
|
108
|
+
principal_stress_norm: np.ndarray
|
|
109
|
+
The normalized principal stress to the tensile strength of the layers.
|
|
110
|
+
Sxx_norm: np.ndarray
|
|
111
|
+
The normalized axial normal stress to the tensile strength of the layers.
|
|
112
|
+
max_principal_stress_norm: float
|
|
113
|
+
The normalized maximum principal stress to the tensile strength of the layers.
|
|
114
|
+
max_Sxx_norm: float
|
|
115
|
+
The normalized maximum axial normal stress to the tensile strength of the layers.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
principal_stress_kPa: np.ndarray
|
|
119
|
+
Sxx_kPa: np.ndarray
|
|
120
|
+
principal_stress_norm: np.ndarray
|
|
121
|
+
Sxx_norm: np.ndarray
|
|
122
|
+
max_principal_stress_norm: float
|
|
123
|
+
max_Sxx_norm: float
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class SteadyStateResult:
|
|
128
|
+
"""
|
|
129
|
+
Holds the results of the Steady State evaluation.
|
|
101
130
|
|
|
102
131
|
Attributes:
|
|
103
132
|
-----------
|
|
@@ -107,15 +136,21 @@ class SSERRResult:
|
|
|
107
136
|
The message of the evaluation.
|
|
108
137
|
touchdown_distance : float
|
|
109
138
|
The touchdown distance.
|
|
110
|
-
|
|
111
|
-
The
|
|
112
|
-
touchdown distance from
|
|
139
|
+
energy_release_rate : float
|
|
140
|
+
The steady-state energy release rate calculated with the
|
|
141
|
+
touchdown distance from the differential energy release rate.
|
|
142
|
+
maximal_stress_result: MaximalStressResult
|
|
143
|
+
The maximal stresses in the system at the touchdown distance.
|
|
144
|
+
system: SystemModel
|
|
145
|
+
The modified system model used for the steady state evaluation.
|
|
113
146
|
"""
|
|
114
147
|
|
|
115
148
|
converged: bool
|
|
116
149
|
message: str
|
|
117
150
|
touchdown_distance: float
|
|
118
|
-
|
|
151
|
+
energy_release_rate: float
|
|
152
|
+
maximal_stress_result: MaximalStressResult
|
|
153
|
+
system: SystemModel
|
|
119
154
|
|
|
120
155
|
|
|
121
156
|
@dataclass
|
|
@@ -641,12 +676,12 @@ class CriteriaEvaluator:
|
|
|
641
676
|
_recursion_depth=_recursion_depth + 1,
|
|
642
677
|
)
|
|
643
678
|
|
|
644
|
-
def
|
|
679
|
+
def evaluate_SteadyState(
|
|
645
680
|
self,
|
|
646
681
|
system: SystemModel,
|
|
647
682
|
vertical: bool = False,
|
|
648
683
|
print_call_stats: bool = False,
|
|
649
|
-
) ->
|
|
684
|
+
) -> SteadyStateResult:
|
|
650
685
|
"""
|
|
651
686
|
Evaluates the Touchdown Distance in the Steady State and the Steady State
|
|
652
687
|
Energy Release Rate.
|
|
@@ -688,12 +723,19 @@ class CriteriaEvaluator:
|
|
|
688
723
|
system_copy.update_scenario(segments=segments, scenario_config=scenario_config)
|
|
689
724
|
touchdown_distance = system_copy.slab_touchdown.touchdown_distance
|
|
690
725
|
analyzer = Analyzer(system_copy, printing_enabled=print_call_stats)
|
|
691
|
-
|
|
692
|
-
|
|
726
|
+
energy_release_rate, _, _ = analyzer.differential_ERR(unit="J/m^2")
|
|
727
|
+
maximal_stress_result = self._calculate_maximal_stresses(
|
|
728
|
+
system_copy, print_call_stats=print_call_stats
|
|
729
|
+
)
|
|
730
|
+
if print_call_stats:
|
|
731
|
+
analyzer.print_call_stats(message="evaluate_SteadyState Call Statistics")
|
|
732
|
+
return SteadyStateResult(
|
|
693
733
|
converged=True,
|
|
694
|
-
message="
|
|
734
|
+
message="Steady State evaluation successful.",
|
|
695
735
|
touchdown_distance=touchdown_distance,
|
|
696
|
-
|
|
736
|
+
energy_release_rate=energy_release_rate,
|
|
737
|
+
maximal_stress_result=maximal_stress_result,
|
|
738
|
+
system=system_copy,
|
|
697
739
|
)
|
|
698
740
|
|
|
699
741
|
def find_minimum_force(
|
|
@@ -1170,3 +1212,51 @@ class CriteriaEvaluator:
|
|
|
1170
1212
|
|
|
1171
1213
|
# Return the difference from the target
|
|
1172
1214
|
return g_delta_diff - target
|
|
1215
|
+
|
|
1216
|
+
def _calculate_maximal_stresses(
|
|
1217
|
+
self,
|
|
1218
|
+
system: SystemModel,
|
|
1219
|
+
print_call_stats: bool = False,
|
|
1220
|
+
) -> MaximalStressResult:
|
|
1221
|
+
"""
|
|
1222
|
+
Calculate the maximal stresses in the system.
|
|
1223
|
+
|
|
1224
|
+
Parameters
|
|
1225
|
+
----------
|
|
1226
|
+
system : SystemModel
|
|
1227
|
+
The system model to analyze.
|
|
1228
|
+
print_call_stats : bool, optional
|
|
1229
|
+
Whether to print analyzer call statistics. Default is False.
|
|
1230
|
+
|
|
1231
|
+
Returns
|
|
1232
|
+
-------
|
|
1233
|
+
MaximalStressResult
|
|
1234
|
+
Object containing both absolute (in kPa) and normalized stress fields,
|
|
1235
|
+
along with maximum normalized stress values.
|
|
1236
|
+
"""
|
|
1237
|
+
analyzer = Analyzer(system, printing_enabled=print_call_stats)
|
|
1238
|
+
_, Z, _ = analyzer.rasterize_solution(num=4000, mode="cracked")
|
|
1239
|
+
Sxx_kPa = analyzer.Sxx(Z=Z, phi=system.scenario.phi, dz=5, unit="kPa")
|
|
1240
|
+
principal_stress_kPa = analyzer.principal_stress_slab(
|
|
1241
|
+
Z=Z, phi=system.scenario.phi, dz=5, unit="kPa"
|
|
1242
|
+
)
|
|
1243
|
+
Sxx_norm = analyzer.Sxx(
|
|
1244
|
+
Z=Z, phi=system.scenario.phi, dz=5, unit="kPa", normalize=True
|
|
1245
|
+
)
|
|
1246
|
+
principal_stress_norm = analyzer.principal_stress_slab(
|
|
1247
|
+
Z=Z, phi=system.scenario.phi, dz=5, unit="kPa", normalize=True
|
|
1248
|
+
)
|
|
1249
|
+
max_principal_stress_norm = np.max(principal_stress_norm)
|
|
1250
|
+
max_Sxx_norm = np.max(Sxx_norm)
|
|
1251
|
+
if print_call_stats:
|
|
1252
|
+
analyzer.print_call_stats(
|
|
1253
|
+
message="_calculate_maximal_stresses Call Statistics"
|
|
1254
|
+
)
|
|
1255
|
+
return MaximalStressResult(
|
|
1256
|
+
principal_stress_kPa=principal_stress_kPa,
|
|
1257
|
+
Sxx_kPa=Sxx_kPa,
|
|
1258
|
+
principal_stress_norm=principal_stress_norm,
|
|
1259
|
+
Sxx_norm=Sxx_norm,
|
|
1260
|
+
max_principal_stress_norm=max_principal_stress_norm,
|
|
1261
|
+
max_Sxx_norm=max_Sxx_norm,
|
|
1262
|
+
)
|
|
@@ -885,48 +885,56 @@ class Plotter:
|
|
|
885
885
|
zmax = np.max(Zsl + scale * Wsl) + pad
|
|
886
886
|
zmin = np.min(Zsl) - pad
|
|
887
887
|
|
|
888
|
-
#
|
|
889
|
-
|
|
888
|
+
# Filter out NaN values from weak layer coordinates
|
|
889
|
+
nanmask = np.isfinite(xwl)
|
|
890
|
+
xwl_finite = xwl[nanmask]
|
|
890
891
|
|
|
891
|
-
#
|
|
892
|
-
|
|
893
|
-
|
|
892
|
+
# Compute weak-layer grid coordinates (cm) - only for finite xwl
|
|
893
|
+
Xwl, Zwl = np.meshgrid(1e-1 * xwl_finite, [1e-1 * (zi[-1] + H / 2), zmax])
|
|
894
|
+
|
|
895
|
+
# Assemble weak-layer displacement field (top and bottom) - only for finite xwl
|
|
896
|
+
Uwl = np.vstack([Usl[-1, nanmask], np.zeros(xwl_finite.shape[0])])
|
|
897
|
+
Wwl = np.vstack([Wsl[-1, nanmask], np.zeros(xwl_finite.shape[0])])
|
|
894
898
|
|
|
895
899
|
# Compute stress or displacement fields
|
|
896
900
|
match field:
|
|
897
901
|
# Horizontal displacements (um)
|
|
898
902
|
case "u":
|
|
899
903
|
slab = 1e4 * Usl
|
|
900
|
-
weak = 1e4 * Usl[-1,
|
|
904
|
+
weak = 1e4 * Usl[-1, nanmask]
|
|
901
905
|
label = r"$u$ ($\mu$m)"
|
|
902
906
|
# Vertical deflection (um)
|
|
903
907
|
case "w":
|
|
904
908
|
slab = 1e4 * Wsl
|
|
905
|
-
weak = 1e4 * Wsl[-1,
|
|
909
|
+
weak = 1e4 * Wsl[-1, nanmask]
|
|
906
910
|
label = r"$w$ ($\mu$m)"
|
|
907
|
-
# Axial normal stresses (kPa)
|
|
908
911
|
case "Sxx":
|
|
909
|
-
slab = analyzer.Sxx(z, phi, dz=dz, unit="kPa")
|
|
910
|
-
weak = np.zeros(
|
|
911
|
-
label =
|
|
912
|
+
slab = analyzer.Sxx(z, phi, dz=dz, unit="kPa", normalize=normalize)
|
|
913
|
+
weak = np.zeros(xwl_finite.shape[0])
|
|
914
|
+
label = (
|
|
915
|
+
r"$\sigma_{xx}/\sigma_+$" if normalize else r"$\sigma_{xx}$ (kPa)"
|
|
916
|
+
)
|
|
912
917
|
# Shear stresses (kPa)
|
|
913
918
|
case "Txz":
|
|
914
|
-
slab = analyzer.Txz(z, phi, dz=dz, unit="kPa")
|
|
915
|
-
weak = Tauwl
|
|
916
|
-
label = r"$\tau_{xz}$ (kPa)"
|
|
919
|
+
slab = analyzer.Txz(z, phi, dz=dz, unit="kPa", normalize=normalize)
|
|
920
|
+
weak = Tauwl[nanmask]
|
|
921
|
+
label = r"$\tau_{xz}/\sigma_+$" if normalize else r"$\tau_{xz}$ (kPa)"
|
|
917
922
|
# Transverse normal stresses (kPa)
|
|
918
923
|
case "Szz":
|
|
919
|
-
slab = analyzer.Szz(z, phi, dz=dz, unit="kPa")
|
|
920
|
-
weak = Sigmawl
|
|
921
|
-
label =
|
|
924
|
+
slab = analyzer.Szz(z, phi, dz=dz, unit="kPa", normalize=normalize)
|
|
925
|
+
weak = Sigmawl[nanmask]
|
|
926
|
+
label = (
|
|
927
|
+
r"$\sigma_{zz}/\sigma_+$" if normalize else r"$\sigma_{zz}$ (kPa)"
|
|
928
|
+
)
|
|
922
929
|
# Principal stresses
|
|
923
930
|
case "principal":
|
|
924
931
|
slab = analyzer.principal_stress_slab(
|
|
925
932
|
z, phi, dz=dz, val="max", unit="kPa", normalize=normalize
|
|
926
933
|
)
|
|
927
|
-
|
|
934
|
+
weak_full = analyzer.principal_stress_weaklayer(
|
|
928
935
|
z, val="min", unit="kPa", normalize=normalize
|
|
929
936
|
)
|
|
937
|
+
weak = weak_full[nanmask]
|
|
930
938
|
if normalize:
|
|
931
939
|
label = (
|
|
932
940
|
r"$\sigma_\mathrm{I}/\sigma_+$ (slab), "
|
|
@@ -937,11 +945,6 @@ class Plotter:
|
|
|
937
945
|
r"$\sigma_\mathrm{I}$ (kPa, slab), "
|
|
938
946
|
r"$\sigma_\mathrm{I\!I\!I}$ (kPa, weak layer)"
|
|
939
947
|
)
|
|
940
|
-
case _:
|
|
941
|
-
raise ValueError(
|
|
942
|
-
f"Invalid input '{field}' for field. Valid options are "
|
|
943
|
-
"'u', 'w', 'Sxx', 'Txz', 'Szz', or 'principal'"
|
|
944
|
-
)
|
|
945
948
|
|
|
946
949
|
# Complement label
|
|
947
950
|
label += r" $\longrightarrow$"
|
|
@@ -968,10 +971,9 @@ class Plotter:
|
|
|
968
971
|
|
|
969
972
|
# Plot deformed weak-layer _outline
|
|
970
973
|
if system_type in ["-pst", "pst-", "-vpst", "vpst-"]:
|
|
971
|
-
nanmask = np.isfinite(xwl)
|
|
972
974
|
ax.plot(
|
|
973
|
-
_outline(Xwl
|
|
974
|
-
_outline(Zwl
|
|
975
|
+
_outline(Xwl + scale * Uwl),
|
|
976
|
+
_outline(Zwl + scale * Wwl),
|
|
975
977
|
"k",
|
|
976
978
|
linewidth=1,
|
|
977
979
|
)
|
|
@@ -1033,6 +1035,354 @@ class Plotter:
|
|
|
1033
1035
|
|
|
1034
1036
|
return fig
|
|
1035
1037
|
|
|
1038
|
+
def plot_visualize_deformation(
|
|
1039
|
+
self,
|
|
1040
|
+
xsl: np.ndarray,
|
|
1041
|
+
xwl: np.ndarray,
|
|
1042
|
+
z: np.ndarray,
|
|
1043
|
+
analyzer: Analyzer,
|
|
1044
|
+
weaklayer_proportion: float | None = None,
|
|
1045
|
+
dz: int = 2,
|
|
1046
|
+
levels: int = 300,
|
|
1047
|
+
field: Literal["w", "u", "principal", "Sxx", "Txz", "Szz"] = "w",
|
|
1048
|
+
normalize: bool = True,
|
|
1049
|
+
filename: str = "visualize_deformation",
|
|
1050
|
+
) -> Figure:
|
|
1051
|
+
"""
|
|
1052
|
+
Plot visualize deformation of the slab and weak layer.
|
|
1053
|
+
|
|
1054
|
+
Parameters
|
|
1055
|
+
----------
|
|
1056
|
+
xsl : np.ndarray
|
|
1057
|
+
Slab x-coordinates.
|
|
1058
|
+
xwl : np.ndarray
|
|
1059
|
+
Weak layer x-coordinates.
|
|
1060
|
+
z : np.ndarray
|
|
1061
|
+
Solution vector.
|
|
1062
|
+
analyzer : Analyzer
|
|
1063
|
+
Analyzer instance.
|
|
1064
|
+
weaklayer_proportion: float | None, optional
|
|
1065
|
+
Proportion of the plot to allocate to the weak layer. Default is None.
|
|
1066
|
+
dz : int, optional
|
|
1067
|
+
Element size along z-axis (mm). Default is 2 mm.
|
|
1068
|
+
levels : int, optional
|
|
1069
|
+
Number of levels for the colormap. Default is 300.
|
|
1070
|
+
field : str, optional
|
|
1071
|
+
Field to plot ('w', 'u', 'principal', 'Sxx', 'Txz', 'Szz'). Default is 'w'.
|
|
1072
|
+
normalize : bool, optional
|
|
1073
|
+
Toggle normalization. Default is True.
|
|
1074
|
+
filename : str, optional
|
|
1075
|
+
Filename for saving plot. Default is "visualize_deformation".
|
|
1076
|
+
|
|
1077
|
+
Returns
|
|
1078
|
+
-------
|
|
1079
|
+
matplotlib.figure.Figure
|
|
1080
|
+
The generated plot figure.
|
|
1081
|
+
"""
|
|
1082
|
+
fig = plt.figure(figsize=(10, 8))
|
|
1083
|
+
ax = fig.add_subplot(111)
|
|
1084
|
+
|
|
1085
|
+
zi = analyzer.get_zmesh(dz=dz)["z"]
|
|
1086
|
+
H = analyzer.sm.slab.H
|
|
1087
|
+
phi = analyzer.sm.scenario.phi
|
|
1088
|
+
system_type = analyzer.sm.scenario.system_type
|
|
1089
|
+
fq = analyzer.sm.fq
|
|
1090
|
+
|
|
1091
|
+
# Compute slab displacements on grid (cm)
|
|
1092
|
+
Usl = np.vstack([fq.u(z, h0=h0, unit="cm") for h0 in zi])
|
|
1093
|
+
Wsl = np.vstack([fq.w(z, unit="cm") for _ in zi])
|
|
1094
|
+
Sigmawl = np.where(np.isfinite(xwl), fq.sig(z, unit="kPa"), np.nan)
|
|
1095
|
+
Tauwl = np.where(np.isfinite(xwl), fq.tau(z, unit="kPa"), np.nan)
|
|
1096
|
+
|
|
1097
|
+
# Put coordinate origin at horizontal center
|
|
1098
|
+
if system_type in ["skier", "skiers"]:
|
|
1099
|
+
xsl = xsl - max(xsl) / 2
|
|
1100
|
+
xwl = xwl - max(xwl) / 2
|
|
1101
|
+
|
|
1102
|
+
# Physical dimensions in cm
|
|
1103
|
+
H_cm = H * 1e-1 # Slab height in cm
|
|
1104
|
+
h_cm = analyzer.sm.weak_layer.h * 1e-1 # Weak layer height in cm
|
|
1105
|
+
crack_h_cm = analyzer.sm.scenario.crack_h * 1e-1 # Crack height in cm
|
|
1106
|
+
|
|
1107
|
+
# Compute slab grid coordinates with vertical origin at top surface (cm)
|
|
1108
|
+
Xsl, Zsl = np.meshgrid(1e-1 * (xsl), 1e-1 * (zi + H / 2))
|
|
1109
|
+
|
|
1110
|
+
# Calculate maximum displacement first (needed for proportion calculation)
|
|
1111
|
+
max_w_displacement = np.nanmax(np.abs(Wsl))
|
|
1112
|
+
|
|
1113
|
+
# Calculate dynamic proportions based on displacement
|
|
1114
|
+
# Weak layer percentage = weak_layer_height / max_displacement (as ratio)
|
|
1115
|
+
# But capped at 40% maximum
|
|
1116
|
+
if weaklayer_proportion is None:
|
|
1117
|
+
if max_w_displacement > 0:
|
|
1118
|
+
weaklayer_proportion = min(0.3, (h_cm / max_w_displacement) * 0.1)
|
|
1119
|
+
else:
|
|
1120
|
+
weaklayer_proportion = 0.3
|
|
1121
|
+
|
|
1122
|
+
# Slab takes the remaining space
|
|
1123
|
+
slab_proportion = 1.0 - weaklayer_proportion
|
|
1124
|
+
cracked_ratio = crack_h_cm / h_cm
|
|
1125
|
+
cracked_proportion = weaklayer_proportion * cracked_ratio
|
|
1126
|
+
|
|
1127
|
+
# Set up plot coordinate system
|
|
1128
|
+
# Plot height is normalized: slab (0 to slab_proportion), weak layer (slab_proportion to slab_proportion+weaklayer_proportion)
|
|
1129
|
+
total_height_plot = (
|
|
1130
|
+
slab_proportion + weaklayer_proportion
|
|
1131
|
+
) # Total height without displacement
|
|
1132
|
+
# Map physical dimensions to plot coordinates
|
|
1133
|
+
deformation_scale = weaklayer_proportion / h_cm
|
|
1134
|
+
|
|
1135
|
+
# Get x-axis limits spanning all provided x values (deformed and undeformed)
|
|
1136
|
+
xmax = np.max([np.max(Xsl), np.max(Xsl + deformation_scale * Usl)]) + 10.0
|
|
1137
|
+
xmin = np.min([np.min(Xsl), np.min(Xsl + deformation_scale * Usl)]) - 10.0
|
|
1138
|
+
|
|
1139
|
+
# Calculate zmax including maximum deformation
|
|
1140
|
+
zmax = total_height_plot
|
|
1141
|
+
|
|
1142
|
+
# Convert physical coordinates to plot coordinates for slab
|
|
1143
|
+
# Zsl is in cm, we need to map it to plot coordinates (0 to slab_proportion)
|
|
1144
|
+
Zsl_plot = (Zsl / H_cm) * slab_proportion
|
|
1145
|
+
|
|
1146
|
+
# Filter out NaN values from weak layer coordinates
|
|
1147
|
+
nanmask = np.isfinite(xwl)
|
|
1148
|
+
xwl_finite = xwl[nanmask]
|
|
1149
|
+
|
|
1150
|
+
# Compute weak-layer grid coordinates in plot units
|
|
1151
|
+
# Weak layer extends from bottom of slab (slab_proportion) to total height (1.0)
|
|
1152
|
+
Xwl, Zwl_plot = np.meshgrid(
|
|
1153
|
+
1e-1 * xwl_finite, [slab_proportion, total_height_plot]
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
# Assemble weak-layer displacement field (top and bottom) - only for finite xwl
|
|
1157
|
+
Uwl = np.vstack([Usl[-1, nanmask], np.zeros(xwl_finite.shape[0])])
|
|
1158
|
+
Wwl = np.vstack([Wsl[-1, nanmask], np.zeros(xwl_finite.shape[0])])
|
|
1159
|
+
|
|
1160
|
+
# Convert slab displacements to plot coordinates
|
|
1161
|
+
# Scale factor for displacements:
|
|
1162
|
+
# So scaled displacement in plot units = scale * Wsl
|
|
1163
|
+
Wsl_plot = (
|
|
1164
|
+
deformation_scale * Wsl
|
|
1165
|
+
) # Already in plot units (proportion of total height)
|
|
1166
|
+
Usl_plot = deformation_scale * Usl # Horizontal displacements also scaled
|
|
1167
|
+
Wwl_plot = deformation_scale * Wwl # Weak layer displacements
|
|
1168
|
+
Uwl_plot = deformation_scale * Uwl # Weak layer horizontal displacements
|
|
1169
|
+
|
|
1170
|
+
# Compute stress or displacement fields
|
|
1171
|
+
match field:
|
|
1172
|
+
# Horizontal displacements (um)
|
|
1173
|
+
case "u":
|
|
1174
|
+
slab = 1e4 * Usl
|
|
1175
|
+
weak = 1e4 * Usl[-1, nanmask]
|
|
1176
|
+
label = r"$u$ ($\mu$m)"
|
|
1177
|
+
# Vertical deflection (um)
|
|
1178
|
+
case "w":
|
|
1179
|
+
slab = 1e4 * Wsl
|
|
1180
|
+
weak = 1e4 * Wsl[-1, nanmask]
|
|
1181
|
+
label = r"$w$ ($\mu$m)"
|
|
1182
|
+
# Axial normal stresses (kPa)
|
|
1183
|
+
case "Sxx":
|
|
1184
|
+
slab = analyzer.Sxx(z, phi, dz=dz, unit="kPa", normalize=normalize)
|
|
1185
|
+
weak = np.zeros(xwl_finite.shape[0])
|
|
1186
|
+
label = (
|
|
1187
|
+
r"$\sigma_{xx}/\sigma_+$" if normalize else r"$\sigma_{xx}$ (kPa)"
|
|
1188
|
+
)
|
|
1189
|
+
# Shear stresses (kPa)
|
|
1190
|
+
case "Txz":
|
|
1191
|
+
slab = analyzer.Txz(z, phi, dz=dz, unit="kPa", normalize=normalize)
|
|
1192
|
+
weak = Tauwl[nanmask]
|
|
1193
|
+
label = r"$\tau_{xz}/\sigma_+$" if normalize else r"$\tau_{xz}$ (kPa)"
|
|
1194
|
+
# Transverse normal stresses (kPa)
|
|
1195
|
+
case "Szz":
|
|
1196
|
+
slab = analyzer.Szz(z, phi, dz=dz, unit="kPa", normalize=normalize)
|
|
1197
|
+
weak = Sigmawl[nanmask]
|
|
1198
|
+
label = (
|
|
1199
|
+
r"$\sigma_{zz}/\sigma_+$" if normalize else r"$\sigma_{zz}$ (kPa)"
|
|
1200
|
+
)
|
|
1201
|
+
# Principal stresses
|
|
1202
|
+
case "principal":
|
|
1203
|
+
slab = analyzer.principal_stress_slab(
|
|
1204
|
+
z, phi, dz=dz, val="max", unit="kPa", normalize=normalize
|
|
1205
|
+
)
|
|
1206
|
+
weak_full = analyzer.principal_stress_weaklayer(
|
|
1207
|
+
z, val="min", unit="kPa", normalize=normalize
|
|
1208
|
+
)
|
|
1209
|
+
weak = weak_full[nanmask]
|
|
1210
|
+
if normalize:
|
|
1211
|
+
label = (
|
|
1212
|
+
r"$\sigma_\mathrm{I}/\sigma_+$ (slab), "
|
|
1213
|
+
r"$\sigma_\mathrm{I\!I\!I}/\sigma_-$ (weak layer)"
|
|
1214
|
+
)
|
|
1215
|
+
else:
|
|
1216
|
+
label = (
|
|
1217
|
+
r"$\sigma_\mathrm{I}$ (kPa, slab), "
|
|
1218
|
+
r"$\sigma_\mathrm{I\!I\!I}$ (kPa, weak layer)"
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
# Complement label
|
|
1222
|
+
label += r" $\longrightarrow$"
|
|
1223
|
+
|
|
1224
|
+
# Assemble weak-layer output on grid
|
|
1225
|
+
weak = np.vstack([weak, weak])
|
|
1226
|
+
|
|
1227
|
+
# Normalize colormap
|
|
1228
|
+
absmax = np.nanmax(np.abs([slab.min(), slab.max(), weak.min(), weak.max()]))
|
|
1229
|
+
clim = np.round(absmax, _significant_digits(absmax))
|
|
1230
|
+
levels = np.linspace(-clim, clim, num=levels + 1, endpoint=True)
|
|
1231
|
+
|
|
1232
|
+
# Plot baseline
|
|
1233
|
+
ax.axhline(zmax, color="k", linewidth=1)
|
|
1234
|
+
|
|
1235
|
+
# Plot outlines of the undeformed and deformed slab (using plot coordinates)
|
|
1236
|
+
ax.plot(_outline(Xsl), _outline(Zsl_plot), "--", alpha=0.3, linewidth=1)
|
|
1237
|
+
ax.plot(
|
|
1238
|
+
_outline(Xsl + Usl_plot),
|
|
1239
|
+
_outline(Zsl_plot + Wsl_plot),
|
|
1240
|
+
"-",
|
|
1241
|
+
linewidth=1,
|
|
1242
|
+
color="k",
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
# Plot cracked weak-layer outline (where there is no weak layer)
|
|
1246
|
+
xwl_cracked = xsl[~nanmask]
|
|
1247
|
+
Xwl_cracked, Zwl_cracked_plot = np.meshgrid(
|
|
1248
|
+
1e-1 * xwl_cracked,
|
|
1249
|
+
[slab_proportion + cracked_proportion, total_height_plot],
|
|
1250
|
+
)
|
|
1251
|
+
# No displacements for the cracked weak layer outline (undeformed)
|
|
1252
|
+
ax.plot(
|
|
1253
|
+
_outline(Xwl_cracked),
|
|
1254
|
+
_outline(Zwl_cracked_plot),
|
|
1255
|
+
"k-",
|
|
1256
|
+
alpha=0.3,
|
|
1257
|
+
linewidth=1,
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
# Then plot the deformed weak-layer outline where it exists
|
|
1261
|
+
if system_type in ["-pst", "pst-", "-vpst", "vpst-"]:
|
|
1262
|
+
ax.plot(
|
|
1263
|
+
_outline(Xwl + Uwl_plot),
|
|
1264
|
+
_outline(Zwl_plot + Wwl_plot),
|
|
1265
|
+
"k",
|
|
1266
|
+
linewidth=1,
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
cmap = plt.get_cmap("RdBu_r")
|
|
1270
|
+
cmap.set_over(_adjust_lightness(cmap(1.0), 0.9))
|
|
1271
|
+
cmap.set_under(_adjust_lightness(cmap(0.0), 0.9))
|
|
1272
|
+
|
|
1273
|
+
# Plot fields (using plot coordinates)
|
|
1274
|
+
ax.contourf(
|
|
1275
|
+
Xsl + Usl_plot,
|
|
1276
|
+
Zsl_plot + Wsl_plot,
|
|
1277
|
+
slab,
|
|
1278
|
+
levels=levels,
|
|
1279
|
+
cmap=cmap,
|
|
1280
|
+
extend="both",
|
|
1281
|
+
)
|
|
1282
|
+
ax.contourf(
|
|
1283
|
+
Xwl + Uwl_plot,
|
|
1284
|
+
Zwl_plot + Wwl_plot,
|
|
1285
|
+
weak,
|
|
1286
|
+
levels=levels,
|
|
1287
|
+
cmap=cmap,
|
|
1288
|
+
extend="both",
|
|
1289
|
+
)
|
|
1290
|
+
ax.contourf(
|
|
1291
|
+
Xwl_cracked,
|
|
1292
|
+
Zwl_cracked_plot,
|
|
1293
|
+
np.zeros((2, xwl_cracked.shape[0])),
|
|
1294
|
+
levels=levels,
|
|
1295
|
+
cmap=cmap,
|
|
1296
|
+
extend="both",
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
# Plot setup
|
|
1300
|
+
# Set y-limits to match plot coordinate system (0 to total_height_plot = 1.0)
|
|
1301
|
+
plot_ymin = -0.1
|
|
1302
|
+
plot_ymax = (
|
|
1303
|
+
total_height_plot # Should be 1.0 (slab_proportion + weaklayer_proportion)
|
|
1304
|
+
)
|
|
1305
|
+
|
|
1306
|
+
# Set limits first, then aspect ratio to avoid matplotlib adjusting limits
|
|
1307
|
+
ax.set_xlim([xmin, xmax])
|
|
1308
|
+
ax.set_ylim([plot_ymin, plot_ymax])
|
|
1309
|
+
ax.invert_yaxis()
|
|
1310
|
+
ax.use_sticky_edges = False
|
|
1311
|
+
|
|
1312
|
+
# Hide the default y-axis on main axis (we'll use custom axes)
|
|
1313
|
+
ax.yaxis.set_visible(False)
|
|
1314
|
+
|
|
1315
|
+
# Set up dual y-axes
|
|
1316
|
+
# Right axis: slab height in cm (0 at top, H_cm at bottom of slab)
|
|
1317
|
+
ax_right = ax.twinx()
|
|
1318
|
+
slab_height_max = H_cm
|
|
1319
|
+
# Map plot coordinates to physical slab height values
|
|
1320
|
+
# Plot: 0 to slab_proportion (0.6) maps to physical: 0 to H_cm
|
|
1321
|
+
slab_height_ticks = np.linspace(0, slab_height_max, num=5)
|
|
1322
|
+
slab_height_positions_plot = (
|
|
1323
|
+
slab_height_ticks / slab_height_max
|
|
1324
|
+
) * slab_proportion
|
|
1325
|
+
ax_right.set_yticks(slab_height_positions_plot)
|
|
1326
|
+
ax_right.set_yticklabels([f"{tick:.1f}" for tick in slab_height_ticks])
|
|
1327
|
+
# Ensure right axis ticks and label are on the right side
|
|
1328
|
+
ax_right.yaxis.tick_right()
|
|
1329
|
+
ax_right.yaxis.set_label_position("right")
|
|
1330
|
+
ax_right.set_ylim([plot_ymin, plot_ymax])
|
|
1331
|
+
ax_right.invert_yaxis()
|
|
1332
|
+
ax_right.set_ylabel(
|
|
1333
|
+
r"slab depth [cm] $\longleftarrow$", rotation=90, labelpad=5, loc="top"
|
|
1334
|
+
)
|
|
1335
|
+
|
|
1336
|
+
# Left axis: weak layer height in mm (0 at bottom of slab, h at bottom of weak layer)
|
|
1337
|
+
ax_left = ax.twinx()
|
|
1338
|
+
weak_layer_h_mm = analyzer.sm.weak_layer.h
|
|
1339
|
+
# Map plot coordinates to physical weak layer height values
|
|
1340
|
+
# Plot: slab_proportion (0.6) to total_height_plot (1.0) maps to physical: 0 to h_mm
|
|
1341
|
+
weaklayer_height_ticks = np.linspace(0, weak_layer_h_mm, num=3)
|
|
1342
|
+
# Map from plot coordinates (slab_proportion to 1.0) to physical (0 to h_mm)
|
|
1343
|
+
weaklayer_height_positions_plot = (
|
|
1344
|
+
slab_proportion
|
|
1345
|
+
+ (weaklayer_height_ticks / weak_layer_h_mm) * weaklayer_proportion
|
|
1346
|
+
)
|
|
1347
|
+
ax_left.set_yticks(weaklayer_height_positions_plot)
|
|
1348
|
+
ax_left.set_yticklabels([f"{tick:.1f}" for tick in weaklayer_height_ticks])
|
|
1349
|
+
# Move left axis to the left side
|
|
1350
|
+
ax_left.yaxis.tick_left()
|
|
1351
|
+
ax_left.yaxis.set_label_position("left")
|
|
1352
|
+
ax_left.set_ylim([plot_ymin, plot_ymax])
|
|
1353
|
+
ax_left.invert_yaxis()
|
|
1354
|
+
ax_left.set_ylabel(
|
|
1355
|
+
r"weaklayer depth [mm] $\longleftarrow$",
|
|
1356
|
+
rotation=90,
|
|
1357
|
+
labelpad=5,
|
|
1358
|
+
loc="bottom",
|
|
1359
|
+
)
|
|
1360
|
+
|
|
1361
|
+
# Plot labels
|
|
1362
|
+
ax.set_xlabel(r"lateral position $x$ (cm) $\longrightarrow$")
|
|
1363
|
+
|
|
1364
|
+
# Show colorbar
|
|
1365
|
+
ticks = np.linspace(levels[0], levels[-1], num=11, endpoint=True)
|
|
1366
|
+
fig.colorbar(
|
|
1367
|
+
ax.contourf(
|
|
1368
|
+
Xsl + Usl_plot,
|
|
1369
|
+
Zsl_plot + Wsl_plot,
|
|
1370
|
+
slab,
|
|
1371
|
+
levels=levels,
|
|
1372
|
+
cmap=cmap,
|
|
1373
|
+
extend="both",
|
|
1374
|
+
),
|
|
1375
|
+
orientation="horizontal",
|
|
1376
|
+
ticks=ticks,
|
|
1377
|
+
label=label,
|
|
1378
|
+
aspect=35,
|
|
1379
|
+
)
|
|
1380
|
+
|
|
1381
|
+
# Save figure
|
|
1382
|
+
self._save_figure(filename, fig)
|
|
1383
|
+
|
|
1384
|
+
return fig
|
|
1385
|
+
|
|
1036
1386
|
def plot_stress_envelope(
|
|
1037
1387
|
self,
|
|
1038
1388
|
system_model: SystemModel,
|
|
@@ -79,6 +79,42 @@ class TestAnalyzer(unittest.TestCase):
|
|
|
79
79
|
self.assertTrue(np.isfinite(Txz).all())
|
|
80
80
|
self.assertTrue(np.isfinite(Szz).all())
|
|
81
81
|
|
|
82
|
+
def test_stress_fields_unit_conversion(self):
|
|
83
|
+
"""Test stress fields unit conversion."""
|
|
84
|
+
_, Z, _ = self.an_ski.rasterize_solution(num=150)
|
|
85
|
+
phi = self.sm_ski.scenario.phi
|
|
86
|
+
Sxx_kPa = self.an_ski.Sxx(Z=Z, phi=phi, dz=5, unit="kPa")
|
|
87
|
+
Sxx_MPa = self.an_ski.Sxx(Z=Z, phi=phi, dz=5, unit="MPa")
|
|
88
|
+
self.assertEqual(Sxx_kPa.shape, Sxx_MPa.shape)
|
|
89
|
+
np.testing.assert_array_almost_equal(Sxx_kPa, Sxx_MPa * 1e3, decimal=8)
|
|
90
|
+
principal_stress_MPa = self.an_ski.principal_stress_slab(
|
|
91
|
+
Z=Z, phi=phi, dz=5, unit="MPa"
|
|
92
|
+
)
|
|
93
|
+
principal_stress_kPa = self.an_ski.principal_stress_slab(
|
|
94
|
+
Z=Z, phi=phi, dz=5, unit="kPa"
|
|
95
|
+
)
|
|
96
|
+
self.assertEqual(principal_stress_MPa.shape, principal_stress_kPa.shape)
|
|
97
|
+
np.testing.assert_array_almost_equal(
|
|
98
|
+
principal_stress_MPa * 1e3, principal_stress_kPa, decimal=8
|
|
99
|
+
)
|
|
100
|
+
# Test normalized is the same irrespective of unit
|
|
101
|
+
Sxx_kPa_norm = self.an_ski.Sxx(Z=Z, phi=phi, dz=5, unit="kPa", normalize=True)
|
|
102
|
+
Sxx_MPa_norm = self.an_ski.Sxx(Z=Z, phi=phi, dz=5, unit="MPa", normalize=True)
|
|
103
|
+
self.assertEqual(Sxx_kPa_norm.shape, Sxx_MPa_norm.shape)
|
|
104
|
+
np.testing.assert_array_almost_equal(Sxx_kPa_norm, Sxx_MPa_norm, decimal=8)
|
|
105
|
+
principal_stress_MPa_norm = self.an_ski.principal_stress_slab(
|
|
106
|
+
Z=Z, phi=phi, dz=5, unit="MPa", normalize=True
|
|
107
|
+
)
|
|
108
|
+
principal_stress_kPa_norm = self.an_ski.principal_stress_slab(
|
|
109
|
+
Z=Z, phi=phi, dz=5, unit="kPa", normalize=True
|
|
110
|
+
)
|
|
111
|
+
self.assertEqual(
|
|
112
|
+
principal_stress_MPa_norm.shape, principal_stress_kPa_norm.shape
|
|
113
|
+
)
|
|
114
|
+
np.testing.assert_array_almost_equal(
|
|
115
|
+
principal_stress_MPa_norm, principal_stress_kPa_norm, decimal=8
|
|
116
|
+
)
|
|
117
|
+
|
|
82
118
|
def test_principal_stress_slab_variants(self):
|
|
83
119
|
"""Test principal stress slab variants."""
|
|
84
120
|
_, Z, _ = self.an_ski.rasterize_solution(num=120)
|
|
@@ -13,7 +13,7 @@ from weac.analysis.criteria_evaluator import (
|
|
|
13
13
|
CoupledCriterionResult,
|
|
14
14
|
CriteriaEvaluator,
|
|
15
15
|
FindMinimumForceResult,
|
|
16
|
-
|
|
16
|
+
SteadyStateResult,
|
|
17
17
|
)
|
|
18
18
|
from weac.components import (
|
|
19
19
|
Config,
|
|
@@ -183,8 +183,8 @@ class TestCriteriaEvaluator(unittest.TestCase):
|
|
|
183
183
|
self.assertIsInstance(results, CoupledCriterionResult)
|
|
184
184
|
self.assertGreater(results.critical_skier_weight, 0)
|
|
185
185
|
|
|
186
|
-
def
|
|
187
|
-
"""Test the
|
|
186
|
+
def test_evaluate_SteadyState(self):
|
|
187
|
+
"""Test the evaluate_SteadyState method."""
|
|
188
188
|
segments = [
|
|
189
189
|
Segment(length=self.segments_length, has_foundation=True, m=0),
|
|
190
190
|
Segment(length=self.segments_length, has_foundation=True, m=0),
|
|
@@ -198,11 +198,17 @@ class TestCriteriaEvaluator(unittest.TestCase):
|
|
|
198
198
|
),
|
|
199
199
|
config=Config(touchdown=True),
|
|
200
200
|
)
|
|
201
|
-
results:
|
|
201
|
+
results: SteadyStateResult = self.evaluator.evaluate_SteadyState(system)
|
|
202
202
|
self.assertTrue(results.converged)
|
|
203
|
-
self.assertGreater(results.
|
|
203
|
+
self.assertGreater(results.energy_release_rate, 0)
|
|
204
204
|
self.assertGreater(results.touchdown_distance, 0)
|
|
205
205
|
self.assertLess(results.touchdown_distance, system.scenario.L)
|
|
206
|
+
max_principal_stress_norm = (
|
|
207
|
+
results.maximal_stress_result.max_principal_stress_norm
|
|
208
|
+
)
|
|
209
|
+
max_Sxx_norm = results.maximal_stress_result.max_Sxx_norm
|
|
210
|
+
self.assertGreater(max_principal_stress_norm, 0)
|
|
211
|
+
self.assertGreater(max_Sxx_norm, 0)
|
|
206
212
|
|
|
207
213
|
def test_find_minimum_crack_length(self):
|
|
208
214
|
"""Test the find_minimum_crack_length method."""
|
|
@@ -416,13 +416,13 @@ class TestRegressionSimulation(unittest.TestCase):
|
|
|
416
416
|
self.assertAlmostEqual(fm.max_dist_stress, 1.0000189267255666, places=6)
|
|
417
417
|
self.assertLess(fm.min_dist_stress, 1.0)
|
|
418
418
|
|
|
419
|
-
#
|
|
420
|
-
ss = evaluator.
|
|
419
|
+
# evaluate_SteadyState baseline
|
|
420
|
+
ss = evaluator.evaluate_SteadyState(system=sm, vertical=False)
|
|
421
421
|
self.assertTrue(ss.converged)
|
|
422
422
|
self.assertGreater(ss.touchdown_distance, 0)
|
|
423
423
|
# Baseline values recorded
|
|
424
424
|
self.assertAlmostEqual(ss.touchdown_distance, 1265.551924690803, places=6)
|
|
425
|
-
np.testing.assert_allclose(ss.
|
|
425
|
+
np.testing.assert_allclose(ss.energy_release_rate, 2.123992, rtol=1e-6, atol=0)
|
|
426
426
|
|
|
427
427
|
# evaluate_coupled_criterion baseline
|
|
428
428
|
cc = evaluator.evaluate_coupled_criterion(system=sm, max_iterations=10)
|
|
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
|
|
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
|
|
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
|
|
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
|