weac 2.6.4__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 +2 -14
- 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/constants.py +37 -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/logging_config.py +39 -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-2.6.4.dist-info → weac-3.0.1.dist-info}/METADATA +196 -64
- weac-3.0.1.dist-info/RECORD +32 -0
- weac-3.0.1.dist-info/licenses/LICENSE +21 -0
- weac/eigensystem.py +0 -658
- weac/inverse.py +0 -51
- weac/layered.py +0 -64
- weac/mixins.py +0 -2083
- weac/plot.py +0 -675
- weac/tools.py +0 -334
- weac-2.6.4.dist-info/RECORD +0 -12
- weac-2.6.4.dist-info/licenses/LICENSE +0 -24
- {weac-2.6.4.dist-info → weac-3.0.1.dist-info}/WHEEL +0 -0
- {weac-2.6.4.dist-info → weac-3.0.1.dist-info}/top_level.txt +0 -0
weac/core/scenario.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the Scenario class, which encapsulates the physical setup of the model.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Sequence, Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from weac.components import ScenarioConfig, Segment, SystemType, WeakLayer
|
|
11
|
+
from weac.core.slab import Slab
|
|
12
|
+
from weac.utils.misc import decompose_to_normal_tangential
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Scenario:
|
|
18
|
+
"""
|
|
19
|
+
Sets up the scenario on which the eigensystem is solved.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
---------
|
|
23
|
+
scenario_config: ScenarioConfig
|
|
24
|
+
segments: List[Segment]
|
|
25
|
+
weak_layer: WeakLayer
|
|
26
|
+
slab: Slab
|
|
27
|
+
|
|
28
|
+
Attributes
|
|
29
|
+
----------
|
|
30
|
+
li : List[float]
|
|
31
|
+
length of segment i [mm]
|
|
32
|
+
ki : List[bool]
|
|
33
|
+
booleans indicating foundation support for segment i
|
|
34
|
+
mi : List[float]
|
|
35
|
+
skier masses (kg) on boundary of segment i and i+1 [kg]
|
|
36
|
+
|
|
37
|
+
system_type : SystemType
|
|
38
|
+
phi : float
|
|
39
|
+
Angle of slab in positive in counter-clockwise direction [deg]
|
|
40
|
+
L : float
|
|
41
|
+
Length of the model [mm]
|
|
42
|
+
crack_h: float
|
|
43
|
+
Height of the crack [mm]
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Inputs
|
|
47
|
+
scenario_config: ScenarioConfig
|
|
48
|
+
segments: List[Segment]
|
|
49
|
+
weak_layer: WeakLayer
|
|
50
|
+
slab: Slab
|
|
51
|
+
|
|
52
|
+
# Attributes
|
|
53
|
+
li: np.ndarray # length of segment i [mm]
|
|
54
|
+
ki: np.ndarray # booleans indicating foundation support for segment i
|
|
55
|
+
mi: np.ndarray # skier masses (kg) on boundary of segment i and i+1 [kg]
|
|
56
|
+
|
|
57
|
+
cum_sum_li: np.ndarray # cumulative sum of segment lengths [mm]
|
|
58
|
+
|
|
59
|
+
system_type: SystemType
|
|
60
|
+
phi: float # Angle in [deg]
|
|
61
|
+
surface_load: float # Surface Line-Load [N/mm]
|
|
62
|
+
qw: float # Weight Line-Load [N/mm]
|
|
63
|
+
qn: float # Total Normal Line-Load [N/mm]
|
|
64
|
+
qt: float # Total Tangential Line-Load [N/mm]
|
|
65
|
+
L: float # Length of the model [mm]
|
|
66
|
+
crack_h: float # Height of the crack [mm]
|
|
67
|
+
cut_length: float # Length of the cut [mm]
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
scenario_config: ScenarioConfig,
|
|
72
|
+
segments: List[Segment],
|
|
73
|
+
weak_layer: WeakLayer,
|
|
74
|
+
slab: Slab,
|
|
75
|
+
):
|
|
76
|
+
self.scenario_config = scenario_config
|
|
77
|
+
self.segments = segments
|
|
78
|
+
self.weak_layer = weak_layer
|
|
79
|
+
self.slab = slab
|
|
80
|
+
|
|
81
|
+
self.system_type = scenario_config.system_type
|
|
82
|
+
self.phi = scenario_config.phi
|
|
83
|
+
self.surface_load = scenario_config.surface_load
|
|
84
|
+
self.cut_length = scenario_config.cut_length
|
|
85
|
+
|
|
86
|
+
self._setup_scenario()
|
|
87
|
+
self._calc_normal_load()
|
|
88
|
+
self._calc_tangential_load()
|
|
89
|
+
self._calc_crack_height()
|
|
90
|
+
|
|
91
|
+
def refresh_from_config(self):
|
|
92
|
+
"""Pull changed values out of scenario_config
|
|
93
|
+
and recompute derived attributes."""
|
|
94
|
+
self.system_type = self.scenario_config.system_type
|
|
95
|
+
self.phi = self.scenario_config.phi
|
|
96
|
+
self.surface_load = self.scenario_config.surface_load
|
|
97
|
+
self.cut_length = self.scenario_config.cut_length
|
|
98
|
+
|
|
99
|
+
self._setup_scenario()
|
|
100
|
+
self._calc_normal_load()
|
|
101
|
+
self._calc_tangential_load()
|
|
102
|
+
self._calc_crack_height()
|
|
103
|
+
|
|
104
|
+
def get_segment_idx(
|
|
105
|
+
self, x: Union[float, Sequence[float], np.ndarray]
|
|
106
|
+
) -> Union[int, np.ndarray]:
|
|
107
|
+
"""
|
|
108
|
+
Get the segment index for a given x-coordinate or coordinates.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
x: Union[float, Sequence[float], np.ndarray]
|
|
113
|
+
A single x-coordinate or a sequence of x-coordinates.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
Union[int, np.ndarray]
|
|
118
|
+
The segment index or an array of indices.
|
|
119
|
+
"""
|
|
120
|
+
x_arr = np.asarray(x)
|
|
121
|
+
indices = np.digitize(x_arr, self.cum_sum_li)
|
|
122
|
+
|
|
123
|
+
if np.any(x_arr > self.L):
|
|
124
|
+
raise ValueError(f"Coordinate {x_arr} exceeds the slab length.")
|
|
125
|
+
|
|
126
|
+
if x_arr.ndim == 0:
|
|
127
|
+
return int(indices)
|
|
128
|
+
|
|
129
|
+
return indices
|
|
130
|
+
|
|
131
|
+
def _setup_scenario(self):
|
|
132
|
+
self.li = np.array([seg.length for seg in self.segments])
|
|
133
|
+
self.ki = np.array([seg.has_foundation for seg in self.segments])
|
|
134
|
+
# masses that act *between* segments: take all but the last one
|
|
135
|
+
self.mi = np.array([seg.m for seg in self.segments[:-1]])
|
|
136
|
+
self.cum_sum_li = np.cumsum(self.li)
|
|
137
|
+
|
|
138
|
+
# Add dummy segment if only one segment provided
|
|
139
|
+
if len(self.li) == 1:
|
|
140
|
+
self.li = np.append(self.li, 0)
|
|
141
|
+
self.ki = np.append(self.ki, True)
|
|
142
|
+
self.mi = np.append(self.mi, 0)
|
|
143
|
+
|
|
144
|
+
# Calculate the total slab length
|
|
145
|
+
self.L = np.sum(self.li)
|
|
146
|
+
|
|
147
|
+
def _calc_tangential_load(self):
|
|
148
|
+
"""
|
|
149
|
+
Total Tangential Load (Surface Load + Weight Load)
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
--------
|
|
153
|
+
qt : float
|
|
154
|
+
Tangential Component of Load [N/mm]
|
|
155
|
+
"""
|
|
156
|
+
# Surface Load & Weight Load
|
|
157
|
+
qw = self.slab.qw
|
|
158
|
+
qs = self.surface_load
|
|
159
|
+
|
|
160
|
+
# Normal components of forces
|
|
161
|
+
phi = self.phi
|
|
162
|
+
_, qwt = decompose_to_normal_tangential(qw, phi)
|
|
163
|
+
_, qst = decompose_to_normal_tangential(qs, phi)
|
|
164
|
+
qt = qwt + qst
|
|
165
|
+
self.qt = qt
|
|
166
|
+
|
|
167
|
+
def _calc_normal_load(self):
|
|
168
|
+
"""
|
|
169
|
+
Total Normal Load (Surface Load + Weight Load)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
--------
|
|
173
|
+
qn : float
|
|
174
|
+
Normal Component of Load [N/mm]
|
|
175
|
+
"""
|
|
176
|
+
# Surface Load & Weight Load
|
|
177
|
+
qw = self.slab.qw
|
|
178
|
+
qs = self.surface_load
|
|
179
|
+
|
|
180
|
+
# Normal components of forces
|
|
181
|
+
phi = self.phi
|
|
182
|
+
qwn, _ = decompose_to_normal_tangential(qw, phi)
|
|
183
|
+
qsn, _ = decompose_to_normal_tangential(qs, phi)
|
|
184
|
+
qn = qwn + qsn
|
|
185
|
+
self.qn = qn
|
|
186
|
+
|
|
187
|
+
def _calc_crack_height(self):
|
|
188
|
+
"""
|
|
189
|
+
Crack Height: Difference between collapsed weak layer and
|
|
190
|
+
Weak Layer (Winkler type) under slab load
|
|
191
|
+
|
|
192
|
+
Example:
|
|
193
|
+
if the collapse layer has a height of 5 and the non-collapsed layer
|
|
194
|
+
has a height of 15 the collapse height is 10
|
|
195
|
+
"""
|
|
196
|
+
self.crack_h = self.weak_layer.collapse_height - self.qn / self.weak_layer.kn
|
|
197
|
+
if self.crack_h < 0:
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"Crack height is negative: {self.crack_h} decrease the surface load"
|
|
200
|
+
)
|
weac/core/slab.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the Slab class, which represents the snow slab and its properties.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from weac.components import Layer
|
|
10
|
+
from weac.constants import EPS, G_MM_S2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Slab: # pylint: disable=too-many-instance-attributes,too-few-public-methods
|
|
14
|
+
"""
|
|
15
|
+
Parameters of all layers assembled into a slab,
|
|
16
|
+
provided as np.ndarray for easier access.
|
|
17
|
+
|
|
18
|
+
Coordinate frame:
|
|
19
|
+
- z-axis points downward (first index: top layer, last index: bottom layer)
|
|
20
|
+
- z = 0 is set at the mid-point of the slab's thickness
|
|
21
|
+
|
|
22
|
+
Attributes
|
|
23
|
+
----------
|
|
24
|
+
zi_mid: np.ndarray
|
|
25
|
+
z-coordinate of the layer i mid-point
|
|
26
|
+
zi_bottom: np.ndarray
|
|
27
|
+
z-coordinate of the layer i (boundary towards bottom)
|
|
28
|
+
rhoi: np.ndarray
|
|
29
|
+
densities of the layer i [t/mm^3]
|
|
30
|
+
hi: np.ndarray
|
|
31
|
+
thickness of the layer i [mm]
|
|
32
|
+
Ei: np.ndarray
|
|
33
|
+
Young's modulus of the layer i [MPa]
|
|
34
|
+
Gi: np.ndarray
|
|
35
|
+
Shear Modulus of the layer i [MPa]
|
|
36
|
+
nui: np.ndarray
|
|
37
|
+
Poisson Ratio of the layer i [-]
|
|
38
|
+
H: float
|
|
39
|
+
Total slab thickness (i.e. assembled layers) [mm]
|
|
40
|
+
z_cog: float
|
|
41
|
+
z-coordinate of Center of Gravity [mm]
|
|
42
|
+
qw: float
|
|
43
|
+
Weight Load of the slab [N/mm]
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Input data
|
|
47
|
+
layers: List[Layer]
|
|
48
|
+
|
|
49
|
+
rhoi: np.ndarray # densities of the layer i [t/mm^3]
|
|
50
|
+
hi: np.ndarray # thickness of the layer i [mm]
|
|
51
|
+
Ei: np.ndarray # Young's modulus of the layer i [MPa]
|
|
52
|
+
Gi: np.ndarray # Shear Modulus of the layer i [MPa]
|
|
53
|
+
nui: np.ndarray # Poisson Ratio of the layer i [-]
|
|
54
|
+
|
|
55
|
+
# Derived Values
|
|
56
|
+
z0: float # z-coordinate of the top of the slab
|
|
57
|
+
zi_mid: np.ndarray # z-coordinate of the layer i mid-point
|
|
58
|
+
zi_bottom: np.ndarray # z-coordinate of the layer i (boundary towards bottom)
|
|
59
|
+
H: float # Total slab thickness (i.e. assembled layers) [mm]
|
|
60
|
+
z_cog: float # z-coordinate of Center of Gravity [mm]
|
|
61
|
+
qw: float # Weight Load of the slab [N/mm]
|
|
62
|
+
|
|
63
|
+
def __init__(self, layers: List[Layer]) -> None:
|
|
64
|
+
self.layers = layers
|
|
65
|
+
self._calc_slab_params()
|
|
66
|
+
|
|
67
|
+
def calc_vertical_center_of_gravity(self, phi: float):
|
|
68
|
+
"""
|
|
69
|
+
Vertical PSTs use triangular slabs (with horizontal cuts on the slab ends)
|
|
70
|
+
Calculate center of gravity of triangular slab segments for vertical PSTs.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
phi : float
|
|
75
|
+
Slope angle [deg]
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
x_cog : float
|
|
80
|
+
Horizontal coordinate of center of gravity [mm]
|
|
81
|
+
z_cog : float
|
|
82
|
+
Vertical coordinate of center of gravity [mm]
|
|
83
|
+
w : float
|
|
84
|
+
Weight of the slab segment that is cut off or added [t]
|
|
85
|
+
"""
|
|
86
|
+
# Convert slope angle to radians
|
|
87
|
+
phi = np.deg2rad(phi)
|
|
88
|
+
|
|
89
|
+
# Catch flat-field case
|
|
90
|
+
if abs(phi) < EPS:
|
|
91
|
+
x_cog = 0
|
|
92
|
+
z_cog = 0
|
|
93
|
+
w = 0
|
|
94
|
+
else:
|
|
95
|
+
n = len(self.hi)
|
|
96
|
+
rho = self.rhoi # [t/mm^3]
|
|
97
|
+
hi = self.hi # [mm]
|
|
98
|
+
H = self.H # [mm]
|
|
99
|
+
# Layer coordinates z_i (top to bottom)
|
|
100
|
+
z = np.array([-H / 2 + sum(hi[0:j]) for j in range(n + 1)])
|
|
101
|
+
zi = z[:-1]
|
|
102
|
+
zii = z[1:]
|
|
103
|
+
# Center of gravity of all layers (top to bottom) derived from
|
|
104
|
+
# triangular slab geometry
|
|
105
|
+
zsi = zi + hi / 3 * (3 / 2 * H - zi - 2 * zii) / (H - zi - zii)
|
|
106
|
+
# Surface area of all layers (top to bottom), area = height * base / 2
|
|
107
|
+
# where base varies with slope angle
|
|
108
|
+
Ai = hi / 2 * (H - zi - zii) * np.tan(phi)
|
|
109
|
+
# Center of gravity in vertical direction
|
|
110
|
+
z_cog = sum(zsi * rho * Ai) / sum(rho * Ai)
|
|
111
|
+
# Center of gravity in horizontal direction
|
|
112
|
+
x_cog = (H / 2 - z_cog) * np.tan(phi / 2)
|
|
113
|
+
# Weight of added or cut off slab segments (t)
|
|
114
|
+
w = sum(Ai * rho)
|
|
115
|
+
|
|
116
|
+
# Return center of gravity and weight of slab segment
|
|
117
|
+
return x_cog, z_cog, w
|
|
118
|
+
|
|
119
|
+
def _calc_slab_params(self) -> None:
|
|
120
|
+
rhoi = (
|
|
121
|
+
np.array([ly.rho for ly in self.layers]) * 1e-12
|
|
122
|
+
) # Layer densities (kg/m^3 -> t/mm^3)
|
|
123
|
+
hi = np.array([ly.h for ly in self.layers]) # Layer thickness
|
|
124
|
+
Ei = np.array([ly.E for ly in self.layers])
|
|
125
|
+
Gi = np.array([ly.G for ly in self.layers])
|
|
126
|
+
nui = np.array([ly.nu for ly in self.layers])
|
|
127
|
+
|
|
128
|
+
H = hi.sum()
|
|
129
|
+
# Vectorized midpoint coordinates per layer (top to bottom)
|
|
130
|
+
# previously: zi_mid = [float(H / 2 - sum(hi[j:n]) + hi[j] / 2) for j in range(n)]
|
|
131
|
+
suffix_cumsum = np.cumsum(hi[::-1])[::-1]
|
|
132
|
+
zi_mid = H / 2 - suffix_cumsum + hi / 2
|
|
133
|
+
zi_bottom = np.cumsum(hi) - H / 2
|
|
134
|
+
z_cog = sum(zi_mid * hi * rhoi) / sum(hi * rhoi)
|
|
135
|
+
|
|
136
|
+
qw = sum(rhoi * G_MM_S2 * hi) # Line load [N/mm]
|
|
137
|
+
|
|
138
|
+
self.rhoi = rhoi
|
|
139
|
+
self.hi = hi
|
|
140
|
+
self.Ei = Ei
|
|
141
|
+
self.Gi = Gi
|
|
142
|
+
self.nui = nui
|
|
143
|
+
|
|
144
|
+
self.zi_mid = zi_mid
|
|
145
|
+
self.zi_bottom = zi_bottom
|
|
146
|
+
self.z0 = -H / 2 # z-coordinate of the top of the slab
|
|
147
|
+
self.H = H
|
|
148
|
+
self.z_cog = z_cog
|
|
149
|
+
self.qw = qw
|