weac 3.0.1__py3-none-any.whl → 3.0.2__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 CHANGED
@@ -2,4 +2,4 @@
2
2
  WEAC - Weak Layer Anticrack Nucleation Model
3
3
  """
4
4
 
5
- __version__ = "3.0.1"
5
+ __version__ = "3.0.2"
@@ -9,7 +9,6 @@ import logging
9
9
  import time
10
10
  import warnings
11
11
  from dataclasses import dataclass
12
- from typing import List, Optional, Union
13
12
 
14
13
  # Third party imports
15
14
  import numpy as np
@@ -34,12 +33,12 @@ logger = logging.getLogger(__name__)
34
33
  class CoupledCriterionHistory:
35
34
  """Stores the history of the coupled criterion evaluation."""
36
35
 
37
- skier_weights: List[float]
38
- crack_lengths: List[float]
39
- incr_energies: List[np.ndarray]
40
- g_deltas: List[float]
41
- dist_maxs: List[float]
42
- dist_mins: List[float]
36
+ skier_weights: list[float]
37
+ crack_lengths: list[float]
38
+ incr_energies: list[np.ndarray]
39
+ g_deltas: list[float]
40
+ dist_maxs: list[float]
41
+ dist_mins: list[float]
43
42
 
44
43
 
45
44
  @dataclass
@@ -89,7 +88,7 @@ class CoupledCriterionResult:
89
88
  g_delta: float
90
89
  dist_ERR_envelope: float
91
90
  iterations: int
92
- history: Optional[CoupledCriterionHistory]
91
+ history: CoupledCriterionHistory | None
93
92
  final_system: SystemModel
94
93
  max_dist_stress: float
95
94
  min_dist_stress: float
@@ -130,9 +129,9 @@ class FindMinimumForceResult:
130
129
  Whether the algorithm converged.
131
130
  critical_skier_weight : float
132
131
  The critical skier weight.
133
- new_segments : List[Segment]
132
+ new_segments : list[Segment]
134
133
  The new segments.
135
- old_segments : List[Segment]
134
+ old_segments : list[Segment]
136
135
  The old segments.
137
136
  iterations : int
138
137
  The number of iterations.
@@ -144,9 +143,9 @@ class FindMinimumForceResult:
144
143
 
145
144
  success: bool
146
145
  critical_skier_weight: float
147
- new_segments: List[Segment]
148
- old_segments: List[Segment]
149
- iterations: Optional[int]
146
+ new_segments: list[Segment]
147
+ old_segments: list[Segment]
148
+ iterations: int | None
150
149
  max_dist_stress: float
151
150
  min_dist_stress: float
152
151
 
@@ -203,10 +202,10 @@ class CriteriaEvaluator:
203
202
 
204
203
  def stress_envelope(
205
204
  self,
206
- sigma: Union[float, np.ndarray],
207
- tau: Union[float, np.ndarray],
205
+ sigma: float | np.ndarray,
206
+ tau: float | np.ndarray,
208
207
  weak_layer: WeakLayer,
209
- method: Optional[str] = None,
208
+ method: str | None = None,
210
209
  ) -> np.ndarray:
211
210
  """
212
211
  Evaluate the stress envelope for given stress components.
@@ -672,16 +671,20 @@ class CriteriaEvaluator:
672
671
  UserWarning,
673
672
  )
674
673
  system_copy = copy.deepcopy(system)
674
+ system_copy.config.touchdown = True
675
+ system_copy.update_scenario(scenario_config=ScenarioConfig(phi=0.0))
676
+ l_BC = system.slab_touchdown.l_BC
677
+
675
678
  segments = [
676
679
  Segment(length=5e3, has_foundation=True, m=0.0),
677
- Segment(length=5e3, has_foundation=False, m=0.0),
680
+ Segment(length=2 * l_BC, has_foundation=False, m=0.0),
678
681
  ]
679
682
  scenario_config = ScenarioConfig(
680
683
  system_type="vpst-" if vertical else "pst-",
681
- phi=system.scenario.phi,
682
- cut_length=5e3,
684
+ phi=0.0, # Slab Touchdown works only for flat slab
685
+ cut_length=2 * l_BC,
683
686
  )
684
- system_copy.config.touchdown = True
687
+ # system_copy.config.touchdown = True
685
688
  system_copy.update_scenario(segments=segments, scenario_config=scenario_config)
686
689
  touchdown_distance = system_copy.slab_touchdown.touchdown_distance
687
690
  analyzer = Analyzer(system_copy, printing_enabled=print_call_stats)
@@ -825,7 +828,7 @@ class CriteriaEvaluator:
825
828
  system: SystemModel,
826
829
  search_interval: tuple[float, float] | None = None,
827
830
  target: float = 1,
828
- ) -> tuple[float, List[Segment]]:
831
+ ) -> tuple[float, list[Segment]]:
829
832
  """
830
833
  Finds the minimum crack length required to surpass the energy release rate envelope.
831
834
 
@@ -838,7 +841,7 @@ class CriteriaEvaluator:
838
841
  --------
839
842
  minimum_crack_length: float
840
843
  The minimum crack length required to surpass the energy release rate envelope [mm]
841
- new_segments: List[Segment]
844
+ new_segments: list[Segment]
842
845
  The updated list of segments
843
846
  """
844
847
  old_segments = copy.deepcopy(system.scenario.segments)
@@ -928,7 +931,7 @@ class CriteriaEvaluator:
928
931
  self,
929
932
  system: SystemModel,
930
933
  skier_weight: float,
931
- ) -> tuple[float, List[Segment]]:
934
+ ) -> tuple[float, list[Segment]]:
932
935
  """
933
936
  Finds the resulting anticrack length and updated segment configurations
934
937
  for a given skier weight.
@@ -944,7 +947,7 @@ class CriteriaEvaluator:
944
947
  -------
945
948
  new_crack_length: float
946
949
  The total length of the new cracked segments [mm]
947
- new_segments: List[Segment]
950
+ new_segments: list[Segment]
948
951
  The updated list of segments
949
952
  """
