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
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the system model for the WEAC simulation. The system
|
|
3
|
+
model is the heart of the WEAC simulation. All data sources are bundled into
|
|
4
|
+
the system model. The system model initializes and calculates all the
|
|
5
|
+
parameterizations and passes relevant data to the different components.
|
|
6
|
+
|
|
7
|
+
We utilize the pydantic library to define the system model.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Literal, Optional
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from numpy.linalg import LinAlgError
|
|
15
|
+
|
|
16
|
+
from weac.components import SystemType
|
|
17
|
+
from weac.constants import G_MM_S2
|
|
18
|
+
from weac.core.eigensystem import Eigensystem
|
|
19
|
+
from weac.core.field_quantities import FieldQuantities
|
|
20
|
+
from weac.core.scenario import Scenario
|
|
21
|
+
|
|
22
|
+
# from weac.constants import G_MM_S2, LSKI_MM
|
|
23
|
+
from weac.utils.misc import decompose_to_normal_tangential, get_skier_point_load
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UnknownConstantsSolver:
|
|
29
|
+
"""
|
|
30
|
+
This class solves the unknown constants for the WEAC simulation.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def solve_for_unknown_constants(
|
|
35
|
+
cls,
|
|
36
|
+
scenario: Scenario,
|
|
37
|
+
eigensystem: Eigensystem,
|
|
38
|
+
system_type: SystemType,
|
|
39
|
+
touchdown_distance: Optional[float] = None,
|
|
40
|
+
touchdown_mode: Optional[
|
|
41
|
+
Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
|
|
42
|
+
] = None,
|
|
43
|
+
collapsed_weak_layer_kR: Optional[float] = None,
|
|
44
|
+
) -> np.ndarray:
|
|
45
|
+
"""
|
|
46
|
+
Compute free constants *C* for system. \\
|
|
47
|
+
Assemble LHS from supported and unsupported segments in the form::
|
|
48
|
+
|
|
49
|
+
[ ] [ zh1 0 0 ... 0 0 0 ][ ] [ ] [ ] (left)
|
|
50
|
+
[ ] [ zh1 zh2 0 ... 0 0 0 ][ ] [ ] [ ] (mid)
|
|
51
|
+
[ ] [ 0 zh2 zh3 ... 0 0 0 ][ ] [ ] [ ] (mid)
|
|
52
|
+
[z0] = [ ... ... ... ... ... ... ... ][ C ] + [ zp ] = [ rhs ] (mid)
|
|
53
|
+
[ ] [ 0 0 0 ... zhL zhM 0 ][ ] [ ] [ ] (mid)
|
|
54
|
+
[ ] [ 0 0 0 ... 0 zhM zhN ][ ] [ ] [ ] (mid)
|
|
55
|
+
[ ] [ 0 0 0 ... 0 0 zhN ][ ] [ ] [ ] (right)
|
|
56
|
+
|
|
57
|
+
and solve for constants C.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
C : ndarray
|
|
62
|
+
Matrix(6xN) of solution constants for a system of N
|
|
63
|
+
segements. Columns contain the 6 constants of each segement.
|
|
64
|
+
"""
|
|
65
|
+
logger.debug("Starting solve unknown constants")
|
|
66
|
+
phi = scenario.phi
|
|
67
|
+
qs = scenario.surface_load
|
|
68
|
+
li = scenario.li
|
|
69
|
+
ki = scenario.ki
|
|
70
|
+
mi = scenario.mi
|
|
71
|
+
|
|
72
|
+
# Determine size of linear system of equations
|
|
73
|
+
nS = len(li) # Number of beam segments
|
|
74
|
+
nDOF = 6 # Number of free constants per segment
|
|
75
|
+
logger.debug("Number of segments: %s, DOF per segment: %s", nS, nDOF)
|
|
76
|
+
|
|
77
|
+
# Assemble position vector
|
|
78
|
+
pi = np.full(nS, "m")
|
|
79
|
+
pi[0], pi[-1] = "length", "r"
|
|
80
|
+
|
|
81
|
+
# Initialize matrices
|
|
82
|
+
Zh0 = np.zeros([nS * 6, nS * nDOF])
|
|
83
|
+
Zp0 = np.zeros([nS * 6, 1])
|
|
84
|
+
rhs = np.zeros([nS * 6, 1])
|
|
85
|
+
logger.debug(
|
|
86
|
+
"Initialized Zh0 shape: %s, Zp0 shape: %s, rhs shape: %s",
|
|
87
|
+
Zh0.shape,
|
|
88
|
+
Zp0.shape,
|
|
89
|
+
rhs.shape,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# LHS: Transmission & Boundary Conditions between segments
|
|
93
|
+
for i in range(nS):
|
|
94
|
+
# Length, foundation and position of segment i
|
|
95
|
+
length, has_foundation, pos = li[i], ki[i], pi[i]
|
|
96
|
+
|
|
97
|
+
logger.debug(
|
|
98
|
+
"Assembling segment %s: length=%s, has_foundation=%s, pos=%s",
|
|
99
|
+
i,
|
|
100
|
+
length,
|
|
101
|
+
has_foundation,
|
|
102
|
+
pos,
|
|
103
|
+
)
|
|
104
|
+
# Matrix of Size one of: (l: [9,6], m: [12,6], r: [9,6])
|
|
105
|
+
Zhi = cls._setup_conditions(
|
|
106
|
+
zl=eigensystem.zh(x=0, length=length, has_foundation=has_foundation),
|
|
107
|
+
zr=eigensystem.zh(
|
|
108
|
+
x=length, length=length, has_foundation=has_foundation
|
|
109
|
+
),
|
|
110
|
+
eigensystem=eigensystem,
|
|
111
|
+
has_foundation=has_foundation,
|
|
112
|
+
pos=pos,
|
|
113
|
+
touchdown_mode=touchdown_mode,
|
|
114
|
+
system_type=system_type,
|
|
115
|
+
collapsed_weak_layer_kR=collapsed_weak_layer_kR,
|
|
116
|
+
)
|
|
117
|
+
# Vector of Size one of: (l: [9,1], m: [12,1], r: [9,1])
|
|
118
|
+
zpi = cls._setup_conditions(
|
|
119
|
+
zl=eigensystem.zp(x=0, phi=phi, has_foundation=has_foundation, qs=qs),
|
|
120
|
+
zr=eigensystem.zp(
|
|
121
|
+
x=length, phi=phi, has_foundation=has_foundation, qs=qs
|
|
122
|
+
),
|
|
123
|
+
eigensystem=eigensystem,
|
|
124
|
+
has_foundation=has_foundation,
|
|
125
|
+
pos=pos,
|
|
126
|
+
touchdown_mode=touchdown_mode,
|
|
127
|
+
system_type=system_type,
|
|
128
|
+
collapsed_weak_layer_kR=collapsed_weak_layer_kR,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Rows for left-hand side assembly
|
|
132
|
+
start = 0 if i == 0 else 3
|
|
133
|
+
stop = 6 if i == nS - 1 else 9
|
|
134
|
+
# Assemble left-hand side
|
|
135
|
+
Zh0[(6 * i - start) : (6 * i + stop), i * nDOF : (i + 1) * nDOF] = Zhi
|
|
136
|
+
Zp0[(6 * i - start) : (6 * i + stop)] += zpi
|
|
137
|
+
logger.debug(
|
|
138
|
+
"Segment %s: Zhi shape: %s, zpi shape: %s", i, Zhi.shape, zpi.shape
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Loop through loads to assemble right-hand side
|
|
142
|
+
for i, m in enumerate(mi, start=1):
|
|
143
|
+
# Get skier point-load
|
|
144
|
+
F = get_skier_point_load(m)
|
|
145
|
+
Fn, Ft = decompose_to_normal_tangential(f=F, phi=phi)
|
|
146
|
+
# Right-hand side for transmission from segment i-1 to segment i
|
|
147
|
+
rhs[6 * i : 6 * i + 3] = np.vstack([Ft, -Ft * scenario.slab.H / 2, Fn])
|
|
148
|
+
logger.debug("Load %s: m=%s, F=%s, Fn=%s, Ft=%s", i, m, F, Fn, Ft)
|
|
149
|
+
logger.debug("RHS %s", rhs[6 * i : 6 * i + 3])
|
|
150
|
+
# Set RHS so that Complementary Integral vanishes at boundaries
|
|
151
|
+
if system_type not in ["pst-", "-pst", "rested"]:
|
|
152
|
+
logger.debug("Pre RHS %s", rhs[:3])
|
|
153
|
+
rhs[:3] = cls._boundary_conditions(
|
|
154
|
+
eigensystem.zp(x=0, phi=phi, has_foundation=ki[0], qs=qs),
|
|
155
|
+
eigensystem,
|
|
156
|
+
False,
|
|
157
|
+
"mid",
|
|
158
|
+
system_type,
|
|
159
|
+
touchdown_mode,
|
|
160
|
+
collapsed_weak_layer_kR,
|
|
161
|
+
)
|
|
162
|
+
logger.debug("Post RHS %s", rhs[:3])
|
|
163
|
+
rhs[-3:] = cls._boundary_conditions(
|
|
164
|
+
eigensystem.zp(x=li[-1], phi=phi, has_foundation=ki[-1], qs=qs),
|
|
165
|
+
eigensystem,
|
|
166
|
+
False,
|
|
167
|
+
"mid",
|
|
168
|
+
system_type,
|
|
169
|
+
touchdown_mode,
|
|
170
|
+
collapsed_weak_layer_kR,
|
|
171
|
+
)
|
|
172
|
+
logger.debug("Post RHS %s", rhs[-3:])
|
|
173
|
+
logger.debug("Set complementary integral vanishing at boundaries.")
|
|
174
|
+
|
|
175
|
+
# Set rhs for vertical faces
|
|
176
|
+
if system_type in ["vpst-", "-vpst"]:
|
|
177
|
+
# Calculate center of gravity and mass of added or cut off slab segement
|
|
178
|
+
x_cog, z_cog, m = scenario.slab.calc_vertical_center_of_gravity(phi)
|
|
179
|
+
logger.debug(
|
|
180
|
+
"Vertical center of gravity: x_cog=%s, z_cog=%s, m=%s", x_cog, z_cog, m
|
|
181
|
+
)
|
|
182
|
+
# Convert slope angle to radians
|
|
183
|
+
phi = np.deg2rad(phi)
|
|
184
|
+
# Translate into section forces and moments
|
|
185
|
+
N = -G_MM_S2 * m * np.sin(phi)
|
|
186
|
+
M = -G_MM_S2 * m * (x_cog * np.cos(phi) + z_cog * np.sin(phi))
|
|
187
|
+
V = G_MM_S2 * m * np.cos(phi)
|
|
188
|
+
# Add to right-hand side
|
|
189
|
+
rhs[:3] = np.vstack([N, M, V]) # left end
|
|
190
|
+
rhs[-3:] = np.vstack([N, M, V]) # right end
|
|
191
|
+
logger.debug("Vertical faces: N=%s, M=%s, V=%s", N, M, V)
|
|
192
|
+
|
|
193
|
+
# Loop through segments to set touchdown conditions at rhs
|
|
194
|
+
for i in range(nS):
|
|
195
|
+
# Length, foundation and position of segment i
|
|
196
|
+
length, has_foundation, pos = li[i], ki[i], pi[i]
|
|
197
|
+
# Set displacement BC in stage B
|
|
198
|
+
if not has_foundation and bool(touchdown_mode in ["B_point_contact"]):
|
|
199
|
+
if i == 0:
|
|
200
|
+
rhs[:3] = np.vstack([0, 0, scenario.crack_h])
|
|
201
|
+
if i == (nS - 1):
|
|
202
|
+
rhs[-3:] = np.vstack([0, 0, scenario.crack_h])
|
|
203
|
+
# Set normal force and displacement BC for stage C
|
|
204
|
+
if not has_foundation and bool(touchdown_mode in ["C_in_contact"]):
|
|
205
|
+
N = scenario.qt * (scenario.cut_length - touchdown_distance)
|
|
206
|
+
if i == 0:
|
|
207
|
+
rhs[:3] = np.vstack([-N, 0, scenario.crack_h])
|
|
208
|
+
if i == (nS - 1):
|
|
209
|
+
rhs[-3:] = np.vstack([N, 0, scenario.crack_h])
|
|
210
|
+
|
|
211
|
+
# Rhs for substitute spring stiffness
|
|
212
|
+
if system_type in ["rot"]:
|
|
213
|
+
# apply arbitrary moment of 1 at left boundary
|
|
214
|
+
rhs = rhs * 0
|
|
215
|
+
rhs[1] = 1
|
|
216
|
+
if system_type in ["trans"]:
|
|
217
|
+
# apply arbitrary force of 1 at left boundary
|
|
218
|
+
rhs = rhs * 0
|
|
219
|
+
rhs[2] = 1
|
|
220
|
+
|
|
221
|
+
# Solve z0 = Zh0*C + Zp0 = rhs for constants, i.e. Zh0*C = rhs - Zp0
|
|
222
|
+
try:
|
|
223
|
+
C = np.linalg.solve(Zh0, rhs - Zp0)
|
|
224
|
+
except LinAlgError as e:
|
|
225
|
+
zh_shape = Zh0.shape
|
|
226
|
+
rhs_shape = rhs.shape
|
|
227
|
+
zp_shape = Zp0.shape
|
|
228
|
+
rank = int(np.linalg.matrix_rank(Zh0))
|
|
229
|
+
min_dim = min(zh_shape)
|
|
230
|
+
try:
|
|
231
|
+
cond_val = float(np.linalg.cond(Zh0))
|
|
232
|
+
cond_text = f"{cond_val:.3e}"
|
|
233
|
+
except np.linalg.LinAlgError: # Fallback if condition number fails
|
|
234
|
+
cond_val = float("inf")
|
|
235
|
+
cond_text = "inf"
|
|
236
|
+
rank_status = "singular" if rank < min_dim else "full-rank"
|
|
237
|
+
msg_format = (
|
|
238
|
+
"Failed to solve linear system (np.linalg.solve) with diagnostics: "
|
|
239
|
+
"Zh0.shape=%s, rhs.shape=%s, Zp0.shape=%s, "
|
|
240
|
+
"rank(Zh0)=%s/%s (%s), cond(Zh0)=%s. "
|
|
241
|
+
"Original error: %s"
|
|
242
|
+
)
|
|
243
|
+
msg_args = (
|
|
244
|
+
zh_shape,
|
|
245
|
+
rhs_shape,
|
|
246
|
+
zp_shape,
|
|
247
|
+
rank,
|
|
248
|
+
min_dim,
|
|
249
|
+
rank_status,
|
|
250
|
+
cond_text,
|
|
251
|
+
e,
|
|
252
|
+
)
|
|
253
|
+
logger.error(msg_format, *msg_args)
|
|
254
|
+
raise LinAlgError(msg_format % msg_args) from e
|
|
255
|
+
# Sort (nDOF = 6) constants for each segment into columns of a matrix
|
|
256
|
+
return C.reshape([-1, nDOF]).T
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def _setup_conditions(
|
|
260
|
+
cls,
|
|
261
|
+
zl: np.ndarray,
|
|
262
|
+
zr: np.ndarray,
|
|
263
|
+
eigensystem: Eigensystem,
|
|
264
|
+
has_foundation: bool,
|
|
265
|
+
pos: Literal["l", "r", "m", "left", "right", "mid"],
|
|
266
|
+
system_type: SystemType,
|
|
267
|
+
touchdown_mode: Optional[
|
|
268
|
+
Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
|
|
269
|
+
] = None,
|
|
270
|
+
collapsed_weak_layer_kR: Optional[float] = None,
|
|
271
|
+
) -> np.ndarray:
|
|
272
|
+
"""
|
|
273
|
+
Provide boundary or transmission conditions for beam segments.
|
|
274
|
+
|
|
275
|
+
Arguments
|
|
276
|
+
---------
|
|
277
|
+
zl : ndarray
|
|
278
|
+
Solution vector (6x1) or (6x6) at left end of beam segement.
|
|
279
|
+
zr : ndarray
|
|
280
|
+
Solution vector (6x1) or (6x6) at right end of beam segement.
|
|
281
|
+
has_foundation : boolean
|
|
282
|
+
Indicates whether segment has foundation(True) or not (False).
|
|
283
|
+
Default is False.
|
|
284
|
+
pos: {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
|
|
285
|
+
Determines whether the segement under consideration
|
|
286
|
+
is a left boundary segement (left, l), one of the
|
|
287
|
+
center segement (mid, m), or a right boundary
|
|
288
|
+
segement (right, r). Default is 'mid'.
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
conditions : ndarray
|
|
293
|
+
`zh`: Matrix of Size one of: (`l: [9,6], m: [12,6], r: [9,6]`)
|
|
294
|
+
|
|
295
|
+
`zp`: Vector of Size one of: (`l: [9,1], m: [12,1], r: [9,1]`)
|
|
296
|
+
"""
|
|
297
|
+
fq = FieldQuantities(eigensystem=eigensystem)
|
|
298
|
+
if pos in ("l", "left"):
|
|
299
|
+
bcs = cls._boundary_conditions(
|
|
300
|
+
zl,
|
|
301
|
+
eigensystem,
|
|
302
|
+
has_foundation,
|
|
303
|
+
pos,
|
|
304
|
+
system_type,
|
|
305
|
+
touchdown_mode,
|
|
306
|
+
collapsed_weak_layer_kR,
|
|
307
|
+
) # Left boundary condition
|
|
308
|
+
conditions = np.array(
|
|
309
|
+
[
|
|
310
|
+
bcs[0],
|
|
311
|
+
bcs[1],
|
|
312
|
+
bcs[2],
|
|
313
|
+
fq.u(zr, h0=0), # ui(xi = li)
|
|
314
|
+
fq.w(zr), # wi(xi = li)
|
|
315
|
+
fq.psi(zr), # psii(xi = li)
|
|
316
|
+
fq.N(zr), # Ni(xi = li)
|
|
317
|
+
fq.M(zr), # Mi(xi = li)
|
|
318
|
+
fq.V(zr), # Vi(xi = li)
|
|
319
|
+
]
|
|
320
|
+
)
|
|
321
|
+
elif pos in ("m", "mid"):
|
|
322
|
+
conditions = np.array(
|
|
323
|
+
[
|
|
324
|
+
-fq.u(zl, h0=0), # -ui(xi = 0)
|
|
325
|
+
-fq.w(zl), # -wi(xi = 0)
|
|
326
|
+
-fq.psi(zl), # -psii(xi = 0)
|
|
327
|
+
-fq.N(zl), # -Ni(xi = 0)
|
|
328
|
+
-fq.M(zl), # -Mi(xi = 0)
|
|
329
|
+
-fq.V(zl), # -Vi(xi = 0)
|
|
330
|
+
fq.u(zr, h0=0), # ui(xi = li)
|
|
331
|
+
fq.w(zr), # wi(xi = li)
|
|
332
|
+
fq.psi(zr), # psii(xi = li)
|
|
333
|
+
fq.N(zr), # Ni(xi = li)
|
|
334
|
+
fq.M(zr), # Mi(xi = li)
|
|
335
|
+
fq.V(zr), # Vi(xi = li)
|
|
336
|
+
]
|
|
337
|
+
)
|
|
338
|
+
elif pos in ("r", "right"):
|
|
339
|
+
bcs = cls._boundary_conditions(
|
|
340
|
+
zr,
|
|
341
|
+
eigensystem,
|
|
342
|
+
has_foundation,
|
|
343
|
+
pos,
|
|
344
|
+
system_type,
|
|
345
|
+
touchdown_mode,
|
|
346
|
+
collapsed_weak_layer_kR,
|
|
347
|
+
) # Right boundary condition
|
|
348
|
+
conditions = np.array(
|
|
349
|
+
[
|
|
350
|
+
-fq.u(zl, h0=0), # -ui(xi = 0)
|
|
351
|
+
-fq.w(zl), # -wi(xi = 0)
|
|
352
|
+
-fq.psi(zl), # -psii(xi = 0)
|
|
353
|
+
-fq.N(zl), # -Ni(xi = 0)
|
|
354
|
+
-fq.M(zl), # -Mi(xi = 0)
|
|
355
|
+
-fq.V(zl), # -Vi(xi = 0)
|
|
356
|
+
bcs[0],
|
|
357
|
+
bcs[1],
|
|
358
|
+
bcs[2],
|
|
359
|
+
]
|
|
360
|
+
)
|
|
361
|
+
logger.debug("Boundary Conditions at pos %s: %s", pos, conditions.shape) # pylint: disable=E0606
|
|
362
|
+
return conditions
|
|
363
|
+
|
|
364
|
+
@classmethod
|
|
365
|
+
def _boundary_conditions(
|
|
366
|
+
cls,
|
|
367
|
+
z,
|
|
368
|
+
eigensystem: Eigensystem,
|
|
369
|
+
has_foundation: bool,
|
|
370
|
+
pos: Literal["l", "r", "m", "left", "right", "mid"],
|
|
371
|
+
system_type: SystemType,
|
|
372
|
+
touchdown_mode: Optional[
|
|
373
|
+
Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
|
|
374
|
+
] = None,
|
|
375
|
+
collapsed_weak_layer_kR: Optional[float] = None,
|
|
376
|
+
):
|
|
377
|
+
"""
|
|
378
|
+
Provide equations for free (pst) or infinite (skiers) ends.
|
|
379
|
+
|
|
380
|
+
Arguments
|
|
381
|
+
---------
|
|
382
|
+
z : ndarray
|
|
383
|
+
Solution vector (6x1) at a certain position x.
|
|
384
|
+
l : float, optional
|
|
385
|
+
Length of the segment in consideration. Default is zero.
|
|
386
|
+
has_foundation : boolean
|
|
387
|
+
Indicates whether segment has foundation(True) or not (False).
|
|
388
|
+
Default is False.
|
|
389
|
+
pos : {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
|
|
390
|
+
Determines whether the segement under consideration
|
|
391
|
+
is a left boundary segement (left, l), one of the
|
|
392
|
+
center segement (mid, m), or a right boundary
|
|
393
|
+
segement (right, r). Default is 'mid'.
|
|
394
|
+
|
|
395
|
+
Returns
|
|
396
|
+
-------
|
|
397
|
+
bc : ndarray
|
|
398
|
+
Boundary condition vector (lenght 3) at position x.
|
|
399
|
+
"""
|
|
400
|
+
fq = FieldQuantities(eigensystem=eigensystem)
|
|
401
|
+
# Set boundary conditions for PST-systems
|
|
402
|
+
if system_type in ["pst-", "-pst"]:
|
|
403
|
+
if not has_foundation:
|
|
404
|
+
if touchdown_mode in ["A_free_hanging"]:
|
|
405
|
+
# Free end
|
|
406
|
+
bc = np.array([fq.N(z), fq.M(z), fq.V(z)])
|
|
407
|
+
elif touchdown_mode in ["B_point_contact"] and pos in ["r", "right"]:
|
|
408
|
+
# Touchdown right
|
|
409
|
+
bc = np.array([fq.N(z), fq.M(z), fq.w(z)])
|
|
410
|
+
elif touchdown_mode in ["B_point_contact"] and pos in ["l", "left"]:
|
|
411
|
+
# Touchdown left
|
|
412
|
+
bc = np.array([fq.N(z), fq.M(z), fq.w(z)])
|
|
413
|
+
elif touchdown_mode in ["C_in_contact"] and pos in ["r", "right"]:
|
|
414
|
+
# Spring stiffness
|
|
415
|
+
kR = collapsed_weak_layer_kR
|
|
416
|
+
# Touchdown right
|
|
417
|
+
bc = np.array([fq.N(z), fq.M(z) + kR * fq.psi(z), fq.w(z)])
|
|
418
|
+
elif touchdown_mode in ["C_in_contact"] and pos in ["l", "left"]:
|
|
419
|
+
# Spring stiffness
|
|
420
|
+
kR = collapsed_weak_layer_kR
|
|
421
|
+
# Touchdown left
|
|
422
|
+
bc = np.array([fq.N(z), fq.M(z) - kR * fq.psi(z), fq.w(z)])
|
|
423
|
+
else:
|
|
424
|
+
# Touchdown not enabled
|
|
425
|
+
bc = np.array([fq.N(z), fq.M(z), fq.V(z)])
|
|
426
|
+
else:
|
|
427
|
+
# Free end
|
|
428
|
+
bc = np.array([fq.N(z), fq.M(z), fq.V(z)])
|
|
429
|
+
# Set boundary conditions for PST-systems with vertical faces
|
|
430
|
+
elif system_type in ["-vpst", "vpst-"]:
|
|
431
|
+
bc = np.array([fq.N(z), fq.M(z), fq.V(z)])
|
|
432
|
+
# Set boundary conditions for SKIER-systems
|
|
433
|
+
elif system_type in ["skier", "skiers"]:
|
|
434
|
+
# Infinite end (vanishing complementary solution)
|
|
435
|
+
bc = np.array([fq.u(z, h0=0), fq.w(z), fq.psi(z)])
|
|
436
|
+
# Set boundary conditions for substitute spring calculus
|
|
437
|
+
elif system_type in ["rot", "trans"]:
|
|
438
|
+
bc = np.array([fq.N(z), fq.M(z), fq.V(z)])
|
|
439
|
+
else:
|
|
440
|
+
raise ValueError(
|
|
441
|
+
f"Boundary conditions not defined for system of type {system_type}."
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
return bc
|
weac/logging_config.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging configuration for weak layer anticrack nucleation model.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from logging.config import dictConfig
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_logging(level: Optional[str] = None) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Initialise the global logging configuration exactly once.
|
|
13
|
+
The level is taken from the env var WEAC_LOG_LEVEL (default WARNING).
|
|
14
|
+
"""
|
|
15
|
+
if level is None:
|
|
16
|
+
level = os.getenv("WEAC_LOG_LEVEL", "WARNING").upper()
|
|
17
|
+
|
|
18
|
+
dictConfig(
|
|
19
|
+
{
|
|
20
|
+
"version": 1,
|
|
21
|
+
"disable_existing_loggers": False, # keep third-party loggers alive
|
|
22
|
+
"formatters": {
|
|
23
|
+
"console": {
|
|
24
|
+
"format": "%(asctime)s | %(levelname)-8s | %(name)s: %(message)s",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
"handlers": {
|
|
28
|
+
"console": {
|
|
29
|
+
"class": "logging.StreamHandler",
|
|
30
|
+
"formatter": "console",
|
|
31
|
+
"level": level,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"root": { # applies to *all* loggers
|
|
35
|
+
"handlers": ["console"],
|
|
36
|
+
"level": level,
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
)
|
weac/utils/__init__.py
ADDED
|
File without changes
|
weac/utils/geldsetzer.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hand hardness + Grain Type Parameterization to Density
|
|
3
|
+
according to Geldsetzer & Jamieson (2000)
|
|
4
|
+
`https://arc.lib.montana.edu/snow-science/objects/issw-2000-121-127.pdf`
|
|
5
|
+
|
|
6
|
+
Inputs:
|
|
7
|
+
Hand Hardness + Grain Type
|
|
8
|
+
Output:
|
|
9
|
+
Density [kg/m^3]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
SKIP_VALUE = "!skip"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DENSITY_PARAMETERS = {
|
|
16
|
+
SKIP_VALUE: (0, 0),
|
|
17
|
+
"SH": (125, 0), # 125 kg/m^3 so that bergfeld is E~1.0
|
|
18
|
+
"PP": (45, 36),
|
|
19
|
+
"PPgp": (83, 37),
|
|
20
|
+
"DF": (65, 36),
|
|
21
|
+
"FCmx": (56, 64),
|
|
22
|
+
"FC": (112, 46),
|
|
23
|
+
"DH": (185, 25),
|
|
24
|
+
"RGmx": (91, 42),
|
|
25
|
+
"RG": (154, 1.51),
|
|
26
|
+
"MFCr": (292.25, 0),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Map SnowPilot grain type to those we know
|
|
30
|
+
GRAIN_TYPE = {
|
|
31
|
+
"": SKIP_VALUE,
|
|
32
|
+
"DF": "DF",
|
|
33
|
+
"DFbk": "DF",
|
|
34
|
+
"DFdc": "DF",
|
|
35
|
+
"DH": "DH",
|
|
36
|
+
"DHch": "DH",
|
|
37
|
+
"DHcp": "DH",
|
|
38
|
+
"DHla": "DH",
|
|
39
|
+
"DHpr": "DH",
|
|
40
|
+
"DHxr": "DH",
|
|
41
|
+
"FC": "FC",
|
|
42
|
+
"FCsf": "FCmx",
|
|
43
|
+
"FCso": "FCmx",
|
|
44
|
+
"FCxr": "FCmx",
|
|
45
|
+
"IF": "MFCr",
|
|
46
|
+
"IFbi": "MFCr",
|
|
47
|
+
"IFic": "MFCr",
|
|
48
|
+
"IFil": "MFCr",
|
|
49
|
+
"IFrc": "MFCr",
|
|
50
|
+
"IFsc": "MFCr",
|
|
51
|
+
"MF": "MFCr",
|
|
52
|
+
"MFcl": "MFCr",
|
|
53
|
+
"MFcr": "MFCr",
|
|
54
|
+
"MFpc": "MFCr",
|
|
55
|
+
"MFsl": "MFCr",
|
|
56
|
+
"PP": "PP",
|
|
57
|
+
"PPco": "PP",
|
|
58
|
+
"PPgp": "PPgp",
|
|
59
|
+
"gp": "PPgp",
|
|
60
|
+
"PPhl": "PP",
|
|
61
|
+
"PPip": "PP",
|
|
62
|
+
"PPir": "PP",
|
|
63
|
+
"PPnd": "PP",
|
|
64
|
+
"PPpl": "PP",
|
|
65
|
+
"PPrm": "PP",
|
|
66
|
+
"PPsd": "PP",
|
|
67
|
+
"RG": "RG",
|
|
68
|
+
"RGlr": "RGmx",
|
|
69
|
+
"RGsr": "RGmx",
|
|
70
|
+
"RGwp": "RGmx",
|
|
71
|
+
"RGxf": "RGmx",
|
|
72
|
+
"SH": "SH",
|
|
73
|
+
"SHcv": "SH",
|
|
74
|
+
"SHsu": "SH",
|
|
75
|
+
"SHxr": "SH",
|
|
76
|
+
"WG": "WG",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Translate hand hardness to numerical values
|
|
80
|
+
HAND_HARDNESS = {
|
|
81
|
+
"": SKIP_VALUE,
|
|
82
|
+
"F-": 0.67,
|
|
83
|
+
"F": 1,
|
|
84
|
+
"F+": 1.33,
|
|
85
|
+
"4F-": 1.67,
|
|
86
|
+
"4F": 2,
|
|
87
|
+
"4F+": 2.33,
|
|
88
|
+
"1F-": 2.67,
|
|
89
|
+
"1F": 3,
|
|
90
|
+
"1F+": 3.33,
|
|
91
|
+
"P-": 3.67,
|
|
92
|
+
"P": 4,
|
|
93
|
+
"P+": 4.33,
|
|
94
|
+
"K-": 4.67,
|
|
95
|
+
"K": 5,
|
|
96
|
+
"K+": 5.33,
|
|
97
|
+
"I-": 5.67,
|
|
98
|
+
"I": 6,
|
|
99
|
+
"I+": 6.33,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
GRAIN_TYPE_TO_DENSITY = {
|
|
103
|
+
"PP": 84.9,
|
|
104
|
+
"PPgp": 162.3,
|
|
105
|
+
"DF": 136.3,
|
|
106
|
+
"RG": 247.4,
|
|
107
|
+
"RGmx": 220.6,
|
|
108
|
+
"FC": 248.2,
|
|
109
|
+
"FCmx": 288.8,
|
|
110
|
+
"DH": 252.8,
|
|
111
|
+
"WG": 254.3,
|
|
112
|
+
"MFCr": 292.3,
|
|
113
|
+
"SH": 125,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
HAND_HARDNESS_TO_DENSITY = {
|
|
117
|
+
"F-": 71.7,
|
|
118
|
+
"F": 103.7,
|
|
119
|
+
"F+": 118.4,
|
|
120
|
+
"4F-": 127.9,
|
|
121
|
+
"4F": 158.2,
|
|
122
|
+
"4F+": 163.7,
|
|
123
|
+
"1F-": 188.6,
|
|
124
|
+
"1F": 208,
|
|
125
|
+
"1F+": 224.4,
|
|
126
|
+
"P-": 252.8,
|
|
127
|
+
"P": 275.9,
|
|
128
|
+
"P+": 314.6,
|
|
129
|
+
"K-": 359.1,
|
|
130
|
+
"K": 347.4,
|
|
131
|
+
"K+": 407.8,
|
|
132
|
+
"I-": 407.8,
|
|
133
|
+
"I": 407.8,
|
|
134
|
+
"I+": 407.8,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def compute_density(grainform: str | None, hardness: str | None) -> float:
|
|
139
|
+
"""
|
|
140
|
+
Geldsetzer & Jamieson (2000)
|
|
141
|
+
`https://arc.lib.montana.edu/snow-science/objects/issw-2000-121-127.pdf`
|
|
142
|
+
"""
|
|
143
|
+
# Adaptation based on CAAML profiles (which sometimes provide top and bottom hardness)
|
|
144
|
+
if hardness is None and grainform is None:
|
|
145
|
+
raise ValueError("Provide at least one of grainform or hardness")
|
|
146
|
+
if hardness is None:
|
|
147
|
+
grain_type = GRAIN_TYPE[grainform]
|
|
148
|
+
return GRAIN_TYPE_TO_DENSITY[grain_type]
|
|
149
|
+
if grainform is None:
|
|
150
|
+
return HAND_HARDNESS_TO_DENSITY[hardness]
|
|
151
|
+
|
|
152
|
+
hardness_value = HAND_HARDNESS[hardness]
|
|
153
|
+
grain_type = GRAIN_TYPE[grainform]
|
|
154
|
+
a, b = DENSITY_PARAMETERS[grain_type]
|
|
155
|
+
|
|
156
|
+
if grain_type == SKIP_VALUE:
|
|
157
|
+
raise ValueError(f"Grain type is {SKIP_VALUE}")
|
|
158
|
+
if hardness_value == SKIP_VALUE:
|
|
159
|
+
raise ValueError(f"Hardness value is {SKIP_VALUE}")
|
|
160
|
+
|
|
161
|
+
if grain_type == "RG":
|
|
162
|
+
# Special computation for 'RG' grain form
|
|
163
|
+
rho = a + b * (hardness_value**3.15)
|
|
164
|
+
else:
|
|
165
|
+
rho = a + b * hardness_value
|
|
166
|
+
return rho
|