weac 2.6.0__py3-none-any.whl → 2.6.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
weac/mixins.py CHANGED
@@ -6,11 +6,11 @@ from functools import partial
6
6
 
7
7
  # Third party imports
8
8
  import numpy as np
9
+ from scipy.integrate import cumulative_trapezoid, quad
9
10
  from scipy.optimize import brentq
10
- from scipy.integrate import romberg, cumulative_trapezoid
11
11
 
12
12
  # Module imports
13
- from weac.tools import tensile_strength_slab, calc_vertical_bc_center_of_gravity
13
+ from weac.tools import calc_vertical_bc_center_of_gravity, tensile_strength_slab
14
14
 
15
15
 
16
16
  class FieldQuantitiesMixin:
@@ -22,7 +22,7 @@ class FieldQuantitiesMixin:
22
22
  """
23
23
 
24
24
  # pylint: disable=no-self-use
25
- def w(self, Z, unit='mm'):
25
+ def w(self, Z, unit="mm"):
26
26
  """
27
27
  Get centerline deflection w.
28
28
 
@@ -39,12 +39,12 @@ class FieldQuantitiesMixin:
39
39
  Deflection w (in specified unit) of the slab.
40
40
  """
41
41
  convert = {
42
- 'm': 1e-3, # meters
43
- 'cm': 1e-1, # centimeters
44
- 'mm': 1, # millimeters
45
- 'um': 1e3 # micrometers
42
+ "m": 1e-3, # meters
43
+ "cm": 1e-1, # centimeters
44
+ "mm": 1, # millimeters
45
+ "um": 1e3, # micrometers
46
46
  }
47
- return convert[unit]*Z[2, :]
47
+ return convert[unit] * Z[2, :]
48
48
 
49
49
  def dw_dx(self, Z):
50
50
  """
@@ -62,7 +62,7 @@ class FieldQuantitiesMixin:
62
62
  """
63
63
  return Z[3, :]
64
64
 
65
- def psi(self, Z, unit='rad'):
65
+ def psi(self, Z, unit="rad"):
66
66
  """
67
67
  Get midplane rotation psi.
68
68
 
@@ -78,9 +78,9 @@ class FieldQuantitiesMixin:
78
78
  psi : float
79
79
  Cross-section rotation psi (radians) of the slab.
80
80
  """
81
- if unit in ['deg', 'degree', 'degrees']:
81
+ if unit in ["deg", "degree", "degrees"]:
82
82
  psi = np.rad2deg(Z[4, :])
83
- elif unit in ['rad', 'radian', 'radians']:
83
+ elif unit in ["rad", "radian", "radians"]:
84
84
  psi = Z[4, :]
85
85
  return psi
86
86
 
@@ -102,7 +102,7 @@ class FieldQuantitiesMixin:
102
102
  return Z[5, :]
103
103
 
104
104
  # pylint: enable=no-self-use
105
- def u(self, Z, z0, unit='mm'):
105
+ def u(self, Z, z0, unit="mm"):
106
106
  """
107
107
  Get horizontal displacement u = u0 + z0 psi.
108
108
 
@@ -121,12 +121,12 @@ class FieldQuantitiesMixin:
121
121
  Horizontal displacement u (unit) of the slab.
122
122
  """
123
123
  convert = {
124
- 'm': 1e-3, # meters
125
- 'cm': 1e-1, # centimeters
126
- 'mm': 1, # millimeters
127
- 'um': 1e3 # micrometers
124
+ "m": 1e-3, # meters
125
+ "cm": 1e-1, # centimeters
126
+ "mm": 1, # millimeters
127
+ "um": 1e3, # micrometers
128
128
  }
129
- return convert[unit]*(Z[0, :] + z0*self.psi(Z))
129
+ return convert[unit] * (Z[0, :] + z0 * self.psi(Z))
130
130
 
131
131
  def du_dx(self, Z, z0):
132
132
  """
@@ -145,7 +145,7 @@ class FieldQuantitiesMixin:
145
145
  First derivative u' = u0' + z0 psi' of the horizontal
146
146
  displacement of the slab.
147
147
  """
148
- return Z[1, :] + z0*self.dpsi_dx(Z)
148
+ return Z[1, :] + z0 * self.dpsi_dx(Z)
149
149
 
150
150
  def N(self, Z):
151
151
  """
@@ -161,7 +161,7 @@ class FieldQuantitiesMixin:
161
161
  float
162
162
  Axial normal force N (N) in the slab.
163
163
  """
164
- return self.A11*Z[1, :] + self.B11*Z[5, :]
164
+ return self.A11 * Z[1, :] + self.B11 * Z[5, :]
165
165
 
166
166
  def M(self, Z):
167
167
  """
@@ -177,7 +177,7 @@ class FieldQuantitiesMixin:
177
177
  float
178
178
  Bending moment M (Nmm) in the slab.
179
179
  """
180
- return self.B11*Z[1, :] + self.D11*Z[5, :]
180
+ return self.B11 * Z[1, :] + self.D11 * Z[5, :]
181
181
 
182
182
  def V(self, Z):
183
183
  """
@@ -193,9 +193,9 @@ class FieldQuantitiesMixin:
193
193
  float
194
194
  Vertical shear force V (N) in the slab.
195
195
  """
196
- return self.kA55*(Z[3, :] + Z[4, :])
196
+ return self.kA55 * (Z[3, :] + Z[4, :])
197
197
 
198
- def sig(self, Z, unit='MPa'):
198
+ def sig(self, Z, unit="MPa"):
199
199
  """
200
200
  Get weak-layer normal stress.
201
201
 
@@ -211,13 +211,10 @@ class FieldQuantitiesMixin:
211
211
  float
212
212
  Weak-layer normal stress sigma (in specified unit).
213
213
  """
214
- convert = {
215
- 'kPa': 1e3,
216
- 'MPa': 1
217
- }
218
- return -convert[unit]*self.kn*self.w(Z)
214
+ convert = {"kPa": 1e3, "MPa": 1}
215
+ return -convert[unit] * self.kn * self.w(Z)
219
216
 
220
- def tau(self, Z, unit='MPa'):
217
+ def tau(self, Z, unit="MPa"):
221
218
  """
222
219
  Get weak-layer shear stress.
223
220
 
@@ -233,12 +230,12 @@ class FieldQuantitiesMixin:
233
230
  float
234
231
  Weak-layer shear stress tau (in specified unit).
235
232
  """
236
- convert = {
237
- 'kPa': 1e3,
238
- 'MPa': 1
239
- }
240
- return -convert[unit]*self.kt*(
241
- self.dw_dx(Z)*self.t/2 - self.u(Z, z0=self.h/2))
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
+ )
242
239
 
243
240
  def eps(self, Z):
244
241
  """
@@ -254,7 +251,7 @@ class FieldQuantitiesMixin:
254
251
  float
255
252
  Weak-layer normal strain epsilon.
256
253
  """
257
- return -self.w(Z)/self.t
254
+ return -self.w(Z) / self.t
258
255
 
259
256
  def gamma(self, Z):
260
257
  """
@@ -270,9 +267,9 @@ class FieldQuantitiesMixin:
270
267
  float
271
268
  Weak-layer shear strain gamma.
272
269
  """
273
- return self.dw_dx(Z)/2 - self.u(Z, z0=self.h/2)/self.t
270
+ return self.dw_dx(Z) / 2 - self.u(Z, z0=self.h / 2) / self.t
274
271
 
275
- def Gi(self, Ztip, unit='kJ/m^2'):
272
+ def Gi(self, Ztip, unit="kJ/m^2"):
276
273
  """
277
274
  Get mode I differential energy release rate at crack tip.
278
275
 
@@ -291,13 +288,13 @@ class FieldQuantitiesMixin:
291
288
  or J/m^2) at the crack tip.
292
289
  """
293
290
  convert = {
294
- 'J/m^2': 1e3, # joule per square meter
295
- 'kJ/m^2': 1, # kilojoule per square meter
296
- 'N/mm': 1 # newton per millimeter
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
297
294
  }
298
- return convert[unit]*self.sig(Ztip)**2/(2*self.kn)
295
+ return convert[unit] * self.sig(Ztip) ** 2 / (2 * self.kn)
299
296
 
300
- def Gii(self, Ztip, unit='kJ/m^2'):
297
+ def Gii(self, Ztip, unit="kJ/m^2"):
301
298
  """
