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.
@@ -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
+ )
@@ -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
@@ -0,0 +1,10 @@
1
+ """
2
+ Core modules for the WEAC model.
3
+ """
4
+
5
+ from .eigensystem import Eigensystem
6
+ from .scenario import Scenario
7
+ from .slab import Slab
8
+ from .system_model import SystemModel
9
+
10
+ __all__ = ["Eigensystem", "Scenario", "Slab", "SystemModel"]