weac 3.0.2__tar.gz → 3.1.1__tar.gz

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.
Files changed (68) hide show
  1. {weac-3.0.2 → weac-3.1.1}/CITATION.cff +1 -1
  2. {weac-3.0.2/src/weac.egg-info → weac-3.1.1}/PKG-INFO +2 -2
  3. {weac-3.0.2 → weac-3.1.1}/pyproject.toml +3 -3
  4. {weac-3.0.2 → weac-3.1.1}/src/weac/__init__.py +1 -1
  5. {weac-3.0.2 → weac-3.1.1}/src/weac/analysis/__init__.py +2 -2
  6. {weac-3.0.2 → weac-3.1.1}/src/weac/analysis/analyzer.py +60 -26
  7. {weac-3.0.2 → weac-3.1.1}/src/weac/analysis/criteria_evaluator.py +112 -15
  8. {weac-3.0.2 → weac-3.1.1}/src/weac/analysis/plotter.py +391 -26
  9. {weac-3.0.2 → weac-3.1.1}/src/weac/components/layer.py +7 -0
  10. {weac-3.0.2 → weac-3.1.1}/src/weac/core/field_quantities.py +27 -23
  11. {weac-3.0.2 → weac-3.1.1}/src/weac/core/unknown_constants_solver.py +13 -13
  12. {weac-3.0.2 → weac-3.1.1}/src/weac/utils/geldsetzer.py +10 -0
  13. {weac-3.0.2 → weac-3.1.1}/src/weac/utils/snow_types.py +5 -0
  14. {weac-3.0.2 → weac-3.1.1/src/weac.egg-info}/PKG-INFO +2 -2
  15. {weac-3.0.2 → weac-3.1.1}/src/weac.egg-info/requires.txt +1 -1
  16. {weac-3.0.2 → weac-3.1.1}/tests/analysis/test_analyzer.py +36 -0
  17. {weac-3.0.2 → weac-3.1.1}/tests/analysis/test_criteria_evaluator.py +53 -5
  18. {weac-3.0.2 → weac-3.1.1}/tests/test_regression_simulation.py +4 -4
  19. {weac-3.0.2 → weac-3.1.1}/LICENSE +0 -0
  20. {weac-3.0.2 → weac-3.1.1}/MANIFEST.in +0 -0
  21. {weac-3.0.2 → weac-3.1.1}/README.md +0 -0
  22. {weac-3.0.2 → weac-3.1.1}/img/bc.png +0 -0
  23. {weac-3.0.2 → weac-3.1.1}/img/layering.png +0 -0
  24. {weac-3.0.2 → weac-3.1.1}/img/logo.png +0 -0
  25. {weac-3.0.2 → weac-3.1.1}/img/model.png +0 -0
  26. {weac-3.0.2 → weac-3.1.1}/img/profiles.png +0 -0
  27. {weac-3.0.2 → weac-3.1.1}/img/systems.png +0 -0
  28. {weac-3.0.2 → weac-3.1.1}/setup.cfg +0 -0
  29. {weac-3.0.2 → weac-3.1.1}/src/weac/components/__init__.py +0 -0
  30. {weac-3.0.2 → weac-3.1.1}/src/weac/components/config.py +0 -0
  31. {weac-3.0.2 → weac-3.1.1}/src/weac/components/criteria_config.py +0 -0
  32. {weac-3.0.2 → weac-3.1.1}/src/weac/components/model_input.py +0 -0
  33. {weac-3.0.2 → weac-3.1.1}/src/weac/components/scenario_config.py +0 -0
  34. {weac-3.0.2 → weac-3.1.1}/src/weac/components/segment.py +0 -0
  35. {weac-3.0.2 → weac-3.1.1}/src/weac/constants.py +0 -0
  36. {weac-3.0.2 → weac-3.1.1}/src/weac/core/__init__.py +0 -0
  37. {weac-3.0.2 → weac-3.1.1}/src/weac/core/eigensystem.py +0 -0
  38. {weac-3.0.2 → weac-3.1.1}/src/weac/core/scenario.py +0 -0
  39. {weac-3.0.2 → weac-3.1.1}/src/weac/core/slab.py +0 -0
  40. {weac-3.0.2 → weac-3.1.1}/src/weac/core/slab_touchdown.py +0 -0
  41. {weac-3.0.2 → weac-3.1.1}/src/weac/core/system_model.py +0 -0
  42. {weac-3.0.2 → weac-3.1.1}/src/weac/logging_config.py +0 -0
  43. {weac-3.0.2 → weac-3.1.1}/src/weac/utils/__init__.py +0 -0
  44. {weac-3.0.2 → weac-3.1.1}/src/weac/utils/misc.py +0 -0
  45. {weac-3.0.2 → weac-3.1.1}/src/weac/utils/snowpilot_parser.py +0 -0
  46. {weac-3.0.2 → weac-3.1.1}/src/weac.egg-info/SOURCES.txt +0 -0
  47. {weac-3.0.2 → weac-3.1.1}/src/weac.egg-info/dependency_links.txt +0 -0
  48. {weac-3.0.2 → weac-3.1.1}/src/weac.egg-info/top_level.txt +0 -0
  49. {weac-3.0.2 → weac-3.1.1}/tests/__init__.py +0 -0
  50. {weac-3.0.2 → weac-3.1.1}/tests/analysis/__init__.py +0 -0
  51. {weac-3.0.2 → weac-3.1.1}/tests/components/__init__.py +0 -0
  52. {weac-3.0.2 → weac-3.1.1}/tests/components/test_configs.py +0 -0
  53. {weac-3.0.2 → weac-3.1.1}/tests/components/test_layer.py +0 -0
  54. {weac-3.0.2 → weac-3.1.1}/tests/core/__init__.py +0 -0
  55. {weac-3.0.2 → weac-3.1.1}/tests/core/test_eigensystem.py +0 -0
  56. {weac-3.0.2 → weac-3.1.1}/tests/core/test_field_quantities.py +0 -0
  57. {weac-3.0.2 → weac-3.1.1}/tests/core/test_scenario.py +0 -0
  58. {weac-3.0.2 → weac-3.1.1}/tests/core/test_slab.py +0 -0
  59. {weac-3.0.2 → weac-3.1.1}/tests/core/test_slab_touchdown.py +0 -0
  60. {weac-3.0.2 → weac-3.1.1}/tests/core/test_system_model.py +0 -0
  61. {weac-3.0.2 → weac-3.1.1}/tests/run_tests.py +0 -0
  62. {weac-3.0.2 → weac-3.1.1}/tests/test_comparison_results.py +0 -0
  63. {weac-3.0.2 → weac-3.1.1}/tests/utils/__init__.py +0 -0
  64. {weac-3.0.2 → weac-3.1.1}/tests/utils/json_helpers.py +0 -0
  65. {weac-3.0.2 → weac-3.1.1}/tests/utils/test_json_helpers.py +0 -0
  66. {weac-3.0.2 → weac-3.1.1}/tests/utils/test_misc.py +0 -0
  67. {weac-3.0.2 → weac-3.1.1}/tests/utils/test_snowpilot_parser.py +0 -0
  68. {weac-3.0.2 → weac-3.1.1}/tests/utils/weac_reference_runner.py +0 -0