302
299
  Get mode II differential energy release rate at crack tip.
303
300
 
@@ -316,11 +313,11 @@ class FieldQuantitiesMixin:
316
313
  or J/m^2) at the crack tip.
317
314
  """
318
315
  convert = {
319
- 'J/m^2': 1e3, # joule per square meter
320
- 'kJ/m^2': 1, # kilojoule per square meter
321
- 'N/mm': 1 # newton per millimeter
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
322
319
  }
323
- return convert[unit]*self.tau(Ztip)**2/(2*self.kt)
320
+ return convert[unit] * self.tau(Ztip) ** 2 / (2 * self.kt)
324
321
 
325
322
  def int1(self, x, z0, z1):
326
323
  """
@@ -342,7 +339,7 @@ class FieldQuantitiesMixin:
342
339
  float or ndarray
343
340
  Integrant of the mode I crack opening integral.
344
341
  """
345
- return self.sig(z0(x))*self.eps(z1(x))*self.t
342
+ return self.sig(z0(x)) * self.eps(z1(x)) * self.t
346
343
 
347
344
  def int2(self, x, z0, z1):
348
345
  """
@@ -364,7 +361,7 @@ class FieldQuantitiesMixin:
364
361
  float or ndarray
365
362
  Integrant of the mode II crack opening integral.
366
363
  """
367
- return self.tau(z0(x))*self.gamma(z1(x))*self.t
364
+ return self.tau(z0(x)) * self.gamma(z1(x)) * self.t
368
365
 
369
366
  def dz_dx(self, z, phi):
370
367
  """
@@ -494,9 +491,10 @@ class SlabContactMixin:
494
491
  Provides Methods for the calculation of substitute spring stiffnesses,
495
492
  cracklength-tresholds and element lengths.
496
493
  """
494
+
497
495
  # pylint: disable=too-many-instance-attributes
498
496
 
499
- def set_columnlength(self,L):
497
+ def set_columnlength(self, L):
500
498
  """
501
499
  Set cracklength.
502
500
 
@@ -507,7 +505,7 @@ class SlabContactMixin:
507
505
  """
508
506
  self.L = L
509
507
 
510
- def set_cracklength(self,a):
508
+ def set_cracklength(self, a):
511
509
  """
512
510
  Set cracklength.
513
511
 
@@ -518,7 +516,7 @@ class SlabContactMixin:
518
516
  """
519
517
  self.a = a
520
518
 
521
- def set_tc(self,cf):
519
+ def set_tc(self, cf):
522
520
  """
523
521
  Set height of the crack.
524
522
 
@@ -528,11 +526,14 @@ class SlabContactMixin:
528
526
  Collapse-factor. Ratio of the crack height to the
529
527
  uncollapsed weak-layer height.
530
528
  """
529
+ # mark argument as intentionally unused (API compatibility)
530
+ _ = cf
531
531
  # subtract displacement under constact load from collapsed wl height
532
532
  qn = self.calc_qn()
533
- self.tc = cf*self.t - qn/self.kn
533
+ collapse_height = 4.70 * (1 - np.exp(-self.t / 7.78))
534
+ self.tc = collapse_height - qn / self.kn
534
535
 
535
- def set_phi(self,phi):
536
+ def set_phi(self, phi):
536
537
  """
537
538
  Set inclination of the slab.
538
539
 
@@ -577,9 +578,9 @@ class SlabContactMixin:
577
578
  """
578
579
  return self.get_weight_load(self.phi)[1] + self.get_surface_load(self.phi)[1]
579
580
 
580
- def substitute_stiffness(self, L, support='rested', dof='rot'):
581
+ def substitute_stiffness(self, L, support="rested", dof="rot"):
581
582
  """
582
- Calc substitute stiffness for beam on elastic foundation.
583
+ Calc substitute stiffness for beam on elastic foundation.
583
584
 
584
585
  Arguments
585
586
  ---------
@@ -587,7 +588,7 @@ class SlabContactMixin:
587
588
  Total length of the PST-column (mm).
588
589
  support : string
589
590
  Type of segment foundation. Defaults to 'rested'.
590
- dof : string
591
+ dof : string
591
592
  Type of substitute spring, either 'rot' or 'trans'. Defaults to 'rot'.
592
593
 
593
594
  Returns
@@ -595,39 +596,40 @@ class SlabContactMixin:
595
596
  k : stiffness of substitute spring.
596
597
  """
597
598
  # adjust system to substitute system
598
- if dof in ['rot']:
599
+ if dof in ["rot"]:
599
600
  tempsys = self.system
600
- self.system = 'rot'
601
- if dof in ['trans']:
601
+ self.system = "rot"
602
+ if dof in ["trans"]:
602
603
  tempsys = self.system
603
- self.system = 'trans'
604
+ self.system = "trans"
604
605
 
605
606
  # Change eigensystem for rested segment
606
- if support in ['rested']:
607
+ if support in ["rested"]:
607
608
  tempkn = self.kn
608
609
  tempkt = self.kt
609
- self.kn = self.ratio*self.kn
610
- self.kt = self.ratio*self.kt
610
+ self.kn = self.ratio * self.kn
611
+ self.kt = self.ratio * self.kt
611
612
  self.calc_system_matrix()
612
613
  self.calc_eigensystem()
613
614
 
614
615
  # prepare list of segment characteristics
615
- segments = {'li': np.array([L, 0.]),
616
- 'mi': np.array([0]),
617
- 'ki': np.array([True, True])}
616
+ segments = {
617
+ "li": np.array([L, 0.0]),
618
+ "mi": np.array([0]),
619
+ "ki": np.array([True, True]),
620
+ }
618
621
  # solve system of equations
619
622
  constants = self.assemble_and_solve(phi=0, **segments)
620
623
  # calculate stiffness
621
- _, z_pst, _ = self.rasterize_solution(
622
- C=constants, phi=0, num=1, **segments)
623
- if dof in ['rot']:
624
- k = abs(1/self.psi(z_pst)[0])
625
- if dof in ['trans']:
626
- k = abs(1/self.w(z_pst)[0])
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])
627
629
 
628
630
  # Reset to previous system and eigensystem
629
631
  self.system = tempsys
630
- if support in ['rested']:
632
+ if support in ["rested"]:
631
633
  self.kn = tempkn
632
634
  self.kt = tempkt
633
635
  self.calc_system_matrix()
@@ -645,7 +647,7 @@ class SlabContactMixin:
645
647
  Length of the crack for transition of stage A to stage B (mm).
646
648
  """
647
649
  # Unpack variables
648
- bs = -(self.B11**2/self.A11 - self.D11)
650
+ bs = -(self.B11**2 / self.A11 - self.D11)
649
651
  ss = self.kA55
650
652
  L = self.L
651
653
  tc = self.tc
@@ -654,17 +656,17 @@ class SlabContactMixin:
654
656
  # Create polynomial expression
655
657
  def polynomial(x):
656
658
  # Spring stiffness supported segment
657
- kRl = self.substitute_stiffness(L-x, 'supported', 'rot')
658
- kNl = self.substitute_stiffness(L-x, 'supported', 'trans')
659
- c1 = 1/(8*bs)
660
- c2 = 1/(2*kRl)
661
- c3 = 1/(2*ss)
662
- c4 = 1/kNl
663
- c5 = -tc/qn
664
- return c1*x**4 + c2*x**3 + c3*x**2 + c4*x + c5
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
665
667
 
666
668
  # Find root
667
- a1 = brentq(polynomial, L/1000, 999/1000*L)
669
+ a1 = brentq(polynomial, L / 1000, 999 / 1000 * L)
668
670
 
669
671
  return a1
670
672
 
@@ -678,7 +680,7 @@ class SlabContactMixin:
678
680
  Length of the crack for transition of stage B to stage C (mm).
