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.
weac/mixins.py DELETED
@@ -1,2083 +0,0 @@
1
- """Mixins for the elastic analysis of layered snow slabs."""
2
- # pylint: disable=invalid-name,too-many-locals,too-many-arguments,too-many-lines
3
-
4
- # Standard library imports
5
- from functools import partial
6
-
7
- # Third party imports
8
- import numpy as np
9
- from scipy.integrate import cumulative_trapezoid, quad
10
- from scipy.optimize import brentq
11
-
12
- # Module imports
13
- from weac.tools import calc_vertical_bc_center_of_gravity, tensile_strength_slab
14
-
15
-
16
- class FieldQuantitiesMixin:
17
- """
18
- Mixin for field quantities.
19
-
20
- Provides methods for the computation of displacements, stresses,
21
- strains, and energy release rates from the solution vector.
22
- """
23
-
24
- # pylint: disable=no-self-use
25
- def w(self, Z, unit="mm"):
26
- """
27
- Get centerline deflection w.
28
-
29
- Arguments
30
- ---------
31
- Z : ndarray
32
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
33
- unit : {'m', 'cm', 'mm', 'um'}, optional
34
- Desired output unit. Default is mm.
35
-
36
- Returns
37
- -------
38
- float
39
- Deflection w (in specified unit) of the slab.
40
- """
41
- convert = {
42
- "m": 1e-3, # meters
43
- "cm": 1e-1, # centimeters
44
- "mm": 1, # millimeters
45
- "um": 1e3, # micrometers
46
- }
47
- return convert[unit] * Z[2, :]
48
-
49
- def dw_dx(self, Z):
50
- """
51
- Get first derivative w' of the centerline deflection.
52
-
53
- Arguments
54
- ---------
55
- Z : ndarray
56
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
57
-
58
- Returns
59
- -------
60
- float
61
- First derivative w' of the deflection of the slab.
62
- """
63
- return Z[3, :]
64
-
65
- def psi(self, Z, unit="rad"):
66
- """
67
- Get midplane rotation psi.
68
-
69
- Arguments
70
- ---------
71
- Z : ndarray
72
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
73
- unit : {'deg', 'degrees', 'rad', 'radians'}, optional
74
- Desired output unit. Default is radians.
75
-
76
- Returns
77
- -------
78
- psi : float
79
- Cross-section rotation psi (radians) of the slab.
80
- """
81
- if unit in ["deg", "degree", "degrees"]:
82
- psi = np.rad2deg(Z[4, :])
83
- elif unit in ["rad", "radian", "radians"]:
84
- psi = Z[4, :]
85
- return psi
86
-
87
- def dpsi_dx(self, Z):
88
- """
89
- Get first derivative psi' of the midplane rotation.
90
-
91
- Arguments
92
- ---------
93
- Z : ndarray
94
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
95
-
96
- Returns
97
- -------
98
- float
99
- First derivative psi' of the midplane rotation (radians/mm)
100
- of the slab.
101
- """
102
- return Z[5, :]
103
-
104
- # pylint: enable=no-self-use
105
- def u(self, Z, z0, unit="mm"):
106
- """
107
- Get horizontal displacement u = u0 + z0 psi.
108
-
109
- Arguments
110
- ---------
111
- Z : ndarray
112
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
113
- z0 : float
114
- Z-coordinate (mm) where u is to be evaluated.
115
- unit : {'m', 'cm', 'mm', 'um'}, optional
116
- Desired output unit. Default is mm.
117
-
118
- Returns
119
- -------
120
- float
121
- Horizontal displacement u (unit) of the slab.
122
- """
123
- convert = {
124
- "m": 1e-3, # meters
125
- "cm": 1e-1, # centimeters
126
- "mm": 1, # millimeters
127
- "um": 1e3, # micrometers
128
- }
129
- return convert[unit] * (Z[0, :] + z0 * self.psi(Z))
130
-
131
- def du_dx(self, Z, z0):
132
- """
133
- Get first derivative of the horizontal displacement.
134
-
135
- Arguments
136
- ---------
137
- Z : ndarray
138
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
139
- z0 : float
140
- Z-coordinate (mm) where u is to be evaluated.
141
-
142
- Returns
143
- -------
144
- float
145
- First derivative u' = u0' + z0 psi' of the horizontal
146
- displacement of the slab.
147
- """
148
- return Z[1, :] + z0 * self.dpsi_dx(Z)
149
-
150
- def N(self, Z):
151
- """
152
- Get the axial normal force N = A11 u' + B11 psi' in the slab.
153
-
154
- Arguments
155
- ---------
156
- Z : ndarray
157
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
158
-
159
- Returns
160
- -------
161
- float
162
- Axial normal force N (N) in the slab.
163
- """
164
- return self.A11 * Z[1, :] + self.B11 * Z[5, :]
165
-
166
- def M(self, Z):
167
- """
168
- Get bending moment M = B11 u' + D11 psi' in the slab.
169
-
170
- Arguments
171
- ---------
172
- Z : ndarray
173
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
174
-
175
- Returns
176
- -------
177
- float
178
- Bending moment M (Nmm) in the slab.
179
- """
180
- return self.B11 * Z[1, :] + self.D11 * Z[5, :]
181
-
182
- def V(self, Z):
183
- """
184
- Get vertical shear force V = kA55(w' + psi).
185
-
186
- Arguments
187
- ---------
188
- Z : ndarray
189
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
190
-
191
- Returns
192
- -------
193
- float
194
- Vertical shear force V (N) in the slab.
195
- """
196
- return self.kA55 * (Z[3, :] + Z[4, :])
197
-
198
- def sig(self, Z, unit="MPa"):
199
- """
200
- Get weak-layer normal stress.
201
-
202
- Arguments
203
- ---------
204
- Z : ndarray
205
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
206
- unit : {'MPa', 'kPa'}, optional
207
- Desired output unit. Default is MPa.
208
-
209
- Returns
210
- -------
211
- float
212
- Weak-layer normal stress sigma (in specified unit).
213
- """
214
- convert = {"kPa": 1e3, "MPa": 1}
215
- return -convert[unit] * self.kn * self.w(Z)
216
-
217
- def tau(self, Z, unit="MPa"):
218
- """
219
- Get weak-layer shear stress.
220
-
221
- Arguments
222
- ---------
223
- Z : ndarray
224
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
225
- unit : {'MPa', 'kPa'}, optional
226
- Desired output unit. Default is MPa.
227
-
228
- Returns
229
- -------
230
- float
231
- Weak-layer shear stress tau (in specified unit).
232
- """
233
- convert = {"kPa": 1e3, "MPa": 1}
234
- return (
235
- -convert[unit]
236
- * self.kt
237
- * (self.dw_dx(Z) * self.t / 2 - self.u(Z, z0=self.h / 2))
238
- )
239
-
240
- def eps(self, Z):
241
- """
242
- Get weak-layer normal strain.
243
-
244
- Arguments
245
- ---------
246
- Z : ndarray
247
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
248
-
249
- Returns
250
- -------
251
- float
252
- Weak-layer normal strain epsilon.
253
- """
254
- return -self.w(Z) / self.t
255
-
256
- def gamma(self, Z):
257
- """
258
- Get weak-layer shear strain.
259
-
260
- Arguments
261
- ---------
262
- Z : ndarray
263
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
264
-
265
- Returns
266
- -------
267
- float
268
- Weak-layer shear strain gamma.
269
- """
270
- return self.dw_dx(Z) / 2 - self.u(Z, z0=self.h / 2) / self.t
271
-
272
- def Gi(self, Ztip, unit="kJ/m^2"):
273
- """
274
- Get mode I differential energy release rate at crack tip.
275
-
276
- Arguments
277
- ---------
278
- Ztip : ndarray
279
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T
280
- at the crack tip.
281
- unit : {'N/mm', 'kJ/m^2', 'J/m^2'}, optional
282
- Desired output unit. Default is kJ/m^2.
283
-
284
- Returns
285
- -------
286
- float
287
- Mode I differential energy release rate (N/mm = kJ/m^2
288
- or J/m^2) at the crack tip.
289
- """
290
- convert = {
291
- "J/m^2": 1e3, # joule per square meter
292
- "kJ/m^2": 1, # kilojoule per square meter
293
- "N/mm": 1, # newton per millimeter
294
- }
295
- return convert[unit] * self.sig(Ztip) ** 2 / (2 * self.kn)
296
-
297
- def Gii(self, Ztip, unit="kJ/m^2"):
298
- """
299
- Get mode II differential energy release rate at crack tip.
300
-
301
- Arguments
302
- ---------
303
- Ztip : ndarray
304
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T
305
- at the crack tip.
306
- unit : {'N/mm', 'kJ/m^2', 'J/m^2'}, optional
307
- Desired output unit. Default is kJ/m^2 = N/mm.
308
-
309
- Returns
310
- -------
311
- float
312
- Mode II differential energy release rate (N/mm = kJ/m^2
313
- or J/m^2) at the crack tip.
314
- """
315
- convert = {
316
- "J/m^2": 1e3, # joule per square meter
317
- "kJ/m^2": 1, # kilojoule per square meter
318
- "N/mm": 1, # newton per millimeter
319
- }
320
- return convert[unit] * self.tau(Ztip) ** 2 / (2 * self.kt)
321
-
322
- def int1(self, x, z0, z1):
323
- """
324
- Get mode I crack opening integrand at integration points xi.
325
-
326
- Arguments
327
- ---------
328
- x : float, ndarray
329
- X-coordinate where integrand is to be evaluated (mm).
330
- z0 : callable
331
- Function that returns the solution vector of the uncracked
332
- configuration.
333
- z1 : callable
334
- Function that returns the solution vector of the cracked
335
- configuration.
336
-
337
- Returns
338
- -------
339
- float or ndarray
340
- Integrant of the mode I crack opening integral.
341
- """
342
- return self.sig(z0(x)) * self.eps(z1(x)) * self.t
343
-
344
- def int2(self, x, z0, z1):
345
- """
346
- Get mode II crack opening integrand at integration points xi.
347
-
348
- Arguments
349
- ---------
350
- x : float, ndarray
351
- X-coordinate where integrand is to be evaluated (mm).
352
- z0 : callable
353
- Function that returns the solution vector of the uncracked
354
- configuration.
355
- z1 : callable
356
- Function that returns the solution vector of the cracked
357
- configuration.
358
-
359
- Returns
360
- -------
361
- float or ndarray
362
- Integrant of the mode II crack opening integral.
363
- """
364
- return self.tau(z0(x)) * self.gamma(z1(x)) * self.t
365
-
366
- def dz_dx(self, z, phi):
367
- """
368
- Get first derivative z'(x) = K*z(x) + q of the solution vector.
369
-
370
- z'(x) = [u'(x) u''(x) w'(x) w''(x) psi'(x), psi''(x)]^T
371
-
372
- Parameters
373
- ----------
374
- z : ndarray
375
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
376
- phi : float
377
- Inclination (degrees). Counterclockwise positive.
378
-
379
- Returns
380
- -------
381
- ndarray, float
382
- First derivative z'(x) for the solution vector (6x1).
383
- """
384
- K = self.calc_system_matrix()
385
- q = self.get_load_vector(phi)
386
- return np.dot(K, z) + q
387
-
388
- def dz_dxdx(self, z, phi):
389
- """
390
- Get second derivative z''(x) = K*z'(x) of the solution vector.
391
-
392
- z''(x) = [u''(x) u'''(x) w''(x) w'''(x) psi''(x), psi'''(x)]^T
393
-
394
- Parameters
395
- ----------
396
- z : ndarray
397
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
398
- phi : float
399
- Inclination (degrees). Counterclockwise positive.
400
-
401
- Returns
402
- -------
403
- ndarray, float
404
- Second derivative z''(x) = (K*z(x) + q)' = K*z'(x) = K*(K*z(x) + q)
405
- of the solution vector (6x1).
406
- """
407
- K = self.calc_system_matrix()
408
- q = self.get_load_vector(phi)
409
- dz_dx = np.dot(K, z) + q
410
- return np.dot(K, dz_dx)
411
-
412
- def du0_dxdx(self, z, phi):
413
- """
414
- Get second derivative of the horiz. centerline displacement u0''(x).
415
-
416
- Parameters
417
- ----------
418
- z : ndarray
419
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
420
- phi : float
421
- Inclination (degrees). Counterclockwise positive.
422
-
423
- Returns
424
- -------
425
- ndarray, float
426
- Second derivative of the horizontal centerline displacement
427
- u0''(x) (1/mm).
428
- """
429
- return self.dz_dx(z, phi)[1, :]
430
-
431
- def dpsi_dxdx(self, z, phi):
432
- """
433
- Get second derivative of the cross-section rotation psi''(x).
434
-
435
- Parameters
436
- ----------
437
- z : ndarray
438
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
439
- phi : float
440
- Inclination (degrees). Counterclockwise positive.
441
-
442
- Returns
443
- -------
444
- ndarray, float
445
- Second derivative of the cross-section rotation psi''(x) (1/mm^2).
446
- """
447
- return self.dz_dx(z, phi)[5, :]
448
-
449
- def du0_dxdxdx(self, z, phi):
450
- """
451
- Get third derivative of the horiz. centerline displacement u0'''(x).
452
-
453
- Parameters
454
- ----------
455
- z : ndarray
456
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
457
- phi : float
458
- Inclination (degrees). Counterclockwise positive.
459
-
460
- Returns
461
- -------
462
- ndarray, float
463
- Third derivative of the horizontal centerline displacement
464
- u0'''(x) (1/mm^2).
465
- """
466
- return self.dz_dxdx(z, phi)[1, :]
467
-
468
- def dpsi_dxdxdx(self, z, phi):
469
- """
470
- Get third derivative of the cross-section rotation psi'''(x).
471
-
472
- Parameters
473
- ----------
474
- z : ndarray
475
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
476
- phi : float
477
- Inclination (degrees). Counterclockwise positive.
478
-
479
- Returns
480
- -------
481
- ndarray, float
482
- Third derivative of the cross-section rotation psi'''(x) (1/mm^3).
483
- """
484
- return self.dz_dxdx(z, phi)[5, :]
485
-
486
-
487
- class SlabContactMixin:
488
- """
489
- Mixin for handling the touchdown situation in a PST.
490
-
491
- Provides Methods for the calculation of substitute spring stiffnesses,
492
- cracklength-tresholds and element lengths.
493
- """
494
-
495
- # pylint: disable=too-many-instance-attributes
496
-
497
- def set_columnlength(self, L):
498
- """
499
- Set cracklength.
500
-
501
- Arguments
502
- ---------
503
- L : float
504
- Column length of a PST (mm).
505
- """
506
- self.L = L
507
-
508
- def set_cracklength(self, a):
509
- """
510
- Set cracklength.
511
-
512
- Arguments
513
- ---------
514
- a : float
515
- Cracklength in a PST (mm).
516
- """
517
- self.a = a
518
-
519
- def set_tc(self, cf):
520
- """
521
- Set height of the crack.
522
-
523
- Arguments
524
- ---------
525
- cf : float
526
- Collapse-factor. Ratio of the crack height to the
527
- uncollapsed weak-layer height.
528
- """
529
- # mark argument as intentionally unused (API compatibility)
530
- _ = cf
531
- # subtract displacement under constact load from collapsed wl height
532
- qn = self.calc_qn()
533
- collapse_height = 4.70 * (1 - np.exp(-self.t / 7.78))
534
- self.tc = collapse_height - qn / self.kn
535
-
536
- def set_phi(self, phi):
537
- """
538
- Set inclination of the slab.
539
-
540
- Arguments
541
- ---------
542
- phi : float
543
- Inclination of the slab (°).
544
- """
545
- self.phi = phi
546
-
547
- def set_stiffness_ratio(self, ratio=1000):
548
- """
549
- Set ratio between collapsed and uncollapsed weak-layer stiffness.
550
-
551
- Parameters
552
- ----------
553
- ratio : int, optional
554
- Stiffness ratio between collapsed and uncollapsed weak layer.
555
- Default is 1000.
556
- """
557
- self.ratio = ratio
558
-
559
- def calc_qn(self):
560
- """
561
- Calc total surface normal load.
562
-
563
- Returns
564
- -------
565
- float
566
- Total surface normal load (N/mm).
567
- """
568
- return self.get_weight_load(self.phi)[0] + self.get_surface_load(self.phi)[0]
569
-
570
- def calc_qt(self):
571
- """
572
- Calc total surface normal load.
573
-
574
- Returns
575
- -------
576
- float
577
- Total surface normal load (N/mm).
578
- """
579
- return self.get_weight_load(self.phi)[1] + self.get_surface_load(self.phi)[1]
580
-
581
- def substitute_stiffness(self, L, support="rested", dof="rot"):
582
- """
583
- Calc substitute stiffness for beam on elastic foundation.
584
-
585
- Arguments
586
- ---------
587
- L : float
588
- Total length of the PST-column (mm).
589
- support : string
590
- Type of segment foundation. Defaults to 'rested'.
591
- dof : string
592
- Type of substitute spring, either 'rot' or 'trans'. Defaults to 'rot'.
593
-
594
- Returns
595
- -------
596
- k : stiffness of substitute spring.
597
- """
598
- # adjust system to substitute system
599
- if dof in ["rot"]:
600
- tempsys = self.system
601
- self.system = "rot"
602
- if dof in ["trans"]:
603
- tempsys = self.system
604
- self.system = "trans"
605
-
606
- # Change eigensystem for rested segment
607
- if support in ["rested"]:
608
- tempkn = self.kn
609
- tempkt = self.kt
610
- self.kn = self.ratio * self.kn
611
- self.kt = self.ratio * self.kt
612
- self.calc_system_matrix()
613
- self.calc_eigensystem()
614
-
615
- # prepare list of segment characteristics
616
- segments = {
617
- "li": np.array([L, 0.0]),
618
- "mi": np.array([0]),
619
- "ki": np.array([True, True]),
620
- }
621
- # solve system of equations
622
- constants = self.assemble_and_solve(phi=0, **segments)
623
- # calculate stiffness
624
- _, z_pst, _ = self.rasterize_solution(C=constants, phi=0, num=1, **segments)
625
- if dof in ["rot"]:
626
- k = abs(1 / self.psi(z_pst)[0])
627
- if dof in ["trans"]:
628
- k = abs(1 / self.w(z_pst)[0])
629
-
630
- # Reset to previous system and eigensystem
631
- self.system = tempsys
632
- if support in ["rested"]:
633
- self.kn = tempkn
634
- self.kt = tempkt
635
- self.calc_system_matrix()
636
- self.calc_eigensystem()
637
-
638
- return k
639
-
640
- def calc_a1(self):
641
- """
642
- Calc transition lengths a1 (aAB).
643
-
644
- Returns
645
- -------
646
- a1 : float
647
- Length of the crack for transition of stage A to stage B (mm).
648
- """
649
- # Unpack variables
650
- bs = -(self.B11**2 / self.A11 - self.D11)
651
- ss = self.kA55
652
- L = self.L
653
- tc = self.tc
654
- qn = self.calc_qn()
655
-
656
- # Create polynomial expression
657
- def polynomial(x):
658
- # Spring stiffness supported segment
659
- kRl = self.substitute_stiffness(L - x, "supported", "rot")
660
- kNl = self.substitute_stiffness(L - x, "supported", "trans")
661
- c1 = 1 / (8 * bs)
662
- c2 = 1 / (2 * kRl)
663
- c3 = 1 / (2 * ss)
664
- c4 = 1 / kNl
665
- c5 = -tc / qn
666
- return c1 * x**4 + c2 * x**3 + c3 * x**2 + c4 * x + c5
667
-
668
- # Find root
669
- a1 = brentq(polynomial, L / 1000, 999 / 1000 * L)
670
-
671
- return a1
672
-
673
- def calc_a2(self):
674
- """
675
- Calc transition lengths a2 (aBC).
676
-
677
- Returns
678
- -------
679
- a2 : float
680
- Length of the crack for transition of stage B to stage C (mm).
681
- """
682
- # Unpack variables
683
- bs = -(self.B11**2 / self.A11 - self.D11)
684
- ss = self.kA55
685
- L = self.L
686
- tc = self.tc
687
- qn = self.calc_qn()
688
-
689
- # Create polynomial function
690
- def polynomial(x):
691
- # Spring stiffness supported segment
692
- kRl = self.substitute_stiffness(L - x, "supported", "rot")
693
- kNl = self.substitute_stiffness(L - x, "supported", "trans")
694
- c1 = ss**2 * kRl * kNl * qn
695
- c2 = 6 * ss**2 * bs * kNl * qn
696
- c3 = 30 * bs * ss * kRl * kNl * qn
697
- c4 = 24 * bs * qn * (2 * ss**2 * kRl + 3 * bs * ss * kNl)
698
- c5 = 72 * bs * (bs * qn * (ss**2 + kRl * kNl) - ss**2 * kRl * kNl * tc)
699
- c6 = 144 * bs * ss * (bs * kRl * qn - bs * ss * kNl * tc)
700
- c7 = -144 * bs**2 * ss * kRl * kNl * tc
701
- return (
702
- c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
703
- )
704
-
705
- # Find root
706
- a2 = brentq(polynomial, L / 1000, 999 / 1000 * L)
707
-
708
- return a2
709
-
710
- def calc_lA(self):
711
- """
712
- Calculate the length of the touchdown element in mode A.
713
- """
714
- lA = self.a
715
-
716
- return lA
717
-
718
- def calc_lB(self):
719
- """
720
- Calculate the length of the touchdown element in mode B.
721
- """
722
- lB = self.a
723
-
724
- return lB
725
-
726
- def calc_lC(self):
727
- """
728
- Calculate the length of the touchdown element in mode C.
729
- """
730
- # Unpack variables
731
- bs = -(self.B11**2 / self.A11 - self.D11)
732
- ss = self.kA55
733
- L = self.L
734
- a = self.a
735
- tc = self.tc
736
- qn = self.calc_qn()
737
-
738
- def polynomial(x):
739
- # Spring stiffness supported segment
740
- kRl = self.substitute_stiffness(L - a, "supported", "rot")
741
- kNl = self.substitute_stiffness(L - a, "supported", "trans")
742
- # Spring stiffness rested segment
743
- kRr = self.substitute_stiffness(a - x, "rested", "rot")
744
- # define constants
745
- c1 = ss**2 * kRl * kNl * qn
746
- c2 = 6 * ss * kNl * qn * (bs * ss + kRl * kRr)
747
- c3 = 30 * bs * ss * kNl * qn * (kRl + kRr)
748
- c4 = (
749
- 24
750
- * bs
751
- * qn
752
- * (2 * ss**2 * kRl + 3 * bs * ss * kNl + 3 * kRl * kRr * kNl)
753
- )
754
- c5 = (
755
- 72
756
- * bs
757
- * (
758
- bs * qn * (ss**2 + kNl * (kRl + kRr))
759
- + ss * kRl * (2 * kRr * qn - ss * kNl * tc)
760
- )
761
- )
762
- c6 = (
763
- 144
764
- * bs
765
- * ss
766
- * (bs * qn * (kRl + kRr) - kNl * tc * (bs * ss + kRl * kRr))
767
- )
768
- c7 = -144 * bs**2 * ss * kNl * tc * (kRl + kRr)
769
- return (
770
- c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
771
- )
772
-
773
- # Find root
774
- lC = brentq(polynomial, a / 1000, 999 / 1000 * a)
775
-
776
- return lC
777
-
778
- def set_touchdown_attributes(self, L, a, cf, phi, ratio):
779
- """Set class attributes for touchdown consideration"""
780
- self.set_columnlength(L)
781
- self.set_cracklength(a)
782
- self.set_phi(phi)
783
- self.set_tc(cf)
784
- self.set_stiffness_ratio(ratio)
785
-
786
- def calc_touchdown_mode(self):
787
- """Calculate touchdown-mode from thresholds"""
788
- if self.touchdown:
789
- # Calculate stage transitions
790
- self.a1 = self.calc_a1()
791
- self.a2 = self.calc_a2()
792
- # Assign stage
793
- if self.a <= self.a1:
794
- mode = "A"
795
- elif self.a1 < self.a <= self.a2:
796
- mode = "B"
797
- elif self.a2 < self.a:
798
- mode = "C"
799
- self.mode = mode
800
- else:
801
- self.mode = "A"
802
-
803
- def calc_touchdown_length(self):
804
- """Calculate touchdown length"""
805
- if self.mode in ["A"]:
806
- self.td = self.calc_lA()
807
- elif self.mode in ["B"]:
808
- self.td = self.calc_lB()
809
- elif self.mode in ["C"]:
810
- self.td = self.calc_lC()
811
-
812
- def calc_touchdown_system(self, L, a, cf, phi, ratio=1000):
813
- """Calculate touchdown"""
814
- self.set_touchdown_attributes(L, a, cf, phi, ratio)
815
- self.calc_touchdown_mode()
816
- self.calc_touchdown_length()
817
-
818
-
819
- class SolutionMixin:
820
- """
821
- Mixin for the solution of boundary value problems.
822
-
823
- Provides methods for the assembly of the system of equations
824
- and for the computation of the free constants.
825
- """
826
-
827
- def bc(self, z, k=False, pos="mid"):
828
- """
829
- Provide equations for free (pst) or infinite (skiers) ends.
830
-
831
- Arguments
832
- ---------
833
- z : ndarray
834
- Solution vector (6x1) at a certain position x.
835
- l : float, optional
836
- Length of the segment in consideration. Default is zero.
837
- k : boolean
838
- Indicates whether segment has foundation(True) or not (False).
839
- Default is False.
840
- pos : {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
841
- Determines whether the segement under consideration
842
- is a left boundary segement (left, l), one of the
843
- center segement (mid, m), or a right boundary
844
- segement (right, r). Default is 'mid'.
845
-
846
- Returns
847
- -------
848
- bc : ndarray
849
- Boundary condition vector (lenght 3) at position x.
850
- """
851
-
852
- # Set boundary conditions for PST-systems
853
- if self.system in ["pst-", "-pst"]:
854
- if not k:
855
- if self.mode in ["A"]:
856
- # Free end
857
- bc = np.array([self.N(z), self.M(z), self.V(z)])
858
- elif self.mode in ["B"] and pos in ["r", "right"]:
859
- # Touchdown right
860
- bc = np.array([self.N(z), self.M(z), self.w(z)])
861
- elif self.mode in ["B"] and pos in ["l", "left"]: # Kann dieser Block
862
- # Touchdown left # verschwinden? Analog zu 'A'
863
- bc = np.array([self.N(z), self.M(z), self.w(z)])
864
- elif self.mode in ["C"] and pos in ["r", "right"]:
865
- # Spring stiffness
866
- kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
867
- # Touchdown right
868
- bc = np.array([self.N(z), self.M(z) + kR * self.psi(z), self.w(z)])
869
- elif self.mode in ["C"] and pos in ["l", "left"]:
870
- # Spring stiffness
871
- kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
872
- # Touchdown left
873
- bc = np.array([self.N(z), self.M(z) - kR * self.psi(z), self.w(z)])
874
- else:
875
- # Free end
876
- bc = np.array([self.N(z), self.M(z), self.V(z)])
877
- # Set boundary conditions for PST-systems with vertical faces
878
- elif self.system in ["-vpst", "vpst-"]:
879
- bc = np.array([self.N(z), self.M(z), self.V(z)])
880
- # Set boundary conditions for SKIER-systems
881
- elif self.system in ["skier", "skiers"]:
882
- # Infinite end (vanishing complementary solution)
883
- bc = np.array([self.u(z, z0=0), self.w(z), self.psi(z)])
884
- # Set boundary conditions for substitute spring calculus
885
- elif self.system in ["rot", "trans"]:
886
- bc = np.array([self.N(z), self.M(z), self.V(z)])
887
- else:
888
- raise ValueError(
889
- f"Boundary conditions not defined for system of type {self.system}."
890
- )
891
-
892
- return bc
893
-
894
- def eqs(self, zl, zr, k=False, pos="mid"):
895
- """
896
- Provide boundary or transmission conditions for beam segments.
897
-
898
- Arguments
899
- ---------
900
- zl : ndarray
901
- Solution vector (6x1) at left end of beam segement.
902
- zr : ndarray
903
- Solution vector (6x1) at right end of beam segement.
904
- k : boolean
905
- Indicates whether segment has foundation(True) or not (False).
906
- Default is False.
907
- pos: {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
908
- Determines whether the segement under consideration
909
- is a left boundary segement (left, l), one of the
910
- center segement (mid, m), or a right boundary
911
- segement (right, r). Default is 'mid'.
912
-
913
- Returns
914
- -------
915
- eqs : ndarray
916
- Vector (of length 9) of boundary conditions (3) and
917
- transmission conditions (6) for boundary segements
918
- or vector of transmission conditions (of length 6+6)
919
- for center segments.
920
- """
921
- if pos in ("l", "left"):
922
- eqs = np.array(
923
- [
924
- self.bc(zl, k, pos)[0], # Left boundary condition
925
- self.bc(zl, k, pos)[1], # Left boundary condition
926
- self.bc(zl, k, pos)[2], # Left boundary condition
927
- self.u(zr, z0=0), # ui(xi = li)
928
- self.w(zr), # wi(xi = li)
929
- self.psi(zr), # psii(xi = li)
930
- self.N(zr), # Ni(xi = li)
931
- self.M(zr), # Mi(xi = li)
932
- self.V(zr),
933
- ]
934
- ) # Vi(xi = li)
935
- elif pos in ("m", "mid"):
936
- eqs = np.array(
937
- [
938
- -self.u(zl, z0=0), # -ui(xi = 0)
939
- -self.w(zl), # -wi(xi = 0)
940
- -self.psi(zl), # -psii(xi = 0)
941
- -self.N(zl), # -Ni(xi = 0)
942
- -self.M(zl), # -Mi(xi = 0)
943
- -self.V(zl), # -Vi(xi = 0)
944
- self.u(zr, z0=0), # ui(xi = li)
945
- self.w(zr), # wi(xi = li)
946
- self.psi(zr), # psii(xi = li)
947
- self.N(zr), # Ni(xi = li)
948
- self.M(zr), # Mi(xi = li)
949
- self.V(zr),
950
- ]
951
- ) # Vi(xi = li)
952
- elif pos in ("r", "right"):
953
- eqs = np.array(
954
- [
955
- -self.u(zl, z0=0), # -ui(xi = 0)
956
- -self.w(zl), # -wi(xi = 0)
957
- -self.psi(zl), # -psii(xi = 0)
958
- -self.N(zl), # -Ni(xi = 0)
959
- -self.M(zl), # -Mi(xi = 0)
960
- -self.V(zl), # -Vi(xi = 0)
961
- self.bc(zr, k, pos)[0], # Right boundary condition
962
- self.bc(zr, k, pos)[1], # Right boundary condition
963
- self.bc(zr, k, pos)[2],
964
- ]
965
- ) # Right boundary condition
966
- else:
967
- raise ValueError(
968
- (
969
- f"Invalid position argument {pos} given. "
970
- "Valid segment positions are l, m, and r, "
971
- "or left, mid and right."
972
- )
973
- )
974
- return eqs
975
-
976
- def calc_segments(
977
- self,
978
- li: list[float] | list[int] | bool = False,
979
- mi: list[float] | list[int] | bool = False,
980
- ki: list[bool] | bool = False,
981
- k0: list[bool] | bool = False,
982
- L: float = 1e4,
983
- a: float = 0,
984
- m: float = 0,
985
- phi: float = 0,
986
- cf: float = 0.5,
987
- ratio: float = 1000,
988
- **kwargs,
989
- ):
990
- """
991
- Assemble lists defining the segments.
992
-
993
- This includes length (li), foundation (ki, k0), and skier
994
- weight (mi).
995
-
996
- Arguments
997
- ---------
998
- li : squence, optional
999
- List of lengths of segements(mm). Used for system 'skiers'.
1000
- mi : squence, optional
1001
- List of skier weigths (kg) at segement boundaries. Used for
1002
- system 'skiers'.
1003
- ki : squence, optional
1004
- List of one bool per segement indicating whether segement
1005
- has foundation (True) or not (False) in the cracked state.
1006
- Used for system 'skiers'.
1007
- k0 : squence, optional
1008
- List of one bool per segement indicating whether segement
1009
- has foundation(True) or not (False) in the uncracked state.
1010
- Used for system 'skiers'.
1011
- L : float, optional
1012
- Total length of model (mm). Used for systems 'pst-', '-pst',
1013
- 'vpst-', '-vpst', and 'skier'.
1014
- a : float, optional
1015
- Crack length (mm). Used for systems 'pst-', '-pst', 'pst-',
1016
- '-pst', and 'skier'.
1017
- phi : float, optional
1018
- Inclination (degree).
1019
- m : float, optional
1020
- Weight of skier (kg) in the axial center of the model.
1021
- Used for system 'skier'.
1022
- cf : float, optional
1023
- Collapse factor. Ratio of the crack height to the uncollapsed
1024
- weak-layer height. Used for systems 'pst-', '-pst'. Default is 0.5.
1025
- ratio : float, optional
1026
- Stiffness ratio between collapsed and uncollapsed weak layer.
1027
- Default is 1000.
1028
-
1029
- Returns
1030
- -------
1031
- segments : dict
1032
- Dictionary with lists of touchdown booleans (tdi), segement
1033
- lengths (li), skier weights (mi), and foundation booleans
1034
- in the cracked (ki) and uncracked (k0) configurations.
1035
- """
1036
-
1037
- _ = kwargs # Unused arguments
1038
-
1039
- # Precompute touchdown properties
1040
- self.calc_touchdown_system(L=L, a=a, cf=cf, phi=phi, ratio=ratio)
1041
-
1042
- # Assemble list defining the segments
1043
- if self.system == "skiers":
1044
- li = np.array(li) # Segment lengths
1045
- mi = np.array(mi) # Skier weights
1046
- ki = np.array(ki) # Crack
1047
- k0 = np.array(k0) # No crack
1048
- elif self.system == "pst-":
1049
- li = np.array([L - self.a, self.td]) # Segment lengths
1050
- mi = np.array([0]) # Skier weights
1051
- ki = np.array([True, False]) # Crack
1052
- k0 = np.array([True, True]) # No crack
1053
- elif self.system == "-pst":
1054
- li = np.array([self.td, L - self.a]) # Segment lengths
1055
- mi = np.array([0]) # Skier weights
1056
- ki = np.array([False, True]) # Crack
1057
- k0 = np.array([True, True]) # No crack
1058
- elif self.system == "vpst-":
1059
- li = np.array([L - a, a]) # Segment lengths
1060
- mi = np.array([0]) # Skier weights
1061
- ki = np.array([True, False]) # Crack
1062
- k0 = np.array([True, True]) # No crack
1063
- elif self.system == "-vpst":
1064
- li = np.array([a, L - a]) # Segment lengths
1065
- mi = np.array([0]) # Skier weights
1066
- ki = np.array([False, True]) # Crack
1067
- k0 = np.array([True, True]) # No crack
1068
- elif self.system == "skier":
1069
- lb = (L - self.a) / 2 # Half bedded length
1070
- lf = self.a / 2 # Half free length
1071
- li = np.array([lb, lf, lf, lb]) # Segment lengths
1072
- mi = np.array([0, m, 0]) # Skier weights
1073
- ki = np.array([True, False, False, True]) # Crack
1074
- k0 = np.array([True, True, True, True]) # No crack
1075
- else:
1076
- raise ValueError(f"System {self.system} is not implemented.")
1077
-
1078
- # Fill dictionary
1079
- segments = {
1080
- "nocrack": {"li": li, "mi": mi, "ki": k0},
1081
- "crack": {"li": li, "mi": mi, "ki": ki},
1082
- "both": {"li": li, "mi": mi, "ki": ki, "k0": k0},
1083
- }
1084
- return segments
1085
-
1086
- def assemble_and_solve(self, phi, li, mi, ki):
1087
- """
1088
- Compute free constants for arbitrary beam assembly.
1089
-
1090
- Assemble LHS from supported and unsupported segments in the form
1091
- [ ] [ zh1 0 0 ... 0 0 0 ][ ] [ ] [ ] left
1092
- [ ] [ zh1 zh2 0 ... 0 0 0 ][ ] [ ] [ ] mid
1093
- [ ] [ 0 zh2 zh3 ... 0 0 0 ][ ] [ ] [ ] mid
1094
- [z0] = [ ... ... ... ... ... ... ... ][ C ] + [ zp ] = [ rhs ] mid
1095
- [ ] [ 0 0 0 ... zhL zhM 0 ][ ] [ ] [ ] mid
1096
- [ ] [ 0 0 0 ... 0 zhM zhN ][ ] [ ] [ ] mid
1097
- [ ] [ 0 0 0 ... 0 0 zhN ][ ] [ ] [ ] right
1098
- and solve for constants C.
1099
-
1100
- Arguments
1101
- ---------
1102
- phi : float
1103
- Inclination (degrees).
1104
- li : ndarray
1105
- List of lengths of segements (mm).
1106
- mi : ndarray
1107
- List of skier weigths (kg) at segement boundaries.
1108
- ki : ndarray
1109
- List of one bool per segement indicating whether segement
1110
- has foundation (True) or not (False).
1111
-
1112
- Returns
1113
- -------
1114
- C : ndarray
1115
- Matrix(6xN) of solution constants for a system of N
1116
- segements. Columns contain the 6 constants of each segement.
1117
- """
1118
- # --- CATCH ERRORS ----------------------------------------------------
1119
-
1120
- # No foundation
1121
- if not any(ki):
1122
- raise ValueError("Provide at least one supported segment.")
1123
- # Mismatch of number of segements and transisions
1124
- if len(li) != len(ki) or len(li) - 1 != len(mi):
1125
- raise ValueError(
1126
- "Make sure len(li)=N, len(ki)=N, and "
1127
- "len(mi)=N-1 for a system of N segments."
1128
- )
1129
-
1130
- if self.system not in ["pst-", "-pst", "vpst-", "-vpst", "rot", "trans"]:
1131
- # Boundary segments must be on foundation for infinite BCs
1132
- if not all([ki[0], ki[-1]]):
1133
- raise ValueError(
1134
- "Provide supported boundary segments in "
1135
- "order to account for infinite extensions."
1136
- )
1137
- # Make sure infinity boundary conditions are far enough from skiers
1138
- if li[0] < 5e3 or li[-1] < 5e3:
1139
- print(
1140
- (
1141
- "WARNING: Boundary segments are short. Make sure "
1142
- "the complementary solution has decayed to the "
1143
- "boundaries."
1144
- )
1145
- )
1146
-
1147
- # --- PREPROCESSING ---------------------------------------------------
1148
-
1149
- # Determine size of linear system of equations
1150
- nS = len(li) # Number of beam segments
1151
-
1152
- nDOF = 6 # Number of free constants per segment
1153
-
1154
- # Add dummy segment if only one segment provided
1155
- if nS == 1:
1156
- li.append(0)
1157
- ki.append(True)
1158
- mi.append(0)
1159
- nS = 2
1160
-
1161
- # Assemble position vector
1162
- pi = np.full(nS, "m")
1163
- pi[0], pi[-1] = "l", "r"
1164
-
1165
- # Initialize matrices
1166
- zh0 = np.zeros([nS * 6, nS * nDOF])
1167
- zp0 = np.zeros([nS * 6, 1])
1168
- rhs = np.zeros([nS * 6, 1])
1169
-
1170
- # --- ASSEMBLE LINEAR SYSTEM OF EQUATIONS -----------------------------
1171
-
1172
- # Loop through segments to assemble left-hand side
1173
- for i in range(nS):
1174
- # Length, foundation and position of segment i
1175
- l, k, pos = li[i], ki[i], pi[i]
1176
- # Transmission conditions at left and right segment ends
1177
- zhi = self.eqs(
1178
- zl=self.zh(x=0, l=l, bed=k), zr=self.zh(x=l, l=l, bed=k), k=k, pos=pos
1179
- )
1180
- zpi = self.eqs(
1181
- zl=self.zp(x=0, phi=phi, bed=k),
1182
- zr=self.zp(x=l, phi=phi, bed=k),
1183
- k=k,
1184
- pos=pos,
1185
- )
1186
- # Rows for left-hand side assembly
1187
- start = 0 if i == 0 else 3
1188
- stop = 6 if i == nS - 1 else 9
1189
- # Assemble left-hand side
1190
- zh0[(6 * i - start) : (6 * i + stop), i * nDOF : (i + 1) * nDOF] = zhi
1191
- zp0[(6 * i - start) : (6 * i + stop)] += zpi
1192
-
1193
- # Loop through loads to assemble right-hand side
1194
- for i, m in enumerate(mi, start=1):
1195
- # Get skier loads
1196
- Fn, Ft = self.get_skier_load(m, phi)
1197
- # Right-hand side for transmission from segment i-1 to segment i
1198
- rhs[6 * i : 6 * i + 3] = np.vstack([Ft, -Ft * self.h / 2, Fn])
1199
- # Set rhs so that complementary integral vanishes at boundaries
1200
- if self.system not in ["pst-", "-pst", "rested"]:
1201
- rhs[:3] = self.bc(self.zp(x=0, phi=phi, bed=ki[0]))
1202
- rhs[-3:] = self.bc(self.zp(x=li[-1], phi=phi, bed=ki[-1]))
1203
-
1204
- # Set rhs for vertical faces
1205
- if self.system in ["vpst-", "-vpst"]:
1206
- # Calculate center of gravity and mass of
1207
- # added or cut off slab segement
1208
- xs, zs, m = calc_vertical_bc_center_of_gravity(self.slab, phi)
1209
- # Convert slope angle to radians
1210
- phi = np.deg2rad(phi)
1211
- # Translate inbto section forces and moments
1212
- N = -self.g * m * np.sin(phi)
1213
- M = -self.g * m * (xs * np.cos(phi) + zs * np.sin(phi))
1214
- V = self.g * m * np.cos(phi)
1215
- # Add to right-hand side
1216
- rhs[:3] = np.vstack([N, M, V]) # left end
1217
- rhs[-3:] = np.vstack([N, M, V]) # right end
1218
-
1219
- # Loop through segments to set touchdown conditions at rhs
1220
- for i in range(nS):
1221
- # Length, foundation and position of segment i
1222
- l, k, pos = li[i], ki[i], pi[i]
1223
- # Set displacement BC in stage B
1224
- if not k and bool(self.mode in ["B"]):
1225
- if i == 0:
1226
- rhs[:3] = np.vstack([0, 0, self.tc])
1227
- if i == (nS - 1):
1228
- rhs[-3:] = np.vstack([0, 0, self.tc])
1229
- # Set normal force and displacement BC for stage C
1230
- if not k and bool(self.mode in ["C"]):
1231
- N = self.calc_qt() * (self.a - self.td)
1232
- if i == 0:
1233
- rhs[:3] = np.vstack([-N, 0, self.tc])
1234
- if i == (nS - 1):
1235
- rhs[-3:] = np.vstack([N, 0, self.tc])
1236
-
1237
- # Rhs for substitute spring stiffness
1238
- if self.system in ["rot"]:
1239
- # apply arbitrary moment of 1 at left boundary
1240
- rhs = rhs * 0
1241
- rhs[1] = 1
1242
- if self.system in ["trans"]:
1243
- # apply arbitrary force of 1 at left boundary
1244
- rhs = rhs * 0
1245
- rhs[2] = 1
1246
-
1247
- # --- SOLVE -----------------------------------------------------------
1248
-
1249
- # Solve z0 = zh0*C + zp0 = rhs for constants, i.e. zh0*C = rhs - zp0
1250
- C = np.linalg.solve(zh0, rhs - zp0)
1251
- # Sort (nDOF = 6) constants for each segment into columns of a matrix
1252
- return C.reshape([-1, nDOF]).T
1253
-
1254
-
1255
- class AnalysisMixin:
1256
- """
1257
- Mixin for the analysis of model outputs.
1258
-
1259
- Provides methods for the analysis of layered slabs on compliant
1260
- elastic foundations.
1261
- """
1262
-
1263
- def rasterize_solution(
1264
- self,
1265
- C: np.ndarray,
1266
- phi: float,
1267
- li: list[float] | bool,
1268
- ki: list[bool] | bool,
1269
- num: int = 250,
1270
- **kwargs,
1271
- ):
1272
- """
1273
- Compute rasterized solution vector.
1274
-
1275
- Arguments
1276
- ---------
1277
- C : ndarray
1278
- Vector of free constants.
1279
- phi : float
1280
- Inclination (degrees).
1281
- li : ndarray
1282
- List of segment lengths (mm).
1283
- ki : ndarray
1284
- List of booleans indicating whether segment lies on
1285
- a foundation or not.
1286
- num : int
1287
- Number of grid points.
1288
-
1289
- Returns
1290
- -------
1291
- xq : ndarray
1292
- Grid point x-coordinates at which solution vector
1293
- is discretized.
1294
- zq : ndarray
1295
- Matrix with solution vectors as colums at grid
1296
- points xq.
1297
- xb : ndarray
1298
- Grid point x-coordinates that lie on a foundation.
1299
- """
1300
- # Unused arguments
1301
- _ = kwargs
1302
-
1303
- # Drop zero-length segments
1304
- li = abs(li)
1305
- isnonzero = li > 0
1306
- C, ki, li = C[:, isnonzero], ki[isnonzero], li[isnonzero]
1307
-
1308
- # Compute number of plot points per segment (+1 for last segment)
1309
- nq = np.ceil(li / li.sum() * num).astype("int")
1310
- nq[-1] += 1
1311
-
1312
- # Provide cumulated length and plot point lists
1313
- lic = np.insert(np.cumsum(li), 0, 0)
1314
- nqc = np.insert(np.cumsum(nq), 0, 0)
1315
-
1316
- # Initialize arrays
1317
- issupported = np.full(nq.sum(), True)
1318
- xq = np.full(nq.sum(), np.nan)
1319
- zq = np.full([6, xq.size], np.nan)
1320
-
1321
- # Loop through segments
1322
- for i, l in enumerate(li):
1323
- # Get local x-coordinates of segment i
1324
- xi = np.linspace(0, l, num=nq[i], endpoint=(i == li.size - 1)) # pylint: disable=superfluous-parens
1325
- # Compute start and end coordinates of segment i
1326
- x0 = lic[i]
1327
- # Assemble global coordinate vector
1328
- xq[nqc[i] : nqc[i + 1]] = x0 + xi
1329
- # Mask coordinates not on foundation (including endpoints)
1330
- if not ki[i]:
1331
- issupported[nqc[i] : nqc[i + 1]] = False
1332
- # Compute segment solution
1333
- zi = self.z(xi, C[:, [i]], l, phi, ki[i])
1334
- # Assemble global solution matrix
1335
- zq[:, nqc[i] : nqc[i + 1]] = zi
1336
-
1337
- # Make sure cracktips are included
1338
- transmissionbool = [ki[j] or ki[j + 1] for j, _ in enumerate(ki[:-1])]
1339
- for i, truefalse in enumerate(transmissionbool, start=1):
1340
- issupported[nqc[i]] = truefalse
1341
-
1342
- # Assemble vector of coordinates on foundation
1343
- xb = np.full(nq.sum(), np.nan)
1344
- xb[issupported] = xq[issupported]
1345
-
1346
- return xq, zq, xb
1347
-
1348
- def ginc(self, C0, C1, phi, li, ki, k0, **kwargs):
1349
- """
1350
- Compute incremental energy relase rate of of all cracks.
1351
-
1352
- Arguments
1353
- ---------
1354
- C0 : ndarray
1355
- Free constants of uncracked solution.
1356
- C1 : ndarray
1357
- Free constants of cracked solution.
1358
- phi : float
1359
- Inclination (degress).
1360
- li : ndarray
1361
- List of segment lengths.
1362
- ki : ndarray
1363
- List of booleans indicating whether segment lies on
1364
- a foundation or not in the cracked configuration.
1365
- k0 : ndarray
1366
- List of booleans indicating whether segment lies on
1367
- a foundation or not in the uncracked configuration.
1368
-
1369
- Returns
1370
- -------
1371
- ndarray
1372
- List of total, mode I, and mode II energy release rates.
1373
- """
1374
- # Unused arguments
1375
- _ = kwargs
1376
-
1377
- # Make sure inputs are np.arrays
1378
- li, ki, k0 = np.array(li), np.array(ki), np.array(k0)
1379
-
1380
- # Reduce inputs to segments with crack advance
1381
- iscrack = k0 & ~ki
1382
- C0, C1, li = C0[:, iscrack], C1[:, iscrack], li[iscrack]
1383
-
1384
- # Compute total crack lenght and initialize outputs
1385
- da = li.sum() if li.sum() > 0 else np.nan
1386
- Ginc1, Ginc2 = 0, 0
1387
-
1388
- # Loop through segments with crack advance
1389
- for j, l in enumerate(li):
1390
- # Uncracked (0) and cracked (1) solutions at integration points
1391
- z0 = partial(self.z, C=C0[:, [j]], l=l, phi=phi, bed=True)
1392
- z1 = partial(self.z, C=C1[:, [j]], l=l, phi=phi, bed=False)
1393
-
1394
- # Mode I (1) and II (2) integrands at integration points
1395
- int1 = partial(self.int1, z0=z0, z1=z1)
1396
- int2 = partial(self.int2, z0=z0, z1=z1)
1397
-
1398
- # Segement contributions to total crack opening integral
1399
- Ginc1 += quad(int1, 0, l, epsabs=self.tol, epsrel=self.tol)[0] / (2 * da)
1400
- Ginc2 += quad(int2, 0, l, epsabs=self.tol, epsrel=self.tol)[0] / (2 * da)
1401
-
1402
- return np.array([Ginc1 + Ginc2, Ginc1, Ginc2]).flatten()
1403
-
1404
- def gdif(self, C, phi, li, ki, unit="kJ/m^2", **kwargs):
1405
- """
1406
- Compute differential energy release rate of all crack tips.
1407
-
1408
- Arguments
1409
- ---------
1410
- C : ndarray
1411
- Free constants of the solution.
1412
- phi : float
1413
- Inclination (degress).
1414
- li : ndarray
1415
- List of segment lengths.
1416
- ki : ndarray
1417
- List of booleans indicating whether segment lies on
1418
- a foundation or not in the cracked configuration.
1419
-
1420
- Returns
1421
- -------
1422
- ndarray
1423
- List of total, mode I, and mode II energy release rates.
1424
- """
1425
- # Unused arguments
1426
- _ = kwargs
1427
-
1428
- # Get number and indices of segment transitions
1429
- ntr = len(li) - 1
1430
- itr = np.arange(ntr)
1431
-
1432
- # Identify supported-free and free-supported transitions as crack tips
1433
- iscracktip = [ki[j] != ki[j + 1] for j in range(ntr)]
1434
-
1435
- # Transition indices of crack tips and total number of crack tips
1436
- ict = itr[iscracktip]
1437
- nct = len(ict)
1438
-
1439
- # Initialize energy release rate array
1440
- Gdif = np.zeros([3, nct])
1441
-
1442
- # Compute energy relase rate of all crack tips
1443
- for j, idx in enumerate(ict):
1444
- # Solution at crack tip
1445
- z = self.z(li[idx], C[:, [idx]], li[idx], phi, bed=ki[idx])
1446
- # Mode I and II differential energy release rates
1447
- Gdif[1:, j] = np.concatenate((self.Gi(z, unit=unit), self.Gii(z, unit=unit)))
1448
-
1449
- # Sum mode I and II contributions
1450
- Gdif[0, :] = Gdif[1, :] + Gdif[2, :]
1451
-
1452
- # Adjust contributions for center cracks
1453
- if nct > 1:
1454
- avgmask = np.full(nct, True) # Initialize mask
1455
- avgmask[[0, -1]] = ki[[0, -1]] # Do not weight edge cracks
1456
- Gdif[:, avgmask] *= 0.5 # Weigth with half crack length
1457
-
1458
- # Return total differential energy release rate of all crack tips
1459
- return Gdif.sum(axis=1)
1460
-
1461
- def get_zmesh(self, dz=2):
1462
- """
1463
- Get z-coordinates of grid points and corresponding elastic properties.
1464
-
1465
- Arguments
1466
- ---------
1467
- dz : float, optional
1468
- Element size along z-axis (mm). Default is 2 mm.
1469
-
1470
- Returns
1471
- -------
1472
- mesh : ndarray
1473
- Mesh along z-axis. Columns are a list of z-coordinates (mm) of
1474
- grid points along z-axis with at least two grid points (top,
1475
- bottom) per layer, Young's modulus of each grid point, shear
1476
- modulus of each grid point, and Poisson's ratio of each grid
1477
- point.
1478
- """
1479
- # Get ply (layer) coordinates
1480
- z = self.get_ply_coordinates()
1481
- # Compute number of grid points per layer
1482
- nlayer = np.ceil((z[1:] - z[:-1]) / dz).astype(np.int32) + 1
1483
- # Calculate grid points as list of z-coordinates (mm)
1484
- zi = np.hstack(
1485
- [np.linspace(z[i], z[i + 1], n, endpoint=True) for i, n in enumerate(nlayer)]
1486
- )
1487
- # Get lists of corresponding elastic properties (E, nu, rho)
1488
- si = np.repeat(self.slab[:, [2, 4, 0]], nlayer, axis=0)
1489
- # Assemble mesh with columns (z, E, G, nu)
1490
- return np.column_stack([zi, si])
1491
-
1492
- def Sxx(self, Z, phi, dz=2, unit="kPa"):
1493
- """
1494
- Compute axial normal stress in slab layers.
1495
-
1496
- Arguments
1497
- ----------
1498
- Z : ndarray
1499
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
1500
- phi : float
1501
- Inclination (degrees). Counterclockwise positive.
1502
- dz : float, optional
1503
- Element size along z-axis (mm). Default is 2 mm.
1504
- unit : {'kPa', 'MPa'}, optional
1505
- Desired output unit. Default is 'kPa'.
1506
-
1507
- Returns
1508
- -------
1509
- ndarray, float
1510
- Axial slab normal stress in specified unit.
1511
- """
1512
- # Unit conversion dict
1513
- convert = {"kPa": 1e3, "MPa": 1}
1514
-
1515
- # Get mesh along z-axis
1516
- zmesh = self.get_zmesh(dz=dz)
1517
- zi = zmesh[:, 0]
1518
- rho = 1e-12 * zmesh[:, 3]
1519
-
1520
- # Get dimensions of stress field (n rows, m columns)
1521
- n = zmesh.shape[0]
1522
- m = Z.shape[1]
1523
-
1524
- # Initialize axial normal stress Sxx
1525
- Sxx = np.zeros(shape=[n, m])
1526
-
1527
- # Compute axial normal stress Sxx at grid points in MPa
1528
- for i, (z, E, nu, _) in enumerate(zmesh):
1529
- Sxx[i, :] = E / (1 - nu**2) * self.du_dx(Z, z)
1530
-
1531
- # Calculate weight load at grid points and superimpose on stress field
1532
- qt = -rho * self.g * np.sin(np.deg2rad(phi))
1533
- for i, qi in enumerate(qt[:-1]):
1534
- Sxx[i, :] += qi * (zi[i + 1] - zi[i])
1535
- Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
1536
-
1537
- # Return axial normal stress in specified unit
1538
- return convert[unit] * Sxx
1539
-
1540
- def Txz(self, Z, phi, dz=2, unit="kPa"):
1541
- """
1542
- Compute shear stress in slab layers.
1543
-
1544
- Arguments
1545
- ----------
1546
- Z : ndarray
1547
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
1548
- phi : float
1549
- Inclination (degrees). Counterclockwise positive.
1550
- dz : float, optional
1551
- Element size along z-axis (mm). Default is 2 mm.
1552
- unit : {'kPa', 'MPa'}, optional
1553
- Desired output unit. Default is 'kPa'.
1554
-
1555
- Returns
1556
- -------
1557
- ndarray
1558
- Shear stress at grid points in the slab in specified unit.
1559
- """
1560
- # Unit conversion dict
1561
- convert = {"kPa": 1e3, "MPa": 1}
1562
- # Get mesh along z-axis
1563
- zmesh = self.get_zmesh(dz=dz)
1564
- zi = zmesh[:, 0]
1565
- rho = 1e-12 * zmesh[:, 3]
1566
-
1567
- # Get dimensions of stress field (n rows, m columns)
1568
- n = zmesh.shape[0]
1569
- m = Z.shape[1]
1570
-
1571
- # Get second derivatives of centerline displacement u0 and
1572
- # cross-section rotaiton psi of all grid points along the x-axis
1573
- du0_dxdx = self.du0_dxdx(Z, phi)
1574
- dpsi_dxdx = self.dpsi_dxdx(Z, phi)
1575
-
1576
- # Initialize first derivative of axial normal stress sxx w.r.t. x
1577
- dsxx_dx = np.zeros(shape=[n, m])
1578
-
1579
- # Calculate first derivative of sxx at z-grid points
1580
- for i, (z, E, nu, _) in enumerate(zmesh):
1581
- dsxx_dx[i, :] = E / (1 - nu**2) * (du0_dxdx + z * dpsi_dxdx)
1582
-
1583
- # Calculate weight load at grid points
1584
- qt = -rho * self.g * np.sin(np.deg2rad(phi))
1585
-
1586
- # Integrate -dsxx_dx along z and add cumulative weight load
1587
- # to obtain shear stress Txz in MPa
1588
- Txz = cumulative_trapezoid(dsxx_dx, zi, axis=0, initial=0)
1589
- Txz += cumulative_trapezoid(qt, zi, initial=0)[:, None]
1590
-
1591
- # Return shear stress Txz in specified unit
1592
- return convert[unit] * Txz
1593
-
1594
- def Szz(self, Z, phi, dz=2, unit="kPa"):
1595
- """
1596
- Compute transverse normal stress in slab layers.
1597
-
1598
- Arguments
1599
- ----------
1600
- Z : ndarray
1601
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
1602
- phi : float
1603
- Inclination (degrees). Counterclockwise positive.
1604
- dz : float, optional
1605
- Element size along z-axis (mm). Default is 2 mm.
1606
- unit : {'kPa', 'MPa'}, optional
1607
- Desired output unit. Default is 'kPa'.
1608
-
1609
- Returns
1610
- -------
1611
- ndarray, float
1612
- Transverse normal stress at grid points in the slab in
1613
- specified unit.
1614
- """
1615
- # Unit conversion dict
1616
- convert = {"kPa": 1e3, "MPa": 1}
1617
-
1618
- # Get mesh along z-axis
1619
- zmesh = self.get_zmesh(dz=dz)
1620
- zi = zmesh[:, 0]
1621
- rho = 1e-12 * zmesh[:, 3]
1622
-
1623
- # Get dimensions of stress field (n rows, m columns)
1624
- n = zmesh.shape[0]
1625
- m = Z.shape[1]
1626
-
1627
- # Get third derivatives of centerline displacement u0 and
1628
- # cross-section rotaiton psi of all grid points along the x-axis
1629
- du0_dxdxdx = self.du0_dxdxdx(Z, phi)
1630
- dpsi_dxdxdx = self.dpsi_dxdxdx(Z, phi)
1631
-
1632
- # Initialize second derivative of axial normal stress sxx w.r.t. x
1633
- dsxx_dxdx = np.zeros(shape=[n, m])
1634
-
1635
- # Calculate second derivative of sxx at z-grid points
1636
- for i, (z, E, nu, _) in enumerate(zmesh):
1637
- dsxx_dxdx[i, :] = E / (1 - nu**2) * (du0_dxdxdx + z * dpsi_dxdxdx)
1638
-
1639
- # Calculate weight load at grid points
1640
- qn = rho * self.g * np.cos(np.deg2rad(phi))
1641
-
1642
- # Integrate dsxx_dxdx twice along z to obtain transverse
1643
- # normal stress Szz in MPa
1644
- integrand = cumulative_trapezoid(dsxx_dxdx, zi, axis=0, initial=0)
1645
- Szz = cumulative_trapezoid(integrand, zi, axis=0, initial=0)
1646
- Szz += cumulative_trapezoid(-qn, zi, initial=0)[:, None]
1647
-
1648
- # Return shear stress txz in specified unit
1649
- return convert[unit] * Szz
1650
-
1651
- def principal_stress_slab(
1652
- self, Z, phi, dz=2, unit="kPa", val="max", normalize=False
1653
- ):
1654
- """
1655
- Compute maxium or minimum principal stress in slab layers.
1656
-
1657
- Arguments
1658
- ---------
1659
- Z : ndarray
1660
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
1661
- phi : float
1662
- Inclination (degrees). Counterclockwise positive.
1663
- dz : float, optional
1664
- Element size along z-axis (mm). Default is 2 mm.
1665
- unit : {'kPa', 'MPa'}, optional
1666
- Desired output unit. Default is 'kPa'.
1667
- val : str, optional
1668
- Maximum 'max' or minimum 'min' principal stress. Default is 'max'.
1669
- normalize : bool
1670
- Toggle layerwise normalization to strength.
1671
-
1672
- Returns
1673
- -------
1674
- ndarray
1675
- Maximum or minimum principal stress in specified unit.
1676
-
1677
- Raises
1678
- ------
1679
- ValueError
1680
- If specified principal stress component is neither 'max' nor
1681
- 'min', or if normalization of compressive principal stress
1682
- is requested.
1683
- """
1684
- # Raise error if specified component is not available
1685
- if val not in ["min", "max"]:
1686
- raise ValueError(f"Component {val} not defined.")
1687
-
1688
- # Multiplier selection dict
1689
- m = {"max": 1, "min": -1}
1690
-
1691
- # Get axial normal stresses, shear stresses, transverse normal stresses
1692
- Sxx = self.Sxx(Z=Z, phi=phi, dz=dz, unit=unit)
1693
- Txz = self.Txz(Z=Z, phi=phi, dz=dz, unit=unit)
1694
- Szz = self.Szz(Z=Z, phi=phi, dz=dz, unit=unit)
1695
-
1696
- # Calculate principal stress
1697
- Ps = (Sxx + Szz) / 2 + m[val] * np.sqrt((Sxx - Szz) ** 2 + 4 * Txz**2) / 2
1698
-
1699
- # Raise error if normalization of compressive stresses is attempted
1700
- if normalize and val == "min":
1701
- raise ValueError("Can only normlize tensile stresses.")
1702
-
1703
- # Normalize tensile stresses to tensile strength
1704
- if normalize and val == "max":
1705
- # Get layer densities
1706
- rho = self.get_zmesh(dz=dz)[:, 3]
1707
- # Normlize maximum principal stress to layers' tensile strength
1708
- return Ps / tensile_strength_slab(rho, unit=unit)[:, None]
1709
-
1710
- # Return absolute principal stresses
1711
- return Ps
1712
-
1713
- def principal_stress_weaklayer(
1714
- self, Z, sc=2.6, unit="kPa", val="min", normalize=False
1715
- ):
1716
- """
1717
- Compute maxium or minimum principal stress in the weak layer.
1718
-
1719
- Arguments
1720
- ---------
1721
- Z : ndarray
1722
- Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
1723
- sc : float
1724
- Weak-layer compressive strength. Default is 2.6 kPa.
1725
- unit : {'kPa', 'MPa'}, optional
1726
- Desired output unit. Default is 'kPa'.
1727
- val : str, optional
1728
- Maximum 'max' or minimum 'min' principal stress. Default is 'min'.
1729
- normalize : bool
1730
- Toggle layerwise normalization to strength.
1731
-
1732
- Returns
1733
- -------
1734
- ndarray
1735
- Maximum or minimum principal stress in specified unit.
1736
-
1737
- Raises
1738
- ------
1739
- ValueError
1740
- If specified principal stress component is neither 'max' nor
1741
- 'min', or if normalization of tensile principal stress
1742
- is requested.
1743
- """
1744
- # Raise error if specified component is not available
1745
- if val not in ["min", "max"]:
1746
- raise ValueError(f"Component {val} not defined.")
1747
-
1748
- # Multiplier selection dict
1749
- m = {"max": 1, "min": -1}
1750
-
1751
- # Get weak-layer normal and shear stresses
1752
- sig = self.sig(Z, unit=unit)
1753
- tau = self.tau(Z, unit=unit)
1754
-
1755
- # Calculate principal stress
1756
- ps = sig / 2 + m[val] * np.sqrt(sig**2 + 4 * tau**2) / 2
1757
-
1758
- # Raise error if normalization of tensile stresses is attempted
1759
- if normalize and val == "max":
1760
- raise ValueError("Can only normlize compressive stresses.")
1761
-
1762
- # Normalize compressive stresses to compressive strength
1763
- if normalize and val == "min":
1764
- return ps / sc
1765
-
1766
- # Return absolute principal stresses
1767
- return ps
1768
-
1769
-
1770
- class OutputMixin:
1771
- """
1772
- Mixin for outputs.
1773
-
1774
- Provides convenience methods for the assembly of output lists
1775
- such as rasterized displacements or rasterized stresses.
1776
- """
1777
-
1778
- def external_potential(self, C, phi, L, **segments):
1779
- """
1780
- Compute total external potential (pst only).
1781
-
1782
- Arguments
1783
- ---------
1784
- C : ndarray
1785
- Matrix(6xN) of solution constants for a system of N
1786
- segements. Columns contain the 6 constants of each segement.
1787
- phi : float
1788
- Inclination of the slab (°).
1789
- L : float, optional
1790
- Total length of model (mm).
1791
- segments : dict
1792
- Dictionary with lists of touchdown booleans (tdi), segement
1793
- lengths (li), skier weights (mi), and foundation booleans
1794
- in the cracked (ki) and uncracked (k0) configurations.
1795
-
1796
- Returns
1797
- -------
1798
- Pi_ext : float
1799
- Total external potential (Nmm).
1800
- """
1801
- # Rasterize solution
1802
- xq, zq, xb = self.rasterize_solution(C=C, phi=phi, **segments)
1803
- _ = xq, xb
1804
- # Compute displacements where weight loads are applied
1805
- w0 = self.w(zq)
1806
- us = self.u(zq, z0=self.zs)
1807
- # Get weight loads
1808
- qn = self.calc_qn()
1809
- qt = self.calc_qt()
1810
- # use +/- and us[0]/us[-1] according to system and phi
1811
- # compute total external potential
1812
- Pi_ext = (
1813
- -qn * (segments["li"][0] + segments["li"][1]) * np.average(w0)
1814
- - qn * (L - (segments["li"][0] + segments["li"][1])) * self.tc
1815
- )
1816
- # Ensure
1817
- if self.system in ["pst-"]:
1818
- ub = us[-1]
1819
- elif self.system in ["-pst"]:
1820
- ub = us[0]
1821
- Pi_ext += (
1822
- -qt * (segments["li"][0] + segments["li"][1]) * np.average(us)
1823
- - qt * (L - (segments["li"][0] + segments["li"][1])) * ub
1824
- )
1825
- if self.system not in ["pst-", "-pst"]:
1826
- print("Input error: Only pst-setup implemented at the moment.")
1827
-
1828
- return Pi_ext
1829
-
1830
- def internal_potential(self, C, phi, L, **segments):
1831
- """
1832
- Compute total internal potential (pst only).
1833
-
1834
- Arguments
1835
- ---------
1836
- C : ndarray
1837
- Matrix(6xN) of solution constants for a system of N
1838
- segements. Columns contain the 6 constants of each segement.
1839
- phi : float
1840
- Inclination of the slab (°).
1841
- L : float, optional
1842
- Total length of model (mm).
1843
- segments : dict
1844
- Dictionary with lists of touchdown booleans (tdi), segement
1845
- lengths (li), skier weights (mi), and foundation booleans
1846
- in the cracked (ki) and uncracked (k0) configurations.
1847
-
1848
- Returns
1849
- -------
1850
- Pi_int : float
1851
- Total internal potential (Nmm).
1852
- """
1853
- # Rasterize solution
1854
- xq, zq, xb = self.rasterize_solution(C=C, phi=phi, **segments)
1855
-
1856
- # Compute section forces
1857
- N, M, V = self.N(zq), self.M(zq), self.V(zq)
1858
-
1859
- # Drop parts of the solution that are not a foundation
1860
- zweak = zq[:, ~np.isnan(xb)]
1861
- xweak = xb[~np.isnan(xb)]
1862
-
1863
- # Compute weak layer displacements
1864
- wweak = self.w(zweak)
1865
- uweak = self.u(zweak, z0=self.h / 2)
1866
-
1867
- # Compute stored energy of the slab (monte-carlo integration)
1868
- n = len(xq)
1869
- nweak = len(xweak)
1870
- # energy share from moment, shear force, wl normal and tangential springs
1871
- Pi_int = (
1872
- L / 2 / n / self.A11 * np.sum([Ni**2 for Ni in N])
1873
- + L
1874
- / 2
1875
- / n
1876
- / (self.D11 - self.B11**2 / self.A11)
1877
- * np.sum([Mi**2 for Mi in M])
1878
- + L / 2 / n / self.kA55 * np.sum([Vi**2 for Vi in V])
1879
- + L * self.kn / 2 / nweak * np.sum([wi**2 for wi in wweak])
1880
- + L * self.kt / 2 / nweak * np.sum([ui**2 for ui in uweak])
1881
- )
1882
- # energy share from substitute rotation spring
1883
- if self.system in ["pst-"]:
1884
- Pi_int += 1 / 2 * M[-1] * (self.psi(zq)[-1]) ** 2
1885
- elif self.system in ["-pst"]:
1886
- Pi_int += 1 / 2 * M[0] * (self.psi(zq)[0]) ** 2
1887
- else:
1888
- print("Input error: Only pst-setup implemented at the moment.")
1889
-
1890
- return Pi_int
1891
-
1892
- def total_potential(self, C, phi, L, **segments):
1893
- """
1894
- Returns total differential potential
1895
-
1896
- Arguments
1897
- ---------
1898
- C : ndarray
1899
- Matrix(6xN) of solution constants for a system of N
1900
- segements. Columns contain the 6 constants of each segement.
1901
- phi : float
1902
- Inclination of the slab (°).
1903
- L : float, optional
1904
- Total length of model (mm).
1905
- segments : dict
1906
- Dictionary with lists of touchdown booleans (tdi), segement
1907
- lengths (li), skier weights (mi), and foundation booleans
1908
- in the cracked (ki) and uncracked (k0) configurations.
1909
-
1910
- Returns
1911
- -------
1912
- Pi : float
1913
- Total differential potential (Nmm).
1914
- """
1915
- Pi_int = self.internal_potential(C, phi, L, **segments)
1916
- Pi_ext = self.external_potential(C, phi, L, **segments)
1917
-
1918
- return Pi_int + Pi_ext
1919
-
1920
- def get_weaklayer_shearstress(self, x, z, unit="MPa", removeNaNs=False):
1921
- """
1922
- Compute weak-layer shear stress.
1923
-
1924
- Arguments
1925
- ---------
1926
- x : ndarray
1927
- Discretized x-coordinates (mm) where coordinates of unsupported
1928
- (no foundation) segments are NaNs.
1929
- z : ndarray
1930
- Solution vectors at positions x as columns of matrix z.
1931
- unit : {'MPa', 'kPa'}, optional
1932
- Stress output unit. Default is MPa.
1933
- keepNaNs : bool
1934
- If set, do not remove
1935
-
1936
- Returns
1937
- -------
1938
- x : ndarray
1939
- Horizontal coordinates (cm).
1940
- sig : ndarray
1941
- Normal stress (stress unit input).
1942
- """
1943
- # Convert coordinates from mm to cm and stresses from MPa to unit
1944
- x = x / 10
1945
- tau = self.tau(z, unit=unit)
1946
- # Filter stresses in unspupported segments
1947
- if removeNaNs:
1948
- # Remove coordinate-stress pairs where no weak layer is present
1949
- tau = tau[~np.isnan(x)]
1950
- x = x[~np.isnan(x)]
1951
- else:
1952
- # Set stress NaN where no weak layer is present
1953
- tau[np.isnan(x)] = np.nan
1954
-
1955
- return x, tau
1956
-
1957
- def get_weaklayer_normalstress(self, x, z, unit="MPa", removeNaNs=False):
1958
- """
1959
- Compute weak-layer normal stress.
1960
-
1961
- Arguments
1962
- ---------
1963
- x : ndarray
1964
- Discretized x-coordinates (mm) where coordinates of unsupported
1965
- (no foundation) segments are NaNs.
1966
- z : ndarray
1967
- Solution vectors at positions x as columns of matrix z.
1968
- unit : {'MPa', 'kPa'}, optional
1969
- Stress output unit. Default is MPa.
1970
- keepNaNs : bool
1971
- If set, do not remove
1972
-
1973
- Returns
1974
- -------
1975
- x : ndarray
1976
- Horizontal coordinates (cm).
1977
- sig : ndarray
1978
- Normal stress (stress unit input).
1979
- """
1980
- # Convert coordinates from mm to cm and stresses from MPa to unit
1981
- x = x / 10
1982
- sig = self.sig(z, unit=unit)
1983
- # Filter stresses in unspupported segments
1984
- if removeNaNs:
1985
- # Remove coordinate-stress pairs where no weak layer is present
1986
- sig = sig[~np.isnan(x)]
1987
- x = x[~np.isnan(x)]
1988
- else:
1989
- # Set stress NaN where no weak layer is present
1990
- sig[np.isnan(x)] = np.nan
1991
-
1992
- return x, sig
1993
-
1994
- def get_slab_displacement(self, x, z, loc="mid", unit="mm"):
1995
- """
1996
- Compute horizontal slab displacement.
1997
-
1998
- Arguments
1999
- ---------
2000
- x : ndarray
2001
- Discretized x-coordinates (mm) where coordinates of
2002
- unsupported (no foundation) segments are NaNs.
2003
- z : ndarray
2004
- Solution vectors at positions x as columns of matrix z.
2005
- loc : {'top', 'mid', 'bot'}
2006
- Get displacements of top, midplane or bottom of slab.
2007
- Default is mid.
2008
- unit : {'m', 'cm', 'mm', 'um'}, optional
2009
- Displacement output unit. Default is mm.
2010
-
2011
- Returns
2012
- -------
2013
- x : ndarray
2014
- Horizontal coordinates (cm).
2015
- ndarray
2016
- Horizontal displacements (unit input).
2017
- """
2018
- # Coordinates (cm)
2019
- x = x / 10
2020
- # Locator
2021
- z0 = {"top": -self.h / 2, "mid": 0, "bot": self.h / 2}
2022
- # Displacement (unit)
2023
- u = self.u(z, z0=z0[loc], unit=unit)
2024
- # Output array
2025
- return x, u
2026
-
2027
- def get_slab_deflection(self, x, z, unit="mm"):
2028
- """
2029
- Compute vertical slab displacement.
2030
-
2031
- Arguments
2032
- ---------
2033
- x : ndarray
2034
- Discretized x-coordinates (mm) where coordinates of
2035
- unsupported (no foundation) segments are NaNs.
2036
- z : ndarray
2037
- Solution vectors at positions x as columns of matrix z.
2038
- Default is mid.
2039
- unit : {'m', 'cm', 'mm', 'um'}, optional
2040
- Displacement output unit. Default is mm.
2041
-
2042
- Returns
2043
- -------
2044
- x : ndarray
2045
- Horizontal coordinates (cm).
2046
- ndarray
2047
- Vertical deflections (unit input).
2048
- """
2049
- # Coordinates (cm)
2050
- x = x / 10
2051
- # Deflection (unit)
2052
- w = self.w(z, unit=unit)
2053
- # Output array
2054
- return x, w
2055
-
2056
- def get_slab_rotation(self, x, z, unit="degrees"):
2057
- """
2058
- Compute slab cross-section rotation angle.
2059
-
2060
- Arguments
2061
- ---------
2062
- x : ndarray
2063
- Discretized x-coordinates (mm) where coordinates of
2064
- unsupported (no foundation) segments are NaNs.
2065
- z : ndarray
2066
- Solution vectors at positions x as columns of matrix z.
2067
- Default is mid.
2068
- unit : {'deg', degrees', 'rad', 'radians'}, optional
2069
- Rotation angle output unit. Default is degrees.
2070
-
2071
- Returns
2072
- -------
2073
- x : ndarray
2074
- Horizontal coordinates (cm).
2075
- ndarray
2076
- Cross section rotations (unit input).
2077
- """
2078
- # Coordinates (cm)
2079
- x = x / 10
2080
- # Cross-section rotation angle (unit)
2081
- psi = self.psi(z, unit=unit)
2082
- # Output array
2083
- return x, psi