@@ -8,7 +8,7 @@ authors:
8
8
  - family-names: "Weissgraeber"
9
9
  given-names: "Philipp"
10
10
  orcid: "https://orcid.org/0000-0001-8320-8672"
11
- version: 3.0.2
11
+ version: 3.1.1
12
12
  date-released: 2021-12-30
13
13
  identifiers:
14
14
  - description: Collection of archived snapshots of all versions of WEAC
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weac
3
- Version: 3.0.2
3
+ Version: 3.1.1
4
4
  Summary: Weak layer anticrack nucleation model
5
5
  Author-email: 2phi GbR <mail@2phi.de>
6
6
  License-Expression: MIT
@@ -17,7 +17,7 @@ Requires-Python: >=3.12
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: matplotlib>=3.9.1
20
- Requires-Dist: numpy>=2.0.1
20
+ Requires-Dist: numpy<2.4.0,>=2.3.5
21
21
  Requires-Dist: scipy>=1.14.0
22
22
  Requires-Dist: pydantic>=2.11.7
23
23
  Requires-Dist: snowpylot>=1.1.3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "weac"
7
- version = "3.0.2"
7
+ version = "3.1.1"
8
8
  authors = [{ name = "2phi GbR", email = "mail@2phi.de" }]
9
9
  description = "Weak layer anticrack nucleation model"