679
681
  """
680
682
  # Unpack variables
681
- bs = -(self.B11**2/self.A11 - self.D11)
683
+ bs = -(self.B11**2 / self.A11 - self.D11)
682
684
  ss = self.kA55
683
685
  L = self.L
684
686
  tc = self.tc
@@ -687,27 +689,21 @@ class SlabContactMixin:
687
689
  # Create polynomial function
688
690
  def polynomial(x):
689
691
  # Spring stiffness supported segment
690
- kRl = self.substitute_stiffness(L-x, 'supported', 'rot')
691
- kNl = self.substitute_stiffness(L-x, 'supported', 'trans')
692
- c1 = ss**2*kRl*kNl*qn
693
- c2 = 6*ss**2*bs*kNl*qn
694
- c3 = 30*bs*ss*kRl*kNl*qn
695
- c4 = 24*bs*qn*(
696
- 2*ss**2*kRl \
697
- + 3*bs*ss*kNl)
698
- c5 = 72*bs*(
699
- bs*qn*(
700
- ss**2 \
701
- + kRl*kNl) \
702
- - ss**2*kRl*kNl*tc)
703
- c6 = 144*bs*ss*(
704
- bs*kRl*qn \
705
- - bs*ss*kNl*tc)
706
- c7 = - 144*bs**2*ss*kRl*kNl*tc
707
- return c1*x**6 + c2*x**5 + c3*x**4 + c4*x**3 + c5*x**2 + c6*x + c7
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
+ )
708
704
 
709
705
  # Find root
710
- a2 = brentq(polynomial, L/1000, 999/1000*L)
706
+ a2 = brentq(polynomial, L / 1000, 999 / 1000 * L)
711
707
 
712
708
  return a2
713
709
 
@@ -732,7 +728,7 @@ class SlabContactMixin:
732
728
  Calculate the length of the touchdown element in mode C.
733
729
  """
734
730
  # Unpack variables
735
- bs = -(self.B11**2/self.A11 - self.D11)
731
+ bs = -(self.B11**2 / self.A11 - self.D11)
736
732
  ss = self.kA55
737
733
  L = self.L
738
734
  a = self.a
@@ -741,37 +737,41 @@ class SlabContactMixin:
741
737
 
742
738
  def polynomial(x):
743
739
  # Spring stiffness supported segment
744
- kRl = self.substitute_stiffness(L-a, 'supported', 'rot')
745
- kNl = self.substitute_stiffness(L-a, 'supported', 'trans')
740
+ kRl = self.substitute_stiffness(L - a, "supported", "rot")
741
+ kNl = self.substitute_stiffness(L - a, "supported", "trans")
746
742
  # Spring stiffness rested segment
747
- kRr = self.substitute_stiffness(a-x, 'rested', 'rot')
743
+ kRr = self.substitute_stiffness(a - x, "rested", "rot")
748
744
  # define constants
749
- c1 = ss**2*kRl*kNl*qn
750
- c2 = 6*ss*kNl*qn*(
751
- bs*ss \
752
- + kRl*kRr)
753
- c3 = 30*bs*ss*kNl*qn*(kRl + kRr)
754
- c4 = 24*bs*qn*(
755
- 2*ss**2*kRl \
756
- + 3*bs*ss*kNl \
757
- + 3*kRl*kRr*kNl)
758
- c5 = 72*bs*(
759
- bs*qn*(
760
- ss**2 \
761
- + kNl*(kRl + kRr)) \
762
- + ss*kRl*(
763
- 2*kRr*qn \
764
- - ss*kNl*tc))
765
- c6 = 144*bs*ss*(
766
- bs*qn*(kRl + kRr) \
767
- - kNl*tc*(
768
- bs*ss \
769
- + kRl*kRr))
770
- c7 = - 144*bs**2*ss*kNl*tc*(kRl + kRr)
771
- return c1*x**6 + c2*x**5 + c3*x**4 + c4*x**3 + c5*x**2 + c6*x + c7
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
772
 
773
773
  # Find root
774
- lC = brentq(polynomial, a/1000, 999/1000*a)
774
+ lC = brentq(polynomial, a / 1000, 999 / 1000 * a)
775
775
 
776
776
  return lC
777
777
 
@@ -791,22 +791,22 @@ class SlabContactMixin:
791
791
  a2 = self.calc_a2()
792
792
  # Assign stage
793
793
  if self.a <= a1:
794
- mode = 'A'
794
+ mode = "A"
795
795
  elif a1 < self.a <= a2:
796
- mode = 'B'
796
+ mode = "B"
797
797
  elif a2 < self.a:
798
- mode = 'C'
798
+ mode = "C"
799
799
  self.mode = mode
800
800
  else:
801
- self.mode = 'A'
801
+ self.mode = "A"
802
802
 
803
803
  def calc_touchdown_length(self):
804
804
  """Calculate touchdown length"""
805
- if self.mode in ['A']:
805
+ if self.mode in ["A"]:
806
806
  self.td = self.calc_lA()
807
- elif self.mode in ['B']:
807
+ elif self.mode in ["B"]:
808
808
  self.td = self.calc_lB()
809
- elif self.mode in ['C']:
809
+ elif self.mode in ["C"]:
810
810
  self.td = self.calc_lC()
811
811
 
812
812
  def calc_touchdown_system(self, L, a, cf, phi, ratio=1000):
@@ -815,6 +815,7 @@ class SlabContactMixin:
815
815
  self.calc_touchdown_mode()
816
816
  self.calc_touchdown_length()
817
817
 
818
+
818
819
  class SolutionMixin:
819
820
  """
820
821
  Mixin for the solution of boundary value problems.
@@ -823,7 +824,7 @@ class SolutionMixin:
823
824
  and for the computation of the free constants.
824
825
  """
825
826
 
826
- def bc(self, z, k=False, pos='mid'):
827
+ def bc(self, z, k=False, pos="mid"):
827
828
  """
828
829
  Provide equations for free (pst) or infinite (skiers) ends.
829
830
 
@@ -849,77 +850,48 @@ class SolutionMixin:
849
850
  """
850
851
 
851
852
  # Set boundary conditions for PST-systems
852
- if self.system in ['pst-', '-pst']:
853
+ if self.system in ["pst-", "-pst"]:
853
854
  if not k:
854
- if self.mode in ['A']:
855
+ if self.mode in ["A"]:
855
856
  # Free end
856
- bc = np.array([self.N(z),
857
- self.M(z),
858
- self.V(z)
859
- ])
860
- elif self.mode in ['B'] and pos in ['r', 'right']:
857
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
858
+ elif self.mode in ["B"] and pos in ["r", "right"]:
861
859
  # Touchdown right
862
- bc = np.array([self.N(z),
863
- self.M(z),
864
- self.w(z)
865
- ])
866
- elif self.mode in ['B'] and pos in ['l', 'left']: # Kann dieser Block
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
867
862
  # Touchdown left # verschwinden? Analog zu 'A'
868
- bc = np.array([self.N(z),
869
- self.M(z),
870
- self.w(z)
871
- ])
872
- elif self.mode in ['C'] and pos in ['r', 'right']:
863
+ bc = np.array([self.N(z), self.M(z), self.w(z)])
864
+ elif self.mode in ["C"] and pos in ["r", "right"]:
873
865
  # Spring stiffness
874
- kR = self.substitute_stiffness(self.a - self.td, 'rested', 'rot')
866
+ kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
875
867
  # Touchdown right
876
- bc = np.array([self.N(z),
877
- self.M(z) + kR*self.psi(z),
878
- self.w(z)
879
- ])
880
- elif self.mode in ['C'] and pos in ['l', 'left']:
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"]:
881
870
  # Spring stiffness
882
- kR = self.substitute_stiffness(self.a - self.td, 'rested', 'rot')
871
+ kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
883
872
  # Touchdown left
884
- bc = np.array([self.N(z),
885
- self.M(z) - kR*self.psi(z),
886
- self.w(z)
887
- ])
873
+ bc = np.array([self.N(z), self.M(z) - kR * self.psi(z), self.w(z)])
888
874
  else:
889
875
  # Free end
890
- bc = np.array([
891
- self.N(z),
892
- self.M(z),
893
- self.V(z)
894
- ])
876
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
895
877
  # Set boundary conditions for PST-systems with vertical faces
