weac 3.0.0__py3-none-any.whl → 3.0.1__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 +23 -0
- weac/analysis/analyzer.py +790 -0
- weac/analysis/criteria_evaluator.py +1169 -0
- weac/analysis/plotter.py +1922 -0
- weac/components/__init__.py +21 -0
- weac/components/config.py +33 -0
- weac/components/criteria_config.py +86 -0
- weac/components/layer.py +284 -0
- weac/components/model_input.py +103 -0
- weac/components/scenario_config.py +72 -0
- weac/components/segment.py +31 -0
- weac/core/__init__.py +10 -0
- weac/core/eigensystem.py +405 -0
- weac/core/field_quantities.py +273 -0
- weac/core/scenario.py +200 -0
- weac/core/slab.py +149 -0
- weac/core/slab_touchdown.py +363 -0
- weac/core/system_model.py +413 -0
- weac/core/unknown_constants_solver.py +444 -0
- weac/utils/__init__.py +0 -0
- weac/utils/geldsetzer.py +166 -0
- weac/utils/misc.py +127 -0
- weac/utils/snow_types.py +82 -0
- weac/utils/snowpilot_parser.py +332 -0
- {weac-3.0.0.dist-info → weac-3.0.1.dist-info}/METADATA +4 -4
- weac-3.0.1.dist-info/RECORD +32 -0
- weac-3.0.1.dist-info/licenses/LICENSE +21 -0
- weac-3.0.0.dist-info/RECORD +0 -8
- weac-3.0.0.dist-info/licenses/LICENSE +0 -24
- {weac-3.0.0.dist-info → weac-3.0.1.dist-info}/WHEEL +0 -0
- {weac-3.0.0.dist-info → weac-3.0.1.dist-info}/top_level.txt +0 -0
weac/utils/misc.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains miscellaneous utility functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from weac.components import Layer
|
|
10
|
+
from weac.constants import G_MM_S2, LSKI_MM
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def decompose_to_normal_tangential(f: float, phi: float) -> tuple[float, float]:
|
|
14
|
+
"""
|
|
15
|
+
Resolve a gravity-type force/line-load into its tangential (downslope) and
|
|
16
|
+
normal (into-slope) components with respect to an inclined surface.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
f : float
|
|
21
|
+
is interpreted as a vertical load magnitude
|
|
22
|
+
acting straight downward (global y negative).
|
|
23
|
+
phi : float
|
|
24
|
+
Surface dip angle `in degrees`, measured from horizontal.
|
|
25
|
+
Positive `phi` means the surface slopes upward in +x.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
f_norm, f_tan : float
|
|
30
|
+
Magnitudes of the tangential ( + downslope ) and normal
|
|
31
|
+
( + into-slope ) components, respectively.
|
|
32
|
+
"""
|
|
33
|
+
# Convert units
|
|
34
|
+
phi = np.deg2rad(phi) # Convert inclination to rad
|
|
35
|
+
# Split into components
|
|
36
|
+
f_norm = f * np.cos(phi) # Normal direction
|
|
37
|
+
f_tan = -f * np.sin(phi) # Tangential direction
|
|
38
|
+
return f_norm, f_tan
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_skier_point_load(m: float) -> float:
|
|
42
|
+
"""
|
|
43
|
+
Calculate skier point load.
|
|
44
|
+
|
|
45
|
+
Arguments
|
|
46
|
+
---------
|
|
47
|
+
m : float
|
|
48
|
+
Skier weight [kg].
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
f : float
|
|
53
|
+
Skier load [N/mm].
|
|
54
|
+
"""
|
|
55
|
+
F = 1e-3 * m * G_MM_S2 / LSKI_MM # Total skier
|
|
56
|
+
return F
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_dummy_profile(
|
|
60
|
+
profile_id: Literal[
|
|
61
|
+
"a", "b", "c", "d", "e", "f", "h", "soft", "medium", "hard", "comp"
|
|
62
|
+
],
|
|
63
|
+
) -> list[Layer]:
|
|
64
|
+
"""Define standard layering types for comparison."""
|
|
65
|
+
soft_layer = Layer(rho=180, h=120, E=5)
|
|
66
|
+
medium_layer = Layer(rho=270, h=120, E=30)
|
|
67
|
+
hard_layer = Layer(rho=350, h=120, E=93.8)
|
|
68
|
+
|
|
69
|
+
tested_layers = [
|
|
70
|
+
Layer(rho=350, h=120),
|
|
71
|
+
Layer(rho=270, h=120),
|
|
72
|
+
Layer(rho=180, h=120),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
# Database (top to bottom)
|
|
76
|
+
database = {
|
|
77
|
+
# Layered
|
|
78
|
+
"a": [hard_layer, medium_layer, soft_layer],
|
|
79
|
+
"b": [soft_layer, medium_layer, hard_layer],
|
|
80
|
+
"c": [hard_layer, soft_layer, hard_layer],
|
|
81
|
+
"d": [soft_layer, hard_layer, soft_layer],
|
|
82
|
+
"e": [hard_layer, soft_layer, soft_layer],
|
|
83
|
+
"f": [soft_layer, soft_layer, hard_layer],
|
|
84
|
+
"tested": tested_layers,
|
|
85
|
+
# Homogeneous
|
|
86
|
+
"h": [medium_layer, medium_layer, medium_layer],
|
|
87
|
+
"soft": [soft_layer, soft_layer, soft_layer],
|
|
88
|
+
"medium": [medium_layer, medium_layer, medium_layer],
|
|
89
|
+
"hard": [hard_layer, hard_layer, hard_layer],
|
|
90
|
+
# Comparison
|
|
91
|
+
"comp": [
|
|
92
|
+
Layer(rho=240, h=200, E=5.23),
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Load profile
|
|
97
|
+
try:
|
|
98
|
+
profile = database[profile_id.lower()]
|
|
99
|
+
except KeyError:
|
|
100
|
+
raise ValueError(f"Profile {profile_id} is not defined.") from None
|
|
101
|
+
return profile
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def isnotebook() -> bool:
|
|
105
|
+
"""
|
|
106
|
+
Check if code is running in a Jupyter notebook environment.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
bool
|
|
111
|
+
True if running in Jupyter notebook, False otherwise.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
# Check if we're in IPython
|
|
115
|
+
from IPython import get_ipython # pylint: disable=import-outside-toplevel
|
|
116
|
+
|
|
117
|
+
if get_ipython() is None:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# Check if we're specifically in a notebook (not just IPython terminal)
|
|
121
|
+
if get_ipython().__class__.__name__ == "ZMQInteractiveShell":
|
|
122
|
+
return True # Jupyter notebook
|
|
123
|
+
if get_ipython().__class__.__name__ == "TerminalInteractiveShell":
|
|
124
|
+
return False # IPython terminal
|
|
125
|
+
return False # Other IPython environments
|
|
126
|
+
except ImportError:
|
|
127
|
+
return False # IPython not available
|
weac/utils/snow_types.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Snow grain types and hand hardness values.
|
|
3
|
+
|
|
4
|
+
These values are used in Pydantic models for validation and correspond to the
|
|
5
|
+
parameterizations available in `geldsetzer.py`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GrainType(str, Enum):
|
|
12
|
+
"""SnowPilot grain type codes (see `geldsetzer.GRAIN_TYPE`)."""
|
|
13
|
+
|
|
14
|
+
DF = "DF"
|
|
15
|
+
DFbk = "DFbk"
|
|
16
|
+
DFdc = "DFdc"
|
|
17
|
+
DH = "DH"
|
|
18
|
+
DHch = "DHch"
|
|
19
|
+
DHcp = "DHcp"
|
|
20
|
+
DHla = "DHla"
|
|
21
|
+
DHpr = "DHpr"
|
|
22
|
+
DHxr = "DHxr"
|
|
23
|
+
FC = "FC"
|
|
24
|
+
FCsf = "FCsf"
|
|
25
|
+
FCso = "FCso"
|
|
26
|
+
FCxr = "FCxr"
|
|
27
|
+
IF = "IF"
|
|
28
|
+
IFbi = "IFbi"
|
|
29
|
+
IFic = "IFic"
|
|
30
|
+
IFil = "IFil"
|
|
31
|
+
IFrc = "IFrc"
|
|
32
|
+
IFsc = "IFsc"
|
|
33
|
+
MF = "MF"
|
|
34
|
+
MFcl = "MFcl"
|
|
35
|
+
MFcr = "MFcr"
|
|
36
|
+
MFpc = "MFpc"
|
|
37
|
+
MFsl = "MFsl"
|
|
38
|
+
PP = "PP"
|
|
39
|
+
PPco = "PPco"
|
|
40
|
+
PPgp = "PPgp"
|
|
41
|
+
PPhl = "PPhl"
|
|
42
|
+
PPip = "PPip"
|
|
43
|
+
PPir = "PPir"
|
|
44
|
+
PPnd = "PPnd"
|
|
45
|
+
PPpl = "PPpl"
|
|
46
|
+
PPrm = "PPrm"
|
|
47
|
+
PPsd = "PPsd"
|
|
48
|
+
RG = "RG"
|
|
49
|
+
RGlr = "RGlr"
|
|
50
|
+
RGsr = "RGsr"
|
|
51
|
+
RGwp = "RGwp"
|
|
52
|
+
RGxf = "RGxf"
|
|
53
|
+
SH = "SH"
|
|
54
|
+
SHcv = "SHcv"
|
|
55
|
+
SHsu = "SHsu"
|
|
56
|
+
SHxr = "SHxr"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class HandHardness(str, Enum):
|
|
60
|
+
"""Field hand hardness codes (see `geldsetzer.HAND_HARDNESS`).
|
|
61
|
+
|
|
62
|
+
Enum member names avoid starting with digits and special characters.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
Fm = "F-"
|
|
66
|
+
F = "F"
|
|
67
|
+
Fp = "F+"
|
|
68
|
+
_4Fm = "4F-"
|
|
69
|
+
_4F = "4F"
|
|
70
|
+
_4Fp = "4F+"
|
|
71
|
+
_1Fm = "1F-"
|
|
72
|
+
_1F = "1F"
|
|
73
|
+
_1Fp = "1F+"
|
|
74
|
+
Pm = "P-"
|
|
75
|
+
P = "P"
|
|
76
|
+
Pp = "P+"
|
|
77
|
+
Km = "K-"
|
|
78
|
+
K = "K"
|
|
79
|
+
Kp = "K+"
|
|
80
|
+
Im = "I-"
|
|
81
|
+
I = "I"
|
|
82
|
+
Ip = "I+"
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilizes the snowpylot library to convert a CAAML file to a WEAC ModelInput.
|
|
3
|
+
|
|
4
|
+
The snowpylot library is used to parse the CAAML file and extract the snowpit.
|
|
5
|
+
The snowpit is then converted to a List of WEAC ModelInput.
|
|
6
|
+
|
|
7
|
+
Based on the different stability tests performed, several scenarios are created.
|
|
8
|
+
Each scenario is a WEAC ModelInput.
|
|
9
|
+
|
|
10
|
+
The scenarios are created based on the following logic:
|
|
11
|
+
- For each PropSawTest, a scenario is created with `the cut length` and `a standard segment.`
|
|
12
|
+
- For each ExtColumnTest, a scenario is created with `a standard segment.`
|
|
13
|
+
- For each ComprTest, a scenario is created with `a standard segment.`
|
|
14
|
+
- For each RBlockTest, a scenario is created with `a standard segment.`
|
|
15
|
+
|
|
16
|
+
The `a standard segment` is a segment with a length of 1000 mm and a foundation of True.
|
|
17
|
+
|
|
18
|
+
The `the cut length` is the cut length of the PropSawTest.
|
|
19
|
+
The `the column length` is the column length of the PropSawTest.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
from typing import List, Tuple
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
from snowpylot import caaml_parser
|
|
27
|
+
from snowpylot.layer import Layer as SnowpylotLayer
|
|
28
|
+
from snowpylot.snow_pit import SnowPit
|
|
29
|
+
from snowpylot.snow_profile import DensityObs
|
|
30
|
+
|
|
31
|
+
# Import WEAC components
|
|
32
|
+
from weac.components import (
|
|
33
|
+
Layer,
|
|
34
|
+
WeakLayer,
|
|
35
|
+
)
|
|
36
|
+
from weac.utils.geldsetzer import compute_density
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
convert_to_mm = {"cm": 10, "mm": 1, "m": 1000, "dm": 100}
|
|
41
|
+
convert_to_deg = {"deg": 1, "rad": 180 / np.pi}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SnowPilotParser:
|
|
45
|
+
"""Parser for SnowPilot files using the snowpylot library."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, file_path: str):
|
|
48
|
+
self.snowpit: SnowPit = caaml_parser(file_path)
|
|
49
|
+
|
|
50
|
+
def extract_layers(self) -> Tuple[List[Layer], List[str]]:
|
|
51
|
+
"""Extract layers from snowpit."""
|
|
52
|
+
snowpit = self.snowpit
|
|
53
|
+
# Extract layers from snowpit: List[SnowpylotLayer]
|
|
54
|
+
sp_layers: List[SnowpylotLayer] = [
|
|
55
|
+
layer
|
|
56
|
+
for layer in snowpit.snow_profile.layers
|
|
57
|
+
if layer.depth_top is not None
|
|
58
|
+
]
|
|
59
|
+
sp_layers = sorted(sp_layers, key=lambda x: x.depth_top[0]) # type: ignore
|
|
60
|
+
|
|
61
|
+
# Extract density layers from snowpit: List[DensityObs]
|
|
62
|
+
sp_density_layers: List[DensityObs] = [
|
|
63
|
+
layer
|
|
64
|
+
for layer in snowpit.snow_profile.density_profile
|
|
65
|
+
if layer.depth_top is not None
|
|
66
|
+
]
|
|
67
|
+
sp_density_layers = sorted(sp_density_layers, key=lambda x: x.depth_top[0]) # type: ignore
|
|
68
|
+
|
|
69
|
+
# Populate WEAC layers: List[Layer]
|
|
70
|
+
layers: List[Layer] = []
|
|
71
|
+
density_methods: List[str] = []
|
|
72
|
+
for _i, layer in enumerate(sp_layers):
|
|
73
|
+
# Parameters
|
|
74
|
+
grain_type = None
|
|
75
|
+
grain_size = None
|
|
76
|
+
hand_hardness = None
|
|
77
|
+
density = None
|
|
78
|
+
thickness = None
|
|
79
|
+
|
|
80
|
+
# extract THICKNESS
|
|
81
|
+
if layer.thickness is not None:
|
|
82
|
+
thickness, unit = layer.thickness
|
|
83
|
+
thickness = thickness * convert_to_mm[unit] # Convert to mm
|
|
84
|
+
else:
|
|
85
|
+
raise ValueError("Thickness not found")
|
|
86
|
+
|
|
87
|
+
# extract GRAIN TYPE and SIZE
|
|
88
|
+
if layer.grain_form_primary:
|
|
89
|
+
if layer.grain_form_primary.grain_form:
|
|
90
|
+
grain_type = layer.grain_form_primary.grain_form
|
|
91
|
+
if layer.grain_form_primary.grain_size_avg:
|
|
92
|
+
grain_size = (
|
|
93
|
+
layer.grain_form_primary.grain_size_avg[0]
|
|
94
|
+
* convert_to_mm[layer.grain_form_primary.grain_size_avg[1]]
|
|
95
|
+
)
|
|
96
|
+
elif layer.grain_form_primary.grain_size_max:
|
|
97
|
+
grain_size = (
|
|
98
|
+
layer.grain_form_primary.grain_size_max[0]
|
|
99
|
+
* convert_to_mm[layer.grain_form_primary.grain_size_max[1]]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# extract DENSITY
|
|
103
|
+
# Get layer depth range in mm for density matching
|
|
104
|
+
layer_depth_top_mm = layer.depth_top[0] * convert_to_mm[layer.depth_top[1]]
|
|
105
|
+
layer_depth_bottom_mm = layer_depth_top_mm + thickness
|
|
106
|
+
# Try to find density measurement that overlaps with this layer
|
|
107
|
+
measured_density = self.get_density_for_layer_range(
|
|
108
|
+
layer_depth_top_mm, layer_depth_bottom_mm, sp_density_layers
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Handle hardness and create layers accordingly
|
|
112
|
+
if layer.hardness_top is not None and layer.hardness_bottom is not None:
|
|
113
|
+
hand_hardness_top = layer.hardness_top
|
|
114
|
+
hand_hardness_bottom = layer.hardness_bottom
|
|
115
|
+
|
|
116
|
+
# Two hardness values - split into two layers
|
|
117
|
+
half_thickness = thickness / 2
|
|
118
|
+
layer_mid_depth_mm = layer_depth_top_mm + half_thickness
|
|
119
|
+
|
|
120
|
+
# Create top layer (first half)
|
|
121
|
+
if measured_density is not None:
|
|
122
|
+
density_top = self.get_density_for_layer_range(
|
|
123
|
+
layer_depth_top_mm, layer_mid_depth_mm, sp_density_layers
|
|
124
|
+
)
|
|
125
|
+
if density_top is None:
|
|
126
|
+
density_methods.append("geldsetzer")
|
|
127
|
+
density_top = compute_density(grain_type, hand_hardness_top)
|
|
128
|
+
else:
|
|
129
|
+
density_methods.append("density_obs")
|
|
130
|
+
else:
|
|
131
|
+
density_methods.append("geldsetzer")
|
|
132
|
+
density_top = compute_density(grain_type, hand_hardness_top)
|
|
133
|
+
|
|
134
|
+
layers.append(
|
|
135
|
+
Layer(
|
|
136
|
+
rho=density_top,
|
|
137
|
+
h=half_thickness,
|
|
138
|
+
grain_type=grain_type,
|
|
139
|
+
grain_size=grain_size,
|
|
140
|
+
hand_hardness=hand_hardness_top,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Create bottom layer (second half)
|
|
145
|
+
if measured_density is not None:
|
|
146
|
+
density_bottom = self.get_density_for_layer_range(
|
|
147
|
+
layer_mid_depth_mm, layer_depth_bottom_mm, sp_density_layers
|
|
148
|
+
)
|
|
149
|
+
if density_bottom is None:
|
|
150
|
+
density_methods.append("geldsetzer")
|
|
151
|
+
density_bottom = compute_density(
|
|
152
|
+
grain_type, hand_hardness_bottom
|
|
153
|
+
)
|
|
154
|
+
else:
|
|
155
|
+
density_methods.append("density_obs")
|
|
156
|
+
else:
|
|
157
|
+
try:
|
|
158
|
+
density_methods.append("geldsetzer")
|
|
159
|
+
density_bottom = compute_density(
|
|
160
|
+
grain_type, hand_hardness_bottom
|
|
161
|
+
)
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
raise AttributeError(
|
|
164
|
+
"Layer is missing density information; density profile, "
|
|
165
|
+
"hand hardness and grain type are all missing. "
|
|
166
|
+
"Excluding SnowPit from calculations."
|
|
167
|
+
) from exc
|
|
168
|
+
|
|
169
|
+
layers.append(
|
|
170
|
+
Layer(
|
|
171
|
+
rho=density_bottom,
|
|
172
|
+
h=half_thickness,
|
|
173
|
+
grain_type=grain_type,
|
|
174
|
+
grain_size=grain_size,
|
|
175
|
+
hand_hardness=hand_hardness_bottom,
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
# Single hardness value - create one layer
|
|
180
|
+
hand_hardness = layer.hardness
|
|
181
|
+
|
|
182
|
+
if measured_density is not None:
|
|
183
|
+
density = measured_density
|
|
184
|
+
density_methods.append("density_obs")
|
|
185
|
+
else:
|
|
186
|
+
try:
|
|
187
|
+
density_methods.append("geldsetzer")
|
|
188
|
+
density = compute_density(grain_type, hand_hardness)
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
raise AttributeError(
|
|
191
|
+
"Layer is missing density information; density profile, "
|
|
192
|
+
"hand hardness and grain type are all missing. "
|
|
193
|
+
"Excluding SnowPit from calculations."
|
|
194
|
+
) from exc
|
|
195
|
+
|
|
196
|
+
layers.append(
|
|
197
|
+
Layer(
|
|
198
|
+
rho=density,
|
|
199
|
+
h=thickness,
|
|
200
|
+
grain_type=grain_type,
|
|
201
|
+
grain_size=grain_size,
|
|
202
|
+
hand_hardness=hand_hardness,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if len(layers) == 0:
|
|
207
|
+
raise AttributeError(
|
|
208
|
+
"No layers found for snowpit. Excluding SnowPit from calculations."
|
|
209
|
+
)
|
|
210
|
+
return layers, density_methods
|
|
211
|
+
|
|
212
|
+
def get_density_for_layer_range(
|
|
213
|
+
self,
|
|
214
|
+
layer_top_mm: float,
|
|
215
|
+
layer_bottom_mm: float,
|
|
216
|
+
sp_density_layers: List[DensityObs],
|
|
217
|
+
) -> float | None:
|
|
218
|
+
"""Find density measurements that overlap with the given layer depth range.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
layer_top_mm: Top depth of layer in mm
|
|
222
|
+
layer_bottom_mm: Bottom depth of layer in mm
|
|
223
|
+
sp_density_layers: List of density observations
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Average density from overlapping measurements, or None if no overlap
|
|
227
|
+
"""
|
|
228
|
+
if not sp_density_layers:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
overlapping_densities = []
|
|
232
|
+
overlapping_weights = []
|
|
233
|
+
|
|
234
|
+
for density_obs in sp_density_layers:
|
|
235
|
+
if density_obs.depth_top is None or density_obs.thickness is None:
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
# Convert density observation depth range to mm
|
|
239
|
+
density_top_mm = (
|
|
240
|
+
density_obs.depth_top[0] * convert_to_mm[density_obs.depth_top[1]]
|
|
241
|
+
)
|
|
242
|
+
density_thickness_mm = (
|
|
243
|
+
density_obs.thickness[0] * convert_to_mm[density_obs.thickness[1]]
|
|
244
|
+
)
|
|
245
|
+
density_bottom_mm = density_top_mm + density_thickness_mm
|
|
246
|
+
|
|
247
|
+
# Check for overlap between layer and density measurement
|
|
248
|
+
overlap_top = max(layer_top_mm, density_top_mm)
|
|
249
|
+
overlap_bottom = min(layer_bottom_mm, density_bottom_mm)
|
|
250
|
+
|
|
251
|
+
if overlap_top < overlap_bottom: # There is overlap
|
|
252
|
+
overlap_thickness = overlap_bottom - overlap_top
|
|
253
|
+
|
|
254
|
+
# Extract density value
|
|
255
|
+
if density_obs.density is not None:
|
|
256
|
+
density_value = density_obs.density[0] # (value, unit)
|
|
257
|
+
|
|
258
|
+
overlapping_densities.append(density_value)
|
|
259
|
+
overlapping_weights.append(overlap_thickness)
|
|
260
|
+
|
|
261
|
+
if overlapping_densities:
|
|
262
|
+
# Calculate weighted average based on overlap thickness
|
|
263
|
+
total_weight = sum(overlapping_weights)
|
|
264
|
+
if total_weight > 0:
|
|
265
|
+
weighted_density = (
|
|
266
|
+
sum(
|
|
267
|
+
d * w
|
|
268
|
+
for d, w in zip(overlapping_densities, overlapping_weights)
|
|
269
|
+
)
|
|
270
|
+
/ total_weight
|
|
271
|
+
)
|
|
272
|
+
return float(weighted_density)
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
def extract_weak_layer_and_layers_above(
|
|
276
|
+
self, weak_layer_depth: float, layers: List[Layer]
|
|
277
|
+
) -> Tuple[WeakLayer, List[Layer]]:
|
|
278
|
+
"""Extract weak layer and layers above the weak layer for the given
|
|
279
|
+
depth_top extracted from the stability test."""
|
|
280
|
+
depth = 0
|
|
281
|
+
layers_above = []
|
|
282
|
+
weak_layer_rho = None
|
|
283
|
+
weak_layer_hand_hardness = None
|
|
284
|
+
weak_layer_grain_type = None
|
|
285
|
+
weak_layer_grain_size = None
|
|
286
|
+
if weak_layer_depth <= 0:
|
|
287
|
+
raise ValueError(
|
|
288
|
+
"The depth of the weak layer is not positive. "
|
|
289
|
+
"Excluding SnowPit from calculations."
|
|
290
|
+
)
|
|
291
|
+
if weak_layer_depth > sum(layer.h for layer in layers):
|
|
292
|
+
raise ValueError(
|
|
293
|
+
"The depth of the weak layer is below the recorded layers. "
|
|
294
|
+
"Excluding SnowPit from calculations."
|
|
295
|
+
)
|
|
296
|
+
layers = [layer.model_copy(deep=True) for layer in layers]
|
|
297
|
+
for i, layer in enumerate(layers):
|
|
298
|
+
if depth + layer.h < weak_layer_depth:
|
|
299
|
+
layers_above.append(layer)
|
|
300
|
+
depth += layer.h
|
|
301
|
+
elif depth < weak_layer_depth < depth + layer.h:
|
|
302
|
+
layer.h = weak_layer_depth - depth
|
|
303
|
+
layers_above.append(layer)
|
|
304
|
+
weak_layer_rho = layers[i].rho
|
|
305
|
+
weak_layer_hand_hardness = layers[i].hand_hardness
|
|
306
|
+
weak_layer_grain_type = layers[i].grain_type
|
|
307
|
+
weak_layer_grain_size = layers[i].grain_size
|
|
308
|
+
break
|
|
309
|
+
elif depth + layer.h == weak_layer_depth:
|
|
310
|
+
if i + 1 < len(layers):
|
|
311
|
+
layers_above.append(layer)
|
|
312
|
+
weak_layer_rho = layers[i + 1].rho
|
|
313
|
+
weak_layer_hand_hardness = layers[i + 1].hand_hardness
|
|
314
|
+
weak_layer_grain_type = layers[i + 1].grain_type
|
|
315
|
+
weak_layer_grain_size = layers[i + 1].grain_size
|
|
316
|
+
else:
|
|
317
|
+
weak_layer_rho = layers[i].rho
|
|
318
|
+
weak_layer_hand_hardness = layers[i].hand_hardness
|
|
319
|
+
weak_layer_grain_type = layers[i].grain_type
|
|
320
|
+
weak_layer_grain_size = layers[i].grain_size
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
weak_layer = WeakLayer(
|
|
324
|
+
rho=weak_layer_rho,
|
|
325
|
+
h=20.0,
|
|
326
|
+
hand_hardness=weak_layer_hand_hardness,
|
|
327
|
+
grain_type=weak_layer_grain_type,
|
|
328
|
+
grain_size=weak_layer_grain_size,
|
|
329
|
+
)
|
|
330
|
+
if len(layers_above) == 0:
|
|
331
|
+
raise ValueError("No layers above weak layer found")
|
|
332
|
+
return weak_layer, layers_above
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weac
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Weak layer anticrack nucleation model
|
|
5
5
|
Author-email: 2phi GbR <mail@2phi.de>
|
|
6
|
-
License:
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/2phi/weac
|
|
8
8
|
Project-URL: Demo, https://github.com/2phi/weac/blob/main/demo/demo.ipynb
|
|
9
9
|
Project-URL: Documentation, https://2phi.github.io/weac
|
|
@@ -11,7 +11,6 @@ Project-URL: Issues and feature requests, https://github.com/2phi/weac/issues
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier: License :: Other/Proprietary License
|
|
15
14
|
Classifier: Operating System :: OS Independent
|
|
16
15
|
Classifier: Topic :: Scientific/Engineering
|
|
17
16
|
Requires-Python: >=3.12
|
|
@@ -55,7 +54,8 @@ Requires-Dist: pylint>=3.2.0; extra == "dev"
|
|
|
55
54
|
Requires-Dist: pycodestyle>=2.11.1; extra == "dev"
|
|
56
55
|
Requires-Dist: black>=24.4.0; extra == "dev"
|
|
57
56
|
Requires-Dist: isort>=5.13.0; extra == "dev"
|
|
58
|
-
Requires-Dist:
|
|
57
|
+
Requires-Dist: bump-my-version; extra == "dev"
|
|
58
|
+
Requires-Dist: build; extra == "dev"
|
|
59
59
|
Dynamic: license-file
|
|
60
60
|
|
|
61
61
|
<!-- LOGO AND TITLE-->
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
weac/__init__.py,sha256=YrbBczmXbMc2L2DRklNEOFxlfY0Z9FLDnpW8ZhbaIww,76
|
|
2
|
+
weac/constants.py,sha256=BX8ifZhFciCuzzako1p-2Wh5CWWzR3cPdvyu501UtUs,1194
|
|
3
|
+
weac/logging_config.py,sha256=BFphsxo5s_7E6rPOO2PbOfu0Wy8zHD4AUnnJdqvrn5I,1134
|
|
4
|
+
weac/analysis/__init__.py,sha256=twF9OYrZiVmBtw6Lyzf5QUhLZNtqFFM2H3OuaYCog1k,486
|
|
5
|
+
weac/analysis/analyzer.py,sha256=AR1yxM5trK1c-_lTEVn9e6e96IhWVWSRgHgQcaLy4Io,27089
|
|
6
|
+
weac/analysis/criteria_evaluator.py,sha256=oIVL7Mq3fjyiVx_ms-miULdHE4-EYE2OXoMalM9eU1Q,43458
|
|
7
|
+
weac/analysis/plotter.py,sha256=UdiPZKb8vzZAdB1tjVUNfV2INtZaqEiJ7EA8Y-dzxoM,66056
|
|
8
|
+
weac/components/__init__.py,sha256=94WIUVjPI3U-grN_7v0oi9bZB3Z8rCBcx9wJPOwJBAQ,439
|
|
9
|
+
weac/components/config.py,sha256=tnOnJ0M-_knZBhdr052nDyyFFAZN0f2hQ68XRuXG6d8,869
|
|
10
|
+
weac/components/criteria_config.py,sha256=f2agU7nXWURFAw8_68igiSk5aICUxwaB9u3Qasit-Q0,2909
|
|
11
|
+
weac/components/layer.py,sha256=sF47006ORRmNnHW7QMk9h-M0vQQR6TL_PKOvInjdNsk,10560
|
|
12
|
+
weac/components/model_input.py,sha256=dp695ZMXkSXzJaD-ih9XO0QhnFRPKn8zUJY7g-bomhE,3354
|
|
13
|
+
weac/components/scenario_config.py,sha256=Tam-m9DQtdjmTm-lKQ8Dcjqje04ttJS2X3v_Nn7AUHQ,2563
|
|
14
|
+
weac/components/segment.py,sha256=F279KcAAkRuJKWav_BZ4BanO96WZm4KXtKHinFZki7s,941
|
|
15
|
+
weac/core/__init__.py,sha256=pRyCKD8XD3qXVUWtFG7N3cS91P5x5d8Jpr1hMEgxQ2U,233
|
|
16
|
+
weac/core/eigensystem.py,sha256=I4AIGD_uDVKIGv_sv9AXbIXhJ4Eol64BLB-lZF8X3jw,13735
|
|
17
|
+
weac/core/field_quantities.py,sha256=ci7MvhJ4aYdbW6xxH8vHVgWtk5iypCYv6dZ6KjFNvt8,8964
|
|
18
|
+
weac/core/scenario.py,sha256=f5EZaeFJWMFN_JNOrzVDv5_CeRruHn6jSjGbV9fdxhY,6076
|
|
19
|
+
weac/core/slab.py,sha256=nidM3FvTwtj30LmM4OxOfPwqm5TpPzJrH5UvQR6w4no,5150
|
|
20
|
+
weac/core/slab_touchdown.py,sha256=D0mVVeRktFpjywuhEiIZHOVJflslfKX3oesx1mMQGxg,14025
|
|
21
|
+
weac/core/system_model.py,sha256=XMovKslSTvoNHyxeL7zTFMhAqVOTHnXD7MUPkaGSl9Q,15577
|
|
22
|
+
weac/core/unknown_constants_solver.py,sha256=cDXMVKIdhA8AWZQbJnNpM19pvLllvIVc6Mw6xaZGqYw,17839
|
|
23
|
+
weac/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
weac/utils/geldsetzer.py,sha256=DxvpqmWulBacl-mGeAmAuJcv2xqFBx92PEfNC3aeDzk,3500
|
|
25
|
+
weac/utils/misc.py,sha256=lGz0IDDJ_3nvYjSkivPJ5Xscl1D_AmvQLSjaL7SUbKs,3674
|
|
26
|
+
weac/utils/snow_types.py,sha256=eX9-5La6Oom7zh6pg5JZ4MZ6nLdWdc7RoUzm5e6b9w8,1483
|
|
27
|
+
weac/utils/snowpilot_parser.py,sha256=5fKC0FKcEJvfFQfZbPW5cHqb_nyzIltweq7Bdz5-a0A,13713
|
|
28
|
+
weac-3.0.1.dist-info/licenses/LICENSE,sha256=ojZPWKFHbFGDrlNOvuAKGH9WcKhpLHWZPcQ4SzhK91M,1082
|
|
29
|
+
weac-3.0.1.dist-info/METADATA,sha256=-14ZLdvT2GKGX5n8XLJbd3wxPUHvj8Fi5hg85GfdJPs,23666
|
|
30
|
+
weac-3.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
31
|
+
weac-3.0.1.dist-info/top_level.txt,sha256=8tyXUHPFU4Ba_5kPtpwvXo5l6GjJmOnODVBJFygpdeE,5
|
|
32
|
+
weac-3.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 2phi GbR and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
weac-3.0.0.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
weac/__init__.py,sha256=dv7A3r-vIVUaQJDEt5pxC8IJq_Lej2camGA7fIhTsmc,76
|
|
2
|
-
weac/constants.py,sha256=BX8ifZhFciCuzzako1p-2Wh5CWWzR3cPdvyu501UtUs,1194
|
|
3
|
-
weac/logging_config.py,sha256=BFphsxo5s_7E6rPOO2PbOfu0Wy8zHD4AUnnJdqvrn5I,1134
|
|
4
|
-
weac-3.0.0.dist-info/licenses/LICENSE,sha256=CZlY87tZ1Kq7QxKLVrMknnDXGpc1yEZ8SKoXMAk-d4k,1463
|
|
5
|
-
weac-3.0.0.dist-info/METADATA,sha256=lDtqRgn6B42TJM7ju83uFvlzzjkFm93qFIIa99-vI-g,23679
|
|
6
|
-
weac-3.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
-
weac-3.0.0.dist-info/top_level.txt,sha256=8tyXUHPFU4Ba_5kPtpwvXo5l6GjJmOnODVBJFygpdeE,5
|
|
8
|
-
weac-3.0.0.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
|
2
|
-
|
|
3
|
-
WEAC (c) 2024 is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
|
|
4
|
-
|
|
5
|
-
You are free to:
|
|
6
|
-
|
|
7
|
-
- Share — copy and redistribute the material in any medium or format
|
|
8
|
-
- Adapt — remix, transform, and build upon the material.
|
|
9
|
-
|
|
10
|
-
Under the following terms:
|
|
11
|
-
|
|
12
|
-
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
|
13
|
-
|
|
14
|
-
- NonCommercial — You may not use the material for commercial purposes.
|
|
15
|
-
|
|
16
|
-
- ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
|
|
17
|
-
|
|
18
|
-
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
|
|
19
|
-
|
|
20
|
-
Notices:
|
|
21
|
-
|
|
22
|
-
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
|
|
23
|
-
|
|
24
|
-
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
|
|
File without changes
|
|
File without changes
|