10
10
  readme = "README.md"
@@ -20,7 +20,7 @@ classifiers = [
20
20
  ]
21
21
  dependencies = [
22
22
  "matplotlib>=3.9.1",
23
- "numpy>=2.0.1",
23
+ "numpy>=2.3.5,<2.4.0",
24
24
  "scipy>=1.14.0",
25
25
  "pydantic>=2.11.7",
26
26
  "snowpylot>=1.1.3",
@@ -123,7 +123,7 @@ ignore = [
123
123
  ]
124
124
 
125
125
  [tool.bumpversion]
126
- current_version = "3.0.2"
126
+ current_version = "3.1.1"
127
127
 
128
128
  [[tool.bumpversion.files]]
129
129
  filename = "pyproject.toml"
@@ -2,4 +2,4 @@
2
2
  WEAC - Weak Layer Anticrack Nucleation Model
3
3
  """
4
4
 
5
- __version__ = "3.0.2"
5
+ __version__ = "3.1.1"
@@ -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
  ]
@@ -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,30 +262,34 @@ 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))
271
- # Old Implementation: Changed for numerical stability
272
- # for i, qi in enumerate(qt[:-1]):
273
- # Sxx[i, :] += qi * (zi[i + 1] - zi[i])
274
- # Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
275
- # New Implementation: Changed for numerical stability
275
+
276
276
  dz = np.diff(zi)
277
- Sxx[:-1, :] += qt[:-1, np.newaxis] * dz[:, np.newaxis]
278
- Sxx[-1, :] += qt[-1] * dz[-1]
277
+ Sxx_MPa[:-1, :] += qt[:-1, np.newaxis] * dz[:, np.newaxis]
278
+ Sxx_MPa[-1, :] += qt[-1] * dz[-1]
279
+
280
+ # Normalize tensile stresses to tensile strength
281
+ if normalize:
282
+ tensile_strength_kPa = zmesh["tensile_strength"]
283
+ tensile_strength_MPa = tensile_strength_kPa / 1e3
284
+ # Normalize axial normal stress to layers' tensile strength
285
+ normalized_Sxx = Sxx_MPa / tensile_strength_MPa[:, None]
286
+ return normalized_Sxx
279
287
 
280
288
  # Return axial normal stress in specified unit
281
- return convert[unit] * Sxx
289
+ return convert[unit] * Sxx_MPa
282
290
 
283
291
  @track_analyzer_call
284
- def Txz(self, Z, phi, dz=2, unit="kPa"):
292
+ def Txz(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
285
293
  """
286
294
  Compute shear stress in slab layers.
287
295
 
@@ -295,6 +303,9 @@ class Analyzer:
295
303
  Element size along z-axis (mm). Default is 2 mm.
296
304
  unit : {'kPa', 'MPa'}, optional
297
305
  Desired output unit. Default is 'kPa'.
306
+ normalize : bool, optional
307
+ Toggle normalization. If True, normalize shear stress values to the tensile strength of each layer (dimensionless).
308
+ When normalized, the `unit` parameter is ignored and values are returned as ratios. Default is False.
298
309
 
299
310
  Returns
300
311
  -------
@@ -332,14 +343,22 @@ class Analyzer:
332
343
 
333
344
  # Integrate -dsxx_dx along z and add cumulative weight load
334
345
  # 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]
346
+ Txz_MPa = cumulative_trapezoid(dsxx_dx, zi, axis=0, initial=0)
347
+ Txz_MPa += cumulative_trapezoid(qt, zi, initial=0)[:, None]
348
+
349
+ # Normalize shear stresses to tensile strength
350
+ if normalize:
351
+ tensile_strength_kPa = zmesh["tensile_strength"]
352
+ tensile_strength_MPa = tensile_strength_kPa / 1e3
353
+ # Normalize shear stress to layers' tensile strength
354
+ normalized_Txz = Txz_MPa / tensile_strength_MPa[:, None]
355
+ return normalized_Txz
337
356
 
338
357
  # Return shear stress Txz in specified unit
339
- return convert[unit] * Txz
358
+ return convert[unit] * Txz_MPa
340
359
 
341
360
  @track_analyzer_call
342
- def Szz(self, Z, phi, dz=2, unit="kPa"):
361
+ def Szz(self, Z, phi, dz=2, unit="kPa", normalize: bool = False):
343
362
  """
344
363
  Compute transverse normal stress in slab layers.
345
364
 
@@ -353,6 +372,10 @@ class Analyzer:
353
372
  Element size along z-axis (mm). Default is 2 mm.
354
373
  unit : {'kPa', 'MPa'}, optional
355
374
  Desired output unit. Default is 'kPa'.
375
+ normalize : bool, optional
376
+ Toggle normalization. If True, normalize stress values to the tensile strength of each layer (dimensionless).
377
+ When normalized, the `unit` parameter is ignored and values are returned as ratios.
378
+ Default is False.
356
379
 
357
380
  Returns
358
381
  -------
@@ -366,7 +389,7 @@ class Analyzer:
366
389
  # Get mesh along z-axis
367
390
  zmesh = self.get_zmesh(dz=dz)
368
391
  zi = zmesh["z"]
369
- rho = zmesh["rho"]
392
+ rho_t_mm3 = zmesh["rho"]
370
393
  qs = self.sm.scenario.surface_load
371
394
  # Get dimensions of stress field (n rows, m columns)
372
395
  n = len(zi)
@@ -387,16 +410,24 @@ class Analyzer:
387
410
  dsxx_dxdx[i, :] = E / (1 - nu**2) * (du0_dxdxdx + z * dpsi_dxdxdx)
388
411
 
389
412
  # Calculate weight load at grid points
390
- qn = rho * G_MM_S2 * np.cos(np.deg2rad(phi))
413
+ qn = -rho_t_mm3 * G_MM_S2 * np.cos(np.deg2rad(phi))
391
414
 
392
415
  # Integrate dsxx_dxdx twice along z to obtain transverse
393
416
  # normal stress Szz in MPa
394
417
  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]
418
+ Szz_MPa = cumulative_trapezoid(integrand, zi, axis=0, initial=0)
419
+ Szz_MPa += cumulative_trapezoid(qn, zi, initial=0)[:, None]
420
+
421
+ # Normalize tensile stresses to tensile strength
422
+ if normalize:
423
+ tensile_strength_kPa = zmesh["tensile_strength"]
424
+ tensile_strength_MPa = tensile_strength_kPa / 1e3
425
+ # Normalize transverse normal stress to layers' tensile strength
426
+ normalized_Szz = Szz_MPa / tensile_strength_MPa[:, None]
427
+ return normalized_Szz
397
428
 
398
- # Return shear stress txz in specified unit
399
- return convert[unit] * Szz
429
+ # Return transverse normal stress Szz in specified unit
430
+ return convert[unit] * Szz_MPa
400
431
 
401
432
  @track_analyzer_call
402
433
  def principal_stress_slab(
@@ -438,6 +469,8 @@ class Analyzer:
438
469
  'min', or if normalization of compressive principal stress
439
470
  is requested.
440
471
  """
472
+ convert = {"kPa": 1e3, "MPa": 1}
473
+
441
474
  # Raise error if specified component is not available
442
475
  if val not in ["min", "max"]:
443
476
  raise ValueError(f"Component {val} not defined.")
@@ -460,9 +493,10 @@ class Analyzer:
460
493
  # Normalize tensile stresses to tensile strength
461
494
  if normalize and val == "max":
462
495
  zmesh = self.get_zmesh(dz=dz)
463
- tensile_strength = zmesh["tensile_strength"]
496
+ tensile_strength_kPa = zmesh["tensile_strength"]
497
+ tensile_strength_converted = tensile_strength_kPa / 1e3 * convert[unit]
464
498
  # Normalize maximum principal stress to layers' tensile strength
465
- normalized_Ps = Ps / tensile_strength[:, None]
499
+ normalized_Ps = Ps / tensile_strength_converted[:, None]
466
500
  return normalized_Ps
467
501
 
468
502
  # Return absolute principal stresses
@@ -95,9 +95,42 @@ class CoupledCriterionResult:
95
95
 
96
96
 
97
97
  @dataclass
98
- class SSERRResult:
98
+ class MaximalStressResult:
99
99
  """
100
- 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
+ slab_tensile_criterion: float
117
+ The slab tensile criterion, i.e. the portion of the slab thickness that is prone
118
+ to fail under tensile stresses in the steady state (between 0 and 1).
119
+ """
120
+
121
+ principal_stress_kPa: np.ndarray
122
+ Sxx_kPa: np.ndarray
123
+ principal_stress_norm: np.ndarray
124
+ Sxx_norm: np.ndarray
125
+ max_principal_stress_norm: float
126
+ max_Sxx_norm: float
127
+ slab_tensile_criterion: float
128
+
129
+
130
+ @dataclass
131
+ class SteadyStateResult:
132
+ """
133
+ Holds the results of the Steady State evaluation.
101
134
 
102
135
  Attributes:
103
136
  -----------
@@ -107,15 +140,21 @@ class SSERRResult:
107
140
  The message of the evaluation.
108
141
  touchdown_distance : float
109
142
  The touchdown distance.
110
- SSERR : float
111
- The Steady-State Energy Release Rate calculated with the
112
- touchdown distance from G_I and G_II.
143
+ energy_release_rate : float
144
+ The steady-state energy release rate calculated with the
145
+ touchdown distance from the differential energy release rate.
146
+ maximal_stress_result: MaximalStressResult
147
+ The maximal stresses in the system at the touchdown distance.
148
+ system: SystemModel
149
+ The modified system model used for the steady state evaluation.
113
150
  """
114
151
 
115
152
  converged: bool
116
153
  message: str
117
154
  touchdown_distance: float
118
- SSERR: float
155
+ energy_release_rate: float
156
+ maximal_stress_result: MaximalStressResult
157
+ system: SystemModel
119
158
 
120
159
 
121
160
  @dataclass
@@ -641,12 +680,12 @@ class CriteriaEvaluator:
641
680
  _recursion_depth=_recursion_depth + 1,
642
681
  )
