weac 3.0.1__py3-none-any.whl → 3.1.0__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.1.0"
weac/analysis/__init__.py CHANGED
@@ -8,7 +8,7 @@ from .criteria_evaluator import (
8
8
  CoupledCriterionResult,
9
9
  CriteriaEvaluator,
10
10
  FindMinimumForceResult,
11
- SSERRResult,
11
+ SteadyStateResult,
12
12
  )
13
13
  from .plotter import Plotter
14
14
 
@@ -18,6 +18,6 @@ __all__ = [
18
18
  "CoupledCriterionHistory",
19
19
  "CoupledCriterionResult",
20
20
  "FindMinimumForceResult",
21
- "SSERRResult",
21
+ "SteadyStateResult",
22
22
  "Plotter",
23
23
  ]
weac/analysis/analyzer.py CHANGED
@@ -214,7 +214,7 @@ class Analyzer:
214
214
  ], # Convert to t/mm^3
215
215
  "tensile_strength": [
216
216
  layer.tensile_strength for layer in self.sm.slab.layers
217
- ],
217
+ ], # in kPa
218
218
  }
219
219
 
220
220
  # Repeat properties for each grid point in the layer
@@ -225,7 +225,7 @@ class Analyzer:
225
225
  return si
226
226
 
227
227
  @track_analyzer_call