950
953
  logger.info(
@@ -1086,7 +1089,7 @@ class CriteriaEvaluator:
1086
1089
 
1087
1090
  def _find_stress_envelope_crossings(
1088
1091
  self, system: SystemModel, weak_layer: WeakLayer
1089
- ) -> List[float]:
1092
+ ) -> list[float]:
1090
1093
  """
1091
1094
  Finds the exact x-coordinates where the stress envelope is crossed.
1092
1095
  """
weac/analysis/plotter.py CHANGED
@@ -6,7 +6,7 @@ This module provides plotting functions for visualizing the results of the WEAC
6
6
  import colorsys
7
7
  import logging
8
8
  import os
9
- from typing import List, Literal, Optional
9
+ from typing import Literal
10
10
 
11
11
  # Third party imports
12
12
  import matplotlib.colors as mc
@@ -219,9 +219,9 @@ class Plotter:
219
219
 
220
220
  def _get_systems_to_plot(
221
221
  self,
222
- system_model: Optional[SystemModel] = None,
223
- system_models: Optional[List[SystemModel]] = None,
224
- ) -> List[SystemModel]:
222
+ system_model: SystemModel | None = None,
223
+ system_models: list[SystemModel] | None = None,
224
+ ) -> list[SystemModel]:
225
225
  """Determine which systems to plot based on override parameters."""
226
226
  if system_model is not None and system_models is not None:
227
227
  raise ValueError(
@@ -236,7 +236,7 @@ class Plotter:
236
236
  "SystemModel or list of SystemModels"
237
237
  )
238
238
 
239
- def _save_figure(self, filename: str, fig: Optional[Figure] = None):
239
+ def _save_figure(self, filename: str, fig: Figure | None = None):
240
240
  """Save figure with proper formatting."""
241
241
  if fig is None:
242
242
  fig = plt.gcf()
@@ -249,20 +249,20 @@ class Plotter:
249
249
 
250
250
  def plot_slab_profile(
251
251
  self,
252
- weak_layers: List[WeakLayer] | WeakLayer,
253
- slabs: List[Slab] | Slab,
252
+ weak_layers: list[WeakLayer] | WeakLayer,
253
+ slabs: list[Slab] | Slab,
254
254
  filename: str = "slab_profile",
255
- labels: Optional[List[str] | str] = None,
256
- colors: Optional[List[str]] = None,
255
+ labels: list[str] | str | None = None,
256
+ colors: list[str] | None = None,
257
257
  ):
258
258
  """
259
259
  Plot slab layer profiles for comparison.
260
260
 
261
261
  Parameters
262
262
  ----------
263
- weak_layers : List[WeakLayer] | WeakLayer
263
+ weak_layers : list[WeakLayer] | WeakLayer
264
264
  The weak layer or layers to plot.
265
- slabs : List[Slab] | Slab
265
+ slabs : list[Slab] | Slab
266
266
  The slab or slabs to plot.
267
267
  filename : str, optional
268
268
  Filename for saving plot
@@ -281,6 +281,9 @@ class Plotter:
281
281
  if isinstance(slabs, Slab):
282
282
  slabs = [slabs]
283
283
 
284
+ if len(weak_layers) != len(slabs):
285
+ raise ValueError("Number of weak layers must match number of slabs")
286
+
284
287
  if labels is None:
285
288
  labels = [f"System {i + 1}" for i in range(len(weak_layers))]
286
289
  elif isinstance(labels, str):
@@ -642,11 +645,11 @@ class Plotter:
642
645
 
643
646
  def plot_section_forces(
644
647
  self,
645
- system_model: Optional[SystemModel] = None,
646
- system_models: Optional[List[SystemModel]] = None,
648
+ system_model: SystemModel | None = None,
649
+ system_models: list[SystemModel] | None = None,
647
650
  filename: str = "section_forces",
648
- labels: Optional[List[str]] = None,
649
- colors: Optional[List[str]] = None,
651
+ labels: list[str] | None = None,
652
+ colors: list[str] | None = None,
650
653
  ):
651
654
  """
652
655
  Plot section forces (N, M, V) for comparison.
@@ -655,7 +658,7 @@ class Plotter:
655
658
  ----------
656
659
  system_model : SystemModel, optional
657
660
  Single system to plot (overrides default)
658
- system_models : List[SystemModel], optional
661
+ system_models : list[SystemModel], optional
659
662
  Multiple systems to plot (overrides default)
660
663
  filename : str, optional
661
664
  Filename for saving plot
@@ -721,11 +724,11 @@ class Plotter:
721
724
 
722
725
  def plot_energy_release_rates(
723
726
  self,
724
- system_model: Optional[SystemModel] = None,
725
- system_models: Optional[List[SystemModel]] = None,
727
+ system_model: SystemModel | None = None,
728
+ system_models: list[SystemModel] | None = None,
726
729
  filename: str = "ERR",
727
- labels: Optional[List[str]] = None,
728
- colors: Optional[List[str]] = None,
730
+ labels: list[str] | None = None,
731
+ colors: list[str] | None = None,
729
732
  ):
730
733
  """
731
734
  Plot energy release rates (G_I, G_II) for comparison.
@@ -734,7 +737,7 @@ class Plotter:
734
737
  ----------
735
738
  system_model : SystemModel, optional
736
739
  Single system to plot (overrides default)
737
- system_models : List[SystemModel], optional
740
+ system_models : list[SystemModel], optional
738
741
  Multiple systems to plot (overrides default)
739
742
  filename : str, optional
740
743
  Filename for saving plot
@@ -1035,7 +1038,7 @@ class Plotter:
1035
1038
  system_model: SystemModel,
1036
1039
  criteria_evaluator: CriteriaEvaluator,
1037
1040
  all_envelopes: bool = False,
1038
- filename: Optional[str] = None,
1041
+ filename: str | None = None,
1039
1042
  ):
1040
1043
  """
