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,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Component Classes for Inputs of the WEAC model.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .config import Config
|
|
6
|
+
from .criteria_config import CriteriaConfig
|
|
7
|
+
from .layer import Layer, WeakLayer
|
|
8
|
+
from .model_input import ModelInput
|
|
9
|
+
from .segment import Segment
|
|
10
|
+
from .scenario_config import ScenarioConfig, SystemType
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Config",
|
|
14
|
+
"WeakLayer",
|
|
15
|
+
"Layer",
|
|
16
|
+
"Segment",
|
|
17
|
+
"CriteriaConfig",
|
|
18
|
+
"ScenarioConfig",
|
|
19
|
+
"ModelInput",
|
|
20
|
+
"SystemType",
|
|
21
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration for the WEAC simulation.
|
|
3
|
+
These settings control runtime parameters for WEAC.
|
|
4
|
+
In general, developers maintain these defaults; end users should see a stable configuration.
|
|
5
|
+
|
|
6
|
+
We utilize the pydantic library to define the configuration.
|
|
7
|
+
|
|
8
|
+
Pydantic syntax is for a field:
|
|
9
|
+
field_name: type = Field(..., gt=0, description="Description")
|
|
10
|
+
- typing, default value, constraints, description
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Config(BaseModel):
|
|
17
|
+
"""
|
|
18
|
+
Configuration for the WEAC simulation.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
touchdown : bool
|
|
23
|
+
Whether slab touchdown on the collapsed weak layer is considered.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
touchdown: bool = Field(
|
|
27
|
+
default=False, description="Whether to include slab touchdown in the analysis"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
config = Config()
|
|
33
|
+
print(config.model_dump_json(indent=2))
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for configuring failure-mode interaction criteria and stress failure envelope selection.
|
|
3
|
+
|
|
4
|
+
Main fields:
|
|
5
|
+
- fn, fm: interaction exponents for normal (sigma) and shear (tau) stresses (> 0).
|
|
6
|
+
- gn, gm: interaction exponents for mode-I (G_I) and mode-II (G_II) energy release rates (> 0).
|
|
7
|
+
- stress_envelope_method: one of
|
|
8
|
+
{"adam_unpublished", "schottner", "mede_s-RG1", "mede_s-RG2", "mede_s-FCDH"}.
|
|
9
|
+
- scaling_factor, order_of_magnitude: positive scalars applied to the stress envelope.
|
|
10
|
+
|
|
11
|
+
Typical usage:
|
|
12
|
+
from weac.components.criteria_config import CriteriaConfig
|
|
13
|
+
|
|
14
|
+
config = CriteriaConfig(
|
|
15
|
+
stress_envelope_method="schottner",
|
|
16
|
+
scaling_factor=1.0,
|
|
17
|
+
order_of_magnitude=1.0,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
See also:
|
|
21
|
+
- weac.analysis.criteria_evaluator for how these parameters influence failure checks.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import Literal
|
|
25
|
+
|
|
26
|
+
from pydantic import BaseModel, Field
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CriteriaConfig(BaseModel):
|
|
30
|
+
"""
|
|
31
|
+
Parameters defining the interaction between different failure modes.
|
|
32
|
+
|
|
33
|
+
Attributes
|
|
34
|
+
----------
|
|
35
|
+
fn : float
|
|
36
|
+
Failure mode interaction exponent for normal stress (sigma). Default is 2.0.
|
|
37
|
+
fm : float
|
|
38
|
+
Failure mode interaction exponent for shear stress (tau). Default is 2.0.
|
|
39
|
+
gn : float
|
|
40
|
+
Failure mode interaction exponent for closing energy release rate (G_I). Default is 5.0.
|
|
41
|
+
gm : float
|
|
42
|
+
Failure mode interaction exponent for shearing energy release rate (G_II). Default is 2.22.
|
|
43
|
+
stress_envelope_method : str
|
|
44
|
+
Method to calculate the stress failure envelope. Default is "adam_unpublished".
|
|
45
|
+
scaling_factor : float
|
|
46
|
+
Scaling factor for stress envelope. Default is 1.0.
|
|
47
|
+
order_of_magnitude : float
|
|
48
|
+
Order of magnitude for stress envelope. Default is 1.0.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
fn: float = Field(
|
|
52
|
+
default=2.0,
|
|
53
|
+
gt=0,
|
|
54
|
+
description="Failure mode interaction exponent for normal stress (sigma)",
|
|
55
|
+
)
|
|
56
|
+
fm: float = Field(
|
|
57
|
+
default=2.0,
|
|
58
|
+
gt=0,
|
|
59
|
+
description="Failure mode interaction exponent for shear stress (tau)",
|
|
60
|
+
)
|
|
61
|
+
gn: float = Field(
|
|
62
|
+
default=1 / 0.2,
|
|
63
|
+
gt=0,
|
|
64
|
+
description="Failure mode interaction exponent for closing energy release rate (G_I)",
|
|
65
|
+
)
|
|
66
|
+
gm: float = Field(
|
|
67
|
+
default=1 / 0.45,
|
|
68
|
+
gt=0,
|
|
69
|
+
description="Failure mode interaction exponent for shearing energy release rate (G_II)",
|
|
70
|
+
)
|
|
71
|
+
stress_envelope_method: Literal[
|
|
72
|
+
"adam_unpublished", "schottner", "mede_s-RG1", "mede_s-RG2", "mede_s-FCDH"
|
|
73
|
+
] = Field(
|
|
74
|
+
default="adam_unpublished",
|
|
75
|
+
description="Method to calculate the stress failure envelope",
|
|
76
|
+
)
|
|
77
|
+
scaling_factor: float = Field(
|
|
78
|
+
default=1,
|
|
79
|
+
gt=0,
|
|
80
|
+
description="Scaling factor for stress envelope",
|
|
81
|
+
)
|
|
82
|
+
order_of_magnitude: float = Field(
|
|
83
|
+
default=1,
|
|
84
|
+
gt=0,
|
|
85
|
+
description="Order of magnitude for stress envelope",
|
|
86
|
+
)
|
weac/components/layer.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mechanical properties of snow-pack layers.
|
|
3
|
+
|
|
4
|
+
* `Layer` - a regular slab layer (no foundation springs)
|
|
5
|
+
* `WeakLayer` - a slab layer that also acts as a Winkler-type foundation
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
12
|
+
|
|
13
|
+
from weac.constants import CB0, CB1, CG0, CG1, NU, RHO_ICE
|
|
14
|
+
from weac.utils.snow_types import GrainType, HandHardness
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _collapse_height(h: float) -> float:
|
|
18
|
+
"""
|
|
19
|
+
Based on data from Herwijnen (van Herwijnen, 2016)
|
|
20
|
+
`Estimating the effective elastic modulus and specific fracture energy of
|
|
21
|
+
snowpack layers from field experiments`
|
|
22
|
+
Data collection 2005 - 2016.
|
|
23
|
+
|
|
24
|
+
Arguments:
|
|
25
|
+
----------
|
|
26
|
+
h : float
|
|
27
|
+
Height/Thickness of the layer [mm].
|
|
28
|
+
"""
|
|
29
|
+
return 4.70 * (1 - np.exp(-h / 7.78))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _bergfeld_youngs_modulus(rho: float, C_0: float = CB0, C_1: float = CB1) -> float:
|
|
33
|
+
"""Young's modulus from Bergfeld et al. (2023) - returns MPa.
|
|
34
|
+
|
|
35
|
+
Arguments
|
|
36
|
+
---------
|
|
37
|
+
rho : float or ndarray
|
|
38
|
+
Density (kg/m^3).
|
|
39
|
+
C0 : float, optional
|
|
40
|
+
Multiplicative constant of Young modulus parametrization
|
|
41
|
+
according to Bergfeld et al. (2023). Default is 6.5.
|
|
42
|
+
C1 : float, optional
|
|
43
|
+
Exponent of Young modulus parameterization according to
|
|
44
|
+
Bergfeld et al. (2023). Default is 4.4.
|
|
45
|
+
"""
|
|
46
|
+
return C_0 * 1e3 * (rho / RHO_ICE) ** C_1
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _scapozza_youngs_modulus(rho: float) -> float:
|
|
50
|
+
"""Young's modulus from Scapozzo et al. (2019) - return MPa
|
|
51
|
+
`rho` in [kg/m^3]"""
|
|
52
|
+
rho = rho * 1e-12 # Convert to [t/mm^3]
|
|
53
|
+
rho_0 = RHO_ICE * 1e-12 # Density of ice in [t/mm^3]
|
|
54
|
+
return 5.07e3 * (rho / rho_0) ** 5.13
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _gerling_youngs_modulus(rho: float, C_0: float = CG0, C_1: float = CG1) -> float:
|
|
58
|
+
"""Young's modulus according to Gerling et al. (2017).
|
|
59
|
+
|
|
60
|
+
Arguments
|
|
61
|
+
---------
|
|
62
|
+
rho : float or ndarray
|
|
63
|
+
Density (kg/m^3).
|
|
64
|
+
C0 : float, optional
|
|
65
|
+
Multiplicative constant of Young modulus parametrization
|
|
66
|
+
according to Gerling et al. (2017). Default is 6.0.
|
|
67
|
+
C1 : float, optional
|
|
68
|
+
Exponent of Young modulus parameterization according to
|
|
69
|
+
Gerling et al. (2017). Default is 4.6.
|
|
70
|
+
"""
|
|
71
|
+
return C_0 * 1e-10 * rho**C_1
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _sigrist_tensile_strength(rho, unit: Literal["kPa", "MPa"] = "kPa"):
|
|
75
|
+
"""
|
|
76
|
+
Estimate the tensile strength of a slab layer from its density.
|
|
77
|
+
|
|
78
|
+
Uses the density parametrization of Sigrist (2006).
|
|
79
|
+
|
|
80
|
+
Arguments
|
|
81
|
+
---------
|
|
82
|
+
rho : ndarray, float
|
|
83
|
+
Layer density (kg/m^3).
|
|
84
|
+
unit : str, optional
|
|
85
|
+
Desired output unit of the layer strength. Default is 'kPa'.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
ndarray
|
|
90
|
+
Tensile strength in specified unit.
|
|
91
|
+
"""
|
|
92
|
+
convert = {"kPa": 1, "MPa": 1e-3}
|
|
93
|
+
# Sigrist's equation is given in kPa
|
|
94
|
+
return convert[unit] * 240 * (rho / RHO_ICE) ** 2.44
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Layer(BaseModel):
|
|
98
|
+
"""
|
|
99
|
+
Regular slab layer (no foundation springs).
|
|
100
|
+
|
|
101
|
+
Attributes
|
|
102
|
+
----------
|
|
103
|
+
rho : float
|
|
104
|
+
Density of the layer [kg m⁻³].
|
|
105
|
+
h : float
|
|
106
|
+
Height/Thickness of the layer [mm].
|
|
107
|
+
nu : float
|
|
108
|
+
Poisson's ratio [-] Defaults to `weac.constants.NU`).
|
|
109
|
+
E : float, optional
|
|
110
|
+
Young's modulus E [MPa]. If omitted it is derived from ``rho``.
|
|
111
|
+
G : float, optional
|
|
112
|
+
Shear modulus G [MPa]. If omitted it is derived from ``E`` and ``nu``.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# has to be provided
|
|
116
|
+
rho: float = Field(default=150, gt=0, description="Density of the Slab [kg m⁻³]")
|
|
117
|
+
h: float = Field(
|
|
118
|
+
default=200, gt=0, description="Height/Thickness of the slab [mm]"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# derived if not provided
|
|
122
|
+
nu: float = Field(default=NU, ge=0, lt=0.5, description="Poisson's ratio [-]")
|
|
123
|
+
E: float = Field(default=0.0, ge=0, description="Young's modulus [MPa]")
|
|
124
|
+
G: float = Field(default=0.0, ge=0, description="Shear modulus [MPa]")
|
|
125
|
+
tensile_strength: float = Field(
|
|
126
|
+
default=0.0, ge=0, description="Tensile strength [kPa]"
|
|
127
|
+
)
|
|
128
|
+
tensile_strength_method: Literal["sigrist"] = Field(
|
|
129
|
+
default="sigrist",
|
|
130
|
+
description="Method to calculate the tensile strength",
|
|
131
|
+
)
|
|
132
|
+
E_method: Literal["bergfeld", "scapazzo", "gerling"] = Field(
|
|
133
|
+
default="bergfeld",
|
|
134
|
+
description="Method to calculate the Young's modulus",
|
|
135
|
+
)
|
|
136
|
+
grain_type: GrainType | None = Field(default=None, description="Grain type")
|
|
137
|
+
grain_size: float | None = Field(default=None, description="Grain size [mm]")
|
|
138
|
+
hand_hardness: HandHardness | None = Field(
|
|
139
|
+
default=None, description="Hand hardness"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def model_post_init(self, _ctx): # pylint: disable=arguments-differ
|
|
143
|
+
if self.E_method == "bergfeld":
|
|
144
|
+
object.__setattr__(self, "E", self.E or _bergfeld_youngs_modulus(self.rho))
|
|
145
|
+
elif self.E_method == "scapazzo":
|
|
146
|
+
object.__setattr__(self, "E", self.E or _scapozza_youngs_modulus(self.rho))
|
|
147
|
+
elif self.E_method == "gerling":
|
|
148
|
+
object.__setattr__(self, "E", self.E or _gerling_youngs_modulus(self.rho))
|
|
149
|
+
else:
|
|
150
|
+
raise ValueError(f"Invalid E_method: {self.E_method}")
|
|
151
|
+
object.__setattr__(self, "G", self.G or self.E / (2 * (1 + self.nu)))
|
|
152
|
+
if self.tensile_strength_method == "sigrist":
|
|
153
|
+
object.__setattr__(
|
|
154
|
+
self,
|
|
155
|
+
"tensile_strength",
|
|
156
|
+
self.tensile_strength
|
|
157
|
+
or _sigrist_tensile_strength(self.rho, unit="kPa"),
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Invalid tensile_strength_method: {self.tensile_strength_method}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
@model_validator(mode="after")
|
|
165
|
+
def validate_positive_E_G(self):
|
|
166
|
+
"""Validate that E and G are positive."""
|
|
167
|
+
if self.E <= 0:
|
|
168
|
+
raise ValueError("E must be positive")
|
|
169
|
+
if self.G <= 0:
|
|
170
|
+
raise ValueError("G must be positive")
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class WeakLayer(BaseModel):
|
|
175
|
+
"""
|
|
176
|
+
Weak layer that also behaves as a Winkler foundation.
|
|
177
|
+
|
|
178
|
+
Attributes
|
|
179
|
+
----------
|
|
180
|
+
rho : float
|
|
181
|
+
Density of the layer [kg m⁻³].
|
|
182
|
+
h : float
|
|
183
|
+
Height/Thickness of the layer [mm].
|
|
184
|
+
nu : float
|
|
185
|
+
Poisson's ratio [-] Defaults to `weac.constants.NU`).
|
|
186
|
+
E : float, optional
|
|
187
|
+
Young's modulus E [MPa]. If omitted it is derived from ``rho``.
|
|
188
|
+
G : float, optional
|
|
189
|
+
Shear modulus G [MPa]. If omitted it is derived from ``E`` and ``nu``.
|
|
190
|
+
kn : float, optional
|
|
191
|
+
Normal (compression) spring stiffness kₙ [N mm⁻³]. If omitted it is
|
|
192
|
+
computed as ``E_plane / t`` where
|
|
193
|
+
``E_plane = E / (1 - nu²)``.
|
|
194
|
+
kt : float, optional
|
|
195
|
+
Shear spring stiffness kₜ [N mm⁻³]. If omitted it is ``G / t``.
|
|
196
|
+
G_c : float
|
|
197
|
+
Total fracture energy Gc [J/m^2]. Default 1.0 J/m^2.
|
|
198
|
+
G_Ic : float
|
|
199
|
+
Mode-I fracture toughness GIc [J/m^2]. Default 0.56 J/m^2.
|
|
200
|
+
G_IIc : float
|
|
201
|
+
Mode-II fracture toughness GIIc [J/m^2]. Default 0.79 J/m^2.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
rho: float = Field(default=125, gt=0, description="Density of the Slab [kg m⁻³]")
|
|
205
|
+
h: float = Field(default=20, gt=0, description="Height/Thickness of the slab [mm]")
|
|
206
|
+
collapse_height: float = Field(
|
|
207
|
+
default=0.0, ge=0, description="Collapse height [mm]"
|
|
208
|
+
)
|
|
209
|
+
nu: float = Field(default=NU, ge=0, lt=0.5, description="Poisson's ratio [-]")
|
|
210
|
+
|
|
211
|
+
E: float = Field(default=0.0, ge=0, description="Young's modulus [MPa]")
|
|
212
|
+
G: float = Field(default=0.0, ge=0, description="Shear modulus [MPa]")
|
|
213
|
+
# Winkler springs (can be overridden by caller)
|
|
214
|
+
kn: float = Field(default=0.0, description="Normal stiffness [N mm⁻³]")
|
|
215
|
+
kt: float = Field(default=0.0, description="Shear stiffness [N mm⁻³]")
|
|
216
|
+
# fracture-mechanics parameters
|
|
217
|
+
G_c: float = Field(
|
|
218
|
+
default=1.0, gt=0, description="Total fracture energy Gc [J/m^2]"
|
|
219
|
+
)
|
|
220
|
+
G_Ic: float = Field(
|
|
221
|
+
default=0.56, gt=0, description="Mode-I fracture toughness GIc [J/m^2]"
|
|
222
|
+
)
|
|
223
|
+
G_IIc: float = Field(
|
|
224
|
+
default=0.79, gt=0, description="Mode-II fracture toughness GIIc [J/m^2]"
|
|
225
|
+
)
|
|
226
|
+
sigma_c: float = Field(default=6.16, gt=0, description="Tensile strength [kPa]")
|
|
227
|
+
tau_c: float = Field(default=5.09, gt=0, description="Shear strength [kPa]")
|
|
228
|
+
E_method: Literal["bergfeld", "scapazzo", "gerling"] = Field(
|
|
229
|
+
default="bergfeld",
|
|
230
|
+
description="Method to calculate the Young's modulus",
|
|
231
|
+
)
|
|
232
|
+
grain_type: GrainType | None = Field(default=None, description="Grain type")
|
|
233
|
+
grain_size: float | None = Field(default=None, description="Grain size [mm]")
|
|
234
|
+
hand_hardness: HandHardness | None = Field(
|
|
235
|
+
default=None, description="Hand hardness"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
model_config = ConfigDict(
|
|
239
|
+
frozen=True,
|
|
240
|
+
extra="forbid",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def model_post_init(self, _ctx): # pylint: disable=arguments-differ
|
|
244
|
+
if self.E_method == "bergfeld":
|
|
245
|
+
object.__setattr__(self, "E", self.E or _bergfeld_youngs_modulus(self.rho))
|
|
246
|
+
elif self.E_method == "scapazzo":
|
|
247
|
+
object.__setattr__(self, "E", self.E or _scapozza_youngs_modulus(self.rho))
|
|
248
|
+
elif self.E_method == "gerling":
|
|
249
|
+
object.__setattr__(self, "E", self.E or _gerling_youngs_modulus(self.rho))
|
|
250
|
+
else:
|
|
251
|
+
raise ValueError(f"Invalid E_method: {self.E_method}")
|
|
252
|
+
object.__setattr__(
|
|
253
|
+
self, "collapse_height", self.collapse_height or _collapse_height(self.h)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Validate that collapse height is smaller than layer height
|
|
257
|
+
if self.collapse_height >= self.h:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
f"Collapse height ({self.collapse_height:.2f} mm) must be smaller than "
|
|
260
|
+
f"layer height ({self.h:.2f} mm). Consider reducing collapse_height or "
|
|
261
|
+
f"increasing layer thickness."
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
object.__setattr__(self, "G", self.G or self.E / (2 * (1 + self.nu)))
|
|
265
|
+
E_plane = self.E / (1 - self.nu**2) # plane-strain Young
|
|
266
|
+
object.__setattr__(self, "kn", self.kn or E_plane / self.h)
|
|
267
|
+
object.__setattr__(self, "kt", self.kt or self.G / self.h)
|
|
268
|
+
|
|
269
|
+
@model_validator(mode="after")
|
|
270
|
+
def validate_positive_E_G(self):
|
|
271
|
+
"""Validate that E and G are positive."""
|
|
272
|
+
if self.E <= 0:
|
|
273
|
+
raise ValueError("E must be positive")
|
|
274
|
+
if self.G <= 0:
|
|
275
|
+
raise ValueError("G must be positive")
|
|
276
|
+
return self
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
if __name__ == "__main__":
|
|
280
|
+
ly1 = Layer(rho=180, h=120) # E,G,k auto-computed
|
|
281
|
+
ly2 = Layer(rho=250, h=80, E=50.0) # override E, derive G
|
|
282
|
+
wl = WeakLayer(rho=170, h=30) # full set incl. kn, kt
|
|
283
|
+
|
|
284
|
+
print(wl.model_dump())
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the input data model for the WEAC simulation.
|
|
3
|
+
|
|
4
|
+
We utilize the pydantic library instead of dataclasses to define the input
|
|
5
|
+
data model. The advantages of pydantic are:
|
|
6
|
+
1. validate the input data for the WEAC simulation, compared to __post_init__ methods.
|
|
7
|
+
2. generate JSON schemas for the input data, which is good for API endpoints.
|
|
8
|
+
3. generate the documentation for the input data.
|
|
9
|
+
|
|
10
|
+
Pydantic syntax is for a field:
|
|
11
|
+
field_name: type = Field(..., gt=0, description="Description")
|
|
12
|
+
- typing, default value, conditions, description
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
from typing import List
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
20
|
+
|
|
21
|
+
from weac.components.layer import Layer, WeakLayer
|
|
22
|
+
from weac.components.scenario_config import ScenarioConfig
|
|
23
|
+
from weac.components.segment import Segment
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ModelInput(BaseModel):
|
|
29
|
+
"""
|
|
30
|
+
Comprehensive input data model for a WEAC simulation.
|
|
31
|
+
|
|
32
|
+
Attributes
|
|
33
|
+
----------
|
|
34
|
+
scenario_config : ScenarioConfig
|
|
35
|
+
Scenario configuration.
|
|
36
|
+
weak_layer : WeakLayer
|
|
37
|
+
Weak layer properties.
|
|
38
|
+
layers : List[Layer]
|
|
39
|
+
List of snow slab layers.
|
|
40
|
+
segments : List[Segment]
|
|
41
|
+
List of segments defining the slab geometry and loading.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
model_config = ConfigDict(
|
|
45
|
+
extra="forbid",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
weak_layer: WeakLayer = Field(
|
|
49
|
+
default_factory=lambda: WeakLayer(rho=125, h=20, E=1.0),
|
|
50
|
+
description="Weak layer",
|
|
51
|
+
)
|
|
52
|
+
layers: List[Layer] = Field(
|
|
53
|
+
default_factory=lambda: [Layer(rho=250, h=100)], description="List of layers"
|
|
54
|
+
)
|
|
55
|
+
scenario_config: ScenarioConfig = Field(
|
|
56
|
+
default_factory=ScenarioConfig, description="Scenario configuration"
|
|
57
|
+
)
|
|
58
|
+
segments: List[Segment] = Field(
|
|
59
|
+
default_factory=lambda: [
|
|
60
|
+
Segment(length=5000, has_foundation=True, m=100),
|
|
61
|
+
Segment(length=5000, has_foundation=True, m=0),
|
|
62
|
+
],
|
|
63
|
+
description="Segments",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@model_validator(mode="after")
|
|
67
|
+
def _validate_non_empty_components(self):
|
|
68
|
+
"""Post-initialization checks."""
|
|
69
|
+
# Check that the last segment does not have a mass
|
|
70
|
+
if not self.segments:
|
|
71
|
+
raise ValueError("At least one segment is required")
|
|
72
|
+
if not self.layers:
|
|
73
|
+
raise ValueError("At least one layer is required")
|
|
74
|
+
if self.segments[-1].m != 0:
|
|
75
|
+
raise ValueError("The last segment must have a mass of 0")
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
# Example usage requiring all mandatory fields for proper instantiation
|
|
81
|
+
example_scenario_config = ScenarioConfig(phi=30, system_type="skiers")
|
|
82
|
+
# example_weak_layer = WeakLayer(
|
|
83
|
+
# rho=200, h=10
|
|
84
|
+
# ) # grain_size, temp, E, G_I have defaults
|
|
85
|
+
|
|
86
|
+
example_layers = [
|
|
87
|
+
Layer(rho=250, h=100), # grain_size, temp have defaults
|
|
88
|
+
Layer(rho=280, h=150),
|
|
89
|
+
]
|
|
90
|
+
example_segments = [
|
|
91
|
+
Segment(length=5000, has_foundation=True, m=80),
|
|
92
|
+
Segment(length=3000, has_foundation=False, m=0),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
model_input = ModelInput(
|
|
96
|
+
scenario_config=example_scenario_config,
|
|
97
|
+
layers=example_layers,
|
|
98
|
+
segments=example_segments,
|
|
99
|
+
)
|
|
100
|
+
print(model_input.model_dump_json(indent=2))
|
|
101
|
+
print("\n\n")
|
|
102
|
+
schema_json = json.dumps(ModelInput.model_json_schema(), indent=2)
|
|
103
|
+
print(schema_json)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the ScenarioConfig class, which contains the configuration for a given scenario.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
SystemType = Literal[
|
|
11
|
+
"skier", "skiers", "pst-", "-pst", "rot", "trans", "vpst-", "-vpst"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ScenarioConfig(BaseModel):
|
|
16
|
+
"""
|
|
17
|
+
Configuration for the overall scenario, such as slope angle.
|
|
18
|
+
|
|
19
|
+
Attributes
|
|
20
|
+
----------
|
|
21
|
+
phi : float, optional
|
|
22
|
+
Slope angle in degrees (counterclockwise positive).
|
|
23
|
+
system_type : SystemType
|
|
24
|
+
Type of system. Allowed values are:
|
|
25
|
+
- skier: single skier in-between two segments
|
|
26
|
+
- skiers: multiple skiers spread over the slope
|
|
27
|
+
- pst-: positive PST: down-slope + slab-normal cuts
|
|
28
|
+
- -pst: negative PST: up-slope + slab-normal cuts
|
|
29
|
+
- rot: rotation: rotation of the slab
|
|
30
|
+
- trans: translation: translation of the slab
|
|
31
|
+
- vpst-: positive VPST: down-slope + vertical cuts
|
|
32
|
+
- -vpst: negative VPST: up-slope + vertical cuts
|
|
33
|
+
cut_length : float, optional
|
|
34
|
+
Cut length for PST/VPST [mm].
|
|
35
|
+
stiffness_ratio : float, optional
|
|
36
|
+
Stiffness ratio between collapsed and uncollapsed weak layer.
|
|
37
|
+
surface_load : float, optional
|
|
38
|
+
Surface line-load on slab [N/mm] (force per mm of out-of-plane width).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
system_type: SystemType = Field(
|
|
42
|
+
default="skiers",
|
|
43
|
+
description="Type of system, '-pst', 'pst-', ....; \n"
|
|
44
|
+
"skier: single skier in-between two segments, \n"
|
|
45
|
+
"skiers: multiple skiers spread over the slope, \n"
|
|
46
|
+
"pst-: positive PST: down-slope + slab-normal cuts, \n"
|
|
47
|
+
"-pst: negative PST: up-slope + slab-normal cuts, \n"
|
|
48
|
+
"rot: rotation: rotation of the slab, \n"
|
|
49
|
+
"trans: translation: translation of the slab, \n"
|
|
50
|
+
"vpst-: positive VPST: down-slope + vertical cuts, \n"
|
|
51
|
+
"-vpst: negative VPST: up-slope + vertical cuts, \n",
|
|
52
|
+
)
|
|
53
|
+
phi: float = Field(
|
|
54
|
+
default=0.0,
|
|
55
|
+
ge=-90.0,
|
|
56
|
+
le=90.0,
|
|
57
|
+
description="Slope angle in degrees (counterclockwise positive)",
|
|
58
|
+
)
|
|
59
|
+
cut_length: float = Field(
|
|
60
|
+
default=0.0, ge=0, description="Cut length of performed PST or VPST [mm]"
|
|
61
|
+
)
|
|
62
|
+
stiffness_ratio: float = Field(
|
|
63
|
+
default=1000.0,
|
|
64
|
+
gt=0.0,
|
|
65
|
+
description="Stiffness ratio between collapsed and uncollapsed weak layer",
|
|
66
|
+
)
|
|
67
|
+
surface_load: float = Field(
|
|
68
|
+
default=0.0,
|
|
69
|
+
ge=0.0,
|
|
70
|
+
description="Surface line-load on slab [N/mm], e.g. evenly spaced weights, "
|
|
71
|
+
"Adam et al. (2024)",
|
|
72
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the Segment class, which represents a segment of the snowpack.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Segment(BaseModel):
|
|
9
|
+
"""
|
|
10
|
+
Defines a snow-slab segment: its length, foundation support, and applied loads.
|
|
11
|
+
|
|
12
|
+
Attributes
|
|
13
|
+
----------
|
|
14
|
+
length: float
|
|
15
|
+
Segment length in millimeters [mm].
|
|
16
|
+
has_foundation: bool
|
|
17
|
+
Whether the segment is supported (foundation present) or cracked/free-hanging
|
|
18
|
+
(no foundation).
|
|
19
|
+
m: float
|
|
20
|
+
Skier mass at the segment's right edge [kg].
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
length: float = Field(default=5e3, ge=0, description="Segment length in [mm]")
|
|
24
|
+
has_foundation: bool = Field(
|
|
25
|
+
default=True,
|
|
26
|
+
description="Whether the segment is supported (foundation present) or "
|
|
27
|
+
"cracked/free-hanging (no foundation)",
|
|
28
|
+
)
|
|
29
|
+
m: float = Field(
|
|
30
|
+
default=0, ge=0, description="Skier mass at the segment's right edge in [kg]"
|
|
31
|
+
)
|
weac/constants.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants for the WEAC simulation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
G_MM_S2: Final[float] = 9810.0 # gravitational acceleration (mm s⁻²)
|
|
8
|
+
NU: Final[float] = 0.25 # Global Poisson's ratio
|
|
9
|
+
SHEAR_CORRECTION_FACTOR: Final[float] = 5.0 / 6.0 # Shear-correction factor (slabs)
|
|
10
|
+
STIFFNESS_COLLAPSE_FACTOR: Final[float] = (
|
|
11
|
+
1000.0 # Stiffness ratio between collapsed and uncollapsed weak layer.
|
|
12
|
+
)
|
|
13
|
+
ROMBERG_TOL: Final[float] = 1e-3 # Romberg integration tolerance
|
|
14
|
+
LSKI_MM: Final[float] = 1000.0 # Effective out-of-plane length of skis (mm)
|
|
15
|
+
EPS: Final[float] = 1e-9 # Global numeric tolerance for float comparisons
|
|
16
|
+
|
|
17
|
+
RHO_ICE: Final[float] = 916.7 # Density of ice (kg/m^3)
|
|
18
|
+
CB0: Final[float] = (
|
|
19
|
+
6.5
|
|
20
|
+
# Multiplicative constant of Young modulus
|
|
21
|
+
# parametrization according to Bergfeld et al. (2023)
|
|
22
|
+
)
|
|
23
|
+
CB1: Final[float] = (
|
|
24
|
+
4.4
|
|
25
|
+
# Exponent of Young modulus parameterization
|
|
26
|
+
# according to Bergfeld et al. (2023)
|
|
27
|
+
)
|
|
28
|
+
CG0: Final[float] = (
|
|
29
|
+
6.0
|
|
30
|
+
# Multiplicative constant of Young modulus
|
|
31
|
+
# parametrization according to Gerling et al. (2017)
|
|
32
|
+
)
|
|
33
|
+
CG1: Final[float] = (
|
|
34
|
+
4.5
|
|
35
|
+
# Exponent of Young modulus parameterization
|
|
36
|
+
# according to Gerling et al. (2017)
|
|
37
|
+
)
|
weac/core/__init__.py
ADDED