228
- def Sxx(self, Z, phi, dz=2, unit="kPa"):
228
+ def Sxx(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
229
229
  """
230
230
  Compute axial normal stress in slab layers.
231
231
 
@@ -239,6 +239,10 @@ class Analyzer:
239
239
  Element size along z-axis (mm). Default is 2 mm.
240
240
  unit : {'kPa', 'MPa'}, optional
241
241
  Desired output unit. Default is 'kPa'.
242
+ normalize : bool, optional
243
+ Toggle normalization. If True, normalize stress values to the tensile strength of each layer (dimensionless).
244
+ When normalized, the `unit` parameter is ignored and values are returned as ratios.
245
+ Default is False.
242
246
 
243
247
  Returns
244
248
  -------
@@ -258,13 +262,13 @@ class Analyzer:
258
262
  m = Z.shape[1]
259
263
 
260
264
  # Initialize axial normal stress Sxx
261
- Sxx = np.zeros(shape=[n, m])
265
+ Sxx_MPa = np.zeros(shape=[n, m])
262
266
 
263
267
  # Compute axial normal stress Sxx at grid points in MPa
264
268
  for i, z in enumerate(zi):
265
- E = zmesh["E"][i]
269
+ E_MPa = zmesh["E"][i]
266
270
  nu = zmesh["nu"][i]
267
- Sxx[i, :] = E / (1 - nu**2) * self.sm.fq.du_dx(Z, z)
271
+ Sxx_MPa[i, :] = E_MPa / (1 - nu**2) * self.sm.fq.du_dx(Z, z)
268
272
 
269
273
  # Calculate weight load at grid points and superimpose on stress field
270
274
  qt = -rho * G_MM_S2 * np.sin(np.deg2rad(phi))
@@ -274,14 +278,22 @@ class Analyzer:
274
278
  # Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
275
279
  # New Implementation: Changed for numerical stability
276
280
  dz = np.diff(zi)
277
- Sxx[:-1, :] += qt[:-1, np.newaxis] * dz[:, np.newaxis]
278
- Sxx[-1, :] += qt[-1] * dz[-1]
281
+ Sxx_MPa[:-1, :] += qt[:-1, np.newaxis] * dz[:, np.newaxis]
282
+ Sxx_MPa[-1, :] += qt[-1] * dz[-1]
283
+
284
+ # Normalize tensile stresses to tensile strength
285
+ if normalize:
286
+ tensile_strength_kPa = zmesh["tensile_strength"]
287
+ tensile_strength_MPa = tensile_strength_kPa / 1e3
288
+ # Normalize axial normal stress to layers' tensile strength
289
+ normalized_Sxx = Sxx_MPa / tensile_strength_MPa[:, None]
290
+ return normalized_Sxx
279
291
 
280
292
  # Return axial normal stress in specified unit
281
- return convert[unit] * Sxx
293
+ return convert[unit] * Sxx_MPa
282
294
 
283
295
  @track_analyzer_call
284
- def Txz(self, Z, phi, dz=2, unit="kPa"):
296
+ def Txz(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
285
297
  """
286
298
  Compute shear stress in slab layers.
287
299
 
@@ -295,6 +307,9 @@ class Analyzer:
295
307
  Element size along z-axis (mm). Default is 2 mm.
296
308
  unit : {'kPa', 'MPa'}, optional
297
309
  Desired output unit. Default is 'kPa'.
310
+ normalize : bool, optional
311
+ Toggle normalization. If True, normalize shear stress values to the tensile strength of each layer (dimensionless).
312
+ When normalized, the `unit` parameter is ignored and values are returned as ratios. Default is False.
298
313
 
299
314
  Returns
300
315
  -------
@@ -332,14 +347,22 @@ class Analyzer:
332
347
 
333
348
  # Integrate -dsxx_dx along z and add cumulative weight load
334
349
  # to obtain shear stress Txz in MPa
335
- Txz = cumulative_trapezoid(dsxx_dx, zi, axis=0, initial=0)
336
- Txz += cumulative_trapezoid(qt, zi, initial=0)[:, None]
350
+ Txz_MPa = cumulative_trapezoid(dsxx_dx, zi, axis=0, initial=0)
351
+ Txz_MPa += cumulative_trapezoid(qt, zi, initial=0)[:, None]
352
+
353
+ # Normalize shear stresses to tensile strength
354
+ if normalize:
355
+ tensile_strength_kPa = zmesh["tensile_strength"]
356
+ tensile_strength_MPa = tensile_strength_kPa / 1e3
357
+ # Normalize shear stress to layers' tensile strength
358
+ normalized_Txz = Txz_MPa / tensile_strength_MPa[:, None]
359
+ return normalized_Txz
337
360
 
338
361
  # Return shear stress Txz in specified unit
339
- return convert[unit] * Txz
362
+ return convert[unit] * Txz_MPa
340
363
 
341
364
  @track_analyzer_call
342
- def Szz(self, Z, phi, dz=2, unit="kPa"):
365
+ def Szz(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
343
366
  """
344
367
  Compute transverse normal stress in slab layers.
345
368
 
@@ -353,6 +376,10 @@ class Analyzer:
353
376
  Element size along z-axis (mm). Default is 2 mm.
354
377
  unit : {'kPa', 'MPa'}, optional
355
378
  Desired output unit. Default is 'kPa'.
379
+ normalize : bool, optional
380
+ Toggle normalization. If True, normalize stress values to the tensile strength of each layer (dimensionless).
381
+ When normalized, the `unit` parameter is ignored and values are returned as ratios.
382
+ Default is False.
356
383
 
357
384
  Returns
358
385
  -------
@@ -392,11 +419,19 @@ class Analyzer:
392
419
  # Integrate dsxx_dxdx twice along z to obtain transverse
393
420
  # normal stress Szz in MPa
394
421
  integrand = cumulative_trapezoid(dsxx_dxdx, zi, axis=0, initial=0)
395
- Szz = cumulative_trapezoid(integrand, zi, axis=0, initial=0)
396
- Szz += cumulative_trapezoid(-qn, zi, initial=0)[:, None]
422
+ Szz_MPa = cumulative_trapezoid(integrand, zi, axis=0, initial=0)
423
+ Szz_MPa += cumulative_trapezoid(-qn, zi, initial=0)[:, None]
397
424
 
398
- # Return shear stress txz in specified unit
399
- return convert[unit] * Szz
425
+ # Normalize tensile stresses to tensile strength
426
+ if normalize:
427
+ tensile_strength_kPa = zmesh["tensile_strength"]
428
+ tensile_strength_MPa = tensile_strength_kPa / 1e3
429
+ # Normalize transverse normal stress to layers' tensile strength
430
+ normalized_Szz = Szz_MPa / tensile_strength_MPa[:, None]
431
+ return normalized_Szz
432
+
433
+ # Return transverse normal stress Szz in specified unit
434
+ return convert[unit] * Szz_MPa
400
435
 
401
436
  @track_analyzer_call
402
437
  def principal_stress_slab(
@@ -438,6 +473,8 @@ class Analyzer:
438
473
  'min', or if normalization of compressive principal stress
439
474
  is requested.
440
475
  """
476
+ convert = {"kPa": 1e3, "MPa": 1}
477
+
441
478
  # Raise error if specified component is not available
442
479
  if val not in ["min", "max"]:
443
480
  raise ValueError(f"Component {val} not defined.")
@@ -460,9 +497,10 @@ class Analyzer:
460
497
  # Normalize tensile stresses to tensile strength
461
498
  if normalize and val == "max":
462
499
  zmesh = self.get_zmesh(dz=dz)
463
- tensile_strength = zmesh["tensile_strength"]
500
+ tensile_strength_kPa = zmesh["tensile_strength"]
501
+ tensile_strength_converted = tensile_strength_kPa / 1e3 * convert[unit]
464
502
  # Normalize maximum principal stress to layers' tensile strength
465
- normalized_Ps = Ps / tensile_strength[:, None]
503
+ normalized_Ps = Ps / tensile_strength_converted[:, None]
466
504
  return normalized_Ps
467
505
 
468
506
  # Return absolute principal stresses
@@ -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,16 +88,45 @@ 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
96
95
 
97
96
 
98
97
  @dataclass
99
- class SSERRResult:
98
+ class MaximalStressResult:
100
99
  """
101
- Holds the results of the SSERR evaluation.
100
+ Holds the results of the maximal stress evaluation.
101
+
102
+ Attributes:
103
+ -----------
104
+ principal_stress_kPa: np.ndarray
105
+ The principal stress in kPa.
106
+ Sxx_kPa: np.ndarray
107
+ The axial normal stress in kPa.
108
+ principal_stress_norm: np.ndarray
109
+ The normalized principal stress to the tensile strength of the layers.
110
+ Sxx_norm: np.ndarray
111
+ The normalized axial normal stress to the tensile strength of the layers.
112
+ max_principal_stress_norm: float
113
+ The normalized maximum principal stress to the tensile strength of the layers.
114
+ max_Sxx_norm: float
115
+ The normalized maximum axial normal stress to the tensile strength of the layers.
116
+ """
117
+
118
+ principal_stress_kPa: np.ndarray
119
+ Sxx_kPa: np.ndarray
120
+ principal_stress_norm: np.ndarray
121
+ Sxx_norm: np.ndarray
122
+ max_principal_stress_norm: float
123
+ max_Sxx_norm: float
124
+
125
+
126
+ @dataclass
127
+ class SteadyStateResult:
128
+ """
129
+ Holds the results of the Steady State evaluation.
102
130
 
103
131
  Attributes:
104
132
  -----------
@@ -108,15 +136,21 @@ class SSERRResult:
108
136
  The message of the evaluation.
109
137
  touchdown_distance : float
110
138
  The touchdown distance.
111
- SSERR : float
112
- The Steady-State Energy Release Rate calculated with the
113
- touchdown distance from G_I and G_II.
139
+ energy_release_rate : float
140
+ The steady-state energy release rate calculated with the
141
+ touchdown distance from the differential energy release rate.
142
+ maximal_stress_result: MaximalStressResult
143
+ The maximal stresses in the system at the touchdown distance.
144
+ system: SystemModel
145
+ The modified system model used for the steady state evaluation.
114
146
  """
115
147
 
116
148
  converged: bool
117
149
  message: str
118
150
  touchdown_distance: float
119
- SSERR: float
151
+ energy_release_rate: float
152
+ maximal_stress_result: MaximalStressResult
153
+ system: SystemModel
120
154
 
121
155
 
122
156
  @dataclass
@@ -130,9 +164,9 @@ class FindMinimumForceResult:
130
164
  Whether the algorithm converged.
131
165
  critical_skier_weight : float
132
166
  The critical skier weight.
133
- new_segments : List[Segment]
167
+ new_segments : list[Segment]
134
168
  The new segments.
135
- old_segments : List[Segment]
169
+ old_segments : list[Segment]
136
170
  The old segments.
137
171
  iterations : int
138
172
  The number of iterations.
@@ -144,9 +178,9 @@ class FindMinimumForceResult:
144
178
 
145
179
  success: bool
146
180
  critical_skier_weight: float
147
- new_segments: List[Segment]
148
- old_segments: List[Segment]
149
- iterations: Optional[int]
181
+ new_segments: list[Segment]
182
+ old_segments: list[Segment]
183
+ iterations: int | None
150
184
  max_dist_stress: float
151
185
  min_dist_stress: float
152
186
 
@@ -203,10 +237,10 @@ class CriteriaEvaluator:
203
237
 
204
238
  def stress_envelope(
205
239
  self,
206
- sigma: Union[float, np.ndarray],
207
- tau: Union[float, np.ndarray],
240
+ sigma: float | np.ndarray,
241
+ tau: float | np.ndarray,
208
242
  weak_layer: WeakLayer,
209
- method: Optional[str] = None,
243
+ method: str | None = None,
210
244
  ) -> np.ndarray:
211
245
  """
212
246
  Evaluate the stress envelope for given stress components.
@@ -642,12 +676,12 @@ class CriteriaEvaluator:
642
676
  _recursion_depth=_recursion_depth + 1,
643
677
  )
644
678
 
645
- def evaluate_SSERR(
679
+ def evaluate_SteadyState(
646
680
  self,
647
681
  system: SystemModel,
648
682
  vertical: bool = False,
649
683
  print_call_stats: bool = False,
650
- ) -> SSERRResult:
684
+ ) -> SteadyStateResult:
651
685
  """
652
686
  Evaluates the Touchdown Distance in the Steady State and the Steady State
653
687
  Energy Release Rate.
@@ -672,25 +706,36 @@ class CriteriaEvaluator:
672
706
  UserWarning,
673
707
  )
674
708
  system_copy = copy.deepcopy(system)
709
+ system_copy.config.touchdown = True
710
+ system_copy.update_scenario(scenario_config=ScenarioConfig(phi=0.0))
711
+ l_BC = system.slab_touchdown.l_BC
712
+
675
713
  segments = [
676
714
  Segment(length=5e3, has_foundation=True, m=0.0),
677
- Segment(length=5e3, has_foundation=False, m=0.0),
715
+ Segment(length=2 * l_BC, has_foundation=False, m=0.0),
678
716
  ]
679
717
  scenario_config = ScenarioConfig(
680
718
  system_type="vpst-" if vertical else "pst-",
681
- phi=system.scenario.phi,
682
- cut_length=5e3,
719
+ phi=0.0, # Slab Touchdown works only for flat slab
720
+ cut_length=2 * l_BC,
683
721
  )
684
- system_copy.config.touchdown = True
722
+ # system_copy.config.touchdown = True
685
723
  system_copy.update_scenario(segments=segments, scenario_config=scenario_config)
686
724
  touchdown_distance = system_copy.slab_touchdown.touchdown_distance
687
725
  analyzer = Analyzer(system_copy, printing_enabled=print_call_stats)
688
- G, _, _ = analyzer.differential_ERR(unit="J/m^2")
689
- return SSERRResult(
726
+ energy_release_rate, _, _ = analyzer.differential_ERR(unit="J/m^2")
727
+ maximal_stress_result = self._calculate_maximal_stresses(
728
+ system_copy, print_call_stats=print_call_stats
729
+ )
730
+ if print_call_stats:
731
+ analyzer.print_call_stats(message="evaluate_SteadyState Call Statistics")
732
+ return SteadyStateResult(
690
733
  converged=True,
691
- message="SSERR evaluation successful.",
734
+ message="Steady State evaluation successful.",
692
735
  touchdown_distance=touchdown_distance,
693
- SSERR=G,
736
+ energy_release_rate=energy_release_rate,
737
+ maximal_stress_result=maximal_stress_result,
738
+ system=system_copy,
694
739
  )
695
740
 
696
741
  def find_minimum_force(
@@ -825,7 +870,7 @@ class CriteriaEvaluator:
825
870
  system: SystemModel,
826
871
  search_interval: tuple[float, float] | None = None,
827
872
  target: float = 1,
828
- ) -> tuple[float, List[Segment]]:
873
+ ) -> tuple[float, list[Segment]]:
829
874
  """
830
875
  Finds the minimum crack length required to surpass the energy release rate envelope.
831
876
 
@@ -838,7 +883,7 @@ class CriteriaEvaluator:
838
883
  --------
839
884
  minimum_crack_length: float
840
885
  The minimum crack length required to surpass the energy release rate envelope [mm]
841
- new_segments: List[Segment]
886
+ new_segments: list[Segment]
842
887
  The updated list of segments
843
888
  """
844
889
  old_segments = copy.deepcopy(system.scenario.segments)
@@ -928,7 +973,7 @@ class CriteriaEvaluator:
928
973
  self,
929
974
  system: SystemModel,
930
975
  skier_weight: float,
931
- ) -> tuple[float, List[Segment]]:
976
+ ) -> tuple[float, list[Segment]]:
932
977
  """
933
978
  Finds the resulting anticrack length and updated segment configurations
934
979
  for a given skier weight.
@@ -944,7 +989,7 @@ class CriteriaEvaluator:
944
989
  -------
945
990
  new_crack_length: float
946
991
  The total length of the new cracked segments [mm]
947
- new_segments: List[Segment]
992
+ new_segments: list[Segment]
948
993
  The updated list of segments
949
994
  """
