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
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the system model for the WEAC simulation.
|
|
3
|
+
The system model is the heart of the WEAC simulation. All data sources
|
|
4
|
+
are bundled into the system model. The system model initializes and
|
|
5
|
+
calculates all the parameterizations and passes relevant data to the
|
|
6
|
+
different components.
|
|
7
|
+
|
|
8
|
+
We utilize the pydantic library to define the system model.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import copy
|
|
12
|
+
import logging
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
from functools import cached_property
|
|
15
|
+
from typing import List, Optional, Union
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
# from weac.constants import G_MM_S2, LSKI_MM
|
|
20
|
+
from weac.components import (
|
|
21
|
+
Config,
|
|
22
|
+
Layer,
|
|
23
|
+
ModelInput,
|
|
24
|
+
ScenarioConfig,
|
|
25
|
+
Segment,
|
|
26
|
+
WeakLayer,
|
|
27
|
+
)
|
|
28
|
+
from weac.core.eigensystem import Eigensystem
|
|
29
|
+
from weac.core.field_quantities import FieldQuantities
|
|
30
|
+
from weac.core.scenario import Scenario
|
|
31
|
+
from weac.core.slab import Slab
|
|
32
|
+
from weac.core.slab_touchdown import SlabTouchdown
|
|
33
|
+
from weac.core.unknown_constants_solver import UnknownConstantsSolver
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SystemModel:
|
|
39
|
+
"""
|
|
40
|
+
The heart of the WEAC simulation system for avalanche release modeling.
|
|
41
|
+
|
|
42
|
+
This class orchestrates all components of the WEAC simulation, including slab mechanics,
|
|
43
|
+
weak layer properties, touchdown calculations, and the solution of unknown constants
|
|
44
|
+
for beam-on-elastic-foundation problems.
|
|
45
|
+
|
|
46
|
+
The SystemModel follows a lazy evaluation pattern using cached properties, meaning
|
|
47
|
+
expensive calculations (eigensystem, touchdown, unknown constants) are only computed
|
|
48
|
+
when first accessed and then cached for subsequent use.
|
|
49
|
+
|
|
50
|
+
**Extracting Unknown Constants:**
|
|
51
|
+
|
|
52
|
+
The primary output of the SystemModel is the `unknown_constants` matrix, which contains
|
|
53
|
+
the solution constants for the beam segments:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
# Basic usage
|
|
57
|
+
system = SystemModel(model_input=model_input, config=config)
|
|
58
|
+
constants = system.unknown_constants # Shape: (6, N) where N = number of segments
|
|
59
|
+
|
|
60
|
+
# Each column represents the 6 constants for one segment:
|
|
61
|
+
# constants[:, i] = [C1, C2, C3, C4, C5, C6] for segment i
|
|
62
|
+
# These constants define the beam deflection solution within that segment
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Calculation Flow:**
|
|
66
|
+
|
|
67
|
+
1. **Eigensystem**: Computes eigenvalues/eigenvectors for the beam-foundation system
|
|
68
|
+
2. **Slab Touchdown** (if enabled): Calculates touchdown behavior and updates segment lengths
|
|
69
|
+
3. **Unknown Constants**: Solves the linear system for beam deflection constants
|
|
70
|
+
|
|
71
|
+
**Touchdown Behavior:**
|
|
72
|
+
|
|
73
|
+
When `config.touchdown=True`, the system automatically:
|
|
74
|
+
- Calculates touchdown mode (A: free-hanging, B: point contact, C: in contact)
|
|
75
|
+
- Determines touchdown length based on slab-foundation interaction
|
|
76
|
+
- **Redefines scenario segments** to use touchdown length instead of crack length
|
|
77
|
+
- This matches the behavior of the original WEAC implementation
|
|
78
|
+
|
|
79
|
+
**Performance Notes:**
|
|
80
|
+
|
|
81
|
+
- First access to `unknown_constants` triggers all necessary calculations
|
|
82
|
+
- Subsequent accesses return cached results instantly
|
|
83
|
+
- Use `update_*` methods to modify parameters and invalidate caches as needed
|
|
84
|
+
|
|
85
|
+
**Example Usage:**
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from weac.components import ModelInput, Layer, Segment, Config
|
|
89
|
+
from weac.core.system_model import SystemModel
|
|
90
|
+
|
|
91
|
+
# Define system components
|
|
92
|
+
layers = [Layer(rho=200, h=150), Layer(rho=300, h=100)]
|
|
93
|
+
segments = [Segment(length=10000, has_foundation=True, m=0),
|
|
94
|
+
Segment(length=4000, has_foundation=False, m=0)]
|
|
95
|
+
|
|
96
|
+
# Create system
|
|
97
|
+
system = SystemModel(model_input=model_input, config=Config(touchdown=True))
|
|
98
|
+
|
|
99
|
+
# Solve system and extract results
|
|
100
|
+
constants = system.unknown_constants # Solution constants (6 x N_segments)
|
|
101
|
+
touchdown_info = system.slab_touchdown # Touchdown analysis (if enabled)
|
|
102
|
+
eigensystem = system.eigensystem # Eigenvalue problem solution
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Attributes:
|
|
106
|
+
config: Configuration settings including touchdown enable/disable
|
|
107
|
+
slab: Slab properties (thickness, material properties per layer)
|
|
108
|
+
weak_layer: Weak layer properties (stiffness, thickness, etc.)
|
|
109
|
+
scenario: Scenario definition (segments, loads, boundary conditions)
|
|
110
|
+
eigensystem: Eigenvalue problem solution (computed lazily)
|
|
111
|
+
slab_touchdown: Touchdown analysis results (computed lazily if enabled)
|
|
112
|
+
unknown_constants: Solution constants matrix (computed lazily)
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
config: Config
|
|
116
|
+
slab: Slab
|
|
117
|
+
weak_layer: WeakLayer
|
|
118
|
+
eigensystem: Eigensystem
|
|
119
|
+
fq: FieldQuantities
|
|
120
|
+
|
|
121
|
+
scenario: Scenario
|
|
122
|
+
slab_touchdown: Optional[SlabTouchdown]
|
|
123
|
+
unknown_constants: np.ndarray
|
|
124
|
+
uncracked_unknown_constants: np.ndarray
|
|
125
|
+
|
|
126
|
+
def __init__(self, model_input: ModelInput, config: Optional[Config] = None):
|
|
127
|
+
if config is None:
|
|
128
|
+
config = Config()
|
|
129
|
+
self.config = config
|
|
130
|
+
self.weak_layer = model_input.weak_layer
|
|
131
|
+
self.slab = Slab(layers=model_input.layers)
|
|
132
|
+
self.scenario = Scenario(
|
|
133
|
+
scenario_config=model_input.scenario_config,
|
|
134
|
+
segments=model_input.segments,
|
|
135
|
+
weak_layer=self.weak_layer,
|
|
136
|
+
slab=self.slab,
|
|
137
|
+
)
|
|
138
|
+
logger.info("Scenario setup")
|
|
139
|
+
|
|
140
|
+
# At this point only the system is initialized
|
|
141
|
+
# The solution to the system (unknown_constants) are only computed
|
|
142
|
+
# when required by the user (at runtime)
|
|
143
|
+
|
|
144
|
+
# Cached properties are invalidated via __dict__.pop in the *invalidate_* helpers.
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def fq(self) -> FieldQuantities:
|
|
148
|
+
"""Compute the field quantities."""
|
|
149
|
+
return FieldQuantities(eigensystem=self.eigensystem)
|
|
150
|
+
|
|
151
|
+
@cached_property
|
|
152
|
+
def eigensystem(self) -> Eigensystem: # heavy
|
|
153
|
+
"""Solve for the eigensystem."""
|
|
154
|
+
logger.info("Solving for Eigensystem")
|
|
155
|
+
return Eigensystem(weak_layer=self.weak_layer, slab=self.slab)
|
|
156
|
+
|
|
157
|
+
@cached_property
|
|
158
|
+
def slab_touchdown(self) -> Optional[SlabTouchdown]:
|
|
159
|
+
"""
|
|
160
|
+
Solve for the slab touchdown.
|
|
161
|
+
Modifies the scenario object in place by replacing the undercut segment
|
|
162
|
+
with a new segment of length equal to the touchdown distance if the system is
|
|
163
|
+
a PST or VPST.
|
|
164
|
+
"""
|
|
165
|
+
if self.config.touchdown:
|
|
166
|
+
logger.info("Solving for Slab Touchdown")
|
|
167
|
+
slab_touchdown = SlabTouchdown(
|
|
168
|
+
scenario=self.scenario, eigensystem=self.eigensystem
|
|
169
|
+
)
|
|
170
|
+
logger.info(
|
|
171
|
+
"Original cut_length: %s, touchdown_distance: %s",
|
|
172
|
+
self.scenario.cut_length,
|
|
173
|
+
slab_touchdown.touchdown_distance,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
new_segments = copy.deepcopy(self.scenario.segments)
|
|
177
|
+
if self.scenario.system_type in ("pst-", "vpst-"):
|
|
178
|
+
new_segments[-1].length = slab_touchdown.touchdown_distance
|
|
179
|
+
elif self.scenario.system_type in ("-pst", "-vpst"):
|
|
180
|
+
new_segments[0].length = slab_touchdown.touchdown_distance
|
|
181
|
+
|
|
182
|
+
# Create new scenario with updated segments
|
|
183
|
+
self.scenario = Scenario(
|
|
184
|
+
scenario_config=self.scenario.scenario_config,
|
|
185
|
+
segments=new_segments,
|
|
186
|
+
weak_layer=self.weak_layer,
|
|
187
|
+
slab=self.slab,
|
|
188
|
+
)
|
|
189
|
+
logger.info(
|
|
190
|
+
"Updated scenario with new segment lengths: %s",
|
|
191
|
+
[seg.length for seg in new_segments],
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return slab_touchdown
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
@cached_property
|
|
198
|
+
def unknown_constants(self) -> np.ndarray:
|
|
199
|
+
"""
|
|
200
|
+
Solve for the unknown constants matrix defining beam deflection in each segment.
|
|
201
|
+
|
|
202
|
+
This is the core solution of the WEAC beam-on-elastic-foundation problem.
|
|
203
|
+
The unknown constants define the deflection, slope, moment, and shear force
|
|
204
|
+
distributions within each beam segment.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
--------
|
|
208
|
+
np.ndarray: Solution constants matrix of shape (6, N_segments)
|
|
209
|
+
Each column contains the 6 constants for one segment:
|
|
210
|
+
[C1, C2, C3, C4, C5, C6]
|
|
211
|
+
|
|
212
|
+
These constants are used in the general solution:
|
|
213
|
+
u(x) = Σ Ci * φi(x) + up(x)
|
|
214
|
+
|
|
215
|
+
Where φi(x) are the homogeneous solutions and up(x)
|
|
216
|
+
is the particular solution.
|
|
217
|
+
|
|
218
|
+
Notes:
|
|
219
|
+
- For touchdown systems, segment lengths are automatically adjusted
|
|
220
|
+
based on touchdown calculations before solving
|
|
221
|
+
- The solution accounts for boundary conditions, load transmission
|
|
222
|
+
between segments, and foundation support
|
|
223
|
+
- Results are cached after first computation for performance
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
```python
|
|
227
|
+
system = SystemModel(model_input, config)
|
|
228
|
+
C = system.unknown_constants # Shape: (6, 2) for 2-segment system
|
|
229
|
+
|
|
230
|
+
# Constants for first segment
|
|
231
|
+
segment_0_constants = C[:, 0]
|
|
232
|
+
|
|
233
|
+
# Use with eigensystem to compute field quantities
|
|
234
|
+
x = 1000 # Position in mm
|
|
235
|
+
segment_length = system.scenario.li[0]
|
|
236
|
+
```
|
|
237
|
+
"""
|
|
238
|
+
if self.slab_touchdown is not None:
|
|
239
|
+
logger.info("Solving for Unknown Constants")
|
|
240
|
+
return UnknownConstantsSolver.solve_for_unknown_constants(
|
|
241
|
+
scenario=self.scenario,
|
|
242
|
+
eigensystem=self.eigensystem,
|
|
243
|
+
system_type=self.scenario.system_type,
|
|
244
|
+
touchdown_distance=self.slab_touchdown.touchdown_distance,
|
|
245
|
+
touchdown_mode=self.slab_touchdown.touchdown_mode,
|
|
246
|
+
collapsed_weak_layer_kR=self.slab_touchdown.collapsed_weak_layer_kR,
|
|
247
|
+
)
|
|
248
|
+
logger.info("Solving for Unknown Constants")
|
|
249
|
+
return UnknownConstantsSolver.solve_for_unknown_constants(
|
|
250
|
+
scenario=self.scenario,
|
|
251
|
+
eigensystem=self.eigensystem,
|
|
252
|
+
system_type=self.scenario.system_type,
|
|
253
|
+
touchdown_distance=None,
|
|
254
|
+
touchdown_mode=None,
|
|
255
|
+
collapsed_weak_layer_kR=None,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
@cached_property
|
|
259
|
+
def uncracked_unknown_constants(self) -> np.ndarray:
|
|
260
|
+
"""
|
|
261
|
+
Solve for the uncracked unknown constants.
|
|
262
|
+
This is the solution for the case where the slab is cracked nowhere.
|
|
263
|
+
"""
|
|
264
|
+
new_segments = copy.deepcopy(self.scenario.segments)
|
|
265
|
+
for _, seg in enumerate(new_segments):
|
|
266
|
+
seg.has_foundation = True
|
|
267
|
+
uncracked_scenario = Scenario(
|
|
268
|
+
scenario_config=self.scenario.scenario_config,
|
|
269
|
+
segments=new_segments,
|
|
270
|
+
weak_layer=self.weak_layer,
|
|
271
|
+
slab=self.slab,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
logger.info("Solving for Uncracked Unknown Constants")
|
|
275
|
+
if self.slab_touchdown is not None:
|
|
276
|
+
return UnknownConstantsSolver.solve_for_unknown_constants(
|
|
277
|
+
scenario=uncracked_scenario,
|
|
278
|
+
eigensystem=self.eigensystem,
|
|
279
|
+
system_type=self.scenario.system_type,
|
|
280
|
+
touchdown_distance=self.slab_touchdown.touchdown_distance,
|
|
281
|
+
touchdown_mode=self.slab_touchdown.touchdown_mode,
|
|
282
|
+
collapsed_weak_layer_kR=self.slab_touchdown.collapsed_weak_layer_kR,
|
|
283
|
+
)
|
|
284
|
+
return UnknownConstantsSolver.solve_for_unknown_constants(
|
|
285
|
+
scenario=uncracked_scenario,
|
|
286
|
+
eigensystem=self.eigensystem,
|
|
287
|
+
system_type=self.scenario.system_type,
|
|
288
|
+
touchdown_distance=None,
|
|
289
|
+
touchdown_mode=None,
|
|
290
|
+
collapsed_weak_layer_kR=None,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Changes that affect the *weak layer* -> rebuild everything
|
|
294
|
+
def update_weak_layer(self, weak_layer: WeakLayer):
|
|
295
|
+
"""Update the weak layer."""
|
|
296
|
+
self.weak_layer = weak_layer
|
|
297
|
+
self.scenario = Scenario(
|
|
298
|
+
scenario_config=self.scenario.scenario_config,
|
|
299
|
+
segments=self.scenario.segments,
|
|
300
|
+
weak_layer=weak_layer,
|
|
301
|
+
slab=self.slab,
|
|
302
|
+
)
|
|
303
|
+
self._invalidate_eigensystem()
|
|
304
|
+
|
|
305
|
+
# Changes that affect the *slab* -> rebuild everything
|
|
306
|
+
def update_layers(self, new_layers: List[Layer]):
|
|
307
|
+
"""Update the layers."""
|
|
308
|
+
slab = Slab(layers=new_layers)
|
|
309
|
+
self.slab = slab
|
|
310
|
+
self.scenario = Scenario(
|
|
311
|
+
scenario_config=self.scenario.scenario_config,
|
|
312
|
+
segments=self.scenario.segments,
|
|
313
|
+
weak_layer=self.weak_layer,
|
|
314
|
+
slab=slab,
|
|
315
|
+
)
|
|
316
|
+
self._invalidate_eigensystem()
|
|
317
|
+
|
|
318
|
+
# Changes that affect the *scenario* -> only rebuild C constants
|
|
319
|
+
def update_scenario(
|
|
320
|
+
self,
|
|
321
|
+
segments: Optional[List[Segment]] = None,
|
|
322
|
+
scenario_config: Optional[ScenarioConfig] = None,
|
|
323
|
+
):
|
|
324
|
+
"""
|
|
325
|
+
Update fields on `scenario_config` (if present) or on the
|
|
326
|
+
Scenario object itself, then refresh and invalidate constants.
|
|
327
|
+
"""
|
|
328
|
+
logger.debug("Updating Scenario...")
|
|
329
|
+
if segments is None:
|
|
330
|
+
segments = self.scenario.segments
|
|
331
|
+
if scenario_config is None:
|
|
332
|
+
scenario_config = self.scenario.scenario_config
|
|
333
|
+
self.scenario = Scenario(
|
|
334
|
+
scenario_config=scenario_config,
|
|
335
|
+
segments=segments,
|
|
336
|
+
weak_layer=self.weak_layer,
|
|
337
|
+
slab=self.slab,
|
|
338
|
+
)
|
|
339
|
+
if self.config.touchdown:
|
|
340
|
+
self._invalidate_slab_touchdown()
|
|
341
|
+
self._invalidate_constants()
|
|
342
|
+
|
|
343
|
+
def toggle_touchdown(self, touchdown: bool):
|
|
344
|
+
"""Toggle the touchdown."""
|
|
345
|
+
if self.config.touchdown != touchdown:
|
|
346
|
+
self.config.touchdown = touchdown
|
|
347
|
+
self._invalidate_slab_touchdown()
|
|
348
|
+
self._invalidate_constants()
|
|
349
|
+
|
|
350
|
+
def _invalidate_eigensystem(self):
|
|
351
|
+
"""Invalidate the eigensystem."""
|
|
352
|
+
self.__dict__.pop("eigensystem", None)
|
|
353
|
+
self.__dict__.pop("slab_touchdown", None)
|
|
354
|
+
self.__dict__.pop("fq", None)
|
|
355
|
+
self._invalidate_constants()
|
|
356
|
+
|
|
357
|
+
def _invalidate_slab_touchdown(self):
|
|
358
|
+
"""Invalidate the slab touchdown."""
|
|
359
|
+
self.__dict__.pop("slab_touchdown", None)
|
|
360
|
+
|
|
361
|
+
def _invalidate_constants(self):
|
|
362
|
+
"""Invalidate the constants."""
|
|
363
|
+
self.__dict__.pop("unknown_constants", None)
|
|
364
|
+
self.__dict__.pop("uncracked_unknown_constants", None)
|
|
365
|
+
|
|
366
|
+
def z(
|
|
367
|
+
self,
|
|
368
|
+
x: Union[float, Sequence[float], np.ndarray],
|
|
369
|
+
C: np.ndarray,
|
|
370
|
+
length: float,
|
|
371
|
+
phi: float,
|
|
372
|
+
has_foundation: bool = True,
|
|
373
|
+
qs: float = 0,
|
|
374
|
+
) -> np.ndarray:
|
|
375
|
+
"""
|
|
376
|
+
Assemble solution vector at positions x.
|
|
377
|
+
|
|
378
|
+
Arguments
|
|
379
|
+
---------
|
|
380
|
+
x : float or sequence
|
|
381
|
+
Horizontal coordinate (mm). Can be sequence of length N.
|
|
382
|
+
C : ndarray
|
|
383
|
+
Vector of constants (6xN) at positions x.
|
|
384
|
+
length : float
|
|
385
|
+
Segment length (mm).
|
|
386
|
+
phi : float
|
|
387
|
+
Inclination (degrees).
|
|
388
|
+
has_foundation : bool
|
|
389
|
+
Indicates whether segment has foundation (True) or not
|
|
390
|
+
(False). Default is True.
|
|
391
|
+
qs : float
|
|
392
|
+
Surface Load [N/mm]
|
|
393
|
+
|
|
394
|
+
Returns
|
|
395
|
+
-------
|
|
396
|
+
z : ndarray
|
|
397
|
+
Solution vector (6xN) at position x.
|
|
398
|
+
"""
|
|
399
|
+
if isinstance(x, (list, tuple, np.ndarray)):
|
|
400
|
+
z = np.concatenate(
|
|
401
|
+
[
|
|
402
|
+
np.dot(self.eigensystem.zh(xi, length, has_foundation), C)
|
|
403
|
+
+ self.eigensystem.zp(xi, phi, has_foundation, qs)
|
|
404
|
+
for xi in x
|
|
405
|
+
],
|
|
406
|
+
axis=1,
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
z = np.dot(
|
|
410
|
+
self.eigensystem.zh(x, length, has_foundation), C
|
|
411
|
+
) + self.eigensystem.zp(x, phi, has_foundation, qs)
|
|
412
|
+
|
|
413
|
+
return z
|