896
- elif self.system in ['-vpst', 'vpst-']:
897
- bc = np.array([
898
- self.N(z),
899
- self.M(z),
900
- self.V(z)
901
- ])
878
+ elif self.system in ["-vpst", "vpst-"]:
879
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
902
880
  # Set boundary conditions for SKIER-systems
903
- elif self.system in ['skier', 'skiers']:
881
+ elif self.system in ["skier", "skiers"]:
904
882
  # Infinite end (vanishing complementary solution)
905
- bc = np.array([self.u(z, z0=0),
906
- self.w(z),
907
- self.psi(z)
908
- ])
883
+ bc = np.array([self.u(z, z0=0), self.w(z), self.psi(z)])
909
884
  # Set boundary conditions for substitute spring calculus
910
- elif self.system in ['rot', 'trans']:
911
- bc = np.array([self.N(z),
912
- self.M(z),
913
- self.V(z)
914
- ])
885
+ elif self.system in ["rot", "trans"]:
886
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
915
887
  else:
916
888
  raise ValueError(
917
- 'Boundary conditions not defined for'
918
- f'system of type {self.system}.')
889
+ f"Boundary conditions not defined for system of type {self.system}."
890
+ )
919
891
 
920
892
  return bc
921
893
 
922
- def eqs(self, zl, zr, k=False, pos='mid'):
894
+ def eqs(self, zl, zr, k=False, pos="mid"):
923
895
  """
924
896
  Provide boundary or transmission conditions for beam segments.
925
897
 
@@ -946,62 +918,75 @@ class SolutionMixin:
946
918
  or vector of transmission conditions (of length 6+6)
947
919
  for center segments.
948
920
  """
949
- if pos in ('l', 'left'):
950
- eqs = np.array([
951
- self.bc(zl, k, pos)[0], # Left boundary condition
952
- self.bc(zl, k, pos)[1], # Left boundary condition
953
- self.bc(zl, k, pos)[2], # Left boundary condition
954
- self.u(zr, z0=0), # ui(xi = li)
955
- self.w(zr), # wi(xi = li)
956
- self.psi(zr), # psii(xi = li)
957
- self.N(zr), # Ni(xi = li)
958
- self.M(zr), # Mi(xi = li)
959
- self.V(zr)]) # Vi(xi = li)
960
- elif pos in ('m', 'mid'):
961
- eqs = np.array([
962
- -self.u(zl, z0=0), # -ui(xi = 0)
963
- -self.w(zl), # -wi(xi = 0)
964
- -self.psi(zl), # -psii(xi = 0)
965
- -self.N(zl), # -Ni(xi = 0)
966
- -self.M(zl), # -Mi(xi = 0)
967
- -self.V(zl), # -Vi(xi = 0)
968
- self.u(zr, z0=0), # ui(xi = li)
969
- self.w(zr), # wi(xi = li)
970
- self.psi(zr), # psii(xi = li)
971
- self.N(zr), # Ni(xi = li)
972
- self.M(zr), # Mi(xi = li)
973
- self.V(zr)]) # Vi(xi = li)
974
- elif pos in ('r', 'right'):
975
- eqs = np.array([
976
- -self.u(zl, z0=0), # -ui(xi = 0)
977
- -self.w(zl), # -wi(xi = 0)
978
- -self.psi(zl), # -psii(xi = 0)
979
- -self.N(zl), # -Ni(xi = 0)
980
- -self.M(zl), # -Mi(xi = 0)
981
- -self.V(zl), # -Vi(xi = 0)
982
- self.bc(zr, k, pos)[0], # Right boundary condition
983
- self.bc(zr, k, pos)[1], # Right boundary condition
984
- self.bc(zr, k, pos)[2]]) # Right boundary condition
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
985
966
  else:
986
967
  raise ValueError(
987
- (f'Invalid position argument {pos} given. '
988
- 'Valid segment positions are l, m, and r, '
989
- 'or left, mid and right.'))
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
+ )
990
974
  return eqs
991
975
 
992
976
  def calc_segments(
993
- self,
994
- li: list[float] | list[int] |bool = False,
995
- mi: list[float] | list[int] |bool = False,
996
- ki: list[bool] | bool = False,
997
- k0: list[bool] | bool = False,
998
- L: float = 1e4,
999
- a: float = 0,
1000
- m: float = 0,
1001
- phi: float = 0,
1002
- cf: float = 0.5,
1003
- ratio: float = 1000,
1004
- **kwargs):
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
+ ):
1005
990
  """
1006
991
  Assemble lists defining the segments.
1007
992
 
@@ -1049,52 +1034,53 @@ class SolutionMixin:
1049
1034
  in the cracked (ki) and uncracked (k0) configurations.
1050
1035
  """
1051
1036
 
1052
- _ = kwargs # Unused arguments
1037
+ _ = kwargs # Unused arguments
1053
1038
 
1054
1039
  # Precompute touchdown properties
1055
1040
  self.calc_touchdown_system(L=L, a=a, cf=cf, phi=phi, ratio=ratio)
1056
1041
 
1057
1042
  # Assemble list defining the segments
1058
- if self.system == 'skiers':
1059
- li = np.array(li) # Segment lengths
1060
- mi = np.array(mi) # Skier weights
1061
- ki = np.array(ki) # Crack
1062
- k0 = np.array(k0) # No crack
1063
- elif self.system == 'pst-':
1064
- li = np.array([L - self.a, self.td]) # Segment lengths
1065
- mi = np.array([0]) # Skier weights
1066
- ki = np.array([True, False]) # Crack
1067
- k0 = np.array([True, True]) # No crack
1068
- elif self.system == '-pst':
1069
- li = np.array([self.td, L - self.a]) # Segment lengths
1070
- mi = np.array([0]) # Skier weights
1071
- ki = np.array([False, True]) # Crack
1072
- k0 = np.array([True, True]) # No crack
1073
- elif self.system == 'vpst-':
1074
- li = np.array([L - a, a]) # Segment lengths
1075
- mi = np.array([0]) # Skier weights
1076
- ki = np.array([True, False]) # Crack
1077
- k0 = np.array([True, True]) # No crack
1078
- elif self.system == '-vpst':
1079
- li = np.array([a, L - a]) # Segment lengths
1080
- mi = np.array([0]) # Skier weights
1081
- ki = np.array([False, True]) # Crack
1082
- k0 = np.array([True, True]) # No crack
1083
- elif self.system == 'skier':
1084
- lb = (L - self.a)/2 # Half bedded length
1085
- lf = self.a/2 # Half free length
1086
- li = np.array([lb, lf, lf, lb]) # Segment lengths
1087
- mi = np.array([0, m, 0]) # Skier weights
1088
- ki = np.array([True, False, False, True]) # Crack
1089
- k0 = np.array([True, True, True, True]) # No crack
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
1090
1075
  else:
1091
- raise ValueError(f'System {self.system} is not implemented.')
1076
+ raise ValueError(f"System {self.system} is not implemented.")
1092
1077
 
1093
1078
  # Fill dictionary
1094
1079
  segments = {
1095
- 'nocrack': {'li': li, 'mi': mi, 'ki': k0},
1096
- 'crack': {'li': li, 'mi': mi, 'ki': ki},
1097
- 'both': {'li': li, 'mi': mi, 'ki': ki, 'k0': k0}}
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
+ }
1098
1084
  return segments
1099
1085
 
1100
1086
  def assemble_and_solve(self, phi, li, mi, ki):
@@ -1133,29 +1119,37 @@ class SolutionMixin:
1133
1119
 
1134
1120
  # No foundation
1135
1121
  if not any(ki):
1136
- raise ValueError('Provide at least one supported segment.')
1122
+ raise ValueError("Provide at least one supported segment.")
1137
1123
  # Mismatch of number of segements and transisions
1138
1124
  if len(li) != len(ki) or len(li) - 1 != len(mi):
1139
- raise ValueError('Make sure len(li)=N, len(ki)=N, and '
1140
- 'len(mi)=N-1 for a system of N segments.')
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
+ )
1141
1129
 
1142
- if self.system not in ['pst-', '-pst', 'vpst-', '-vpst', 'rot', 'trans']:
1130
+ if self.system not in ["pst-", "-pst", "vpst-", "-vpst", "rot", "trans"]:
1143
1131
  # Boundary segments must be on foundation for infinite BCs
