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,363 @@
1
+ """
2
+ This module handles the calculation of slab touchdown events.
3
+ Handling the touchdown situation in a PST.
4
+ """
5
+
6
+ import logging
7
+ from typing import Literal, Optional
8
+
9
+ from scipy.optimize import brentq
10
+
11
+ from weac.components.layer import WeakLayer
12
+ from weac.components.scenario_config import ScenarioConfig
13
+ from weac.components.segment import Segment
14
+ from weac.constants import STIFFNESS_COLLAPSE_FACTOR
15
+ from weac.core.eigensystem import Eigensystem
16
+ from weac.core.field_quantities import FieldQuantities
17
+ from weac.core.scenario import Scenario
18
+ from weac.core.unknown_constants_solver import UnknownConstantsSolver
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class SlabTouchdown: # pylint: disable=too-many-instance-attributes,too-few-public-methods
24
+ """
25
+ Handling the touchdown situation in a PST.
26
+ Calculations follow paper Rosendahl et al. (2024)
27
+ `The effect of slab touchdown on anticrack arrest in propagation saw tests`
28
+
29
+ Types of Touchdown:
30
+ `A_free_hanging` : Slab is free hanging (not in contact with the collapsed weak layer)
31
+ touchdown_distance `=` cut_length -> the unsupported segment (touchdown_distance)
32
+ equals the cut length
33
+ `B_point_contact` : End of slab is in contact with the collapsed weak layer
34
+ touchdown_distance `=` cut_length -> the unsupported segment (touchdown_distance)
35
+ equals the cut length
36
+ `C_in_contact` : more of the slab is in contact with the collapsed weak layer
37
+ touchdown_distance `<` cut_length -> the unsupported segment (touchdown_distance)
38
+ is strictly smaller than the cut length
39
+
40
+ The Module does:
41
+ 1. Calculation of Zones of modes `[A_free_hanging, B_point_contact, C_in_contact]`::
42
+
43
+ |+++++++++++++++++++|-------A-------|-------B-------|--------C-------- [...]
44
+ | supported segment | free-hanging | point contact | in contact
45
+ 0 `l_AB` `l_BC`
46
+ through calculation of boundary touchdown_distance `l_AB` and `l_BC`
47
+
48
+ Parameters:
49
+ -----------
50
+ scenario: `Scenario`
51
+ eigensystem: `Eigensystem`
52
+
53
+ Attributes:
54
+ -----------
55
+ l_AB : float
56
+ Length of the crack for transition of stage A to stage B [mm]
57
+ l_BC : float
58
+ Length of the crack for transition of stage B to stage C [mm]
59
+ touchdown_mode : Literal["A_free_hanging", "B_point_contact", "C_in_contact"]
60
+ Type of touchdown mode
61
+ touchdown_distance : float
62
+ Length of the touchdown segment [mm]
63
+ collapsed_weak_layer_kR : Optional[float]
64
+ Rotational spring stiffness of the collapsed weak layer segment
65
+ """
66
+
67
+ # Inputs
68
+ scenario: Scenario
69
+ eigensystem: Eigensystem
70
+
71
+ # Attributes
72
+ collapsed_weak_layer: WeakLayer # WeakLayer with modified stiffness
73
+ collapsed_eigensystem: Eigensystem
74
+ straight_scenario: Scenario
75
+ l_AB: float
76
+ l_BC: float
77
+ touchdown_mode: Literal[
78
+ "A_free_hanging", "B_point_contact", "C_in_contact"
79
+ ] # Three types of contact with collapsed weak layer
80
+ touchdown_distance: float
81
+ collapsed_weak_layer_kR: Optional[float] = None
82
+
83
+ def __init__(self, scenario: Scenario, eigensystem: Eigensystem):
84
+ self.scenario = scenario
85
+ self.eigensystem = eigensystem
86
+
87
+ # Create a new scenario config with phi=0 (flat slab) while preserving other settings
88
+ self.flat_config = ScenarioConfig(
89
+ phi=0.0, # Flat slab for collapsed scenario
90
+ system_type=self.scenario.scenario_config.system_type,
91
+ cut_length=self.scenario.scenario_config.cut_length,
92
+ stiffness_ratio=self.scenario.scenario_config.stiffness_ratio,
93
+ surface_load=self.scenario.scenario_config.surface_load,
94
+ )
95
+
96
+ self.collapsed_eigensystem = self._create_collapsed_eigensystem()
97
+
98
+ self._setup_touchdown_system()
99
+
100
+ def _setup_touchdown_system(self):
101
+ """Calculate touchdown"""
102
+ self._calc_touchdown_mode()
103
+ self._calc_touchdown_distance()
104
+
105
+ def _calc_touchdown_mode(self):
106
+ """Calculate touchdown-mode from thresholds"""
107
+ # Calculate stage transitions
108
+ try:
109
+ self.l_AB = self._calc_l_AB()
110
+ except ValueError:
111
+ self.l_AB = self.scenario.L
112
+ try:
113
+ self.l_BC = self._calc_l_BC()
114
+ except ValueError:
115
+ self.l_BC = self.scenario.L
116
+ # Assign stage
117
+ touchdown_mode = "A_free_hanging"
118
+ if self.scenario.cut_length <= self.l_AB:
119
+ touchdown_mode = "A_free_hanging"
120
+ elif self.l_AB < self.scenario.cut_length <= self.l_BC:
121
+ touchdown_mode = "B_point_contact"
122
+ elif self.l_BC < self.scenario.cut_length:
123
+ touchdown_mode = "C_in_contact"
124
+ self.touchdown_mode = touchdown_mode
125
+
126
+ def _calc_touchdown_distance(self):
127
+ """Calculate touchdown distance"""
128
+ if self.touchdown_mode in ["A_free_hanging"]:
129
+ self.touchdown_distance = self.scenario.cut_length
130
+ elif self.touchdown_mode in ["B_point_contact"]:
131
+ self.touchdown_distance = self.scenario.cut_length
132
+ elif self.touchdown_mode in ["C_in_contact"]:
133
+ self.touchdown_distance = self._calc_touchdown_distance_in_mode_C()
134
+ self.collapsed_weak_layer_kR = self._calc_collapsed_weak_layer_kR()
135
+
136
+ def _calc_l_AB(self):
137
+ """
138
+ Calc transition lengths l_AB
139
+
140
+ Returns
141
+ -------
142
+ l_AB : float
143
+ Length of the crack for transition of stage A to stage B [mm]
144
+ """
145
+ # Unpack variables
146
+ bs = -(self.eigensystem.B11**2 / self.eigensystem.A11 - self.eigensystem.D11)
147
+ ss = self.eigensystem.kA55
148
+ L = self.scenario.L
149
+ crack_h = self.scenario.crack_h
150
+ qn = self.scenario.qn
151
+
152
+ # Create polynomial expression
153
+ def polynomial(x: float) -> float:
154
+ # Spring stiffness of uncollapsed eigensystem of length L - x
155
+ straight_scenario = self._generate_straight_scenario(L - x)
156
+ kRl = self._substitute_stiffness(
157
+ straight_scenario, self.eigensystem, "rot"
158
+ ) # rotational stiffness
159
+ kNl = self._substitute_stiffness(
160
+ straight_scenario, self.eigensystem, "trans"
161
+ ) # pulling stiffness
162
+ c1 = 1 / (8 * bs)
163
+ c2 = 1 / (2 * kRl)
164
+ c3 = 1 / (2 * ss)
165
+ c4 = 1 / kNl
166
+ c5 = -crack_h / qn
167
+ return c1 * x**4 + c2 * x**3 + c3 * x**2 + c4 * x + c5
168
+
169
+ # Find root
170
+ l_AB = brentq(polynomial, L / 1000, 999 / 1000 * L)
171
+
172
+ return l_AB
173
+
174
+ def _calc_l_BC(self) -> float:
175
+ """
176
+ Calc transition lengths l_BC
177
+
178
+ Returns
179
+ -------
180
+ l_BC : float
181
+ Length of the crack for transition of stage B to stage C [mm]
182
+ """
183
+ # Unpack variables
184
+ bs = -(self.eigensystem.B11**2 / self.eigensystem.A11 - self.eigensystem.D11)
185
+ ss = self.eigensystem.kA55
186
+ L = self.scenario.L
187
+ crack_h = self.scenario.crack_h
188
+ qn = self.scenario.qn
189
+
190
+ # Create polynomial function
191
+ def polynomial(x: float) -> float:
192
+ # Spring stiffness of uncollapsed eigensystem of length L - x
193
+ straight_scenario = self._generate_straight_scenario(L - x)
194
+ kRl = self._substitute_stiffness(straight_scenario, self.eigensystem, "rot")
195
+ kNl = self._substitute_stiffness(
196
+ straight_scenario, self.eigensystem, "trans"
197
+ )
198
+ c1 = ss**2 * kRl * kNl * qn
199
+ c2 = 6 * ss**2 * bs * kNl * qn
200
+ c3 = 30 * bs * ss * kRl * kNl * qn
201
+ c4 = 24 * bs * qn * (2 * ss**2 * kRl + 3 * bs * ss * kNl)
202
+ c5 = 72 * bs * (bs * qn * (ss**2 + kRl * kNl) - ss**2 * kRl * kNl * crack_h)
203
+ c6 = 144 * bs * ss * (bs * kRl * qn - bs * ss * kNl * crack_h)
204
+ c7 = -144 * bs**2 * ss * kRl * kNl * crack_h
205
+ return (
206
+ c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
207
+ )
208
+
209
+ # Find root
210
+ l_BC = brentq(polynomial, L / 1000, 999 / 1000 * L)
211
+
212
+ return l_BC
213
+
214
+ def _create_collapsed_eigensystem(self) -> Eigensystem:
215
+ """
216
+ Create the collapsed weak layer and eigensystem with modified stiffness values.
217
+ This centralizes all collapsed-related logic within the SlabTouchdown class.
218
+ """
219
+ # Create collapsed weak layer with increased stiffness
220
+ self.collapsed_weak_layer = self.scenario.weak_layer.model_copy(
221
+ update={
222
+ "kn": self.scenario.weak_layer.kn * STIFFNESS_COLLAPSE_FACTOR,
223
+ "kt": self.scenario.weak_layer.kt * STIFFNESS_COLLAPSE_FACTOR,
224
+ }
225
+ )
226
+
227
+ # Create eigensystem for the collapsed weak layer
228
+ return Eigensystem(
229
+ weak_layer=self.collapsed_weak_layer, slab=self.scenario.slab
230
+ )
231
+
232
+ def _calc_touchdown_distance_in_mode_C(self) -> float:
233
+ """
234
+ Calculate the length of the touchdown element in mode C
235
+ when the slab is in contact.
236
+ """
237
+ # Unpack variables
238
+ bs = -(self.eigensystem.B11**2 / self.eigensystem.A11 - self.eigensystem.D11)
239
+ ss = self.eigensystem.kA55
240
+ L = self.scenario.L
241
+ cut_length = self.scenario.cut_length
242
+ crack_h = self.scenario.crack_h
243
+ qn = self.scenario.qn
244
+
245
+ # Spring stiffness of uncollapsed eigensystem of length L - cut_length
246
+ straight_scenario = self._generate_straight_scenario(L - cut_length)
247
+ kRl = self._substitute_stiffness(straight_scenario, self.eigensystem, "rot")
248
+ kNl = self._substitute_stiffness(straight_scenario, self.eigensystem, "trans")
249
+
250
+ def polynomial(x: float) -> float:
251
+ logger.debug("Eval. Slab Geometry with Touchdown Distance x=%.2f mm", x)
252
+ # Spring stiffness of collapsed eigensystem of length cut_length - x
253
+ straight_scenario = self._generate_straight_scenario(cut_length - x)
254
+ kRr = self._substitute_stiffness(
255
+ straight_scenario, self.collapsed_eigensystem, "rot"
256
+ )
257
+ # define constants
258
+ c1 = ss**2 * kRl * kNl * qn
259
+ c2 = 6 * ss * kNl * qn * (bs * ss + kRl * kRr)
260
+ c3 = 30 * bs * ss * kNl * qn * (kRl + kRr)
261
+ c4 = (
262
+ 24
263
+ * bs
264
+ * qn
265
+ * (2 * ss**2 * kRl + 3 * bs * ss * kNl + 3 * kRl * kRr * kNl)
266
+ )
267
+ c5 = (
268
+ 72
269
+ * bs
270
+ * (
271
+ bs * qn * (ss**2 + kNl * (kRl + kRr))
272
+ + ss * kRl * (2 * kRr * qn - ss * kNl * crack_h)
273
+ )
274
+ )
275
+ c6 = (
276
+ 144
277
+ * bs
278
+ * ss
279
+ * (bs * qn * (kRl + kRr) - kNl * crack_h * (bs * ss + kRl * kRr))
280
+ )
281
+ c7 = -144 * bs**2 * ss * kNl * crack_h * (kRl + kRr)
282
+ return (
283
+ c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
284
+ )
285
+
286
+ # Find root
287
+ touchdown_distance = brentq(
288
+ polynomial, cut_length / 1000, 999 / 1000 * cut_length
289
+ )
290
+
291
+ return touchdown_distance
292
+
293
+ def _calc_collapsed_weak_layer_kR(self) -> float:
294
+ """
295
+ Calculate the rotational stiffness of the collapsed weak layer
296
+ """
297
+ straight_scenario = self._generate_straight_scenario(
298
+ self.scenario.cut_length - self.touchdown_distance
299
+ )
300
+ kR = self._substitute_stiffness(
301
+ straight_scenario, self.collapsed_eigensystem, "rot"
302
+ )
303
+ return kR
304
+
305
+ def _generate_straight_scenario(self, L: float) -> Scenario:
306
+ """
307
+ Generate a straight scenario with a given length.
308
+ """
309
+ segments = [Segment(length=L, has_foundation=True, m=0)]
310
+ straight_scenario = Scenario(
311
+ scenario_config=self.flat_config,
312
+ segments=segments,
313
+ weak_layer=self.scenario.weak_layer,
314
+ slab=self.scenario.slab,
315
+ )
316
+ return straight_scenario
317
+
318
+ def _substitute_stiffness(
319
+ self,
320
+ scenario: Scenario,
321
+ eigensystem: Eigensystem,
322
+ dof: Literal["rot", "trans"] = "rot",
323
+ ) -> float:
324
+ """
325
+ Calc substitute stiffness for beam on elastic foundation.
326
+
327
+ Arguments
328
+ ---------
329
+ dof : string
330
+ Type of substitute spring, either 'rot' or 'trans'. Defaults to 'rot'.
331
+
332
+ Returns
333
+ -------
334
+ has_foundation : stiffness of substitute spring.
335
+ """
336
+
337
+ unknown_constants = UnknownConstantsSolver.solve_for_unknown_constants(
338
+ scenario=scenario, eigensystem=eigensystem, system_type=dof
339
+ )
340
+
341
+ # Calculate field quantities at x=0 (left end)
342
+ Zh0 = eigensystem.zh(x=0, length=scenario.L, has_foundation=True)
343
+ zp0 = eigensystem.zp(x=0, phi=0, has_foundation=True, qs=0)
344
+ C_at_x0 = unknown_constants[:, 0].reshape(-1, 1) # Ensure column vector
345
+ z_at_x0 = Zh0 @ C_at_x0 + zp0
346
+
347
+ # Calculate stiffness based on field quantities
348
+ fq = FieldQuantities(eigensystem=eigensystem)
349
+
350
+ stiffness = None
351
+ if dof in ["rot"]:
352
+ # For rotational stiffness: has_foundation = M / psi
353
+ # Uses M = 1.0 for the moment of inertia.
354
+ psi_val = fq.psi(z_at_x0)[0] # Extract scalar value from the result
355
+ stiffness = abs(1 / psi_val) if abs(psi_val) > 1e-12 else 1e12
356
+ elif dof in ["trans"]:
357
+ # For translational stiffness: has_foundation = V / w
358
+ # Uses w = 1.0 for the weight of the slab.
359
+ w_val = fq.w(z_at_x0)[0] # Extract scalar value from the result
360
+ stiffness = abs(1 / w_val) if abs(w_val) > 1e-12 else 1e12
361
+ if stiffness is None:
362
+ raise ValueError(f"Stiffness for {dof} is None")
363
+ return stiffness