1041
1044
  Plot stress envelope in τ-σ space.
@@ -1074,7 +1077,7 @@ class Plotter:
1074
1077
  weak_layer = system_model.weak_layer
1075
1078
 
1076
1079
  # Define a function to find the root for a given tau
1077
- def find_sigma_for_tau(tau_val, sigma_c, method: Optional[str] = None):
1080
+ def find_sigma_for_tau(tau_val, sigma_c, method: str | None = None):
1078
1081
  # Target function to find the root of: envelope(sigma, tau) - 1 = 0
1079
1082
  def envelope_root_func(sigma_val):
1080
1083
  return (
@@ -14,7 +14,6 @@ field_name: type = Field(..., gt=0, description="Description")
14
14
 
15
15
  import json
16
16
  import logging
17
- from typing import List
18
17
 
19
18
  from pydantic import BaseModel, ConfigDict, Field, model_validator
20
19
 
@@ -49,13 +48,13 @@ class ModelInput(BaseModel):
49
48
  default_factory=lambda: WeakLayer(rho=125, h=20, E=1.0),
50
49
  description="Weak layer",
51
50
  )
52
- layers: List[Layer] = Field(
53
- default_factory=lambda: [Layer(rho=250, h=100)], description="List of layers"
51
+ layers: list[Layer] = Field(
52
+ default_factory=lambda: [Layer(rho=250, h=100)], description="list of layers"
54
53
  )
55
54
  scenario_config: ScenarioConfig = Field(
56
55
  default_factory=ScenarioConfig, description="Scenario configuration"
57
56
  )
58
- segments: List[Segment] = Field(
57
+ segments: list[Segment] = Field(
59
58
  default_factory=lambda: [
60
59
  Segment(length=5000, has_foundation=True, m=100),
61
60
  Segment(length=5000, has_foundation=True, m=0),
weac/core/eigensystem.py CHANGED
@@ -4,10 +4,8 @@ the eigenvalue problem for a layered beam on an elastic foundation.
4
4
  """
5
5
 
6
6
  import logging
7
- from typing import Optional
8
7
 
9
8
  import numpy as np
10
- from numpy.typing import NDArray
11
9
 
12
10
  from weac.components import WeakLayer
13
11
  from weac.constants import SHEAR_CORRECTION_FACTOR
@@ -58,17 +56,17 @@ class Eigensystem:
58
56
  kA55: float # shear stiffness
59
57
  K0: float # foundation stiffness
60
58
 
61
- K: NDArray # System Matrix
59
+ K: np.ndarray # System Matrix
62
60
 
63
61
  # Eigenvalues and Eigenvectors
64
- ewC: NDArray[np.complex128] # shape (k): Complex Eigenvalues
65
- ewR: NDArray[np.float64] # shape (k): Real Eigenvalues
66
- evC: NDArray[np.complex128] # shape (6, k): Complex Eigenvectors
67
- evR: NDArray[np.float64] # shape (6, k): Real Eigenvectors
68
- sR: NDArray[
62
+ ewC: np.ndarray[np.complex128] # shape (k): Complex Eigenvalues
63
+ ewR: np.ndarray[np.float64] # shape (k): Real Eigenvalues
64
+ evC: np.ndarray[np.complex128] # shape (6, k): Complex Eigenvectors
65
+ evR: np.ndarray[np.float64] # shape (6, k): Real Eigenvectors
66
+ sR: np.ndarray[
69
67
  np.float64
70
68
  ] # shape (k): Real positive eigenvalue shifts (for numerical robustness)
71
- sC: NDArray[
69
+ sC: np.ndarray[
72
70
  np.float64
73
71
  ] # shape (k): Complex positive eigenvalue shifts (for numerical robustness)
74
72
 
@@ -114,8 +112,8 @@ class Eigensystem:
114
112
  self.K0 = B11**2 - A11 * D11
115
113
 
116
114
  def assemble_system_matrix(
117
- self, kn: Optional[float], kt: Optional[float]
118
- ) -> NDArray[np.float64]:
115
+ self, kn: float | None, kt: float | None
116
+ ) -> np.ndarray[np.float64]:
119
117
  """
120
118
  Assemble first-order ODE system matrix K.
121
119
 
@@ -171,14 +169,14 @@ class Eigensystem:
171
169
  return np.array(K, dtype=np.float64)
172
170
 
173
171
  def calc_eigenvalues_and_eigenvectors(
174
- self, system_matrix: NDArray[np.float64]
172
+ self, system_matrix: np.ndarray[np.float64]
175
173
  ) -> tuple[
176
- NDArray[np.complex128],
177
- NDArray[np.float64],
178
- NDArray[np.complex128],
179
- NDArray[np.float64],
180
- NDArray[np.float64],
181
- NDArray[np.float64],
174
+ np.ndarray[np.complex128],
175
+ np.ndarray[np.float64],
176
+ np.ndarray[np.complex128],
177
+ np.ndarray[np.float64],
178
+ np.ndarray[np.float64],
179
+ np.ndarray[np.float64],
182
180
  ]:
183
181
  """
184
182
  Calculate eigenvalues and eigenvectors of the system matrix.
@@ -215,7 +213,9 @@ class Eigensystem:
215
213
  sR[ewR > 0], sC[ewC > 0] = -1, -1
216
214
  return ewC, ewR, evC, evR, sR, sC
217
215
 
218
- def zh(self, x: float, length: float = 0, has_foundation: bool = True) -> NDArray:
216
+ def zh(
217
+ self, x: float, length: float = 0, has_foundation: bool = True
218
+ ) -> np.ndarray:
219
219
  """
220
220
  Compute bedded or free complementary solution at position x.
221
221
 
@@ -275,7 +275,7 @@ class Eigensystem:
275
275
 
276
276
  def zp(
277
277
  self, x: float, phi: float = 0, has_foundation=True, qs: float = 0
278
- ) -> NDArray:
278
+ ) -> np.ndarray:
279
279
  """
280
280
  Compute bedded or free particular integrals at position x.
281
281
 
@@ -357,7 +357,7 @@ class Eigensystem:
357
357
 
358
358
  return zp
359
359
 
360
- def get_load_vector(self, phi: float, qs: float = 0) -> NDArray:
360
+ def get_load_vector(self, phi: float, qs: float = 0) -> np.ndarray:
361
361
  """
362
362
  Compute system load vector q.
363
363
 
weac/core/scenario.py CHANGED
@@ -3,7 +3,7 @@ This module defines the Scenario class, which encapsulates the physical setup of
3
3
  """
4
4
 
5
5
  import logging
6
- from typing import List, Sequence, Union
6
+ from collections.abc import Sequence
7
7
 
8
8
  import numpy as np
9
9
 
@@ -45,7 +45,7 @@ class Scenario:
45
45
 
46
46
  # Inputs
47
47
  scenario_config: ScenarioConfig
48
- segments: List[Segment]
48
+ segments: list[Segment]
49
49
  weak_layer: WeakLayer
50
50
  slab: Slab
51
51
 
@@ -69,7 +69,7 @@ class Scenario:
69
69
  def __init__(
70
70
  self,
71
71
  scenario_config: ScenarioConfig,
72
- segments: List[Segment],
72
+ segments: list[Segment],
73
73
  weak_layer: WeakLayer,
74
74
  slab: Slab,
75
75
  ):
@@ -102,19 +102,19 @@ class Scenario:
102
102
  self._calc_crack_height()
103
103
 
104
104
  def get_segment_idx(
105
- self, x: Union[float, Sequence[float], np.ndarray]
106
- ) -> Union[int, np.ndarray]:
105
+ self, x: float | Sequence[float] | np.ndarray
106
+ ) -> int | np.ndarray:
107
107
  """
108
108
  Get the segment index for a given x-coordinate or coordinates.
109
109
 
110
110
  Parameters
111
111
  ----------
112
- x: Union[float, Sequence[float], np.ndarray]
112
+ x: float | Sequence[float] | np.ndarray
113
113
  A single x-coordinate or a sequence of x-coordinates.
114
114
 
115
115
  Returns
116
116
  -------
117
- Union[int, np.ndarray]
117
+ int | np.ndarray
118
118
  The segment index or an array of indices.
119
119
  """
120
120
  x_arr = np.asarray(x)
weac/core/slab.py CHANGED
@@ -2,8 +2,6 @@
2
2
  This module defines the Slab class, which represents the snow slab and its properties.
3
3
  """
4
4
 
5
- from typing import List
6
-
7
5
  import numpy as np
8
6
 
9
7
  from weac.components import Layer
@@ -44,7 +42,7 @@ class Slab: # pylint: disable=too-many-instance-attributes,too-few-public-metho
44
42
  """
45
43
 
46
44
  # Input data
47
- layers: List[Layer]
45
+ layers: list[Layer]
48
46
 
49
47
  rhoi: np.ndarray # densities of the layer i [t/mm^3]
50
48
  hi: np.ndarray # thickness of the layer i [mm]
@@ -60,7 +58,7 @@ class Slab: # pylint: disable=too-many-instance-attributes,too-few-public-metho
60
58
  z_cog: float # z-coordinate of Center of Gravity [mm]
61
59
  qw: float # Weight Load of the slab [N/mm]
62
60
 
63
- def __init__(self, layers: List[Layer]) -> None:
61
+ def __init__(self, layers: list[Layer]) -> None:
64
62
  self.layers = layers
65
63
  self._calc_slab_params()
66
64
 
@@ -4,7 +4,7 @@ Handling the touchdown situation in a PST.
4
4
  """
5
5
 
6
6
  import logging
7
- from typing import Literal, Optional
7
+ from typing import Literal
8
8
 
9
9
  from scipy.optimize import brentq
10
10
 
@@ -60,7 +60,7 @@ class SlabTouchdown: # pylint: disable=too-many-instance-attributes,too-few-pub
60
60
  Type of touchdown mode
61
61
  touchdown_distance : float
62
62
  Length of the touchdown segment [mm]
63
- collapsed_weak_layer_kR : Optional[float]
63
+ collapsed_weak_layer_kR : float | None
64
64
  Rotational spring stiffness of the collapsed weak layer segment
65
65
  """
66
66
 
@@ -78,7 +78,7 @@ class SlabTouchdown: # pylint: disable=too-many-instance-attributes,too-few-pub
78
78
  "A_free_hanging", "B_point_contact", "C_in_contact"
79
79
  ] # Three types of contact with collapsed weak layer
80
80
  touchdown_distance: float
81
- collapsed_weak_layer_kR: Optional[float] = None
81
+ collapsed_weak_layer_kR: float | None = None
82
82
 
83
83
  def __init__(self, scenario: Scenario, eigensystem: Eigensystem):
84
84
  self.scenario = scenario
weac/core/system_model.py CHANGED
@@ -12,7 +12,6 @@ import copy
12
12
  import logging
13
13
  from collections.abc import Sequence
14
14
  from functools import cached_property
15
- from typing import List, Optional, Union
16
15
 
17
16
  import numpy as np
18
17
 
@@ -119,11 +118,11 @@ class SystemModel:
119
118
  fq: FieldQuantities
120
119
 
121
120
  scenario: Scenario
122
- slab_touchdown: Optional[SlabTouchdown]
121
+ slab_touchdown: SlabTouchdown | None
123
122
  unknown_constants: np.ndarray
124
123
  uncracked_unknown_constants: np.ndarray
125
124
 
126
- def __init__(self, model_input: ModelInput, config: Optional[Config] = None):
125
+ def __init__(self, model_input: ModelInput, config: Config | None = None):
127
126
  if config is None:
128
127
  config = Config()
129
128
  self.config = config
@@ -155,7 +154,7 @@ class SystemModel:
155
154
  return Eigensystem(weak_layer=self.weak_layer, slab=self.slab)
156
155
 
157
156
  @cached_property
158
- def slab_touchdown(self) -> Optional[SlabTouchdown]:
157
+ def slab_touchdown(self) -> SlabTouchdown | None:
159
158
  """
160
159
  Solve for the slab touchdown.
161
160
  Modifies the scenario object in place by replacing the undercut segment
@@ -303,7 +302,7 @@ class SystemModel:
303
302
  self._invalidate_eigensystem()
304
303
 
305
304
  # Changes that affect the *slab* -> rebuild everything
306
- def update_layers(self, new_layers: List[Layer]):
305
+ def update_layers(self, new_layers: list[Layer]):
307
306
  """Update the layers."""
308
307
  slab = Slab(layers=new_layers)
309
308
  self.slab = slab
@@ -318,8 +317,8 @@ class SystemModel:
318
317
  # Changes that affect the *scenario* -> only rebuild C constants
319
318
  def update_scenario(
320
319
  self,
321
- segments: Optional[List[Segment]] = None,
322
- scenario_config: Optional[ScenarioConfig] = None,
320
+ segments: list[Segment] | None = None,
321
+ scenario_config: ScenarioConfig | None = None,
323
322
  ):
324
323
  """
325
324
  Update fields on `scenario_config` (if present) or on the
@@ -365,7 +364,7 @@ class SystemModel:
365
364
 
366
365
  def z(
367
366
  self,
368
- x: Union[float, Sequence[float], np.ndarray],
367
+ x: float | Sequence[float] | np.ndarray,
369
368
  C: np.ndarray,
370
369
  length: float,
371
370
  phi: float,
@@ -396,7 +395,7 @@ class SystemModel:
396
395
  z : ndarray
397
396
  Solution vector (6xN) at position x.
398
397
  """
399
- if isinstance(x, (list, tuple, np.ndarray)):
398
+ if isinstance(x, (np.ndarray, Sequence)):
400
399
  z = np.concatenate(
401
400
  [
402
401
  np.dot(self.eigensystem.zh(xi, length, has_foundation), C)
@@ -8,7 +8,7 @@ We utilize the pydantic library to define the system model.
8
8
  """
9
9
 
10
10
  import logging
11
- from typing import Literal, Optional
11
+ from typing import Literal
12
12
 
13
13
  import numpy as np
14
14
  from numpy.linalg import LinAlgError
@@ -36,11 +36,10 @@ class UnknownConstantsSolver:
36
36
  scenario: Scenario,
37
37
  eigensystem: Eigensystem,
38
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,
39
+ touchdown_distance: float | None = None,
40
+ touchdown_mode: Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
41
+ | None = None,
42
+ collapsed_weak_layer_kR: float | None = None,
44
43
  ) -> np.ndarray:
45
44
  """
46
45
  Compute free constants *C* for system. \\
@@ -264,10 +263,9 @@ class UnknownConstantsSolver:
264
263
  has_foundation: bool,
265
264
  pos: Literal["l", "r", "m", "left", "right", "mid"],
266
265
  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,
266
+ touchdown_mode: Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
267
+ | None = None,
268
+ collapsed_weak_layer_kR: float | None = None,
271
269
  ) -> np.ndarray:
272
270
  """
273
271
  Provide boundary or transmission conditions for beam segments.
@@ -369,10 +367,9 @@ class UnknownConstantsSolver:
369
367
  has_foundation: bool,
370
368
  pos: Literal["l", "r", "m", "left", "right", "mid"],
371
369
  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,
370
+ touchdown_mode: Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
371
+ | None = None,
372
+ collapsed_weak_layer_kR: float | None = None,
376
373
  ):
377
374
  """
378
375
  Provide equations for free (pst) or infinite (skiers) ends.
weac/logging_config.py CHANGED
@@ -4,10 +4,9 @@ Logging configuration for weak layer anticrack nucleation model.
4
4
 
5
5
  import os
6
6
  from logging.config import dictConfig
7
- from typing import Optional
8
7
 
9
8
 
10
- def setup_logging(level: Optional[str] = None) -> None:
9
+ def setup_logging(level: str | None = None) -> None:
11
10
  """
12
11
  Initialise the global logging configuration exactly once.
13
12
  The level is taken from the env var WEAC_LOG_LEVEL (default WARNING).
@@ -20,7 +20,6 @@ The `the column length` is the column length of the PropSawTest.
20
20
  """
21
21
 
22
22
  import logging
23
- from typing import List, Tuple
24
23
 
25
24
  import numpy as np
26
25
  from snowpylot import caaml_parser
@@ -47,28 +46,28 @@ class SnowPilotParser:
47
46
  def __init__(self, file_path: str):
48
47
  self.snowpit: SnowPit = caaml_parser(file_path)
49
48
 
50
- def extract_layers(self) -> Tuple[List[Layer], List[str]]:
49
+ def extract_layers(self) -> tuple[list[Layer], list[str]]:
51
50
  """Extract layers from snowpit."""
52
51
  snowpit = self.snowpit
53
- # Extract layers from snowpit: List[SnowpylotLayer]
54
- sp_layers: List[SnowpylotLayer] = [
52
+ # Extract layers from snowpit: list[SnowpylotLayer]
53
+ sp_layers: list[SnowpylotLayer] = [
55
54
  layer
56
55
  for layer in snowpit.snow_profile.layers
57
56
  if layer.depth_top is not None
58
57
  ]
59
58
  sp_layers = sorted(sp_layers, key=lambda x: x.depth_top[0]) # type: ignore
60
59
 
61
- # Extract density layers from snowpit: List[DensityObs]
62
- sp_density_layers: List[DensityObs] = [
60
+ # Extract density layers from snowpit: list[DensityObs]
61
+ sp_density_layers: list[DensityObs] = [
63
62
  layer
64
63
  for layer in snowpit.snow_profile.density_profile
65
64
  if layer.depth_top is not None
66
65
  ]
67
66
  sp_density_layers = sorted(sp_density_layers, key=lambda x: x.depth_top[0]) # type: ignore
68
67
 
69
- # Populate WEAC layers: List[Layer]
70
- layers: List[Layer] = []
71
- density_methods: List[str] = []
68
+ # Populate WEAC layers: list[Layer]
69
+ layers: list[Layer] = []
70
+ density_methods: list[str] = []
72
71
  for _i, layer in enumerate(sp_layers):
73
72
  # Parameters
74
73
  grain_type = None
@@ -213,14 +212,14 @@ class SnowPilotParser:
213
212
  self,
214
213
  layer_top_mm: float,
215
214
  layer_bottom_mm: float,
216
- sp_density_layers: List[DensityObs],
215
+ sp_density_layers: list[DensityObs],
217
216
  ) -> float | None:
218
217
  """Find density measurements that overlap with the given layer depth range.
219
218
 
220
219
  Args:
221
220
  layer_top_mm: Top depth of layer in mm
222
221
  layer_bottom_mm: Bottom depth of layer in mm
223
- sp_density_layers: List of density observations
222
+ sp_density_layers: list of density observations
224
223
 
225
224
  Returns:
226
225
  Average density from overlapping measurements, or None if no overlap
@@ -273,8 +272,8 @@ class SnowPilotParser:
273
272
  return None
274
273
 
275
274
  def extract_weak_layer_and_layers_above(
276
- self, weak_layer_depth: float, layers: List[Layer]
277
- ) -> Tuple[WeakLayer, List[Layer]]:
275
+ self, weak_layer_depth: float, layers: list[Layer]
276
+ ) -> tuple[WeakLayer, list[Layer]]:
278
277
  """Extract weak layer and layers above the weak layer for the given
279
278
  depth_top extracted from the stability test."""
280
279
  depth = 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weac
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: Weak layer anticrack nucleation model
5
5
  Author-email: 2phi GbR <mail@2phi.de>
6
6
  License-Expression: MIT
@@ -35,6 +35,9 @@ Requires-Dist: traitlets>=5.14.3; extra == "interactive"
35
35
  Provides-Extra: docs
36
36
  Requires-Dist: sphinx; extra == "docs"
37
37
  Requires-Dist: sphinxawesome-theme; extra == "docs"
38
+ Provides-Extra: build
39
+ Requires-Dist: build; extra == "build"
40
+ Requires-Dist: twine; extra == "build"
38
41
  Provides-Extra: dev
39
42
  Requires-Dist: nbclient>=0.10.0; extra == "dev"
40
43
  Requires-Dist: nbconvert>=7.16.4; extra == "dev"
@@ -55,7 +58,6 @@ Requires-Dist: pycodestyle>=2.11.1; extra == "dev"
55
58
  Requires-Dist: black>=24.4.0; extra == "dev"
56
59
  Requires-Dist: isort>=5.13.0; extra == "dev"
57
60
  Requires-Dist: bump-my-version; extra == "dev"
58
- Requires-Dist: build; extra == "dev"
59
61
  Dynamic: license-file
60
62
 
61
63
  <!-- LOGO AND TITLE-->
@@ -128,12 +130,13 @@ Dynamic: license-file
128
130
 
129
131
  1. [About the project](#about-the-project)
130
132
  2. [Installation](#installation)
131
- 3. [Usage](#usage)
132
- 4. [Roadmap](#roadmap)
133
- 5. [Release history](#release-history)
134
- 6. [How to contribute](#how-to-contribute)
135
- 7. [License](#license)
136
- 8. [Contact](#contact)
133
+ 3. [Development Setup](#development-setup)
134
+ 4. [Usage](#usage)
135
+ 5. [Roadmap](#roadmap)
136
+ 6. [Release history](#release-history)
137
+ 7. [How to contribute](#how-to-contribute)
138
+ 8. [License](#license)
139
+ 9. [Contact](#contact)
137
140
 
138
141
  <!-- ABOUT THE PROJECT -->
139
142
  ## About the project
@@ -189,6 +192,100 @@ Needs (runtime dependencies are declared in [pyproject.toml](https://github.com/
189
192
  - [Snowpylot](https://github.com/connellymk/snowpylot) ≥ 1.1.3
190
193
 
191
194
 
195
+ <!-- DEVELOPMENT SETUP -->
196
+ ## Development Setup
197
+
198
+ This project uses [uv](https://github.com/astral-sh/uv) for fast Python package management and project handling.
199
+
200
+ ### Installing uv
201
+
202
+ Install uv following the [official installation guide](https://github.com/astral-sh/uv#installation):
203
+
204
+ ```bash
205
+ # On macOS and Linux
206
+ curl -LsSf https://astral.sh/uv/install.sh | sh
207
+
208
+ # Using pip (alternative)
209
+ pip install uv
210
+ ```
211
+
212
+ ### Setting up the development environment
213
+
214
+ Clone the repository and set up the development environment:
215
+
216
+ ```bash
217
+ git clone https://github.com/2phi/weac
218
+ cd weac
219
+
220
+ # Install Python 3.12+ if not already available
221
+ # uv will automatically use the version specified in .python-version
222
+
223
+ # For basic setup (if only running the package):
224
+ uv sync
225
+
226
+ # For development (recommended for contributors):
227
+ uv sync --extra dev
228
+
229
+ # Activate the virtual environment
230
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
231
+ ```
232
+
233
+ ### Running tests
234
+
235
+ Run the test suite using uv:
236
+
237
+ ```bash
238
+ # Run all tests
239
+ uv run python tests/run_tests.py
240
+
241
+ # Or use pytest directly (if installed)
242
+ uv run pytest
243
+ ```
244
+
245
+ ### Code formatting and linting
246
+
247
+ This project uses [ruff](https://github.com/astral-sh/ruff) for fast Python linting and formatting:
248
+
249
+ ```bash
250
+ # Format code
251
+ uv run ruff format .
252
+
253
+ # Check for linting issues
254
+ uv run ruff check .
255
+
256
+ # Fix auto-fixable linting issues
257
+ uv run ruff check . --fix
258
+ ```
259
+
260
+ ### Building the package
261
+
262
+ Build the package for distribution:
263
+
264
+ ```bash
265
+ # Build wheel and source distribution
266
+ uv build
267
+
268
+ # Install in editable mode for development
269
+ uv pip install -e .
270
+ ```
271
+
272
+ ### Additional uv commands
273
+
274
+ ```bash
275
+ # Update dependencies
276
+ uv sync --upgrade
277
+
278
+ # Add a new dependency
279
+ uv add package-name
280
+
281
+ # Add a development dependency
282
+ uv add --dev package-name
283
+
284
+ # Show environment info
285
+ uv run python --version
286
+ uv run pip list
287
+ ```
288
+
192
289
  <!-- USAGE EXAMPLES -->
193
290
  ## Usage
194
291
 
@@ -0,0 +1,32 @@
1
+ weac/__init__.py,sha256=mOpFbjyMROJ4BdyLMOUvSfVmzVGLZTYXeIqvGZXGtl4,76
2
+ weac/constants.py,sha256=BX8ifZhFciCuzzako1p-2Wh5CWWzR3cPdvyu501UtUs,1194
3
+ weac/logging_config.py,sha256=GJn4Fs80dDRpbPkSPByCmIOLkEI01dbeuMMSv_IDgoM,1103
4
+ weac/analysis/__init__.py,sha256=twF9OYrZiVmBtw6Lyzf5QUhLZNtqFFM2H3OuaYCog1k,486
5
+ weac/analysis/analyzer.py,sha256=AR1yxM5trK1c-_lTEVn9e6e96IhWVWSRgHgQcaLy4Io,27089
6
+ weac/analysis/criteria_evaluator.py,sha256=WY0FQnfzDfN0h1lI5b4LCUV0bwnvUAVDmS4mKCHGbmo,43599
7
+ weac/analysis/plotter.py,sha256=6KzqiXWIDTIFE6qB947eXhtvEAuPp8B4P5idoSX8OwY,66120
8
+ weac/components/__init__.py,sha256=94WIUVjPI3U-grN_7v0oi9bZB3Z8rCBcx9wJPOwJBAQ,439
9
+ weac/components/config.py,sha256=tnOnJ0M-_knZBhdr052nDyyFFAZN0f2hQ68XRuXG6d8,869
10
+ weac/components/criteria_config.py,sha256=f2agU7nXWURFAw8_68igiSk5aICUxwaB9u3Qasit-Q0,2909
11
+ weac/components/layer.py,sha256=sF47006ORRmNnHW7QMk9h-M0vQQR6TL_PKOvInjdNsk,10560
12
+ weac/components/model_input.py,sha256=oyX4p7PgaAtUXXtpvV8XO_KxwOXtG1TB0x7rXxNfCFA,3330
13
+ weac/components/scenario_config.py,sha256=Tam-m9DQtdjmTm-lKQ8Dcjqje04ttJS2X3v_Nn7AUHQ,2563
14
+ weac/components/segment.py,sha256=F279KcAAkRuJKWav_BZ4BanO96WZm4KXtKHinFZki7s,941
15
+ weac/core/__init__.py,sha256=pRyCKD8XD3qXVUWtFG7N3cS91P5x5d8Jpr1hMEgxQ2U,233
16
+ weac/core/eigensystem.py,sha256=b7KXi2weCY9IVlH_7lCTXzKSx_pLdWY-x8BPjHL5nKo,13736
17
+ weac/core/field_quantities.py,sha256=ci7MvhJ4aYdbW6xxH8vHVgWtk5iypCYv6dZ6KjFNvt8,8964
18
+ weac/core/scenario.py,sha256=vHHe-JDWc-8ECVfy6lFGVtJmIIKRc0J9iUEGR8zckdw,6050
19
+ weac/core/slab.py,sha256=YwbAf7ogYDn3p0nXfj3IjVqwA6ro3jBSg2wXb_io_gQ,5125
20
+ weac/core/slab_touchdown.py,sha256=k30FyvclH_Q3mCZNTbJkNhSenv3ja6qSNhhkmi5p04A,14009
21
+ weac/core/system_model.py,sha256=HAZuquY6p1wf5QuUPA6VOVDIICeCxOaeXqk1dSfEzNY,15513
22
+ weac/core/unknown_constants_solver.py,sha256=pzE7soTo7v-jdDECa21pOWRGBp8YeMU3k7egJOemfSY,17766
23
+ weac/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ weac/utils/geldsetzer.py,sha256=DxvpqmWulBacl-mGeAmAuJcv2xqFBx92PEfNC3aeDzk,3500
25
+ weac/utils/misc.py,sha256=lGz0IDDJ_3nvYjSkivPJ5Xscl1D_AmvQLSjaL7SUbKs,3674
26
+ weac/utils/snow_types.py,sha256=eX9-5La6Oom7zh6pg5JZ4MZ6nLdWdc7RoUzm5e6b9w8,1483
27
+ weac/utils/snowpilot_parser.py,sha256=drOLnUg7uzMJMO9qU9x79xZkqQfFPCCQonVpU9S0x8U,13682
28
+ weac-3.0.2.dist-info/licenses/LICENSE,sha256=ojZPWKFHbFGDrlNOvuAKGH9WcKhpLHWZPcQ4SzhK91M,1082
29
+ weac-3.0.2.dist-info/METADATA,sha256=p-UwzxwB89KHKa1zQIahEoGoymDvmhzRAieD3xzDPt0,25586
30
+ weac-3.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ weac-3.0.2.dist-info/top_level.txt,sha256=8tyXUHPFU4Ba_5kPtpwvXo5l6GjJmOnODVBJFygpdeE,5
32
+ weac-3.0.2.dist-info/RECORD,,
@@ -1,32 +0,0 @@
1
- weac/__init__.py,sha256=YrbBczmXbMc2L2DRklNEOFxlfY0Z9FLDnpW8ZhbaIww,76
2
- weac/constants.py,sha256=BX8ifZhFciCuzzako1p-2Wh5CWWzR3cPdvyu501UtUs,1194
3
- weac/logging_config.py,sha256=BFphsxo5s_7E6rPOO2PbOfu0Wy8zHD4AUnnJdqvrn5I,1134
4
- weac/analysis/__init__.py,sha256=twF9OYrZiVmBtw6Lyzf5QUhLZNtqFFM2H3OuaYCog1k,486
5
- weac/analysis/analyzer.py,sha256=AR1yxM5trK1c-_lTEVn9e6e96IhWVWSRgHgQcaLy4Io,27089
6
- weac/analysis/criteria_evaluator.py,sha256=oIVL7Mq3fjyiVx_ms-miULdHE4-EYE2OXoMalM9eU1Q,43458
7
- weac/analysis/plotter.py,sha256=UdiPZKb8vzZAdB1tjVUNfV2INtZaqEiJ7EA8Y-dzxoM,66056
8
- weac/components/__init__.py,sha256=94WIUVjPI3U-grN_7v0oi9bZB3Z8rCBcx9wJPOwJBAQ,439
9
- weac/components/config.py,sha256=tnOnJ0M-_knZBhdr052nDyyFFAZN0f2hQ68XRuXG6d8,869
10
- weac/components/criteria_config.py,sha256=f2agU7nXWURFAw8_68igiSk5aICUxwaB9u3Qasit-Q0,2909
11
- weac/components/layer.py,sha256=sF47006ORRmNnHW7QMk9h-M0vQQR6TL_PKOvInjdNsk,10560
12
- weac/components/model_input.py,sha256=dp695ZMXkSXzJaD-ih9XO0QhnFRPKn8zUJY7g-bomhE,3354
13
- weac/components/scenario_config.py,sha256=Tam-m9DQtdjmTm-lKQ8Dcjqje04ttJS2X3v_Nn7AUHQ,2563
14
- weac/components/segment.py,sha256=F279KcAAkRuJKWav_BZ4BanO96WZm4KXtKHinFZki7s,941
15
- weac/core/__init__.py,sha256=pRyCKD8XD3qXVUWtFG7N3cS91P5x5d8Jpr1hMEgxQ2U,233
16
- weac/core/eigensystem.py,sha256=I4AIGD_uDVKIGv_sv9AXbIXhJ4Eol64BLB-lZF8X3jw,13735
17
- weac/core/field_quantities.py,sha256=ci7MvhJ4aYdbW6xxH8vHVgWtk5iypCYv6dZ6KjFNvt8,8964
18
- weac/core/scenario.py,sha256=f5EZaeFJWMFN_JNOrzVDv5_CeRruHn6jSjGbV9fdxhY,6076
19
- weac/core/slab.py,sha256=nidM3FvTwtj30LmM4OxOfPwqm5TpPzJrH5UvQR6w4no,5150
20
- weac/core/slab_touchdown.py,sha256=D0mVVeRktFpjywuhEiIZHOVJflslfKX3oesx1mMQGxg,14025
21
- weac/core/system_model.py,sha256=XMovKslSTvoNHyxeL7zTFMhAqVOTHnXD7MUPkaGSl9Q,15577
22
- weac/core/unknown_constants_solver.py,sha256=cDXMVKIdhA8AWZQbJnNpM19pvLllvIVc6Mw6xaZGqYw,17839
23
- weac/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- weac/utils/geldsetzer.py,sha256=DxvpqmWulBacl-mGeAmAuJcv2xqFBx92PEfNC3aeDzk,3500
25
- weac/utils/misc.py,sha256=lGz0IDDJ_3nvYjSkivPJ5Xscl1D_AmvQLSjaL7SUbKs,3674
26
- weac/utils/snow_types.py,sha256=eX9-5La6Oom7zh6pg5JZ4MZ6nLdWdc7RoUzm5e6b9w8,1483
27
- weac/utils/snowpilot_parser.py,sha256=5fKC0FKcEJvfFQfZbPW5cHqb_nyzIltweq7Bdz5-a0A,13713
28
- weac-3.0.1.dist-info/licenses/LICENSE,sha256=ojZPWKFHbFGDrlNOvuAKGH9WcKhpLHWZPcQ4SzhK91M,1082
29
- weac-3.0.1.dist-info/METADATA,sha256=-14ZLdvT2GKGX5n8XLJbd3wxPUHvj8Fi5hg85GfdJPs,23666
30
- weac-3.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- weac-3.0.1.dist-info/top_level.txt,sha256=8tyXUHPFU4Ba_5kPtpwvXo5l6GjJmOnODVBJFygpdeE,5
32
- weac-3.0.1.dist-info/RECORD,,
File without changes