1144
1132
  if not all([ki[0], ki[-1]]):
1145
- raise ValueError('Provide supported boundary segments in '
1146
- 'order to account for infinite extensions.')
1133
+ raise ValueError(
1134
+ "Provide supported boundary segments in "
1135
+ "order to account for infinite extensions."
1136
+ )
1147
1137
  # Make sure infinity boundary conditions are far enough from skiers
1148
1138
  if li[0] < 5e3 or li[-1] < 5e3:
1149
- print(('WARNING: Boundary segments are short. Make sure '
1150
- 'the complementary solution has decayed to the '
1151
- 'boundaries.'))
1139
+ print(
1140
+ (
1141
+ "WARNING: Boundary segments are short. Make sure "
1142
+ "the complementary solution has decayed to the "
1143
+ "boundaries."
1144
+ )
1145
+ )
1152
1146
 
1153
1147
  # --- PREPROCESSING ---------------------------------------------------
1154
1148
 
1155
1149
  # Determine size of linear system of equations
1156
- nS = len(li) # Number of beam segments
1150
+ nS = len(li) # Number of beam segments
1157
1151
 
1158
- nDOF = 6 # Number of free constants per segment
1152
+ nDOF = 6 # Number of free constants per segment
1159
1153
 
1160
1154
  # Add dummy segment if only one segment provided
1161
1155
  if nS == 1:
@@ -1165,13 +1159,13 @@ class SolutionMixin:
1165
1159
  nS = 2
1166
1160
 
1167
1161
  # Assemble position vector
1168
- pi = np.full(nS, 'm')
1169
- pi[0], pi[-1] = 'l', 'r'
1162
+ pi = np.full(nS, "m")
1163
+ pi[0], pi[-1] = "l", "r"
1170
1164
 
1171
1165
  # Initialize matrices
1172
- zh0 = np.zeros([nS*6, nS*nDOF])
1173
- zp0 = np.zeros([nS*6, 1])
1174
- rhs = np.zeros([nS*6, 1])
1166
+ zh0 = np.zeros([nS * 6, nS * nDOF])
1167
+ zp0 = np.zeros([nS * 6, 1])
1168
+ rhs = np.zeros([nS * 6, 1])
1175
1169
 
1176
1170
  # --- ASSEMBLE LINEAR SYSTEM OF EQUATIONS -----------------------------
1177
1171
 
@@ -1181,72 +1175,73 @@ class SolutionMixin:
1181
1175
  l, k, pos = li[i], ki[i], pi[i]
1182
1176
  # Transmission conditions at left and right segment ends
1183
1177
  zhi = self.eqs(
1184
- zl=self.zh(x=0, l=l, bed=k),
1185
- zr=self.zh(x=l, l=l, bed=k),
1186
- k=k, pos=pos)
1178
+ zl=self.zh(x=0, l=l, bed=k), zr=self.zh(x=l, l=l, bed=k), k=k, pos=pos
1179
+ )
1187
1180
  zpi = self.eqs(
1188
1181
  zl=self.zp(x=0, phi=phi, bed=k),
1189
1182
  zr=self.zp(x=l, phi=phi, bed=k),
1190
- k=k, pos=pos)
1183
+ k=k,
1184
+ pos=pos,
1185
+ )
1191
1186
  # Rows for left-hand side assembly
1192
1187
  start = 0 if i == 0 else 3
1193
1188
  stop = 6 if i == nS - 1 else 9
1194
1189
  # Assemble left-hand side
1195
- zh0[(6*i - start):(6*i + stop), i*nDOF:(i + 1)*nDOF] = zhi
1196
- zp0[(6*i - start):(6*i + stop)] += zpi
1190
+ zh0[(6 * i - start) : (6 * i + stop), i * nDOF : (i + 1) * nDOF] = zhi
1191
+ zp0[(6 * i - start) : (6 * i + stop)] += zpi
1197
1192
 
1198
1193
  # Loop through loads to assemble right-hand side
1199
1194
  for i, m in enumerate(mi, start=1):
1200
1195
  # Get skier loads
1201
1196
  Fn, Ft = self.get_skier_load(m, phi)
1202
1197
  # Right-hand side for transmission from segment i-1 to segment i
1203
- rhs[6*i:6*i + 3] = np.vstack([Ft, -Ft*self.h/2, Fn])
1198
+ rhs[6 * i : 6 * i + 3] = np.vstack([Ft, -Ft * self.h / 2, Fn])
1204
1199
  # Set rhs so that complementary integral vanishes at boundaries
1205
- if self.system not in ['pst-', '-pst', 'rested']:
1200
+ if self.system not in ["pst-", "-pst", "rested"]:
1206
1201
  rhs[:3] = self.bc(self.zp(x=0, phi=phi, bed=ki[0]))
1207
1202
  rhs[-3:] = self.bc(self.zp(x=li[-1], phi=phi, bed=ki[-1]))
1208
1203
 
1209
1204
  # Set rhs for vertical faces
1210
- if self.system in ['vpst-', '-vpst']:
1205
+ if self.system in ["vpst-", "-vpst"]:
1211
1206
  # Calculate center of gravity and mass of
1212
1207
  # added or cut off slab segement
1213
1208
  xs, zs, m = calc_vertical_bc_center_of_gravity(self.slab, phi)
1214
1209
  # Convert slope angle to radians
1215
1210
  phi = np.deg2rad(phi)
1216
1211
  # Translate inbto section forces and moments
1217
- N = -self.g*m*np.sin(phi)
1218
- M = -self.g*m*(xs*np.cos(phi) + zs*np.sin(phi))
1219
- V = self.g*m*np.cos(phi)
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)
1220
1215
  # Add to right-hand side
1221
- rhs[:3] = np.vstack([N, M, V]) # left end
1222
- rhs[-3:] = np.vstack([N, M, V]) # right end
1216
+ rhs[:3] = np.vstack([N, M, V]) # left end
1217
+ rhs[-3:] = np.vstack([N, M, V]) # right end
1223
1218
 
1224
1219
  # Loop through segments to set touchdown conditions at rhs
1225
1220
  for i in range(nS):
1226
1221
  # Length, foundation and position of segment i
1227
1222
  l, k, pos = li[i], ki[i], pi[i]
1228
1223
  # Set displacement BC in stage B
1229
- if not k and bool(self.mode in ['B']):
1230
- if i==0:
1231
- rhs[:3] = np.vstack([0,0,self.tc])
1224
+ if not k and bool(self.mode in ["B"]):
1225
+ if i == 0:
1226
+ rhs[:3] = np.vstack([0, 0, self.tc])
1232
1227
  if i == (nS - 1):
1233
- rhs[-3:] = np.vstack([0,0,self.tc])
1228
+ rhs[-3:] = np.vstack([0, 0, self.tc])
1234
1229
  # Set normal force and displacement BC for stage C
1235
- if not k and bool(self.mode in ['C']):
1236
- N = self.calc_qt()*(self.a - self.td)
1237
- if i==0:
1238
- rhs[:3] = np.vstack([-N,0,self.tc])
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])
1239
1234
  if i == (nS - 1):
1240
- rhs[-3:] = np.vstack([N,0,self.tc])
1235
+ rhs[-3:] = np.vstack([N, 0, self.tc])
1241
1236
 
1242
1237
  # Rhs for substitute spring stiffness
1243
- if self.system in ['rot']:
1238
+ if self.system in ["rot"]:
1244
1239
  # apply arbitrary moment of 1 at left boundary
1245
- rhs = rhs*0
1240
+ rhs = rhs * 0
1246
1241
  rhs[1] = 1
1247
- if self.system in ['trans']:
1242
+ if self.system in ["trans"]:
1248
1243
  # apply arbitrary force of 1 at left boundary
1249
- rhs = rhs*0
1244
+ rhs = rhs * 0
1250
1245
  rhs[2] = 1
1251
1246
 
1252
1247
  # --- SOLVE -----------------------------------------------------------
@@ -1266,13 +1261,14 @@ class AnalysisMixin:
1266
1261
  """
1267
1262
 
1268
1263
  def rasterize_solution(
1269
- self,
1270
- C: np.ndarray,
1271
- phi: float,
1272
- li: list[float] | bool,
1273
- ki: list[bool] | bool,
1274
- num: int = 250,
1275
- **kwargs):
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
+ ):
1276
1272
  """
