weac 3.1.0__tar.gz → 3.1.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.
- {weac-3.1.0 → weac-3.1.2}/CITATION.cff +1 -1
- {weac-3.1.0/src/weac.egg-info → weac-3.1.2}/PKG-INFO +2 -2
- {weac-3.1.0 → weac-3.1.2}/pyproject.toml +3 -3
- {weac-3.1.0 → weac-3.1.2}/src/weac/__init__.py +1 -1
- {weac-3.1.0 → weac-3.1.2}/src/weac/analysis/analyzer.py +4 -8
- {weac-3.1.0 → weac-3.1.2}/src/weac/analysis/criteria_evaluator.py +30 -6
- {weac-3.1.0 → weac-3.1.2}/src/weac/analysis/plotter.py +32 -17
- {weac-3.1.0 → weac-3.1.2}/src/weac/components/layer.py +65 -13
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/field_quantities.py +27 -23
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/slab_touchdown.py +5 -4
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/system_model.py +1 -2
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/unknown_constants_solver.py +13 -13
- {weac-3.1.0 → weac-3.1.2}/src/weac/utils/geldsetzer.py +10 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/utils/snow_types.py +5 -0
- {weac-3.1.0 → weac-3.1.2/src/weac.egg-info}/PKG-INFO +2 -2
- {weac-3.1.0 → weac-3.1.2}/src/weac.egg-info/requires.txt +1 -1
- {weac-3.1.0 → weac-3.1.2}/tests/analysis/test_criteria_evaluator.py +82 -0
- weac-3.1.2/tests/components/test_layer.py +441 -0
- {weac-3.1.0 → weac-3.1.2}/tests/test_regression_simulation.py +1 -1
- weac-3.1.0/tests/components/test_layer.py +0 -221
- {weac-3.1.0 → weac-3.1.2}/LICENSE +0 -0
- {weac-3.1.0 → weac-3.1.2}/MANIFEST.in +0 -0
- {weac-3.1.0 → weac-3.1.2}/README.md +0 -0
- {weac-3.1.0 → weac-3.1.2}/img/bc.png +0 -0
- {weac-3.1.0 → weac-3.1.2}/img/layering.png +0 -0
- {weac-3.1.0 → weac-3.1.2}/img/logo.png +0 -0
- {weac-3.1.0 → weac-3.1.2}/img/model.png +0 -0
- {weac-3.1.0 → weac-3.1.2}/img/profiles.png +0 -0
- {weac-3.1.0 → weac-3.1.2}/img/systems.png +0 -0
- {weac-3.1.0 → weac-3.1.2}/setup.cfg +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/analysis/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/components/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/components/config.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/components/criteria_config.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/components/model_input.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/components/scenario_config.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/components/segment.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/constants.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/eigensystem.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/scenario.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/core/slab.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/logging_config.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/utils/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/utils/misc.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac/utils/snowpilot_parser.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac.egg-info/SOURCES.txt +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac.egg-info/dependency_links.txt +0 -0
- {weac-3.1.0 → weac-3.1.2}/src/weac.egg-info/top_level.txt +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/analysis/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/analysis/test_analyzer.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/components/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/components/test_configs.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/core/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/core/test_eigensystem.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/core/test_field_quantities.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/core/test_scenario.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/core/test_slab.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/core/test_slab_touchdown.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/core/test_system_model.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/run_tests.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/test_comparison_results.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/utils/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/utils/json_helpers.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/utils/test_json_helpers.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/utils/test_misc.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/tests/utils/test_snowpilot_parser.py +0 -0
- {weac-3.1.0 → weac-3.1.2}/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.1.
|
|
11
|
+
version: 3.1.2
|
|
12
12
|
date-released: 2021-12-30
|
|
13
13
|
identifiers:
|
|
14
14
|
- description: Collection of archived snapshots of all versions of WEAC
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weac
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: Weak layer anticrack nucleation model
|
|
5
5
|
Author-email: 2phi GbR <mail@2phi.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.12
|
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: matplotlib>=3.9.1
|
|
20
|
-
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: numpy<2.4.0,>=2.3.5
|
|
21
21
|
Requires-Dist: scipy>=1.14.0
|
|
22
22
|
Requires-Dist: pydantic>=2.11.7
|
|
23
23
|
Requires-Dist: snowpylot>=1.1.3
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weac"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.2"
|
|
8
8
|
authors = [{ name = "2phi GbR", email = "mail@2phi.de" }]
|
|
9
9
|
description = "Weak layer anticrack nucleation model"
|
|
10
10
|
readme = "README.md"
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
22
|
"matplotlib>=3.9.1",
|
|
23
|
-
"numpy>=2.0
|
|
23
|
+
"numpy>=2.3.5,<2.4.0",
|
|
24
24
|
"scipy>=1.14.0",
|
|
25
25
|
"pydantic>=2.11.7",
|
|
26
26
|
"snowpylot>=1.1.3",
|
|
@@ -123,7 +123,7 @@ ignore = [
|
|
|
123
123
|
]
|
|
124
124
|
|
|
125
125
|
[tool.bumpversion]
|
|
126
|
-
current_version = "3.1.
|
|
126
|
+
current_version = "3.1.2"
|
|
127
127
|
|
|
128
128
|
[[tool.bumpversion.files]]
|
|
129
129
|
filename = "pyproject.toml"
|
|
@@ -272,11 +272,7 @@ class Analyzer:
|
|
|
272
272
|
|
|
273
273
|
# Calculate weight load at grid points and superimpose on stress field
|
|
274
274
|
qt = -rho * G_MM_S2 * np.sin(np.deg2rad(phi))
|
|
275
|
-
|
|
276
|
-
# for i, qi in enumerate(qt[:-1]):
|
|
277
|
-
# Sxx[i, :] += qi * (zi[i + 1] - zi[i])
|
|
278
|
-
# Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
|
|
279
|
-
# New Implementation: Changed for numerical stability
|
|
275
|
+
|
|
280
276
|
dz = np.diff(zi)
|
|
281
277
|
Sxx_MPa[:-1, :] += qt[:-1, np.newaxis] * dz[:, np.newaxis]
|
|
282
278
|
Sxx_MPa[-1, :] += qt[-1] * dz[-1]
|
|
@@ -393,7 +389,7 @@ class Analyzer:
|
|
|
393
389
|
# Get mesh along z-axis
|
|
394
390
|
zmesh = self.get_zmesh(dz=dz)
|
|
395
391
|
zi = zmesh["z"]
|
|
396
|
-
|
|
392
|
+
rho_t_mm3 = zmesh["rho"]
|
|
397
393
|
qs = self.sm.scenario.surface_load
|
|
398
394
|
# Get dimensions of stress field (n rows, m columns)
|
|
399
395
|
n = len(zi)
|
|
@@ -414,13 +410,13 @@ class Analyzer:
|
|
|
414
410
|
dsxx_dxdx[i, :] = E / (1 - nu**2) * (du0_dxdxdx + z * dpsi_dxdxdx)
|
|
415
411
|
|
|
416
412
|
# Calculate weight load at grid points
|
|
417
|
-
qn =
|
|
413
|
+
qn = -rho_t_mm3 * G_MM_S2 * np.cos(np.deg2rad(phi))
|
|
418
414
|
|
|
419
415
|
# Integrate dsxx_dxdx twice along z to obtain transverse
|
|
420
416
|
# normal stress Szz in MPa
|
|
421
417
|
integrand = cumulative_trapezoid(dsxx_dxdx, zi, axis=0, initial=0)
|
|
422
418
|
Szz_MPa = cumulative_trapezoid(integrand, zi, axis=0, initial=0)
|
|
423
|
-
Szz_MPa += cumulative_trapezoid(
|
|
419
|
+
Szz_MPa += cumulative_trapezoid(qn, zi, initial=0)[:, None]
|
|
424
420
|
|
|
425
421
|
# Normalize tensile stresses to tensile strength
|
|
426
422
|
if normalize:
|
|
@@ -24,6 +24,7 @@ from weac.components import (
|
|
|
24
24
|
WeakLayer,
|
|
25
25
|
)
|
|
26
26
|
from weac.constants import RHO_ICE
|
|
27
|
+
from weac.core.slab_touchdown import TouchdownMode
|
|
27
28
|
from weac.core.system_model import SystemModel
|
|
28
29
|
|
|
29
30
|
logger = logging.getLogger(__name__)
|
|
@@ -113,6 +114,9 @@ class MaximalStressResult:
|
|
|
113
114
|
The normalized maximum principal stress to the tensile strength of the layers.
|
|
114
115
|
max_Sxx_norm: float
|
|
115
116
|
The normalized maximum axial normal stress to the tensile strength of the layers.
|
|
117
|
+
slab_tensile_criterion: float
|
|
118
|
+
The slab tensile criterion, i.e. the portion of the slab thickness that is prone
|
|
119
|
+
to fail under tensile stresses in the steady state (between 0 and 1).
|
|
116
120
|
"""
|
|
117
121
|
|
|
118
122
|
principal_stress_kPa: np.ndarray
|
|
@@ -121,6 +125,7 @@ class MaximalStressResult:
|
|
|
121
125
|
Sxx_norm: np.ndarray
|
|
122
126
|
max_principal_stress_norm: float
|
|
123
127
|
max_Sxx_norm: float
|
|
128
|
+
slab_tensile_criterion: float
|
|
124
129
|
|
|
125
130
|
|
|
126
131
|
@dataclass
|
|
@@ -679,6 +684,7 @@ class CriteriaEvaluator:
|
|
|
679
684
|
def evaluate_SteadyState(
|
|
680
685
|
self,
|
|
681
686
|
system: SystemModel,
|
|
687
|
+
mode: TouchdownMode = "C_in_contact",
|
|
682
688
|
vertical: bool = False,
|
|
683
689
|
print_call_stats: bool = False,
|
|
684
690
|
) -> SteadyStateResult:
|
|
@@ -706,20 +712,34 @@ class CriteriaEvaluator:
|
|
|
706
712
|
UserWarning,
|
|
707
713
|
)
|
|
708
714
|
system_copy = copy.deepcopy(system)
|
|
709
|
-
|
|
710
|
-
system_copy.
|
|
711
|
-
|
|
715
|
+
# Evaluate touchdown distance for flat slab
|
|
716
|
+
system_copy.toggle_touchdown(True)
|
|
717
|
+
segments = [
|
|
718
|
+
Segment(length=5e3, has_foundation=True, m=0.0),
|
|
719
|
+
Segment(length=5e3, has_foundation=False, m=0.0),
|
|
720
|
+
]
|
|
721
|
+
system_copy.update_scenario(
|
|
722
|
+
segments=segments, scenario_config=ScenarioConfig(phi=0.0)
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
cut_distance = 0
|
|
726
|
+
match mode:
|
|
727
|
+
case "C_in_contact":
|
|
728
|
+
cut_distance = 2 * system_copy.slab_touchdown.l_BC
|
|
729
|
+
case "B_point_contact":
|
|
730
|
+
cut_distance = system_copy.slab_touchdown.l_BC - 1e-3
|
|
731
|
+
case "A_free_hanging":
|
|
732
|
+
cut_distance = system_copy.slab_touchdown.l_AB - 1e-3
|
|
712
733
|
|
|
713
734
|
segments = [
|
|
714
735
|
Segment(length=5e3, has_foundation=True, m=0.0),
|
|
715
|
-
Segment(length=
|
|
736
|
+
Segment(length=cut_distance, has_foundation=False, m=0.0),
|
|
716
737
|
]
|
|
717
738
|
scenario_config = ScenarioConfig(
|
|
718
739
|
system_type="vpst-" if vertical else "pst-",
|
|
719
740
|
phi=0.0, # Slab Touchdown works only for flat slab
|
|
720
|
-
cut_length=
|
|
741
|
+
cut_length=cut_distance,
|
|
721
742
|
)
|
|
722
|
-
# system_copy.config.touchdown = True
|
|
723
743
|
system_copy.update_scenario(segments=segments, scenario_config=scenario_config)
|
|
724
744
|
touchdown_distance = system_copy.slab_touchdown.touchdown_distance
|
|
725
745
|
analyzer = Analyzer(system_copy, printing_enabled=print_call_stats)
|
|
@@ -1248,6 +1268,9 @@ class CriteriaEvaluator:
|
|
|
1248
1268
|
)
|
|
1249
1269
|
max_principal_stress_norm = np.max(principal_stress_norm)
|
|
1250
1270
|
max_Sxx_norm = np.max(Sxx_norm)
|
|
1271
|
+
# evaluate for each height level if the slab is prone to fail under tensile stresses
|
|
1272
|
+
height_level_prone_to_fail = np.max(Sxx_norm, axis=1)
|
|
1273
|
+
slab_tensile_criterion = np.mean(height_level_prone_to_fail)
|
|
1251
1274
|
if print_call_stats:
|
|
1252
1275
|
analyzer.print_call_stats(
|
|
1253
1276
|
message="_calculate_maximal_stresses Call Statistics"
|
|
@@ -1259,4 +1282,5 @@ class CriteriaEvaluator:
|
|
|
1259
1282
|
Sxx_norm=Sxx_norm,
|
|
1260
1283
|
max_principal_stress_norm=max_principal_stress_norm,
|
|
1261
1284
|
max_Sxx_norm=max_Sxx_norm,
|
|
1285
|
+
slab_tensile_criterion=slab_tensile_criterion,
|
|
1262
1286
|
)
|
|
@@ -1041,6 +1041,7 @@ class Plotter:
|
|
|
1041
1041
|
xwl: np.ndarray,
|
|
1042
1042
|
z: np.ndarray,
|
|
1043
1043
|
analyzer: Analyzer,
|
|
1044
|
+
window: float | None = None,
|
|
1044
1045
|
weaklayer_proportion: float | None = None,
|
|
1045
1046
|
dz: int = 2,
|
|
1046
1047
|
levels: int = 300,
|
|
@@ -1061,6 +1062,8 @@ class Plotter:
|
|
|
1061
1062
|
Solution vector.
|
|
1062
1063
|
analyzer : Analyzer
|
|
1063
1064
|
Analyzer instance.
|
|
1065
|
+
window: float | None, optional
|
|
1066
|
+
Window size for the plot. Shows the right edge of the slab, where the slab is deformed. Default is None.
|
|
1064
1067
|
weaklayer_proportion: float | None, optional
|
|
1065
1068
|
Proportion of the plot to allocate to the weak layer. Default is None.
|
|
1066
1069
|
dz : int, optional
|
|
@@ -1087,6 +1090,9 @@ class Plotter:
|
|
|
1087
1090
|
phi = analyzer.sm.scenario.phi
|
|
1088
1091
|
system_type = analyzer.sm.scenario.system_type
|
|
1089
1092
|
fq = analyzer.sm.fq
|
|
1093
|
+
sigma_comp = (
|
|
1094
|
+
analyzer.sm.weak_layer.sigma_comp
|
|
1095
|
+
) # Compressive strength of the weak layer [kPa]
|
|
1090
1096
|
|
|
1091
1097
|
# Compute slab displacements on grid (cm)
|
|
1092
1098
|
Usl = np.vstack([fq.u(z, h0=h0, unit="cm") for h0 in zi])
|
|
@@ -1204,7 +1210,7 @@ class Plotter:
|
|
|
1204
1210
|
z, phi, dz=dz, val="max", unit="kPa", normalize=normalize
|
|
1205
1211
|
)
|
|
1206
1212
|
weak_full = analyzer.principal_stress_weaklayer(
|
|
1207
|
-
z, val="min", unit="kPa", normalize=normalize
|
|
1213
|
+
z, sc=sigma_comp, val="min", unit="kPa", normalize=normalize
|
|
1208
1214
|
)
|
|
1209
1215
|
weak = weak_full[nanmask]
|
|
1210
1216
|
if normalize:
|
|
@@ -1249,13 +1255,14 @@ class Plotter:
|
|
|
1249
1255
|
[slab_proportion + cracked_proportion, total_height_plot],
|
|
1250
1256
|
)
|
|
1251
1257
|
# No displacements for the cracked weak layer outline (undeformed)
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1258
|
+
if xwl_cracked.shape[0] > 0:
|
|
1259
|
+
ax.plot(
|
|
1260
|
+
_outline(Xwl_cracked),
|
|
1261
|
+
_outline(Zwl_cracked_plot),
|
|
1262
|
+
"k-",
|
|
1263
|
+
alpha=0.3,
|
|
1264
|
+
linewidth=1,
|
|
1265
|
+
)
|
|
1259
1266
|
|
|
1260
1267
|
# Then plot the deformed weak-layer outline where it exists
|
|
1261
1268
|
if system_type in ["-pst", "pst-", "-vpst", "vpst-"]:
|
|
@@ -1287,14 +1294,15 @@ class Plotter:
|
|
|
1287
1294
|
cmap=cmap,
|
|
1288
1295
|
extend="both",
|
|
1289
1296
|
)
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1297
|
+
if xwl_cracked.shape[0] > 0:
|
|
1298
|
+
ax.contourf(
|
|
1299
|
+
Xwl_cracked,
|
|
1300
|
+
Zwl_cracked_plot,
|
|
1301
|
+
np.zeros((2, xwl_cracked.shape[0])),
|
|
1302
|
+
levels=levels,
|
|
1303
|
+
cmap=cmap,
|
|
1304
|
+
extend="both",
|
|
1305
|
+
)
|
|
1298
1306
|
|
|
1299
1307
|
# Plot setup
|
|
1300
1308
|
# Set y-limits to match plot coordinate system (0 to total_height_plot = 1.0)
|
|
@@ -1304,7 +1312,10 @@ class Plotter:
|
|
|
1304
1312
|
)
|
|
1305
1313
|
|
|
1306
1314
|
# Set limits first, then aspect ratio to avoid matplotlib adjusting limits
|
|
1307
|
-
|
|
1315
|
+
if window is None:
|
|
1316
|
+
ax.set_xlim([xmin, xmax])
|
|
1317
|
+
else:
|
|
1318
|
+
ax.set_xlim([xmax - window, xmax])
|
|
1308
1319
|
ax.set_ylim([plot_ymin, plot_ymax])
|
|
1309
1320
|
ax.invert_yaxis()
|
|
1310
1321
|
ax.use_sticky_edges = False
|
|
@@ -1360,6 +1371,10 @@ class Plotter:
|
|
|
1360
1371
|
|
|
1361
1372
|
# Plot labels
|
|
1362
1373
|
ax.set_xlabel(r"lateral position $x$ (cm) $\longrightarrow$")
|
|
1374
|
+
ax.set_title(
|
|
1375
|
+
f"{field}{' (normalized to tensile strength)' if normalize else ''}",
|
|
1376
|
+
size=10,
|
|
1377
|
+
)
|
|
1363
1378
|
|
|
1364
1379
|
# Show colorbar
|
|
1365
1380
|
ticks = np.linspace(levels[0], levels[-1], num=11, endpoint=True)
|
|
@@ -94,6 +94,45 @@ def _sigrist_tensile_strength(rho, unit: Literal["kPa", "MPa"] = "kPa"):
|
|
|
94
94
|
return convert[unit] * 240 * (rho / RHO_ICE) ** 2.44
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def _adam_tensile_strength(rho, unit: Literal["kPa", "MPa"] = "kPa"):
|
|
98
|
+
"""
|
|
99
|
+
Estimate the tensile strength of a slab layer from its density.
|
|
100
|
+
|
|
101
|
+
Uses the density parametrization of Adam (2025).
|
|
102
|
+
|
|
103
|
+
Arguments
|
|
104
|
+
---------
|
|
105
|
+
rho : ndarray, float
|
|
106
|
+
Layer density (kg/m^3).
|
|
107
|
+
unit : str, optional
|
|
108
|
+
Desired output unit of the layer strength. Default is 'kPa'.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
ndarray
|
|
113
|
+
Tensile strength in specified unit.
|
|
114
|
+
"""
|
|
115
|
+
convert = {"kPa": 1e3, "MPa": 1}
|
|
116
|
+
TS_0 = 1.0 # [MPa]
|
|
117
|
+
kappa = 3.45 # [-]
|
|
118
|
+
# Adam's equation is given in MPa
|
|
119
|
+
return TS_0 * (rho / RHO_ICE) ** kappa * convert[unit]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# # TODO: Compressive Strength from Schöttner
|
|
123
|
+
# def _schotter_compressive_strength(rho, unit: Literal["kPa", "MPa"] = "kPa"):
|
|
124
|
+
# """
|
|
125
|
+
# Estimate the compressive strength of a slab layer from its density.
|
|
126
|
+
# On the compressive strength of weak snow layers of depth hoar - Schöttner (2025).
|
|
127
|
+
|
|
128
|
+
# Uses the density parametrization of Schöttner (2025).
|
|
129
|
+
# """
|
|
130
|
+
# convert = {"kPa": 1e3, "MPa": 1}
|
|
131
|
+
# CS_0 = 11.0 # [MPa]
|
|
132
|
+
# CS_1 = 5.4 # [-]
|
|
133
|
+
# return CS_0 * (rho / RHO_ICE) ** CS_1 * convert[unit]
|
|
134
|
+
|
|
135
|
+
|
|
97
136
|
class Layer(BaseModel):
|
|
98
137
|
"""
|
|
99
138
|
Regular slab layer (no foundation springs).
|
|
@@ -110,6 +149,10 @@ class Layer(BaseModel):
|
|
|
110
149
|
Young's modulus E [MPa]. If omitted it is derived from ``rho``.
|
|
111
150
|
G : float, optional
|
|
112
151
|
Shear modulus G [MPa]. If omitted it is derived from ``E`` and ``nu``.
|
|
152
|
+
tensile_strength: float
|
|
153
|
+
Tensile strength [kPa].
|
|
154
|
+
tensile_strength_method: Literal["sigrist", "adam", "hybrid"]
|
|
155
|
+
Method to calculate the tensile strength.
|
|
113
156
|
"""
|
|
114
157
|
|
|
115
158
|
# has to be provided
|
|
@@ -125,8 +168,8 @@ class Layer(BaseModel):
|
|
|
125
168
|
tensile_strength: float = Field(
|
|
126
169
|
default=0.0, ge=0, description="Tensile strength [kPa]"
|
|
127
170
|
)
|
|
128
|
-
tensile_strength_method: Literal["sigrist"] = Field(
|
|
129
|
-
default="
|
|
171
|
+
tensile_strength_method: Literal["sigrist", "adam", "hybrid"] = Field(
|
|
172
|
+
default="hybrid",
|
|
130
173
|
description="Method to calculate the tensile strength",
|
|
131
174
|
)
|
|
132
175
|
E_method: Literal["bergfeld", "scapazzo", "gerling"] = Field(
|
|
@@ -149,17 +192,23 @@ class Layer(BaseModel):
|
|
|
149
192
|
else:
|
|
150
193
|
raise ValueError(f"Invalid E_method: {self.E_method}")
|
|
151
194
|
object.__setattr__(self, "G", self.G or self.E / (2 * (1 + self.nu)))
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
195
|
+
|
|
196
|
+
if not self.tensile_strength:
|
|
197
|
+
if self.tensile_strength_method == "sigrist":
|
|
198
|
+
ts_value = _sigrist_tensile_strength(self.rho, unit="kPa")
|
|
199
|
+
elif self.tensile_strength_method == "adam":
|
|
200
|
+
ts_value = _adam_tensile_strength(self.rho, unit="kPa")
|
|
201
|
+
elif self.tensile_strength_method == "hybrid":
|
|
202
|
+
# Use Sigrist for rho < 250, Adam for rho >= 250
|
|
203
|
+
if self.rho < 250:
|
|
204
|
+
ts_value = _sigrist_tensile_strength(self.rho, unit="kPa")
|
|
205
|
+
else:
|
|
206
|
+
ts_value = _adam_tensile_strength(self.rho, unit="kPa")
|
|
207
|
+
else:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
f"Invalid tensile_strength_method: {self.tensile_strength_method}"
|
|
210
|
+
)
|
|
211
|
+
object.__setattr__(self, "tensile_strength", ts_value)
|
|
163
212
|
|
|
164
213
|
@model_validator(mode="after")
|
|
165
214
|
def validate_positive_E_G(self):
|
|
@@ -225,6 +274,9 @@ class WeakLayer(BaseModel):
|
|
|
225
274
|
)
|
|
226
275
|
sigma_c: float = Field(default=6.16, gt=0, description="Tensile strength [kPa]")
|
|
227
276
|
tau_c: float = Field(default=5.09, gt=0, description="Shear strength [kPa]")
|
|
277
|
+
sigma_comp: float = Field(
|
|
278
|
+
default=2.6, gt=0, description="Compressive strength [kPa]"
|
|
279
|
+
)
|
|
228
280
|
E_method: Literal["bergfeld", "scapazzo", "gerling"] = Field(
|
|
229
281
|
default="bergfeld",
|
|
230
282
|
description="Method to calculate the Young's modulus",
|
|
@@ -58,19 +58,23 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
58
58
|
h0: float = 0,
|
|
59
59
|
unit: LengthUnit = "mm",
|
|
60
60
|
) -> float | np.ndarray:
|
|
61
|
-
"""
|
|
61
|
+
"""
|
|
62
|
+
Horizontal displacement `u = u₀ + h₀ ψ` at depth `h₀` (mm).
|
|
63
|
+
"""
|
|
62
64
|
return self._unit_factor(unit) * (Z[0, :] + h0 * self.psi(Z))
|
|
63
65
|
|
|
64
66
|
def du_dx(self, Z: np.ndarray, h0: float) -> float | np.ndarray:
|
|
65
|
-
"""Derivative u' = u₀' + h₀ ψ'."""
|
|
67
|
+
"""Derivative u' = u₀' + h₀ ψ' (-)."""
|
|
66
68
|
return Z[1, :] + h0 * self.dpsi_dx(Z)
|
|
67
69
|
|
|
68
70
|
def w(self, Z: np.ndarray, unit: LengthUnit = "mm") -> float | np.ndarray:
|
|
69
|
-
"""
|
|
71
|
+
"""
|
|
72
|
+
Center-line (vertical) deflection `w` (mm).
|
|
73
|
+
"""
|
|
70
74
|
return self._unit_factor(unit) * Z[2, :]
|
|
71
75
|
|
|
72
76
|
def dw_dx(self, Z: np.ndarray) -> float | np.ndarray:
|
|
73
|
-
"""First derivative w'."""
|
|
77
|
+
"""First derivative `w'` (-)."""
|
|
74
78
|
return Z[3, :]
|
|
75
79
|
|
|
76
80
|
def psi(
|
|
@@ -78,32 +82,32 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
78
82
|
Z: np.ndarray,
|
|
79
83
|
unit: AngleUnit = "rad",
|
|
80
84
|
) -> float | np.ndarray:
|
|
81
|
-
"""Rotation
|
|
85
|
+
"""Rotation `ψ` of the mid-plane (rad)."""
|
|
82
86
|
factor = self._unit_factor(unit)
|
|
83
87
|
return factor * Z[4, :]
|
|
84
88
|
|
|
85
89
|
def dpsi_dx(self, Z: np.ndarray) -> float | np.ndarray:
|
|
86
|
-
"""First derivative
|
|
90
|
+
"""First derivative `ψ'` (rad/mm)."""
|
|
87
91
|
return Z[5, :]
|
|
88
92
|
|
|
89
93
|
def N(self, Z: np.ndarray) -> float | np.ndarray:
|
|
90
|
-
"""Axial normal force N = A11 u' + B11
|
|
94
|
+
"""Axial normal force `N = A11 u' + B11 ψ'` in the slab [N]"""
|
|
91
95
|
return self.es.A11 * Z[1, :] + self.es.B11 * Z[5, :]
|
|
92
96
|
|
|
93
97
|
def M(self, Z: np.ndarray) -> float | np.ndarray:
|
|
94
|
-
"""Bending moment M = B11 u' + D11
|
|
98
|
+
"""Bending moment `M = B11 u' + D11 ψ'` in the slab [Nmm]"""
|
|
95
99
|
return self.es.B11 * Z[1, :] + self.es.D11 * Z[5, :]
|
|
96
100
|
|
|
97
101
|
def V(self, Z: np.ndarray) -> float | np.ndarray:
|
|
98
|
-
"""Vertical shear force V = kA55(w' +
|
|
102
|
+
"""Vertical shear force `V = kA55(w' + ψ)` [N]"""
|
|
99
103
|
return self.es.kA55 * (Z[3, :] + Z[4, :])
|
|
100
104
|
|
|
101
105
|
def sig(self, Z: np.ndarray, unit: StressUnit = "MPa") -> float | np.ndarray:
|
|
102
|
-
"""Weak-layer normal stress"""
|
|
106
|
+
"""Weak-layer normal stress `sig = -kn * w`"""
|
|
103
107
|
return -self._unit_factor(unit) * self.es.weak_layer.kn * self.w(Z)
|
|
104
108
|
|
|
105
109
|
def tau(self, Z: np.ndarray, unit: StressUnit = "MPa") -> float | np.ndarray:
|
|
106
|
-
"""Weak-layer shear stress"""
|
|
110
|
+
"""Weak-layer shear stress `tau = -kt * (w' * h/2 - u(h=H/2))`"""
|
|
107
111
|
return (
|
|
108
112
|
-self._unit_factor(unit)
|
|
109
113
|
* self.es.weak_layer.kt
|
|
@@ -114,11 +118,11 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
114
118
|
)
|
|
115
119
|
|
|
116
120
|
def eps(self, Z: np.ndarray) -> float | np.ndarray:
|
|
117
|
-
"""Weak-layer normal strain"""
|
|
121
|
+
"""Weak-layer normal strain `eps = -w / h`"""
|
|
118
122
|
return -self.w(Z) / self.es.weak_layer.h
|
|
119
123
|
|
|
120
124
|
def gamma(self, Z: np.ndarray) -> float | np.ndarray:
|
|
121
|
-
"""Weak-layer shear strain
|
|
125
|
+
"""Weak-layer shear strain `gamma = (w' * h/2 - u(h=H/2)) / h`"""
|
|
122
126
|
return (
|
|
123
127
|
self.dw_dx(Z) / 2 - self.u(Z, h0=self.es.slab.H / 2) / self.es.weak_layer.h
|
|
124
128
|
)
|
|
@@ -176,9 +180,9 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
176
180
|
|
|
177
181
|
def dz_dxdx(self, z: np.ndarray, phi: float, qs: float) -> np.ndarray:
|
|
178
182
|
"""
|
|
179
|
-
Get second derivative z''(x) = K*z'(x) of the solution vector.
|
|
183
|
+
Get second derivative `z''(x) = K*z'(x)` of the solution vector.
|
|
180
184
|
|
|
181
|
-
z''(x) = [u''(x) u'''(x) w''(x) w'''(x) psi''(x), psi'''(x)]^T
|
|
185
|
+
`z''(x) = [u''(x) u'''(x) w''(x) w'''(x) psi''(x), psi'''(x)]^T`
|
|
182
186
|
|
|
183
187
|
Parameters
|
|
184
188
|
----------
|
|
@@ -200,7 +204,7 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
200
204
|
|
|
201
205
|
def du0_dxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
202
206
|
"""
|
|
203
|
-
Get second derivative of the horiz. centerline displacement u0''(x).
|
|
207
|
+
Get second derivative of the horiz. centerline displacement `u0''(x)` (mm⁻¹).
|
|
204
208
|
|
|
205
209
|
Parameters
|
|
206
210
|
----------
|
|
@@ -213,13 +217,13 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
213
217
|
-------
|
|
214
218
|
ndarray, float
|
|
215
219
|
Second derivative of the horizontal centerline displacement
|
|
216
|
-
u0''(x)
|
|
220
|
+
`u0''(x)`.
|
|
217
221
|
"""
|
|
218
222
|
return self.dz_dx(z, phi, qs)[1, :]
|
|
219
223
|
|
|
220
224
|
def dpsi_dxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
221
225
|
"""
|
|
222
|
-
Get second derivative of the cross-section rotation psi''(x).
|
|
226
|
+
Get second derivative of the cross-section rotation `psi''(x)` (mm⁻²).
|
|
223
227
|
|
|
224
228
|
Parameters
|
|
225
229
|
----------
|
|
@@ -231,13 +235,13 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
231
235
|
Returns
|
|
232
236
|
-------
|
|
233
237
|
ndarray, float
|
|
234
|
-
Second derivative of the cross-section rotation psi''(x)
|
|
238
|
+
Second derivative of the cross-section rotation psi''(x).
|
|
235
239
|
"""
|
|
236
240
|
return self.dz_dx(z, phi, qs)[5, :]
|
|
237
241
|
|
|
238
242
|
def du0_dxdxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
239
243
|
"""
|
|
240
|
-
Get third derivative of the horiz. centerline displacement u0'''(x).
|
|
244
|
+
Get third derivative of the horiz. centerline displacement `u0'''(x)` (mm⁻²).
|
|
241
245
|
|
|
242
246
|
Parameters
|
|
243
247
|
----------
|
|
@@ -250,13 +254,13 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
250
254
|
-------
|
|
251
255
|
ndarray, float
|
|
252
256
|
Third derivative of the horizontal centerline displacement
|
|
253
|
-
u0'''(x)
|
|
257
|
+
u0'''(x).
|
|
254
258
|
"""
|
|
255
259
|
return self.dz_dxdx(z, phi, qs)[1, :]
|
|
256
260
|
|
|
257
261
|
def dpsi_dxdxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
258
262
|
"""
|
|
259
|
-
Get third derivative of the cross-section rotation psi'''(x).
|
|
263
|
+
Get third derivative of the cross-section rotation `psi'''(x)` (mm⁻³).
|
|
260
264
|
|
|
261
265
|
Parameters
|
|
262
266
|
----------
|
|
@@ -268,6 +272,6 @@ class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many
|
|
|
268
272
|
Returns
|
|
269
273
|
-------
|
|
270
274
|
ndarray, float
|
|
271
|
-
Third derivative of the cross-section rotation psi'''(x)
|
|
275
|
+
Third derivative of the cross-section rotation psi'''(x).
|
|
272
276
|
"""
|
|
273
277
|
return self.dz_dxdx(z, phi, qs)[5, :]
|
|
@@ -20,6 +20,9 @@ from weac.core.unknown_constants_solver import UnknownConstantsSolver
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
TouchdownMode = Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
|
|
24
|
+
|
|
25
|
+
|
|
23
26
|
class SlabTouchdown: # pylint: disable=too-many-instance-attributes,too-few-public-methods
|
|
24
27
|
"""
|
|
25
28
|
Handling the touchdown situation in a PST.
|
|
@@ -56,7 +59,7 @@ class SlabTouchdown: # pylint: disable=too-many-instance-attributes,too-few-pub
|
|
|
56
59
|
Length of the crack for transition of stage A to stage B [mm]
|
|
57
60
|
l_BC : float
|
|
58
61
|
Length of the crack for transition of stage B to stage C [mm]
|
|
59
|
-
touchdown_mode :
|
|
62
|
+
touchdown_mode : TouchdownMode
|
|
60
63
|
Type of touchdown mode
|
|
61
64
|
touchdown_distance : float
|
|
62
65
|
Length of the touchdown segment [mm]
|
|
@@ -74,9 +77,7 @@ class SlabTouchdown: # pylint: disable=too-many-instance-attributes,too-few-pub
|
|
|
74
77
|
straight_scenario: Scenario
|
|
75
78
|
l_AB: float
|
|
76
79
|
l_BC: float
|
|
77
|
-
touchdown_mode:
|
|
78
|
-
"A_free_hanging", "B_point_contact", "C_in_contact"
|
|
79
|
-
] # Three types of contact with collapsed weak layer
|
|
80
|
+
touchdown_mode: TouchdownMode # Three types of contact with collapsed weak layer
|
|
80
81
|
touchdown_distance: float
|
|
81
82
|
collapsed_weak_layer_kR: float | None = None
|
|
82
83
|
|
|
@@ -335,8 +335,7 @@ class SystemModel:
|
|
|
335
335
|
weak_layer=self.weak_layer,
|
|
336
336
|
slab=self.slab,
|
|
337
337
|
)
|
|
338
|
-
|
|
339
|
-
self._invalidate_slab_touchdown()
|
|
338
|
+
self._invalidate_slab_touchdown()
|
|
340
339
|
self._invalidate_constants()
|
|
341
340
|
|
|
342
341
|
def toggle_touchdown(self, touchdown: bool):
|