weac 3.0.1__py3-none-any.whl → 3.1.0__py3-none-any.whl
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/__init__.py +1 -1
- weac/analysis/__init__.py +2 -2
- weac/analysis/analyzer.py +57 -19
- weac/analysis/criteria_evaluator.py +130 -37
- weac/analysis/plotter.py +402 -49
- weac/components/model_input.py +3 -4
- weac/core/eigensystem.py +21 -21
- weac/core/scenario.py +7 -7
- weac/core/slab.py +2 -4
- weac/core/slab_touchdown.py +3 -3
- weac/core/system_model.py +8 -9
- weac/core/unknown_constants_solver.py +11 -14
- weac/logging_config.py +1 -2
- weac/utils/snowpilot_parser.py +12 -13
- {weac-3.0.1.dist-info → weac-3.1.0.dist-info}/METADATA +105 -8
- weac-3.1.0.dist-info/RECORD +32 -0
- weac-3.0.1.dist-info/RECORD +0 -32
- {weac-3.0.1.dist-info → weac-3.1.0.dist-info}/WHEEL +0 -0
- {weac-3.0.1.dist-info → weac-3.1.0.dist-info}/licenses/LICENSE +0 -0
- {weac-3.0.1.dist-info → weac-3.1.0.dist-info}/top_level.txt +0 -0
weac/analysis/plotter.py
CHANGED
|
@@ -6,7 +6,7 @@ This module provides plotting functions for visualizing the results of the WEAC
|
|
|
6
6
|
import colorsys
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Literal
|
|
10
10
|
|
|
11
11
|
# Third party imports
|
|
12
12
|
import matplotlib.colors as mc
|
|
@@ -219,9 +219,9 @@ class Plotter:
|
|
|
219
219
|
|
|
220
220
|
def _get_systems_to_plot(
|
|
221
221
|
self,
|
|
222
|
-
system_model:
|
|
223
|
-
system_models:
|
|
224
|
-
) ->
|
|
222
|
+
system_model: SystemModel | None = None,
|
|
223
|
+
system_models: list[SystemModel] | None = None,
|
|
224
|
+
) -> list[SystemModel]:
|
|
225
225
|
"""Determine which systems to plot based on override parameters."""
|
|
226
226
|
if system_model is not None and system_models is not None:
|
|
227
227
|
raise ValueError(
|
|
@@ -236,7 +236,7 @@ class Plotter:
|
|
|
236
236
|
"SystemModel or list of SystemModels"
|
|
237
237
|
)
|
|
238
238
|
|
|
239
|
-
def _save_figure(self, filename: str, fig:
|
|
239
|
+
def _save_figure(self, filename: str, fig: Figure | None = None):
|
|
240
240
|
"""Save figure with proper formatting."""
|
|
241
241
|
if fig is None:
|
|
242
242
|
fig = plt.gcf()
|
|
@@ -249,20 +249,20 @@ class Plotter:
|
|
|
249
249
|
|
|
250
250
|
def plot_slab_profile(
|
|
251
251
|
self,
|
|
252
|
-
weak_layers:
|
|
253
|
-
slabs:
|
|
252
|
+
weak_layers: list[WeakLayer] | WeakLayer,
|
|
253
|
+
slabs: list[Slab] | Slab,
|
|
254
254
|
filename: str = "slab_profile",
|
|
255
|
-
labels:
|
|
256
|
-
colors:
|
|
255
|
+
labels: list[str] | str | None = None,
|
|
256
|
+
colors: list[str] | None = None,
|
|
257
257
|
):
|
|
258
258
|
"""
|
|
259
259
|
Plot slab layer profiles for comparison.
|
|
260
260
|
|
|
261
261
|
Parameters
|
|
262
262
|
----------
|
|
263
|
-
weak_layers :
|
|
263
|
+
weak_layers : list[WeakLayer] | WeakLayer
|
|
264
264
|
The weak layer or layers to plot.
|
|
265
|
-
slabs :
|
|
265
|
+
slabs : list[Slab] | Slab
|
|
266
266
|
The slab or slabs to plot.
|
|
267
267
|
filename : str, optional
|
|
268
268
|
Filename for saving plot
|
|
@@ -281,6 +281,9 @@ class Plotter:
|
|
|
281
281
|
if isinstance(slabs, Slab):
|
|
282
282
|
slabs = [slabs]
|
|
283
283
|
|
|
284
|
+
if len(weak_layers) != len(slabs):
|
|
285
|
+
raise ValueError("Number of weak layers must match number of slabs")
|
|
286
|
+
|
|
284
287
|
if labels is None:
|
|
285
288
|
labels = [f"System {i + 1}" for i in range(len(weak_layers))]
|
|
286
289
|
elif isinstance(labels, str):
|
|
@@ -642,11 +645,11 @@ class Plotter:
|
|
|
642
645
|
|
|
643
646
|
def plot_section_forces(
|
|
644
647
|
self,
|
|
645
|
-
system_model:
|
|
646
|
-
system_models:
|
|
648
|
+
system_model: SystemModel | None = None,
|
|
649
|
+
system_models: list[SystemModel] | None = None,
|
|
647
650
|
filename: str = "section_forces",
|
|
648
|
-
labels:
|
|
649
|
-
colors:
|
|
651
|
+
labels: list[str] | None = None,
|
|
652
|
+
colors: list[str] | None = None,
|
|
650
653
|
):
|
|
651
654
|
"""
|
|
652
655
|
Plot section forces (N, M, V) for comparison.
|
|
@@ -655,7 +658,7 @@ class Plotter:
|
|
|
655
658
|
----------
|
|
656
659
|
system_model : SystemModel, optional
|
|
657
660
|
Single system to plot (overrides default)
|
|
658
|
-
system_models :
|
|
661
|
+
system_models : list[SystemModel], optional
|
|
659
662
|
Multiple systems to plot (overrides default)
|
|
660
663
|
filename : str, optional
|
|
661
664
|
Filename for saving plot
|
|
@@ -721,11 +724,11 @@ class Plotter:
|
|
|
721
724
|
|
|
722
725
|
def plot_energy_release_rates(
|
|
723
726
|
self,
|
|
724
|
-
system_model:
|
|
725
|
-
system_models:
|
|
727
|
+
system_model: SystemModel | None = None,
|
|
728
|
+
system_models: list[SystemModel] | None = None,
|
|
726
729
|
filename: str = "ERR",
|
|
727
|
-
labels:
|
|
728
|
-
colors:
|
|
730
|
+
labels: list[str] | None = None,
|
|
731
|
+
colors: list[str] | None = None,
|
|
729
732
|
):
|
|
730
733
|
"""
|
|
731
734
|
Plot energy release rates (G_I, G_II) for comparison.
|
|
@@ -734,7 +737,7 @@ class Plotter:
|
|
|
734
737
|
----------
|
|
735
738
|
system_model : SystemModel, optional
|
|
736
739
|
Single system to plot (overrides default)
|
|
737
|
-
system_models :
|
|
740
|
+
system_models : list[SystemModel], optional
|
|
738
741
|
Multiple systems to plot (overrides default)
|
|
739
742
|
filename : str, optional
|
|
740
743
|
Filename for saving plot
|
|
@@ -882,48 +885,56 @@ class Plotter:
|
|
|
882
885
|
zmax = np.max(Zsl + scale * Wsl) + pad
|
|
883
886
|
zmin = np.min(Zsl) - pad
|
|
884
887
|
|
|
885
|
-
#
|
|
886
|
-
|
|
888
|
+
# Filter out NaN values from weak layer coordinates
|
|
889
|
+
nanmask = np.isfinite(xwl)
|
|
890
|
+
xwl_finite = xwl[nanmask]
|
|
887
891
|
|
|
888
|
-
#
|
|
889
|
-
|
|
890
|
-
|
|
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])])
|
|
891
898
|
|
|
892
899
|
# Compute stress or displacement fields
|
|
893
900
|
match field:
|
|
894
901
|
# Horizontal displacements (um)
|
|
895
902
|
case "u":
|
|
896
903
|
slab = 1e4 * Usl
|
|
897
|
-
weak = 1e4 * Usl[-1,
|
|
904
|
+
weak = 1e4 * Usl[-1, nanmask]
|
|
898
905
|
label = r"$u$ ($\mu$m)"
|
|
899
906
|
# Vertical deflection (um)
|
|
900
907
|
case "w":
|
|
901
908
|
slab = 1e4 * Wsl
|
|
902
|
-
weak = 1e4 * Wsl[-1,
|
|
909
|
+
weak = 1e4 * Wsl[-1, nanmask]
|
|
903
910
|
label = r"$w$ ($\mu$m)"
|
|
904
|
-
# Axial normal stresses (kPa)
|
|
905
911
|
case "Sxx":
|
|
906
|
-
slab = analyzer.Sxx(z, phi, dz=dz, unit="kPa")
|
|
907
|
-
weak = np.zeros(
|
|
908
|
-
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
|
+
)
|
|
909
917
|
# Shear stresses (kPa)
|
|
910
918
|
case "Txz":
|
|
911
|
-
slab = analyzer.Txz(z, phi, dz=dz, unit="kPa")
|
|
912
|
-
weak = Tauwl
|
|
913
|
-
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)"
|
|
914
922
|
# Transverse normal stresses (kPa)
|
|
915
923
|
case "Szz":
|
|
916
|
-
slab = analyzer.Szz(z, phi, dz=dz, unit="kPa")
|
|
917
|
-
weak = Sigmawl
|
|
918
|
-
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
|
+
)
|
|
919
929
|
# Principal stresses
|
|
920
930
|
case "principal":
|
|
921
931
|
slab = analyzer.principal_stress_slab(
|
|
922
932
|
z, phi, dz=dz, val="max", unit="kPa", normalize=normalize
|
|
923
933
|
)
|
|
924
|
-
|
|
934
|
+
weak_full = analyzer.principal_stress_weaklayer(
|
|
925
935
|
z, val="min", unit="kPa", normalize=normalize
|
|
926
936
|
)
|
|
937
|
+
weak = weak_full[nanmask]
|
|
927
938
|
if normalize:
|
|
928
939
|
label = (
|
|
929
940
|
r"$\sigma_\mathrm{I}/\sigma_+$ (slab), "
|
|
@@ -934,11 +945,6 @@ class Plotter:
|
|
|
934
945
|
r"$\sigma_\mathrm{I}$ (kPa, slab), "
|
|
935
946
|
r"$\sigma_\mathrm{I\!I\!I}$ (kPa, weak layer)"
|
|
936
947
|
)
|
|
937
|
-
case _:
|
|
938
|
-
raise ValueError(
|
|
939
|
-
f"Invalid input '{field}' for field. Valid options are "
|
|
940
|
-
"'u', 'w', 'Sxx', 'Txz', 'Szz', or 'principal'"
|
|
941
|
-
)
|
|
942
948
|
|
|
943
949
|
# Complement label
|
|
944
950
|
label += r" $\longrightarrow$"
|
|
@@ -965,10 +971,9 @@ class Plotter:
|
|
|
965
971
|
|
|
966
972
|
# Plot deformed weak-layer _outline
|
|
967
973
|
if system_type in ["-pst", "pst-", "-vpst", "vpst-"]:
|
|
968
|
-
nanmask = np.isfinite(xwl)
|
|
969
974
|
ax.plot(
|
|
970
|
-
_outline(Xwl
|
|
971
|
-
_outline(Zwl
|
|
975
|
+
_outline(Xwl + scale * Uwl),
|
|
976
|
+
_outline(Zwl + scale * Wwl),
|
|
972
977
|
"k",
|
|
973
978
|
linewidth=1,
|
|
974
979
|
)
|
|
@@ -1030,12 +1035,360 @@ class Plotter:
|
|
|
1030
1035
|
|
|
1031
1036
|
return fig
|
|
1032
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
|
+
|
|
1033
1386
|
def plot_stress_envelope(
|
|
1034
1387
|
self,
|
|
1035
1388
|
system_model: SystemModel,
|
|
1036
1389
|
criteria_evaluator: CriteriaEvaluator,
|
|
1037
1390
|
all_envelopes: bool = False,
|
|
1038
|
-
filename:
|
|
1391
|
+
filename: str | None = None,
|
|
1039
1392
|
):
|
|
1040
1393
|
"""
|
|
1041
1394
|
Plot stress envelope in τ-σ space.
|
|
@@ -1074,7 +1427,7 @@ class Plotter:
|
|
|
1074
1427
|
weak_layer = system_model.weak_layer
|
|
1075
1428
|
|
|
1076
1429
|
# Define a function to find the root for a given tau
|
|
1077
|
-
def find_sigma_for_tau(tau_val, sigma_c, method:
|
|
1430
|
+
def find_sigma_for_tau(tau_val, sigma_c, method: str | None = None):
|
|
1078
1431
|
# Target function to find the root of: envelope(sigma, tau) - 1 = 0
|
|
1079
1432
|
def envelope_root_func(sigma_val):
|
|
1080
1433
|
return (
|
weac/components/model_input.py
CHANGED
|
@@ -14,7 +14,6 @@ field_name: type = Field(..., gt=0, description="Description")
|
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
16
|
import logging
|
|
17
|
-
from typing import List
|
|
18
17
|
|
|
19
18
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
20
19
|
|
|
@@ -49,13 +48,13 @@ class ModelInput(BaseModel):
|
|
|
49
48
|
default_factory=lambda: WeakLayer(rho=125, h=20, E=1.0),
|
|
50
49
|
description="Weak layer",
|
|
51
50
|
)
|
|
52
|
-
layers:
|
|
53
|
-
default_factory=lambda: [Layer(rho=250, h=100)], description="
|
|
51
|
+
layers: list[Layer] = Field(
|
|
52
|
+
default_factory=lambda: [Layer(rho=250, h=100)], description="list of layers"
|
|
54
53
|
)
|
|
55
54
|
scenario_config: ScenarioConfig = Field(
|
|
56
55
|
default_factory=ScenarioConfig, description="Scenario configuration"
|
|
57
56
|
)
|
|
58
|
-
segments:
|
|
57
|
+
segments: list[Segment] = Field(
|
|
59
58
|
default_factory=lambda: [
|
|
60
59
|
Segment(length=5000, has_foundation=True, m=100),
|
|
61
60
|
Segment(length=5000, has_foundation=True, m=0),
|