1277
1273
  Compute rasterized solution vector.
1278
1274
 
@@ -1310,7 +1306,7 @@ class AnalysisMixin:
1310
1306
  C, ki, li = C[:, isnonzero], ki[isnonzero], li[isnonzero]
1311
1307
 
1312
1308
  # Compute number of plot points per segment (+1 for last segment)
1313
- nq = np.ceil(li/li.sum()*num).astype('int')
1309
+ nq = np.ceil(li / li.sum() * num).astype("int")
1314
1310
  nq[-1] += 1
1315
1311
 
1316
1312
  # Provide cumulated length and plot point lists
@@ -1329,14 +1325,14 @@ class AnalysisMixin:
1329
1325
  # Compute start and end coordinates of segment i
1330
1326
  x0 = lic[i]
1331
1327
  # Assemble global coordinate vector
1332
- xq[nqc[i]:nqc[i + 1]] = x0 + xi
1328
+ xq[nqc[i] : nqc[i + 1]] = x0 + xi
1333
1329
  # Mask coordinates not on foundation (including endpoints)
1334
1330
  if not ki[i]:
1335
- issupported[nqc[i]:nqc[i + 1]] = False
1331
+ issupported[nqc[i] : nqc[i + 1]] = False
1336
1332
  # Compute segment solution
1337
1333
  zi = self.z(xi, C[:, [i]], l, phi, ki[i])
1338
1334
  # Assemble global solution matrix
1339
- zq[:, nqc[i]:nqc[i + 1]] = zi
1335
+ zq[:, nqc[i] : nqc[i + 1]] = zi
1340
1336
 
1341
1337
  # Make sure cracktips are included
1342
1338
  transmissionbool = [ki[j] or ki[j + 1] for j, _ in enumerate(ki[:-1])]
@@ -1391,7 +1387,6 @@ class AnalysisMixin:
1391
1387
 
1392
1388
  # Loop through segments with crack advance
1393
1389
  for j, l in enumerate(li):
1394
-
1395
1390
  # Uncracked (0) and cracked (1) solutions at integration points
1396
1391
  z0 = partial(self.z, C=C0[:, [j]], l=l, phi=phi, bed=True)
1397
1392
  z1 = partial(self.z, C=C1[:, [j]], l=l, phi=phi, bed=False)
@@ -1401,14 +1396,12 @@ class AnalysisMixin:
1401
1396
  int2 = partial(self.int2, z0=z0, z1=z1)
1402
1397
 
1403
1398
  # Segement contributions to total crack opening integral
1404
- Ginc1 += romberg(int1, 0, l, rtol=self.tol,
1405
- vec_func=True)/(2*da)
1406
- Ginc2 += romberg(int2, 0, l, rtol=self.tol,
1407
- vec_func=True)/(2*da)
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)
1408
1401
 
1409
1402
  return np.array([Ginc1 + Ginc2, Ginc1, Ginc2]).flatten()
1410
1403
 
1411
- def gdif(self, C, phi, li, ki, unit='kJ/m^2', **kwargs):
1404
+ def gdif(self, C, phi, li, ki, unit="kJ/m^2", **kwargs):
1412
1405
  """
1413
1406
  Compute differential energy release rate of all crack tips.
1414
1407
 
@@ -1458,9 +1451,9 @@ class AnalysisMixin:
1458
1451
 
1459
1452
  # Adjust contributions for center cracks
1460
1453
  if nct > 1:
1461
- avgmask = np.full(nct, True) # Initialize mask
1454
+ avgmask = np.full(nct, True) # Initialize mask
1462
1455
  avgmask[[0, -1]] = ki[[0, -1]] # Do not weight edge cracks
1463
- Gdif[:, avgmask] *= 0.5 # Weigth with half crack length
1456
+ Gdif[:, avgmask] *= 0.5 # Weigth with half crack length
1464
1457
 
1465
1458
  # Return total differential energy release rate of all crack tips
1466
1459
  return Gdif.sum(axis=1)
@@ -1486,18 +1479,17 @@ class AnalysisMixin:
1486
1479
  # Get ply (layer) coordinates
1487
1480
  z = self.get_ply_coordinates()
1488
1481
  # Compute number of grid points per layer
1489
- nlayer = np.ceil((z[1:] - z[:-1])/dz).astype(np.int32) + 1
1482
+ nlayer = np.ceil((z[1:] - z[:-1]) / dz).astype(np.int32) + 1
1490
1483
  # Calculate grid points as list of z-coordinates (mm)
1491
- zi = np.hstack([
1492
- np.linspace(z[i], z[i + 1], n, endpoint=True)
1493
- for i, n in enumerate(nlayer)
1494
- ])
1484
+ zi = np.hstack(
1485
+ [np.linspace(z[i], z[i + 1], n, endpoint=True) for i, n in enumerate(nlayer)]
1486
+ )
1495
1487
  # Get lists of corresponding elastic properties (E, nu, rho)
1496
1488
  si = np.repeat(self.slab[:, [2, 4, 0]], nlayer, axis=0)
1497
1489
  # Assemble mesh with columns (z, E, G, nu)
1498
1490
  return np.column_stack([zi, si])
1499
1491
 
1500
- def Sxx(self, Z, phi, dz=2, unit='kPa'):
1492
+ def Sxx(self, Z, phi, dz=2, unit="kPa"):
1501
1493
  """
1502
1494
  Compute axial normal stress in slab layers.
1503
1495
 
@@ -1518,15 +1510,12 @@ class AnalysisMixin:
1518
1510
  Axial slab normal stress in specified unit.
1519
1511
  """
1520
1512
  # Unit conversion dict
1521
- convert = {
1522
- 'kPa': 1e3,
1523
- 'MPa': 1
1524
- }
1513
+ convert = {"kPa": 1e3, "MPa": 1}
1525
1514
 
1526
1515
  # Get mesh along z-axis
1527
1516
  zmesh = self.get_zmesh(dz=dz)
1528
1517
  zi = zmesh[:, 0]
1529
- rho = 1e-12*zmesh[:, 3]
1518
+ rho = 1e-12 * zmesh[:, 3]
1530
1519
 
1531
1520
  # Get dimensions of stress field (n rows, m columns)
1532
1521
  n = zmesh.shape[0]
@@ -1537,18 +1526,18 @@ class AnalysisMixin:
1537
1526
 
1538
1527
  # Compute axial normal stress Sxx at grid points in MPa
1539
1528
  for i, (z, E, nu, _) in enumerate(zmesh):
1540
- Sxx[i, :] = E/(1-nu**2)*self.du_dx(Z, z)
1529
+ Sxx[i, :] = E / (1 - nu**2) * self.du_dx(Z, z)
1541
1530
 
1542
1531
  # Calculate weight load at grid points and superimpose on stress field
1543
- qt = -rho*self.g*np.sin(np.deg2rad(phi))
1532
+ qt = -rho * self.g * np.sin(np.deg2rad(phi))
1544
1533
  for i, qi in enumerate(qt[:-1]):
1545
- Sxx[i, :] += qi*(zi[i+1] - zi[i])
1546
- Sxx[-1, :] += qt[-1]*(zi[-1] - zi[-2])
1534
+ Sxx[i, :] += qi * (zi[i + 1] - zi[i])
1535
+ Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
1547
1536
 
1548
1537
  # Return axial normal stress in specified unit
1549
- return convert[unit]*Sxx
1538
+ return convert[unit] * Sxx
1550
1539
 
1551
- def Txz(self, Z, phi, dz=2, unit='kPa'):
1540
+ def Txz(self, Z, phi, dz=2, unit="kPa"):
1552
1541
  """
1553
1542
  Compute shear stress in slab layers.
1554
1543
 
@@ -1569,14 +1558,11 @@ class AnalysisMixin:
1569
1558
  Shear stress at grid points in the slab in specified unit.