643
682
 
644
- def evaluate_SSERR(
683
+ def evaluate_SteadyState(
645
684
  self,
646
685
  system: SystemModel,
647
686
  vertical: bool = False,
648
687
  print_call_stats: bool = False,
649
- ) -> SSERRResult:
688
+ ) -> SteadyStateResult:
650
689
  """
651
690
  Evaluates the Touchdown Distance in the Steady State and the Steady State
652
691
  Energy Release Rate.
@@ -671,9 +710,9 @@ class CriteriaEvaluator:
671
710
  UserWarning,
672
711
  )
673
712
  system_copy = copy.deepcopy(system)
674
- system_copy.config.touchdown = True
713
+ system_copy.toggle_touchdown(True)
675
714
  system_copy.update_scenario(scenario_config=ScenarioConfig(phi=0.0))
676
- l_BC = system.slab_touchdown.l_BC
715
+ l_BC = system_copy.slab_touchdown.l_BC
677
716
 
678
717
  segments = [
679
718
  Segment(length=5e3, has_foundation=True, m=0.0),
@@ -684,16 +723,22 @@ class CriteriaEvaluator:
684
723
  phi=0.0, # Slab Touchdown works only for flat slab
685
724
  cut_length=2 * l_BC,
686
725
  )
687
- # system_copy.config.touchdown = True
688
726
  system_copy.update_scenario(segments=segments, scenario_config=scenario_config)
689
727
  touchdown_distance = system_copy.slab_touchdown.touchdown_distance
690
728
  analyzer = Analyzer(system_copy, printing_enabled=print_call_stats)
691
- G, _, _ = analyzer.differential_ERR(unit="J/m^2")
692
- return SSERRResult(
729
+ energy_release_rate, _, _ = analyzer.differential_ERR(unit="J/m^2")
730
+ maximal_stress_result = self._calculate_maximal_stresses(
731
+ system_copy, print_call_stats=print_call_stats
732
+ )
733
+ if print_call_stats:
734
+ analyzer.print_call_stats(message="evaluate_SteadyState Call Statistics")
735
+ return SteadyStateResult(
693
736
  converged=True,
694
- message="SSERR evaluation successful.",
737
+ message="Steady State evaluation successful.",
695
738
  touchdown_distance=touchdown_distance,
696
- SSERR=G,
739
+ energy_release_rate=energy_release_rate,
740
+ maximal_stress_result=maximal_stress_result,
741
+ system=system_copy,
697
742
  )
698
743
 
699
744
  def find_minimum_force(
@@ -1170,3 +1215,55 @@ class CriteriaEvaluator:
1170
1215
 
1171
1216
  # Return the difference from the target
1172
1217
  return g_delta_diff - target
1218
+
1219
+ def _calculate_maximal_stresses(
1220
+ self,
1221
+ system: SystemModel,
1222
+ print_call_stats: bool = False,
1223
+ ) -> MaximalStressResult:
1224
+ """
1225
+ Calculate the maximal stresses in the system.
1226
+
1227
+ Parameters
1228
+ ----------
1229
+ system : SystemModel
1230
+ The system model to analyze.
1231
+ print_call_stats : bool, optional
1232
+ Whether to print analyzer call statistics. Default is False.
1233
+
1234
+ Returns
1235
+ -------
1236
+ MaximalStressResult
1237
+ Object containing both absolute (in kPa) and normalized stress fields,
1238
+ along with maximum normalized stress values.
1239
+ """
1240
+ analyzer = Analyzer(system, printing_enabled=print_call_stats)
1241
+ _, Z, _ = analyzer.rasterize_solution(num=4000, mode="cracked")
1242
+ Sxx_kPa = analyzer.Sxx(Z=Z, phi=system.scenario.phi, dz=5, unit="kPa")
1243
+ principal_stress_kPa = analyzer.principal_stress_slab(
1244
+ Z=Z, phi=system.scenario.phi, dz=5, unit="kPa"
1245
+ )
1246
+ Sxx_norm = analyzer.Sxx(
1247
+ Z=Z, phi=system.scenario.phi, dz=5, unit="kPa", normalize=True
1248
+ )
1249
+ principal_stress_norm = analyzer.principal_stress_slab(
1250
+ Z=Z, phi=system.scenario.phi, dz=5, unit="kPa", normalize=True
1251
+ )
1252
+ max_principal_stress_norm = np.max(principal_stress_norm)
1253
+ max_Sxx_norm = np.max(Sxx_norm)
1254
+ # evaluate for each height level if the slab is prone to fail under tensile stresses
1255
+ height_level_prone_to_fail = np.max(Sxx_norm, axis=1)
1256
+ slab_tensile_criterion = np.mean(height_level_prone_to_fail)
1257
+ if print_call_stats:
1258
+ analyzer.print_call_stats(
1259
+ message="_calculate_maximal_stresses Call Statistics"
1260
+ )
1261
+ return MaximalStressResult(
1262
+ principal_stress_kPa=principal_stress_kPa,
1263
+ Sxx_kPa=Sxx_kPa,
1264
+ principal_stress_norm=principal_stress_norm,
1265
+ Sxx_norm=Sxx_norm,
1266
+ max_principal_stress_norm=max_principal_stress_norm,
1267
+ max_Sxx_norm=max_Sxx_norm,
1268
+ slab_tensile_criterion=slab_tensile_criterion,
1269
+ )