weac 3.1.0__tar.gz → 3.1.1__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.1}/CITATION.cff +1 -1
- {weac-3.1.0/src/weac.egg-info → weac-3.1.1}/PKG-INFO +2 -2
- {weac-3.1.0 → weac-3.1.1}/pyproject.toml +3 -3
- {weac-3.1.0 → weac-3.1.1}/src/weac/__init__.py +1 -1
- {weac-3.1.0 → weac-3.1.1}/src/weac/analysis/analyzer.py +4 -8
- {weac-3.1.0 → weac-3.1.1}/src/weac/analysis/criteria_evaluator.py +10 -3
- {weac-3.1.0 → weac-3.1.1}/src/weac/analysis/plotter.py +32 -17
- {weac-3.1.0 → weac-3.1.1}/src/weac/components/layer.py +7 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/field_quantities.py +27 -23
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/unknown_constants_solver.py +13 -13
- {weac-3.1.0 → weac-3.1.1}/src/weac/utils/geldsetzer.py +10 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/utils/snow_types.py +5 -0
- {weac-3.1.0 → weac-3.1.1/src/weac.egg-info}/PKG-INFO +2 -2
- {weac-3.1.0 → weac-3.1.1}/src/weac.egg-info/requires.txt +1 -1
- {weac-3.1.0 → weac-3.1.1}/tests/analysis/test_criteria_evaluator.py +42 -0
- {weac-3.1.0 → weac-3.1.1}/tests/test_regression_simulation.py +1 -1
- {weac-3.1.0 → weac-3.1.1}/LICENSE +0 -0
- {weac-3.1.0 → weac-3.1.1}/MANIFEST.in +0 -0
- {weac-3.1.0 → weac-3.1.1}/README.md +0 -0
- {weac-3.1.0 → weac-3.1.1}/img/bc.png +0 -0
- {weac-3.1.0 → weac-3.1.1}/img/layering.png +0 -0
- {weac-3.1.0 → weac-3.1.1}/img/logo.png +0 -0
- {weac-3.1.0 → weac-3.1.1}/img/model.png +0 -0
- {weac-3.1.0 → weac-3.1.1}/img/profiles.png +0 -0
- {weac-3.1.0 → weac-3.1.1}/img/systems.png +0 -0
- {weac-3.1.0 → weac-3.1.1}/setup.cfg +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/analysis/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/components/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/components/config.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/components/criteria_config.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/components/model_input.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/components/scenario_config.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/components/segment.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/constants.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/eigensystem.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/scenario.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/slab.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/slab_touchdown.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/core/system_model.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/logging_config.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/utils/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/utils/misc.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac/utils/snowpilot_parser.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac.egg-info/SOURCES.txt +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac.egg-info/dependency_links.txt +0 -0
- {weac-3.1.0 → weac-3.1.1}/src/weac.egg-info/top_level.txt +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/analysis/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/analysis/test_analyzer.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/components/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/components/test_configs.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/components/test_layer.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/core/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/core/test_eigensystem.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/core/test_field_quantities.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/core/test_scenario.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/core/test_slab.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/core/test_slab_touchdown.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/core/test_system_model.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/run_tests.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/test_comparison_results.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/utils/__init__.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/utils/json_helpers.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/utils/test_json_helpers.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/utils/test_misc.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/tests/utils/test_snowpilot_parser.py +0 -0
- {weac-3.1.0 → weac-3.1.1}/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.1
|
|
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.1
|
|
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.1"
|
|
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.1"
|
|
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:
|
|
@@ -113,6 +113,9 @@ class MaximalStressResult:
|
|
|
113
113
|
The normalized maximum principal stress to the tensile strength of the layers.
|
|
114
114
|
max_Sxx_norm: float
|
|
115
115
|
The normalized maximum axial normal stress to the tensile strength of the layers.
|
|
116
|
+
slab_tensile_criterion: float
|
|
117
|
+
The slab tensile criterion, i.e. the portion of the slab thickness that is prone
|
|
118
|
+
to fail under tensile stresses in the steady state (between 0 and 1).
|
|
116
119
|
"""
|
|
117
120
|
|
|
118
121
|
principal_stress_kPa: np.ndarray
|
|
@@ -121,6 +124,7 @@ class MaximalStressResult:
|
|
|
121
124
|
Sxx_norm: np.ndarray
|
|
122
125
|
max_principal_stress_norm: float
|
|
123
126
|
max_Sxx_norm: float
|
|
127
|
+
slab_tensile_criterion: float
|
|
124
128
|
|
|
125
129
|
|
|
126
130
|
@dataclass
|
|
@@ -706,9 +710,9 @@ class CriteriaEvaluator:
|
|
|
706
710
|
UserWarning,
|
|
707
711
|
)
|
|
708
712
|
system_copy = copy.deepcopy(system)
|
|
709
|
-
system_copy.
|
|
713
|
+
system_copy.toggle_touchdown(True)
|
|
710
714
|
system_copy.update_scenario(scenario_config=ScenarioConfig(phi=0.0))
|
|
711
|
-
l_BC =
|
|
715
|
+
l_BC = system_copy.slab_touchdown.l_BC
|
|
712
716
|
|
|
713
717
|
segments = [
|
|
714
718
|
Segment(length=5e3, has_foundation=True, m=0.0),
|
|
@@ -719,7 +723,6 @@ class CriteriaEvaluator:
|
|
|
719
723
|
phi=0.0, # Slab Touchdown works only for flat slab
|
|
720
724
|
cut_length=2 * l_BC,
|
|
721
725
|
)
|
|
722
|
-
# system_copy.config.touchdown = True
|
|
723
726
|
system_copy.update_scenario(segments=segments, scenario_config=scenario_config)
|
|
724
727
|
touchdown_distance = system_copy.slab_touchdown.touchdown_distance
|
|
725
728
|
analyzer = Analyzer(system_copy, printing_enabled=print_call_stats)
|
|
@@ -1248,6 +1251,9 @@ class CriteriaEvaluator:
|
|
|
1248
1251
|
)
|
|
1249
1252
|
max_principal_stress_norm = np.max(principal_stress_norm)
|
|
1250
1253
|
max_Sxx_norm = np.max(Sxx_norm)
|
|
1254
|
+
# evaluate for each height level if the slab is prone to fail under tensile stresses
|
|
1255
|
+
height_level_prone_to_fail = np.max(Sxx_norm, axis=1)
|
|
1256
|
+
slab_tensile_criterion = np.mean(height_level_prone_to_fail)
|
|
1251
1257
|
if print_call_stats:
|
|
1252
1258
|
analyzer.print_call_stats(
|
|
1253
1259
|
message="_calculate_maximal_stresses Call Statistics"
|
|
@@ -1259,4 +1265,5 @@ class CriteriaEvaluator:
|
|
|
1259
1265
|
Sxx_norm=Sxx_norm,
|
|
1260
1266
|
max_principal_stress_norm=max_principal_stress_norm,
|
|
1261
1267
|
max_Sxx_norm=max_Sxx_norm,
|
|
1268
|
+
slab_tensile_criterion=slab_tensile_criterion,
|
|
1262
1269
|
)
|
|
@@ -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,10 @@ 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
|
+
# TODO: Compressive Strength from Schöttner
|
|
98
|
+
# (11 +/- 7) * (rho/rho_0) ^ (5.4 +/- 0.5)
|
|
99
|
+
|
|
100
|
+
|
|
97
101
|
class Layer(BaseModel):
|
|
98
102
|
"""
|
|
99
103
|
Regular slab layer (no foundation springs).
|
|
@@ -225,6 +229,9 @@ class WeakLayer(BaseModel):
|
|
|
225
229
|
)
|
|
226
230
|
sigma_c: float = Field(default=6.16, gt=0, description="Tensile strength [kPa]")
|
|
227
231
|
tau_c: float = Field(default=5.09, gt=0, description="Shear strength [kPa]")
|
|
232
|
+
sigma_comp: float = Field(
|
|
233
|
+
default=2.6, gt=0, description="Compressive strength [kPa]"
|
|
234
|
+
)
|
|
228
235
|
E_method: Literal["bergfeld", "scapazzo", "gerling"] = Field(
|
|
229
236
|
default="bergfeld",
|
|
230
237
|
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, :]
|
|
@@ -273,17 +273,17 @@ class UnknownConstantsSolver:
|
|
|
273
273
|
Arguments
|
|
274
274
|
---------
|
|
275
275
|
zl : ndarray
|
|
276
|
-
Solution vector (6x1) or (6x6) at left end of beam
|
|
276
|
+
Solution vector (6x1) or (6x6) at left end of beam segment.
|
|
277
277
|
zr : ndarray
|
|
278
|
-
Solution vector (6x1) or (6x6) at right end of beam
|
|
278
|
+
Solution vector (6x1) or (6x6) at right end of beam segment.
|
|
279
279
|
has_foundation : boolean
|
|
280
280
|
Indicates whether segment has foundation(True) or not (False).
|
|
281
281
|
Default is False.
|
|
282
282
|
pos: {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
|
|
283
|
-
Determines whether the
|
|
284
|
-
is a left boundary
|
|
285
|
-
center
|
|
286
|
-
|
|
283
|
+
Determines whether the segment under consideration
|
|
284
|
+
is a left boundary segment (left, l), one of the
|
|
285
|
+
center segment (mid, m), or a right boundary
|
|
286
|
+
segment (right, r). Default is 'mid'.
|
|
287
287
|
|
|
288
288
|
Returns
|
|
289
289
|
-------
|
|
@@ -310,7 +310,7 @@ class UnknownConstantsSolver:
|
|
|
310
310
|
bcs[2],
|
|
311
311
|
fq.u(zr, h0=0), # ui(xi = li)
|
|
312
312
|
fq.w(zr), # wi(xi = li)
|
|
313
|
-
fq.psi(zr), #
|
|
313
|
+
fq.psi(zr), # psi(xi = li)
|
|
314
314
|
fq.N(zr), # Ni(xi = li)
|
|
315
315
|
fq.M(zr), # Mi(xi = li)
|
|
316
316
|
fq.V(zr), # Vi(xi = li)
|
|
@@ -321,13 +321,13 @@ class UnknownConstantsSolver:
|
|
|
321
321
|
[
|
|
322
322
|
-fq.u(zl, h0=0), # -ui(xi = 0)
|
|
323
323
|
-fq.w(zl), # -wi(xi = 0)
|
|
324
|
-
-fq.psi(zl), # -
|
|
324
|
+
-fq.psi(zl), # -psi(xi = 0)
|
|
325
325
|
-fq.N(zl), # -Ni(xi = 0)
|
|
326
326
|
-fq.M(zl), # -Mi(xi = 0)
|
|
327
327
|
-fq.V(zl), # -Vi(xi = 0)
|
|
328
328
|
fq.u(zr, h0=0), # ui(xi = li)
|
|
329
329
|
fq.w(zr), # wi(xi = li)
|
|
330
|
-
fq.psi(zr), #
|
|
330
|
+
fq.psi(zr), # psi(xi = li)
|
|
331
331
|
fq.N(zr), # Ni(xi = li)
|
|
332
332
|
fq.M(zr), # Mi(xi = li)
|
|
333
333
|
fq.V(zr), # Vi(xi = li)
|
|
@@ -347,7 +347,7 @@ class UnknownConstantsSolver:
|
|
|
347
347
|
[
|
|
348
348
|
-fq.u(zl, h0=0), # -ui(xi = 0)
|
|
349
349
|
-fq.w(zl), # -wi(xi = 0)
|
|
350
|
-
-fq.psi(zl), # -
|
|
350
|
+
-fq.psi(zl), # -psi(xi = 0)
|
|
351
351
|
-fq.N(zl), # -Ni(xi = 0)
|
|
352
352
|
-fq.M(zl), # -Mi(xi = 0)
|
|
353
353
|
-fq.V(zl), # -Vi(xi = 0)
|
|
@@ -385,9 +385,9 @@ class UnknownConstantsSolver:
|
|
|
385
385
|
Default is False.
|
|
386
386
|
pos : {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
|
|
387
387
|
Determines whether the segement under consideration
|
|
388
|
-
is a left boundary
|
|
389
|
-
center
|
|
390
|
-
|
|
388
|
+
is a left boundary segment (left, l), one of the
|
|
389
|
+
center segment (mid, m), or a right boundary
|
|
390
|
+
segment (right, r). Default is 'mid'.
|
|
391
391
|
|
|
392
392
|
Returns
|
|
393
393
|
-------
|
|
@@ -82,18 +82,23 @@ HAND_HARDNESS = {
|
|
|
82
82
|
"F-": 0.67,
|
|
83
83
|
"F": 1,
|
|
84
84
|
"F+": 1.33,
|
|
85
|
+
"F-4F": 1.5,
|
|
85
86
|
"4F-": 1.67,
|
|
86
87
|
"4F": 2,
|
|
87
88
|
"4F+": 2.33,
|
|
89
|
+
"4F-1F": 2.5,
|
|
88
90
|
"1F-": 2.67,
|
|
89
91
|
"1F": 3,
|
|
90
92
|
"1F+": 3.33,
|
|
93
|
+
"1F-P": 3.5,
|
|
91
94
|
"P-": 3.67,
|
|
92
95
|
"P": 4,
|
|
93
96
|
"P+": 4.33,
|
|
97
|
+
"P-K": 4.5,
|
|
94
98
|
"K-": 4.67,
|
|
95
99
|
"K": 5,
|
|
96
100
|
"K+": 5.33,
|
|
101
|
+
"K-I": 5.5,
|
|
97
102
|
"I-": 5.67,
|
|
98
103
|
"I": 6,
|
|
99
104
|
"I+": 6.33,
|
|
@@ -117,18 +122,23 @@ HAND_HARDNESS_TO_DENSITY = {
|
|
|
117
122
|
"F-": 71.7,
|
|
118
123
|
"F": 103.7,
|
|
119
124
|
"F+": 118.4,
|
|
125
|
+
"F-4F": 123.15,
|
|
120
126
|
"4F-": 127.9,
|
|
121
127
|
"4F": 158.2,
|
|
122
128
|
"4F+": 163.7,
|
|
129
|
+
"4F-1F": 176.15,
|
|
123
130
|
"1F-": 188.6,
|
|
124
131
|
"1F": 208,
|
|
125
132
|
"1F+": 224.4,
|
|
133
|
+
"1F-P": 238.6,
|
|
126
134
|
"P-": 252.8,
|
|
127
135
|
"P": 275.9,
|
|
128
136
|
"P+": 314.6,
|
|
137
|
+
"P-K": 336.85,
|
|
129
138
|
"K-": 359.1,
|
|
130
139
|
"K": 347.4,
|
|
131
140
|
"K+": 407.8,
|
|
141
|
+
"K-I": 407.8,
|
|
132
142
|
"I-": 407.8,
|
|
133
143
|
"I": 407.8,
|
|
134
144
|
"I+": 407.8,
|
|
@@ -65,18 +65,23 @@ class HandHardness(str, Enum):
|
|
|
65
65
|
Fm = "F-"
|
|
66
66
|
F = "F"
|
|
67
67
|
Fp = "F+"
|
|
68
|
+
F_4F = "F-4F"
|
|
68
69
|
_4Fm = "4F-"
|
|
69
70
|
_4F = "4F"
|
|
70
71
|
_4Fp = "4F+"
|
|
72
|
+
_4F_1F = "4F-1F"
|
|
71
73
|
_1Fm = "1F-"
|
|
72
74
|
_1F = "1F"
|
|
73
75
|
_1Fp = "1F+"
|
|
76
|
+
_1F_P = "1F-P"
|
|
74
77
|
Pm = "P-"
|
|
75
78
|
P = "P"
|
|
76
79
|
Pp = "P+"
|
|
80
|
+
P_K = "P-K"
|
|
77
81
|
Km = "K-"
|
|
78
82
|
K = "K"
|
|
79
83
|
Kp = "K+"
|
|
84
|
+
K_I = "K-I"
|
|
80
85
|
Im = "I-"
|
|
81
86
|
I = "I"
|
|
82
87
|
Ip = "I+"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weac
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.1
|
|
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
|
|
@@ -210,6 +210,48 @@ class TestCriteriaEvaluator(unittest.TestCase):
|
|
|
210
210
|
self.assertGreater(max_principal_stress_norm, 0)
|
|
211
211
|
self.assertGreater(max_Sxx_norm, 0)
|
|
212
212
|
|
|
213
|
+
def test_evaluate_SteadyState_without_touchdown_in_config(self):
|
|
214
|
+
"""
|
|
215
|
+
Test evaluate_SteadyState when SystemModel is initialized without touchdown=True.
|
|
216
|
+
|
|
217
|
+
This is a regression test for bug #37: SteadyState evaluation should work
|
|
218
|
+
even if the SystemModel is not initialized with touchdown=True in Config.
|
|
219
|
+
The evaluate_SteadyState method should internally enable touchdown mode
|
|
220
|
+
using toggle_touchdown() to properly invalidate cached properties.
|
|
221
|
+
"""
|
|
222
|
+
segments = [
|
|
223
|
+
Segment(length=self.segments_length, has_foundation=True, m=0),
|
|
224
|
+
Segment(length=self.segments_length, has_foundation=True, m=0),
|
|
225
|
+
]
|
|
226
|
+
# Initialize system WITHOUT touchdown=True (default is False)
|
|
227
|
+
system = SystemModel(
|
|
228
|
+
model_input=ModelInput(
|
|
229
|
+
layers=self.layers,
|
|
230
|
+
weak_layer=self.weak_layer,
|
|
231
|
+
segments=segments,
|
|
232
|
+
scenario_config=ScenarioConfig(phi=self.phi),
|
|
233
|
+
),
|
|
234
|
+
config=Config(), # touchdown defaults to False
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# This should not raise AttributeError: 'NoneType' object has no attribute 'l_BC'
|
|
238
|
+
results: SteadyStateResult = self.evaluator.evaluate_SteadyState(system)
|
|
239
|
+
|
|
240
|
+
# Verify results are valid
|
|
241
|
+
self.assertTrue(results.converged)
|
|
242
|
+
self.assertGreater(results.energy_release_rate, 0)
|
|
243
|
+
self.assertGreater(results.touchdown_distance, 0)
|
|
244
|
+
self.assertLess(results.touchdown_distance, results.system.scenario.L)
|
|
245
|
+
max_principal_stress_norm = (
|
|
246
|
+
results.maximal_stress_result.max_principal_stress_norm
|
|
247
|
+
)
|
|
248
|
+
max_Sxx_norm = results.maximal_stress_result.max_Sxx_norm
|
|
249
|
+
self.assertGreater(max_principal_stress_norm, 0)
|
|
250
|
+
self.assertGreater(max_Sxx_norm, 0)
|
|
251
|
+
|
|
252
|
+
# Verify the original system's touchdown state was not modified
|
|
253
|
+
self.assertFalse(system.config.touchdown)
|
|
254
|
+
|
|
213
255
|
def test_find_minimum_crack_length(self):
|
|
214
256
|
"""Test the find_minimum_crack_length method."""
|
|
215
257
|
segments = [
|
|
@@ -421,7 +421,7 @@ class TestRegressionSimulation(unittest.TestCase):
|
|
|
421
421
|
self.assertTrue(ss.converged)
|
|
422
422
|
self.assertGreater(ss.touchdown_distance, 0)
|
|
423
423
|
# Baseline values recorded
|
|
424
|
-
self.assertAlmostEqual(ss.touchdown_distance, 1265.
|
|
424
|
+
self.assertAlmostEqual(ss.touchdown_distance, 1265.551937834, places=6)
|
|
425
425
|
np.testing.assert_allclose(ss.energy_release_rate, 2.123992, rtol=1e-6, atol=0)
|
|
426
426
|
|
|
427
427
|
# evaluate_coupled_criterion baseline
|
|
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
|