1570
1559
  """
1571
1560
  # Unit conversion dict
1572
- convert = {
1573
- 'kPa': 1e3,
1574
- 'MPa': 1
1575
- }
1561
+ convert = {"kPa": 1e3, "MPa": 1}
1576
1562
  # Get mesh along z-axis
1577
1563
  zmesh = self.get_zmesh(dz=dz)
1578
1564
  zi = zmesh[:, 0]
1579
- rho = 1e-12*zmesh[:, 3]
1565
+ rho = 1e-12 * zmesh[:, 3]
1580
1566
 
1581
1567
  # Get dimensions of stress field (n rows, m columns)
1582
1568
  n = zmesh.shape[0]
@@ -1592,10 +1578,10 @@ class AnalysisMixin:
1592
1578
 
1593
1579
  # Calculate first derivative of sxx at z-grid points
1594
1580
  for i, (z, E, nu, _) in enumerate(zmesh):
1595
- dsxx_dx[i, :] = E/(1-nu**2)*(du0_dxdx + z*dpsi_dxdx)
1581
+ dsxx_dx[i, :] = E / (1 - nu**2) * (du0_dxdx + z * dpsi_dxdx)
1596
1582
 
1597
1583
  # Calculate weight load at grid points
1598
- qt = -rho*self.g*np.sin(np.deg2rad(phi))
1584
+ qt = -rho * self.g * np.sin(np.deg2rad(phi))
1599
1585
 
1600
1586
  # Integrate -dsxx_dx along z and add cumulative weight load
1601
1587
  # to obtain shear stress Txz in MPa
@@ -1603,9 +1589,9 @@ class AnalysisMixin:
1603
1589
  Txz += cumulative_trapezoid(qt, zi, initial=0)[:, None]
1604
1590
 
1605
1591
  # Return shear stress Txz in specified unit
1606
- return convert[unit]*Txz
1592
+ return convert[unit] * Txz
1607
1593
 
1608
- def Szz(self, Z, phi, dz=2, unit='kPa'):
1594
+ def Szz(self, Z, phi, dz=2, unit="kPa"):
1609
1595
  """
1610
1596
  Compute transverse normal stress in slab layers.
1611
1597
 
@@ -1627,15 +1613,12 @@ class AnalysisMixin:
1627
1613
  specified unit.
1628
1614
  """
1629
1615
  # Unit conversion dict
1630
- convert = {
1631
- 'kPa': 1e3,
1632
- 'MPa': 1
1633
- }
1616
+ convert = {"kPa": 1e3, "MPa": 1}
1634
1617
 
1635
1618
  # Get mesh along z-axis
1636
1619
  zmesh = self.get_zmesh(dz=dz)
1637
1620
  zi = zmesh[:, 0]
1638
- rho = 1e-12*zmesh[:, 3]
1621
+ rho = 1e-12 * zmesh[:, 3]
1639
1622
 
1640
1623
  # Get dimensions of stress field (n rows, m columns)
1641
1624
  n = zmesh.shape[0]
@@ -1651,10 +1634,10 @@ class AnalysisMixin:
1651
1634
 
1652
1635
  # Calculate second derivative of sxx at z-grid points
1653
1636
  for i, (z, E, nu, _) in enumerate(zmesh):
1654
- dsxx_dxdx[i, :] = E/(1-nu**2)*(du0_dxdxdx + z*dpsi_dxdxdx)
1637
+ dsxx_dxdx[i, :] = E / (1 - nu**2) * (du0_dxdxdx + z * dpsi_dxdxdx)
1655
1638
 
1656
1639
  # Calculate weight load at grid points
1657
- qn = rho*self.g*np.cos(np.deg2rad(phi))
1640
+ qn = rho * self.g * np.cos(np.deg2rad(phi))
1658
1641
 
1659
1642
  # Integrate dsxx_dxdx twice along z to obtain transverse
1660
1643
  # normal stress Szz in MPa
@@ -1663,10 +1646,11 @@ class AnalysisMixin:
1663
1646
  Szz += cumulative_trapezoid(-qn, zi, initial=0)[:, None]
1664
1647
 
1665
1648
  # Return shear stress txz in specified unit
1666
- return convert[unit]*Szz
1649
+ return convert[unit] * Szz
1667
1650
 
1668
- def principal_stress_slab(self, Z, phi, dz=2, unit='kPa',
1669
- val='max', normalize=False):
1651
+ def principal_stress_slab(
1652
+ self, Z, phi, dz=2, unit="kPa", val="max", normalize=False
1653
+ ):
1670
1654
  """
1671
1655
  Compute maxium or minimum principal stress in slab layers.
1672
1656
 
@@ -1698,11 +1682,11 @@ class AnalysisMixin:
1698
1682
  is requested.
1699
1683
  """
1700
1684
  # Raise error if specified component is not available
1701
- if val not in ['min', 'max']:
1702
- raise ValueError(f'Component {val} not defined.')
1685
+ if val not in ["min", "max"]:
1686
+ raise ValueError(f"Component {val} not defined.")
1703
1687
 
1704
1688
  # Multiplier selection dict
1705
- m = {'max': 1, 'min': -1}
1689
+ m = {"max": 1, "min": -1}
1706
1690
 
1707
1691
  # Get axial normal stresses, shear stresses, transverse normal stresses
1708
1692
  Sxx = self.Sxx(Z=Z, phi=phi, dz=dz, unit=unit)
@@ -1710,24 +1694,25 @@ class AnalysisMixin:
1710
1694
  Szz = self.Szz(Z=Z, phi=phi, dz=dz, unit=unit)
1711
1695
 
1712
1696
  # Calculate principal stress
1713
- Ps = (Sxx + Szz)/2 + m[val]*np.sqrt((Sxx - Szz)**2 + 4*Txz**2)/2
1697
+ Ps = (Sxx + Szz) / 2 + m[val] * np.sqrt((Sxx - Szz) ** 2 + 4 * Txz**2) / 2
1714
1698
 
1715
1699
  # Raise error if normalization of compressive stresses is attempted
1716
- if normalize and val == 'min':
1717
- raise ValueError('Can only normlize tensile stresses.')
1700
+ if normalize and val == "min":
1701
+ raise ValueError("Can only normlize tensile stresses.")
1718
1702
 
1719
1703
  # Normalize tensile stresses to tensile strength
1720
- if normalize and val == 'max':
1704
+ if normalize and val == "max":
1721
1705
  # Get layer densities
1722
1706
  rho = self.get_zmesh(dz=dz)[:, 3]
1723
1707
  # Normlize maximum principal stress to layers' tensile strength
1724
- return Ps/tensile_strength_slab(rho, unit=unit)[:, None]
1708
+ return Ps / tensile_strength_slab(rho, unit=unit)[:, None]
1725
1709
 
1726
1710
  # Return absolute principal stresses
1727
1711
  return Ps
1728
1712
 
1729
- def principal_stress_weaklayer(self, Z, sc=2.6, unit='kPa', val='min',
1730
- normalize=False):
1713
+ def principal_stress_weaklayer(
1714
+ self, Z, sc=2.6, unit="kPa", val="min", normalize=False
1715
+ ):
1731
1716
  """
1732
1717
  Compute maxium or minimum principal stress in the weak layer.
1733
1718
 
@@ -1757,30 +1742,31 @@ class AnalysisMixin:
1757
1742
  is requested.
1758
1743
  """
1759
1744
  # Raise error if specified component is not available
1760
- if val not in ['min', 'max']:
1761
- raise ValueError(f'Component {val} not defined.')
1745
+ if val not in ["min", "max"]:
1746
+ raise ValueError(f"Component {val} not defined.")
1762
1747
 
1763
1748
  # Multiplier selection dict
1764
- m = {'max': 1, 'min': -1}
1749
+ m = {"max": 1, "min": -1}
1765
1750
 
1766
1751
  # Get weak-layer normal and shear stresses
1767
1752
  sig = self.sig(Z, unit=unit)
1768
1753
  tau = self.tau(Z, unit=unit)
1769
1754
 
1770
1755
  # Calculate principal stress
1771
- ps = sig/2 + m[val]*np.sqrt(sig**2 + 4*tau**2)/2
1756
+ ps = sig / 2 + m[val] * np.sqrt(sig**2 + 4 * tau**2) / 2
1772
1757
 
1773
1758
  # Raise error if normalization of tensile stresses is attempted
1774
- if normalize and val == 'max':
1775
- raise ValueError('Can only normlize compressive stresses.')
1759
+ if normalize and val == "max":
1760
+ raise ValueError("Can only normlize compressive stresses.")
1776
1761
 
1777
1762
  # Normalize compressive stresses to compressive strength
1778
- if normalize and val == 'min':
1779
- return ps/sc
1763
+ if normalize and val == "min":
1764
+ return ps / sc
1780
1765
 
1781
1766
  # Return absolute principal stresses
1782
1767
  return ps
1783
1768
 
1769
+
1784
1770
  class OutputMixin:
1785
1771
  """