950
995
  logger.info(
@@ -1086,7 +1131,7 @@ class CriteriaEvaluator:
1086
1131
 
1087
1132
  def _find_stress_envelope_crossings(
1088
1133
  self, system: SystemModel, weak_layer: WeakLayer
1089
- ) -> List[float]:
1134
+ ) -> list[float]:
1090
1135
  """
1091
1136
  Finds the exact x-coordinates where the stress envelope is crossed.
1092
1137
  """
@@ -1167,3 +1212,51 @@ class CriteriaEvaluator:
1167
1212
 
1168
1213
  # Return the difference from the target
1169
1214
  return g_delta_diff - target
1215
+
1216
+ def _calculate_maximal_stresses(
1217
+ self,
1218
+ system: SystemModel,
1219
+ print_call_stats: bool = False,
1220
+ ) -> MaximalStressResult:
1221
+ """
1222
+ Calculate the maximal stresses in the system.
1223
+
1224
+ Parameters
1225
+ ----------
1226
+ system : SystemModel
1227
+ The system model to analyze.
1228
+ print_call_stats : bool, optional
1229
+ Whether to print analyzer call statistics. Default is False.
1230
+
1231
+ Returns
1232
+ -------
1233
+ MaximalStressResult
1234
+ Object containing both absolute (in kPa) and normalized stress fields,
1235
+ along with maximum normalized stress values.
1236
+ """
1237
+ analyzer = Analyzer(system, printing_enabled=print_call_stats)
1238
+ _, Z, _ = analyzer.rasterize_solution(num=4000, mode="cracked")
1239
+ Sxx_kPa = analyzer.Sxx(Z=Z, phi=system.scenario.phi, dz=5, unit="kPa")
1240
+ principal_stress_kPa = analyzer.principal_stress_slab(
1241
+ Z=Z, phi=system.scenario.phi, dz=5, unit="kPa"
1242
+ )
1243
+ Sxx_norm = analyzer.Sxx(
1244
+ Z=Z, phi=system.scenario.phi, dz=5, unit="kPa", normalize=True
1245
+ )
1246
+ principal_stress_norm = analyzer.principal_stress_slab(
1247
+ Z=Z, phi=system.scenario.phi, dz=5, unit="kPa", normalize=True
1248
+ )
1249
+ max_principal_stress_norm = np.max(principal_stress_norm)
1250
+ max_Sxx_norm = np.max(Sxx_norm)
1251
+ if print_call_stats:
1252
+ analyzer.print_call_stats(
1253
+ message="_calculate_maximal_stresses Call Statistics"
1254
+ )
1255
+ return MaximalStressResult(
1256
+ principal_stress_kPa=principal_stress_kPa,
1257
+ Sxx_kPa=Sxx_kPa,
1258
+ principal_stress_norm=principal_stress_norm,
1259
+ Sxx_norm=Sxx_norm,
1260
+ max_principal_stress_norm=max_principal_stress_norm,
1261
+ max_Sxx_norm=max_Sxx_norm,
1262
+ )