1786
1772
  Mixin for outputs.
@@ -1788,6 +1774,7 @@ class OutputMixin:
1788
1774
  Provides convenience methods for the assembly of output lists
1789
1775
  such as rasterized displacements or rasterized stresses.
1790
1776
  """
1777
+
1791
1778
  def external_potential(self, C, phi, L, **segments):
1792
1779
  """
1793
1780
  Compute total external potential (pst only).
@@ -1822,17 +1809,21 @@ class OutputMixin:
1822
1809
  qt = self.calc_qt()
1823
1810
  # use +/- and us[0]/us[-1] according to system and phi
1824
1811
  # compute total external potential
1825
- Pi_ext = - qn*(segments['li'][0] + segments['li'][1])*np.average(w0) \
1826
- - qn*(L - (segments['li'][0] + segments['li'][1]))*self.tc
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
+ )
1827
1816
  # Ensure
1828
- if self.system in ['pst-']:
1817
+ if self.system in ["pst-"]:
1829
1818
  ub = us[-1]
1830
- elif self.system in ['-pst']:
1819
+ elif self.system in ["-pst"]:
1831
1820
  ub = us[0]
1832
- Pi_ext += - qt*(segments['li'][0] + segments['li'][1])*np.average(us) \
1833
- - qt*(L - (segments['li'][0] + segments['li'][1]))*ub
1834
- if self.system not in ['pst-', '-pst']:
1835
- print('Input error: Only pst-setup implemented at the moment.')
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.")
1836
1827
 
1837
1828
  return Pi_ext
1838
1829
 
@@ -1871,31 +1862,37 @@ class OutputMixin:
1871
1862
 
1872
1863
  # Compute weak layer displacements
1873
1864
  wweak = self.w(zweak)
1874
- uweak = self.u(zweak, z0=self.h/2)
1865
+ uweak = self.u(zweak, z0=self.h / 2)
1875
1866
 
1876
1867
  # Compute stored energy of the slab (monte-carlo integration)
1877
1868
  n = len(xq)
1878
1869
  nweak = len(xweak)
1879
1870
  # energy share from moment, shear force, wl normal and tangential springs
1880
- Pi_int = L/2/n/self.A11*np.sum([Ni**2 for Ni in N]) \
1881
- + L/2/n/(self.D11-self.B11**2/self.A11)*np.sum([Mi**2 for Mi in M]) \
1882
- + L/2/n/self.kA55*np.sum([Vi**2 for Vi in V]) \
1883
- + L*self.kn/2/nweak*np.sum([wi**2 for wi in wweak]) \
1884
- + L*self.kt/2/nweak*np.sum([ui**2 for ui in uweak])
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
+ )
1885
1882
  # energy share from substitute rotation spring
1886
- if self.system in ['pst-']:
1887
- Pi_int += 1/2*M[-1]*(self.psi(zq)[-1])**2
1888
- elif self.system in ['-pst']:
1889
- Pi_int += 1/2*M[0]*(self.psi(zq)[0])**2
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
1890
1887
  else:
1891
- print('Input error: Only pst-setup implemented at the moment.')
1888
+ print("Input error: Only pst-setup implemented at the moment.")
1892
1889
 
1893
1890
  return Pi_int
1894
1891
 
1895
1892
  def total_potential(self, C, phi, L, **segments):
1896
1893
  """
1897
1894
  Returns total differential potential
1898
-
1895
+
1899
1896
  Arguments
1900
1897
  ---------
1901
1898
  C : ndarray
@@ -1920,7 +1917,7 @@ class OutputMixin:
1920
1917
 
1921
1918
  return Pi_int + Pi_ext
1922
1919
 
1923
- def get_weaklayer_shearstress(self, x, z, unit='MPa', removeNaNs=False):
1920
+ def get_weaklayer_shearstress(self, x, z, unit="MPa", removeNaNs=False):
1924
1921
  """
1925
1922
  Compute weak-layer shear stress.
1926
1923
 
@@ -1944,7 +1941,7 @@ class OutputMixin:
1944
1941
  Normal stress (stress unit input).
1945
1942
  """
1946
1943
  # Convert coordinates from mm to cm and stresses from MPa to unit
1947
- x = x/10
1944
+ x = x / 10
1948
1945
  tau = self.tau(z, unit=unit)
1949
1946
  # Filter stresses in unspupported segments
1950
1947
  if removeNaNs:
@@ -1957,7 +1954,7 @@ class OutputMixin:
1957
1954
 
1958
1955
  return x, tau
1959
1956
 
1960
- def get_weaklayer_normalstress(self, x, z, unit='MPa', removeNaNs=False):
1957
+ def get_weaklayer_normalstress(self, x, z, unit="MPa", removeNaNs=False):
1961
1958
  """
1962
1959
  Compute weak-layer normal stress.
1963
1960
 
@@ -1981,7 +1978,7 @@ class OutputMixin:
1981
1978
  Normal stress (stress unit input).
1982
1979
  """
1983
1980
  # Convert coordinates from mm to cm and stresses from MPa to unit
1984
- x = x/10
1981
+ x = x / 10
1985
1982
  sig = self.sig(z, unit=unit)
1986
1983
  # Filter stresses in unspupported segments
1987
1984
  if removeNaNs:
@@ -1994,7 +1991,7 @@ class OutputMixin:
1994
1991
 
1995
1992
  return x, sig
1996
1993
 
1997
- def get_slab_displacement(self, x, z, loc='mid', unit='mm'):
1994
+ def get_slab_displacement(self, x, z, loc="mid", unit="mm"):
1998
1995
  """
1999
1996
  Compute horizontal slab displacement.
2000
1997
 
@@ -2019,15 +2016,15 @@ class OutputMixin:
2019
2016
  Horizontal displacements (unit input).
2020
2017
  """
2021
2018
  # Coordinates (cm)
2022
- x = x/10
2019
+ x = x / 10
2023
2020
  # Locator
2024
- z0 = {'top': -self.h/2, 'mid': 0, 'bot': self.h/2}
2021
+ z0 = {"top": -self.h / 2, "mid": 0, "bot": self.h / 2}
2025
2022
  # Displacement (unit)
2026
2023
  u = self.u(z, z0=z0[loc], unit=unit)
2027
2024
  # Output array
2028
2025
  return x, u
2029
2026
 
2030
- def get_slab_deflection(self, x, z, unit='mm'):
2027
+ def get_slab_deflection(self, x, z, unit="mm"):
2031
2028
  """
2032
2029
  Compute vertical slab displacement.
2033
2030
 
@@ -2050,13 +2047,13 @@ class OutputMixin:
2050
2047
  Vertical deflections (unit input).
2051
2048
  """
2052
2049
  # Coordinates (cm)
2053
- x = x/10
2050
+ x = x / 10
2054
2051
  # Deflection (unit)
2055
2052
  w = self.w(z, unit=unit)
2056
2053
  # Output array
2057
2054
  return x, w
2058
2055
 
2059
- def get_slab_rotation(self, x, z, unit='degrees'):
2056
+ def get_slab_rotation(self, x, z, unit="degrees"):
2060
2057
  """
2061
2058
  Compute slab cross-section rotation angle.
2062
2059
 
@@ -2079,7 +2076,7 @@ class OutputMixin:
2079
2076
  Cross section rotations (unit input).
2080
2077
  """
2081
2078
  # Coordinates (cm)
2082
- x = x/10
2079
+ x = x / 10
2083
2080
  # Cross-section rotation angle (unit)
2084
2081
  psi = self.psi(z, unit=unit)
2085
2082
  # Output array