weac 2.5.2__py3-none-any.whl → 2.6.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 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
 
@@ -530,9 +528,9 @@ class SlabContactMixin:
530
528
  """
531
529
  # subtract displacement under constact load from collapsed wl height
532
530
  qn = self.calc_qn()
533
- self.tc = cf*self.t - qn/self.kn
531
+ self.tc = cf * self.t - qn / self.kn
534
532
 
535
- def set_phi(self,phi):
533
+ def set_phi(self, phi):
536
534
  """
537
535
  Set inclination of the slab.
538
536
 
@@ -577,9 +575,9 @@ class SlabContactMixin:
577
575
  """
578
576
  return self.get_weight_load(self.phi)[1] + self.get_surface_load(self.phi)[1]
579
577
 
580
- def substitute_stiffness(self, L, support='rested', dof='rot'):
578
+ def substitute_stiffness(self, L, support="rested", dof="rot"):
581
579
  """
582
- Calc substitute stiffness for beam on elastic foundation.
580
+ Calc substitute stiffness for beam on elastic foundation.
583
581
 
584
582
  Arguments
585
583
  ---------
@@ -587,7 +585,7 @@ class SlabContactMixin:
587
585
  Total length of the PST-column (mm).
588
586
  support : string
589
587
  Type of segment foundation. Defaults to 'rested'.
590
- dof : string
588
+ dof : string
591
589
  Type of substitute spring, either 'rot' or 'trans'. Defaults to 'rot'.
592
590
 
593
591
  Returns
@@ -595,39 +593,40 @@ class SlabContactMixin:
595
593
  k : stiffness of substitute spring.
596
594
  """
597
595
  # adjust system to substitute system
598
- if dof in ['rot']:
596
+ if dof in ["rot"]:
599
597
  tempsys = self.system
600
- self.system = 'rot'
601
- if dof in ['trans']:
598
+ self.system = "rot"
599
+ if dof in ["trans"]:
602
600
  tempsys = self.system
603
- self.system = 'trans'
601
+ self.system = "trans"
604
602
 
605
603
  # Change eigensystem for rested segment
606
- if support in ['rested']:
604
+ if support in ["rested"]:
607
605
  tempkn = self.kn
608
606
  tempkt = self.kt
609
- self.kn = self.ratio*self.kn
610
- self.kt = self.ratio*self.kt
607
+ self.kn = self.ratio * self.kn
608
+ self.kt = self.ratio * self.kt
611
609
  self.calc_system_matrix()
612
610
  self.calc_eigensystem()
613
611
 
614
612
  # prepare list of segment characteristics
615
- segments = {'li': np.array([L, 0.]),
616
- 'mi': np.array([0]),
617
- 'ki': np.array([True, True])}
613
+ segments = {
614
+ "li": np.array([L, 0.0]),
615
+ "mi": np.array([0]),
616
+ "ki": np.array([True, True]),
617
+ }
618
618
  # solve system of equations
619
619
  constants = self.assemble_and_solve(phi=0, **segments)
620
620
  # 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])
621
+ _, z_pst, _ = self.rasterize_solution(C=constants, phi=0, num=1, **segments)
622
+ if dof in ["rot"]:
623
+ k = abs(1 / self.psi(z_pst)[0])
624
+ if dof in ["trans"]:
625
+ k = abs(1 / self.w(z_pst)[0])
627
626
 
628
627
  # Reset to previous system and eigensystem
629
628
  self.system = tempsys
630
- if support in ['rested']:
629
+ if support in ["rested"]:
631
630
  self.kn = tempkn
632
631
  self.kt = tempkt
633
632
  self.calc_system_matrix()
@@ -645,7 +644,7 @@ class SlabContactMixin:
645
644
  Length of the crack for transition of stage A to stage B (mm).
646
645
  """
647
646
  # Unpack variables
648
- bs = -(self.B11**2/self.A11 - self.D11)
647
+ bs = -(self.B11**2 / self.A11 - self.D11)
649
648
  ss = self.kA55
650
649
  L = self.L
651
650
  tc = self.tc
@@ -654,17 +653,17 @@ class SlabContactMixin:
654
653
  # Create polynomial expression
655
654
  def polynomial(x):
656
655
  # 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
656
+ kRl = self.substitute_stiffness(L - x, "supported", "rot")
657
+ kNl = self.substitute_stiffness(L - x, "supported", "trans")
658
+ c1 = 1 / (8 * bs)
659
+ c2 = 1 / (2 * kRl)
660
+ c3 = 1 / (2 * ss)
661
+ c4 = 1 / kNl
662
+ c5 = -tc / qn
663
+ return c1 * x**4 + c2 * x**3 + c3 * x**2 + c4 * x + c5
665
664
 
666
665
  # Find root
667
- a1 = brentq(polynomial, L/1000, 999/1000*L)
666
+ a1 = brentq(polynomial, L / 1000, 999 / 1000 * L)
668
667
 
669
668
  return a1
670
669
 
@@ -678,7 +677,7 @@ class SlabContactMixin:
678
677
  Length of the crack for transition of stage B to stage C (mm).
679
678
  """
680
679
  # Unpack variables
681
- bs = -(self.B11**2/self.A11 - self.D11)
680
+ bs = -(self.B11**2 / self.A11 - self.D11)
682
681
  ss = self.kA55
683
682
  L = self.L
684
683
  tc = self.tc
@@ -687,27 +686,21 @@ class SlabContactMixin:
687
686
  # Create polynomial function
688
687
  def polynomial(x):
689
688
  # 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
689
+ kRl = self.substitute_stiffness(L - x, "supported", "rot")
690
+ kNl = self.substitute_stiffness(L - x, "supported", "trans")
691
+ c1 = ss**2 * kRl * kNl * qn
692
+ c2 = 6 * ss**2 * bs * kNl * qn
693
+ c3 = 30 * bs * ss * kRl * kNl * qn
694
+ c4 = 24 * bs * qn * (2 * ss**2 * kRl + 3 * bs * ss * kNl)
695
+ c5 = 72 * bs * (bs * qn * (ss**2 + kRl * kNl) - ss**2 * kRl * kNl * tc)
696
+ c6 = 144 * bs * ss * (bs * kRl * qn - bs * ss * kNl * tc)
697
+ c7 = -144 * bs**2 * ss * kRl * kNl * tc
698
+ return (
699
+ c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
700
+ )
708
701
 
709
702
  # Find root
710
- a2 = brentq(polynomial, L/1000, 999/1000*L)
703
+ a2 = brentq(polynomial, L / 1000, 999 / 1000 * L)
711
704
 
712
705
  return a2
713
706
 
@@ -732,7 +725,7 @@ class SlabContactMixin:
732
725
  Calculate the length of the touchdown element in mode C.
733
726
  """
734
727
  # Unpack variables
735
- bs = -(self.B11**2/self.A11 - self.D11)
728
+ bs = -(self.B11**2 / self.A11 - self.D11)
736
729
  ss = self.kA55
737
730
  L = self.L
738
731
  a = self.a
@@ -741,37 +734,41 @@ class SlabContactMixin:
741
734
 
742
735
  def polynomial(x):
743
736
  # Spring stiffness supported segment
744
- kRl = self.substitute_stiffness(L-a, 'supported', 'rot')
745
- kNl = self.substitute_stiffness(L-a, 'supported', 'trans')
737
+ kRl = self.substitute_stiffness(L - a, "supported", "rot")
738
+ kNl = self.substitute_stiffness(L - a, "supported", "trans")
746
739
  # Spring stiffness rested segment
747
- kRr = self.substitute_stiffness(a-x, 'rested', 'rot')
740
+ kRr = self.substitute_stiffness(a - x, "rested", "rot")
748
741
  # 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
742
+ c1 = ss**2 * kRl * kNl * qn
743
+ c2 = 6 * ss * kNl * qn * (bs * ss + kRl * kRr)
744
+ c3 = 30 * bs * ss * kNl * qn * (kRl + kRr)
745
+ c4 = (
746
+ 24
747
+ * bs
748
+ * qn
749
+ * (2 * ss**2 * kRl + 3 * bs * ss * kNl + 3 * kRl * kRr * kNl)
750
+ )
751
+ c5 = (
752
+ 72
753
+ * bs
754
+ * (
755
+ bs * qn * (ss**2 + kNl * (kRl + kRr))
756
+ + ss * kRl * (2 * kRr * qn - ss * kNl * tc)
757
+ )
758
+ )
759
+ c6 = (
760
+ 144
761
+ * bs
762
+ * ss
763
+ * (bs * qn * (kRl + kRr) - kNl * tc * (bs * ss + kRl * kRr))
764
+ )
765
+ c7 = -144 * bs**2 * ss * kNl * tc * (kRl + kRr)
766
+ return (
767
+ c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
768
+ )
772
769
 
773
770
  # Find root
774
- lC = brentq(polynomial, a/1000, 999/1000*a)
771
+ lC = brentq(polynomial, a / 1000, 999 / 1000 * a)
775
772
 
776
773
  return lC
777
774
 
@@ -791,22 +788,22 @@ class SlabContactMixin:
791
788
  a2 = self.calc_a2()
792
789
  # Assign stage
793
790
  if self.a <= a1:
794
- mode = 'A'
791
+ mode = "A"
795
792
  elif a1 < self.a <= a2:
796
- mode = 'B'
793
+ mode = "B"
797
794
  elif a2 < self.a:
798
- mode = 'C'
795
+ mode = "C"
799
796
  self.mode = mode
800
797
  else:
801
- self.mode = 'A'
798
+ self.mode = "A"
802
799
 
803
800
  def calc_touchdown_length(self):
804
801
  """Calculate touchdown length"""
805
- if self.mode in ['A']:
802
+ if self.mode in ["A"]:
806
803
  self.td = self.calc_lA()
807
- elif self.mode in ['B']:
804
+ elif self.mode in ["B"]:
808
805
  self.td = self.calc_lB()
809
- elif self.mode in ['C']:
806
+ elif self.mode in ["C"]:
810
807
  self.td = self.calc_lC()
811
808
 
812
809
  def calc_touchdown_system(self, L, a, cf, phi, ratio=1000):
@@ -815,6 +812,7 @@ class SlabContactMixin:
815
812
  self.calc_touchdown_mode()
816
813
  self.calc_touchdown_length()
817
814
 
815
+
818
816
  class SolutionMixin:
819
817
  """
820
818
  Mixin for the solution of boundary value problems.
@@ -823,7 +821,7 @@ class SolutionMixin:
823
821
  and for the computation of the free constants.
824
822
  """
825
823
 
826
- def bc(self, z, k=False, pos='mid'):
824
+ def bc(self, z, k=False, pos="mid"):
827
825
  """
828
826
  Provide equations for free (pst) or infinite (skiers) ends.
829
827
 
@@ -849,77 +847,48 @@ class SolutionMixin:
849
847
  """
850
848
 
851
849
  # Set boundary conditions for PST-systems
852
- if self.system in ['pst-', '-pst']:
850
+ if self.system in ["pst-", "-pst"]:
853
851
  if not k:
854
- if self.mode in ['A']:
852
+ if self.mode in ["A"]:
855
853
  # 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']:
854
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
855
+ elif self.mode in ["B"] and pos in ["r", "right"]:
861
856
  # 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
857
+ bc = np.array([self.N(z), self.M(z), self.w(z)])
858
+ elif self.mode in ["B"] and pos in ["l", "left"]: # Kann dieser Block
867
859
  # 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']:
860
+ bc = np.array([self.N(z), self.M(z), self.w(z)])
861
+ elif self.mode in ["C"] and pos in ["r", "right"]:
873
862
  # Spring stiffness
874
- kR = self.substitute_stiffness(self.a - self.td, 'rested', 'rot')
863
+ kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
875
864
  # 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']:
865
+ bc = np.array([self.N(z), self.M(z) + kR * self.psi(z), self.w(z)])
866
+ elif self.mode in ["C"] and pos in ["l", "left"]:
881
867
  # Spring stiffness
882
- kR = self.substitute_stiffness(self.a - self.td, 'rested', 'rot')
868
+ kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
883
869
  # Touchdown left
884
- bc = np.array([self.N(z),
885
- self.M(z) - kR*self.psi(z),
886
- self.w(z)
887
- ])
870
+ bc = np.array([self.N(z), self.M(z) - kR * self.psi(z), self.w(z)])
888
871
  else:
889
872
  # Free end
890
- bc = np.array([
891
- self.N(z),
892
- self.M(z),
893
- self.V(z)
894
- ])
873
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
895
874
  # 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
- ])
875
+ elif self.system in ["-vpst", "vpst-"]:
876
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
902
877
  # Set boundary conditions for SKIER-systems
903
- elif self.system in ['skier', 'skiers']:
878
+ elif self.system in ["skier", "skiers"]:
904
879
  # Infinite end (vanishing complementary solution)
905
- bc = np.array([self.u(z, z0=0),
906
- self.w(z),
907
- self.psi(z)
908
- ])
880
+ bc = np.array([self.u(z, z0=0), self.w(z), self.psi(z)])
909
881
  # 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
- ])
882
+ elif self.system in ["rot", "trans"]:
883
+ bc = np.array([self.N(z), self.M(z), self.V(z)])
915
884
  else:
916
885
  raise ValueError(
917
- 'Boundary conditions not defined for'
918
- f'system of type {self.system}.')
886
+ "Boundary conditions not defined for" f"system of type {self.system}."
887
+ )
919
888
 
920
889
  return bc
921
890
 
922
- def eqs(self, zl, zr, k=False, pos='mid'):
891
+ def eqs(self, zl, zr, k=False, pos="mid"):
923
892
  """
924
893
  Provide boundary or transmission conditions for beam segments.
925
894
 
@@ -946,62 +915,75 @@ class SolutionMixin:
946
915
  or vector of transmission conditions (of length 6+6)
947
916
  for center segments.
948
917
  """
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
918
+ if pos in ("l", "left"):
919
+ eqs = np.array(
920
+ [
921
+ self.bc(zl, k, pos)[0], # Left boundary condition
922
+ self.bc(zl, k, pos)[1], # Left boundary condition
923
+ self.bc(zl, k, pos)[2], # Left boundary condition
924
+ self.u(zr, z0=0), # ui(xi = li)
925
+ self.w(zr), # wi(xi = li)
926
+ self.psi(zr), # psii(xi = li)
927
+ self.N(zr), # Ni(xi = li)
928
+ self.M(zr), # Mi(xi = li)
929
+ self.V(zr),
930
+ ]
931
+ ) # Vi(xi = li)
932
+ elif pos in ("m", "mid"):
933
+ eqs = np.array(
934
+ [
935
+ -self.u(zl, z0=0), # -ui(xi = 0)
936
+ -self.w(zl), # -wi(xi = 0)
937
+ -self.psi(zl), # -psii(xi = 0)
938
+ -self.N(zl), # -Ni(xi = 0)
939
+ -self.M(zl), # -Mi(xi = 0)
940
+ -self.V(zl), # -Vi(xi = 0)
941
+ self.u(zr, z0=0), # ui(xi = li)
942
+ self.w(zr), # wi(xi = li)
943
+ self.psi(zr), # psii(xi = li)
944
+ self.N(zr), # Ni(xi = li)
945
+ self.M(zr), # Mi(xi = li)
946
+ self.V(zr),
947
+ ]
948
+ ) # Vi(xi = li)
949
+ elif pos in ("r", "right"):
950
+ eqs = np.array(
951
+ [
952
+ -self.u(zl, z0=0), # -ui(xi = 0)
953
+ -self.w(zl), # -wi(xi = 0)
954
+ -self.psi(zl), # -psii(xi = 0)
955
+ -self.N(zl), # -Ni(xi = 0)
956
+ -self.M(zl), # -Mi(xi = 0)
957
+ -self.V(zl), # -Vi(xi = 0)
958
+ self.bc(zr, k, pos)[0], # Right boundary condition
959
+ self.bc(zr, k, pos)[1], # Right boundary condition
960
+ self.bc(zr, k, pos)[2],
961
+ ]
962
+ ) # Right boundary condition
985
963
  else:
986
964
  raise ValueError(
987
- (f'Invalid position argument {pos} given. '
988
- 'Valid segment positions are l, m, and r, '
989
- 'or left, mid and right.'))
965
+ (
966
+ f"Invalid position argument {pos} given. "
967
+ "Valid segment positions are l, m, and r, "
968
+ "or left, mid and right."
969
+ )
970
+ )
990
971
  return eqs
991
972
 
992
973
  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):
974
+ self,
975
+ li: list[float] | list[int] | bool = False,
976
+ mi: list[float] | list[int] | bool = False,
977
+ ki: list[bool] | bool = False,
978
+ k0: list[bool] | bool = False,
979
+ L: float = 1e4,
980
+ a: float = 0,
981
+ m: float = 0,
982
+ phi: float = 0,
983
+ cf: float = 0.5,
984
+ ratio: float = 1000,
985
+ **kwargs,
986
+ ):
1005
987
  """
1006
988
  Assemble lists defining the segments.
1007
989
 
@@ -1049,52 +1031,53 @@ class SolutionMixin:
1049
1031
  in the cracked (ki) and uncracked (k0) configurations.
1050
1032
  """
1051
1033
 
1052
- _ = kwargs # Unused arguments
1034
+ _ = kwargs # Unused arguments
1053
1035
 
1054
1036
  # Precompute touchdown properties
1055
1037
  self.calc_touchdown_system(L=L, a=a, cf=cf, phi=phi, ratio=ratio)
1056
1038
 
1057
1039
  # 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
1040
+ if self.system == "skiers":
1041
+ li = np.array(li) # Segment lengths
1042
+ mi = np.array(mi) # Skier weights
1043
+ ki = np.array(ki) # Crack
1044
+ k0 = np.array(k0) # No crack
1045
+ elif self.system == "pst-":
1046
+ li = np.array([L - self.a, self.td]) # Segment lengths
1047
+ mi = np.array([0]) # Skier weights
1048
+ ki = np.array([True, False]) # Crack
1049
+ k0 = np.array([True, True]) # No crack
1050
+ elif self.system == "-pst":
1051
+ li = np.array([self.td, L - self.a]) # Segment lengths
1052
+ mi = np.array([0]) # Skier weights
1053
+ ki = np.array([False, True]) # Crack
1054
+ k0 = np.array([True, True]) # No crack
1055
+ elif self.system == "vpst-":
1056
+ li = np.array([L - a, a]) # Segment lengths
1057
+ mi = np.array([0]) # Skier weights
1058
+ ki = np.array([True, False]) # Crack
1059
+ k0 = np.array([True, True]) # No crack
1060
+ elif self.system == "-vpst":
1061
+ li = np.array([a, L - a]) # Segment lengths
1062
+ mi = np.array([0]) # Skier weights
1063
+ ki = np.array([False, True]) # Crack
1064
+ k0 = np.array([True, True]) # No crack
1065
+ elif self.system == "skier":
1066
+ lb = (L - self.a) / 2 # Half bedded length
1067
+ lf = self.a / 2 # Half free length
1068
+ li = np.array([lb, lf, lf, lb]) # Segment lengths
1069
+ mi = np.array([0, m, 0]) # Skier weights
1070
+ ki = np.array([True, False, False, True]) # Crack
1071
+ k0 = np.array([True, True, True, True]) # No crack
1090
1072
  else:
1091
- raise ValueError(f'System {self.system} is not implemented.')
1073
+ raise ValueError(f"System {self.system} is not implemented.")
1092
1074
 
1093
1075
  # Fill dictionary
1094
1076
  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}}
1077
+ "nocrack": {"li": li, "mi": mi, "ki": k0},
1078
+ "crack": {"li": li, "mi": mi, "ki": ki},
1079
+ "both": {"li": li, "mi": mi, "ki": ki, "k0": k0},
1080
+ }
1098
1081
  return segments
1099
1082
 
1100
1083
  def assemble_and_solve(self, phi, li, mi, ki):
@@ -1133,29 +1116,37 @@ class SolutionMixin:
1133
1116
 
1134
1117
  # No foundation
1135
1118
  if not any(ki):
1136
- raise ValueError('Provide at least one supported segment.')
1119
+ raise ValueError("Provide at least one supported segment.")
1137
1120
  # Mismatch of number of segements and transisions
1138
1121
  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.')
1122
+ raise ValueError(
1123
+ "Make sure len(li)=N, len(ki)=N, and "
1124
+ "len(mi)=N-1 for a system of N segments."
1125
+ )
1141
1126
 
1142
- if self.system not in ['pst-', '-pst', 'vpst-', '-vpst', 'rot', 'trans']:
1127
+ if self.system not in ["pst-", "-pst", "vpst-", "-vpst", "rot", "trans"]:
1143
1128
  # Boundary segments must be on foundation for infinite BCs
1144
1129
  if not all([ki[0], ki[-1]]):
1145
- raise ValueError('Provide supported boundary segments in '
1146
- 'order to account for infinite extensions.')
1130
+ raise ValueError(
1131
+ "Provide supported boundary segments in "
1132
+ "order to account for infinite extensions."
1133
+ )
1147
1134
  # Make sure infinity boundary conditions are far enough from skiers
1148
1135
  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.'))
1136
+ print(
1137
+ (
1138
+ "WARNING: Boundary segments are short. Make sure "
1139
+ "the complementary solution has decayed to the "
1140
+ "boundaries."
1141
+ )
1142
+ )
1152
1143
 
1153
1144
  # --- PREPROCESSING ---------------------------------------------------
1154
1145
 
1155
1146
  # Determine size of linear system of equations
1156
- nS = len(li) # Number of beam segments
1147
+ nS = len(li) # Number of beam segments
1157
1148
 
1158
- nDOF = 6 # Number of free constants per segment
1149
+ nDOF = 6 # Number of free constants per segment
1159
1150
 
1160
1151
  # Add dummy segment if only one segment provided
1161
1152
  if nS == 1:
@@ -1165,13 +1156,13 @@ class SolutionMixin:
1165
1156
  nS = 2
1166
1157
 
1167
1158
  # Assemble position vector
1168
- pi = np.full(nS, 'm')
1169
- pi[0], pi[-1] = 'l', 'r'
1159
+ pi = np.full(nS, "m")
1160
+ pi[0], pi[-1] = "l", "r"
1170
1161
 
1171
1162
  # Initialize matrices
1172
- zh0 = np.zeros([nS*6, nS*nDOF])
1173
- zp0 = np.zeros([nS*6, 1])
1174
- rhs = np.zeros([nS*6, 1])
1163
+ zh0 = np.zeros([nS * 6, nS * nDOF])
1164
+ zp0 = np.zeros([nS * 6, 1])
1165
+ rhs = np.zeros([nS * 6, 1])
1175
1166
 
1176
1167
  # --- ASSEMBLE LINEAR SYSTEM OF EQUATIONS -----------------------------
1177
1168
 
@@ -1181,72 +1172,73 @@ class SolutionMixin:
1181
1172
  l, k, pos = li[i], ki[i], pi[i]
1182
1173
  # Transmission conditions at left and right segment ends
1183
1174
  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)
1175
+ zl=self.zh(x=0, l=l, bed=k), zr=self.zh(x=l, l=l, bed=k), k=k, pos=pos
1176
+ )
1187
1177
  zpi = self.eqs(
1188
1178
  zl=self.zp(x=0, phi=phi, bed=k),
1189
1179
  zr=self.zp(x=l, phi=phi, bed=k),
1190
- k=k, pos=pos)
1180
+ k=k,
1181
+ pos=pos,
1182
+ )
1191
1183
  # Rows for left-hand side assembly
1192
1184
  start = 0 if i == 0 else 3
1193
1185
  stop = 6 if i == nS - 1 else 9
1194
1186
  # 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
1187
+ zh0[(6 * i - start) : (6 * i + stop), i * nDOF : (i + 1) * nDOF] = zhi
1188
+ zp0[(6 * i - start) : (6 * i + stop)] += zpi
1197
1189
 
1198
1190
  # Loop through loads to assemble right-hand side
1199
1191
  for i, m in enumerate(mi, start=1):
1200
1192
  # Get skier loads
1201
1193
  Fn, Ft = self.get_skier_load(m, phi)
1202
1194
  # 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])
1195
+ rhs[6 * i : 6 * i + 3] = np.vstack([Ft, -Ft * self.h / 2, Fn])
1204
1196
  # Set rhs so that complementary integral vanishes at boundaries
1205
- if self.system not in ['pst-', '-pst', 'rested']:
1197
+ if self.system not in ["pst-", "-pst", "rested"]:
1206
1198
  rhs[:3] = self.bc(self.zp(x=0, phi=phi, bed=ki[0]))
1207
1199
  rhs[-3:] = self.bc(self.zp(x=li[-1], phi=phi, bed=ki[-1]))
1208
1200
 
1209
1201
  # Set rhs for vertical faces
1210
- if self.system in ['vpst-', '-vpst']:
1202
+ if self.system in ["vpst-", "-vpst"]:
1211
1203
  # Calculate center of gravity and mass of
1212
1204
  # added or cut off slab segement
1213
1205
  xs, zs, m = calc_vertical_bc_center_of_gravity(self.slab, phi)
1214
1206
  # Convert slope angle to radians
1215
1207
  phi = np.deg2rad(phi)
1216
1208
  # 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)
1209
+ N = -self.g * m * np.sin(phi)
1210
+ M = -self.g * m * (xs * np.cos(phi) + zs * np.sin(phi))
1211
+ V = self.g * m * np.cos(phi)
1220
1212
  # 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
1213
+ rhs[:3] = np.vstack([N, M, V]) # left end
1214
+ rhs[-3:] = np.vstack([N, M, V]) # right end
1223
1215
 
1224
1216
  # Loop through segments to set touchdown conditions at rhs
1225
1217
  for i in range(nS):
1226
1218
  # Length, foundation and position of segment i
1227
1219
  l, k, pos = li[i], ki[i], pi[i]
1228
1220
  # 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])
1221
+ if not k and bool(self.mode in ["B"]):
1222
+ if i == 0:
1223
+ rhs[:3] = np.vstack([0, 0, self.tc])
1232
1224
  if i == (nS - 1):
1233
- rhs[-3:] = np.vstack([0,0,self.tc])
1225
+ rhs[-3:] = np.vstack([0, 0, self.tc])
1234
1226
  # 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])
1227
+ if not k and bool(self.mode in ["C"]):
1228
+ N = self.calc_qt() * (self.a - self.td)
1229
+ if i == 0:
1230
+ rhs[:3] = np.vstack([-N, 0, self.tc])
1239
1231
  if i == (nS - 1):
1240
- rhs[-3:] = np.vstack([N,0,self.tc])
1232
+ rhs[-3:] = np.vstack([N, 0, self.tc])
1241
1233
 
1242
1234
  # Rhs for substitute spring stiffness
1243
- if self.system in ['rot']:
1235
+ if self.system in ["rot"]:
1244
1236
  # apply arbitrary moment of 1 at left boundary
1245
- rhs = rhs*0
1237
+ rhs = rhs * 0
1246
1238
  rhs[1] = 1
1247
- if self.system in ['trans']:
1239
+ if self.system in ["trans"]:
1248
1240
  # apply arbitrary force of 1 at left boundary
1249
- rhs = rhs*0
1241
+ rhs = rhs * 0
1250
1242
  rhs[2] = 1
1251
1243
 
1252
1244
  # --- SOLVE -----------------------------------------------------------
@@ -1266,13 +1258,14 @@ class AnalysisMixin:
1266
1258
  """
1267
1259
 
1268
1260
  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):
1261
+ self,
1262
+ C: np.ndarray,
1263
+ phi: float,
1264
+ li: list[float] | bool,
1265
+ ki: list[bool] | bool,
1266
+ num: int = 250,
1267
+ **kwargs,
1268
+ ):
1276
1269
  """
1277
1270
  Compute rasterized solution vector.
1278
1271
 
@@ -1310,7 +1303,7 @@ class AnalysisMixin:
1310
1303
  C, ki, li = C[:, isnonzero], ki[isnonzero], li[isnonzero]
1311
1304
 
1312
1305
  # Compute number of plot points per segment (+1 for last segment)
1313
- nq = np.ceil(li/li.sum()*num).astype('int')
1306
+ nq = np.ceil(li / li.sum() * num).astype("int")
1314
1307
  nq[-1] += 1
1315
1308
 
1316
1309
  # Provide cumulated length and plot point lists
@@ -1329,14 +1322,14 @@ class AnalysisMixin:
1329
1322
  # Compute start and end coordinates of segment i
1330
1323
  x0 = lic[i]
1331
1324
  # Assemble global coordinate vector
1332
- xq[nqc[i]:nqc[i + 1]] = x0 + xi
1325
+ xq[nqc[i] : nqc[i + 1]] = x0 + xi
1333
1326
  # Mask coordinates not on foundation (including endpoints)
1334
1327
  if not ki[i]:
1335
- issupported[nqc[i]:nqc[i + 1]] = False
1328
+ issupported[nqc[i] : nqc[i + 1]] = False
1336
1329
  # Compute segment solution
1337
1330
  zi = self.z(xi, C[:, [i]], l, phi, ki[i])
1338
1331
  # Assemble global solution matrix
1339
- zq[:, nqc[i]:nqc[i + 1]] = zi
1332
+ zq[:, nqc[i] : nqc[i + 1]] = zi
1340
1333
 
1341
1334
  # Make sure cracktips are included
1342
1335
  transmissionbool = [ki[j] or ki[j + 1] for j, _ in enumerate(ki[:-1])]
@@ -1391,7 +1384,6 @@ class AnalysisMixin:
1391
1384
 
1392
1385
  # Loop through segments with crack advance
1393
1386
  for j, l in enumerate(li):
1394
-
1395
1387
  # Uncracked (0) and cracked (1) solutions at integration points
1396
1388
  z0 = partial(self.z, C=C0[:, [j]], l=l, phi=phi, bed=True)
1397
1389
  z1 = partial(self.z, C=C1[:, [j]], l=l, phi=phi, bed=False)
@@ -1401,14 +1393,12 @@ class AnalysisMixin:
1401
1393
  int2 = partial(self.int2, z0=z0, z1=z1)
1402
1394
 
1403
1395
  # 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)
1396
+ Ginc1 += quad(int1, 0, l, epsabs=self.tol, epsrel=self.tol)[0] / (2 * da)
1397
+ Ginc2 += quad(int2, 0, l, epsabs=self.tol, epsrel=self.tol)[0] / (2 * da)
1408
1398
 
1409
1399
  return np.array([Ginc1 + Ginc2, Ginc1, Ginc2]).flatten()
1410
1400
 
1411
- def gdif(self, C, phi, li, ki, unit='kJ/m^2', **kwargs):
1401
+ def gdif(self, C, phi, li, ki, unit="kJ/m^2", **kwargs):
1412
1402
  """
1413
1403
  Compute differential energy release rate of all crack tips.
1414
1404
 
@@ -1451,16 +1441,18 @@ class AnalysisMixin:
1451
1441
  # Solution at crack tip
1452
1442
  z = self.z(li[idx], C[:, [idx]], li[idx], phi, bed=ki[idx])
1453
1443
  # Mode I and II differential energy release rates
1454
- Gdif[1:, j] = np.concatenate((self.Gi(z, unit=unit), self.Gii(z, unit=unit)))
1444
+ Gdif[1:, j] = np.concatenate(
1445
+ (self.Gi(z, unit=unit), self.Gii(z, unit=unit))
1446
+ )
1455
1447
 
1456
1448
  # Sum mode I and II contributions
1457
1449
  Gdif[0, :] = Gdif[1, :] + Gdif[2, :]
1458
1450
 
1459
1451
  # Adjust contributions for center cracks
1460
1452
  if nct > 1:
1461
- avgmask = np.full(nct, True) # Initialize mask
1453
+ avgmask = np.full(nct, True) # Initialize mask
1462
1454
  avgmask[[0, -1]] = ki[[0, -1]] # Do not weight edge cracks
1463
- Gdif[:, avgmask] *= 0.5 # Weigth with half crack length
1455
+ Gdif[:, avgmask] *= 0.5 # Weigth with half crack length
1464
1456
 
1465
1457
  # Return total differential energy release rate of all crack tips
1466
1458
  return Gdif.sum(axis=1)
@@ -1486,18 +1478,20 @@ class AnalysisMixin:
1486
1478
  # Get ply (layer) coordinates
1487
1479
  z = self.get_ply_coordinates()
1488
1480
  # Compute number of grid points per layer
1489
- nlayer = np.ceil((z[1:] - z[:-1])/dz).astype(np.int32) + 1
1481
+ nlayer = np.ceil((z[1:] - z[:-1]) / dz).astype(np.int32) + 1
1490
1482
  # 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
- ])
1483
+ zi = np.hstack(
1484
+ [
1485
+ np.linspace(z[i], z[i + 1], n, endpoint=True)
1486
+ for i, n in enumerate(nlayer)
1487
+ ]
1488
+ )
1495
1489
  # Get lists of corresponding elastic properties (E, nu, rho)
1496
1490
  si = np.repeat(self.slab[:, [2, 4, 0]], nlayer, axis=0)
1497
1491
  # Assemble mesh with columns (z, E, G, nu)
1498
1492
  return np.column_stack([zi, si])
1499
1493
 
1500
- def Sxx(self, Z, phi, dz=2, unit='kPa'):
1494
+ def Sxx(self, Z, phi, dz=2, unit="kPa"):
1501
1495
  """
1502
1496
  Compute axial normal stress in slab layers.
1503
1497
 
@@ -1518,15 +1512,12 @@ class AnalysisMixin:
1518
1512
  Axial slab normal stress in specified unit.
1519
1513
  """
1520
1514
  # Unit conversion dict
1521
- convert = {
1522
- 'kPa': 1e3,
1523
- 'MPa': 1
1524
- }
1515
+ convert = {"kPa": 1e3, "MPa": 1}
1525
1516
 
1526
1517
  # Get mesh along z-axis
1527
1518
  zmesh = self.get_zmesh(dz=dz)
1528
1519
  zi = zmesh[:, 0]
1529
- rho = 1e-12*zmesh[:, 3]
1520
+ rho = 1e-12 * zmesh[:, 3]
1530
1521
 
1531
1522
  # Get dimensions of stress field (n rows, m columns)
1532
1523
  n = zmesh.shape[0]
@@ -1537,18 +1528,18 @@ class AnalysisMixin:
1537
1528
 
1538
1529
  # Compute axial normal stress Sxx at grid points in MPa
1539
1530
  for i, (z, E, nu, _) in enumerate(zmesh):
1540
- Sxx[i, :] = E/(1-nu**2)*self.du_dx(Z, z)
1531
+ Sxx[i, :] = E / (1 - nu**2) * self.du_dx(Z, z)
1541
1532
 
1542
1533
  # Calculate weight load at grid points and superimpose on stress field
1543
- qt = -rho*self.g*np.sin(np.deg2rad(phi))
1534
+ qt = -rho * self.g * np.sin(np.deg2rad(phi))
1544
1535
  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])
1536
+ Sxx[i, :] += qi * (zi[i + 1] - zi[i])
1537
+ Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
1547
1538
 
1548
1539
  # Return axial normal stress in specified unit
1549
- return convert[unit]*Sxx
1540
+ return convert[unit] * Sxx
1550
1541
 
1551
- def Txz(self, Z, phi, dz=2, unit='kPa'):
1542
+ def Txz(self, Z, phi, dz=2, unit="kPa"):
1552
1543
  """
1553
1544
  Compute shear stress in slab layers.
1554
1545
 
@@ -1569,14 +1560,11 @@ class AnalysisMixin:
1569
1560
  Shear stress at grid points in the slab in specified unit.
1570
1561
  """
1571
1562
  # Unit conversion dict
1572
- convert = {
1573
- 'kPa': 1e3,
1574
- 'MPa': 1
1575
- }
1563
+ convert = {"kPa": 1e3, "MPa": 1}
1576
1564
  # Get mesh along z-axis
1577
1565
  zmesh = self.get_zmesh(dz=dz)
1578
1566
  zi = zmesh[:, 0]
1579
- rho = 1e-12*zmesh[:, 3]
1567
+ rho = 1e-12 * zmesh[:, 3]
1580
1568
 
1581
1569
  # Get dimensions of stress field (n rows, m columns)
1582
1570
  n = zmesh.shape[0]
@@ -1592,10 +1580,10 @@ class AnalysisMixin:
1592
1580
 
1593
1581
  # Calculate first derivative of sxx at z-grid points
1594
1582
  for i, (z, E, nu, _) in enumerate(zmesh):
1595
- dsxx_dx[i, :] = E/(1-nu**2)*(du0_dxdx + z*dpsi_dxdx)
1583
+ dsxx_dx[i, :] = E / (1 - nu**2) * (du0_dxdx + z * dpsi_dxdx)
1596
1584
 
1597
1585
  # Calculate weight load at grid points
1598
- qt = -rho*self.g*np.sin(np.deg2rad(phi))
1586
+ qt = -rho * self.g * np.sin(np.deg2rad(phi))
1599
1587
 
1600
1588
  # Integrate -dsxx_dx along z and add cumulative weight load
1601
1589
  # to obtain shear stress Txz in MPa
@@ -1603,9 +1591,9 @@ class AnalysisMixin:
1603
1591
  Txz += cumulative_trapezoid(qt, zi, initial=0)[:, None]
1604
1592
 
1605
1593
  # Return shear stress Txz in specified unit
1606
- return convert[unit]*Txz
1594
+ return convert[unit] * Txz
1607
1595
 
1608
- def Szz(self, Z, phi, dz=2, unit='kPa'):
1596
+ def Szz(self, Z, phi, dz=2, unit="kPa"):
1609
1597
  """
1610
1598
  Compute transverse normal stress in slab layers.
1611
1599
 
@@ -1627,15 +1615,12 @@ class AnalysisMixin:
1627
1615
  specified unit.
1628
1616
  """
1629
1617
  # Unit conversion dict
1630
- convert = {
1631
- 'kPa': 1e3,
1632
- 'MPa': 1
1633
- }
1618
+ convert = {"kPa": 1e3, "MPa": 1}
1634
1619
 
1635
1620
  # Get mesh along z-axis
1636
1621
  zmesh = self.get_zmesh(dz=dz)
1637
1622
  zi = zmesh[:, 0]
1638
- rho = 1e-12*zmesh[:, 3]
1623
+ rho = 1e-12 * zmesh[:, 3]
1639
1624
 
1640
1625
  # Get dimensions of stress field (n rows, m columns)
1641
1626
  n = zmesh.shape[0]
@@ -1651,10 +1636,10 @@ class AnalysisMixin:
1651
1636
 
1652
1637
  # Calculate second derivative of sxx at z-grid points
1653
1638
  for i, (z, E, nu, _) in enumerate(zmesh):
1654
- dsxx_dxdx[i, :] = E/(1-nu**2)*(du0_dxdxdx + z*dpsi_dxdxdx)
1639
+ dsxx_dxdx[i, :] = E / (1 - nu**2) * (du0_dxdxdx + z * dpsi_dxdxdx)
1655
1640
 
1656
1641
  # Calculate weight load at grid points
1657
- qn = rho*self.g*np.cos(np.deg2rad(phi))
1642
+ qn = rho * self.g * np.cos(np.deg2rad(phi))
1658
1643
 
1659
1644
  # Integrate dsxx_dxdx twice along z to obtain transverse
1660
1645
  # normal stress Szz in MPa
@@ -1663,10 +1648,11 @@ class AnalysisMixin:
1663
1648
  Szz += cumulative_trapezoid(-qn, zi, initial=0)[:, None]
1664
1649
 
1665
1650
  # Return shear stress txz in specified unit
1666
- return convert[unit]*Szz
1651
+ return convert[unit] * Szz
1667
1652
 
1668
- def principal_stress_slab(self, Z, phi, dz=2, unit='kPa',
1669
- val='max', normalize=False):
1653
+ def principal_stress_slab(
1654
+ self, Z, phi, dz=2, unit="kPa", val="max", normalize=False
1655
+ ):
1670
1656
  """
1671
1657
  Compute maxium or minimum principal stress in slab layers.
1672
1658
 
@@ -1698,11 +1684,11 @@ class AnalysisMixin:
1698
1684
  is requested.
1699
1685
  """
1700
1686
  # Raise error if specified component is not available
1701
- if val not in ['min', 'max']:
1702
- raise ValueError(f'Component {val} not defined.')
1687
+ if val not in ["min", "max"]:
1688
+ raise ValueError(f"Component {val} not defined.")
1703
1689
 
1704
1690
  # Multiplier selection dict
1705
- m = {'max': 1, 'min': -1}
1691
+ m = {"max": 1, "min": -1}
1706
1692
 
1707
1693
  # Get axial normal stresses, shear stresses, transverse normal stresses
1708
1694
  Sxx = self.Sxx(Z=Z, phi=phi, dz=dz, unit=unit)
@@ -1710,24 +1696,25 @@ class AnalysisMixin:
1710
1696
  Szz = self.Szz(Z=Z, phi=phi, dz=dz, unit=unit)
1711
1697
 
1712
1698
  # Calculate principal stress
1713
- Ps = (Sxx + Szz)/2 + m[val]*np.sqrt((Sxx - Szz)**2 + 4*Txz**2)/2
1699
+ Ps = (Sxx + Szz) / 2 + m[val] * np.sqrt((Sxx - Szz) ** 2 + 4 * Txz**2) / 2
1714
1700
 
1715
1701
  # Raise error if normalization of compressive stresses is attempted
1716
- if normalize and val == 'min':
1717
- raise ValueError('Can only normlize tensile stresses.')
1702
+ if normalize and val == "min":
1703
+ raise ValueError("Can only normlize tensile stresses.")
1718
1704
 
1719
1705
  # Normalize tensile stresses to tensile strength
1720
- if normalize and val == 'max':
1706
+ if normalize and val == "max":
1721
1707
  # Get layer densities
1722
1708
  rho = self.get_zmesh(dz=dz)[:, 3]
1723
1709
  # Normlize maximum principal stress to layers' tensile strength
1724
- return Ps/tensile_strength_slab(rho, unit=unit)[:, None]
1710
+ return Ps / tensile_strength_slab(rho, unit=unit)[:, None]
1725
1711
 
1726
1712
  # Return absolute principal stresses
1727
1713
  return Ps
1728
1714
 
1729
- def principal_stress_weaklayer(self, Z, sc=2.6, unit='kPa', val='min',
1730
- normalize=False):
1715
+ def principal_stress_weaklayer(
1716
+ self, Z, sc=2.6, unit="kPa", val="min", normalize=False
1717
+ ):
1731
1718
  """
1732
1719
  Compute maxium or minimum principal stress in the weak layer.
1733
1720
 
@@ -1757,30 +1744,31 @@ class AnalysisMixin:
1757
1744
  is requested.
1758
1745
  """
1759
1746
  # Raise error if specified component is not available
1760
- if val not in ['min', 'max']:
1761
- raise ValueError(f'Component {val} not defined.')
1747
+ if val not in ["min", "max"]:
1748
+ raise ValueError(f"Component {val} not defined.")
1762
1749
 
1763
1750
  # Multiplier selection dict
1764
- m = {'max': 1, 'min': -1}
1751
+ m = {"max": 1, "min": -1}
1765
1752
 
1766
1753
  # Get weak-layer normal and shear stresses
1767
1754
  sig = self.sig(Z, unit=unit)
1768
1755
  tau = self.tau(Z, unit=unit)
1769
1756
 
1770
1757
  # Calculate principal stress
1771
- ps = sig/2 + m[val]*np.sqrt(sig**2 + 4*tau**2)/2
1758
+ ps = sig / 2 + m[val] * np.sqrt(sig**2 + 4 * tau**2) / 2
1772
1759
 
1773
1760
  # Raise error if normalization of tensile stresses is attempted
1774
- if normalize and val == 'max':
1775
- raise ValueError('Can only normlize compressive stresses.')
1761
+ if normalize and val == "max":
1762
+ raise ValueError("Can only normlize compressive stresses.")
1776
1763
 
1777
1764
  # Normalize compressive stresses to compressive strength
1778
- if normalize and val == 'min':
1779
- return ps/sc
1765
+ if normalize and val == "min":
1766
+ return ps / sc
1780
1767
 
1781
1768
  # Return absolute principal stresses
1782
1769
  return ps
1783
1770
 
1771
+
1784
1772
  class OutputMixin:
1785
1773
  """
1786
1774
  Mixin for outputs.
@@ -1788,6 +1776,7 @@ class OutputMixin:
1788
1776
  Provides convenience methods for the assembly of output lists
1789
1777
  such as rasterized displacements or rasterized stresses.
1790
1778
  """
1779
+
1791
1780
  def external_potential(self, C, phi, L, **segments):
1792
1781
  """
1793
1782
  Compute total external potential (pst only).
@@ -1822,17 +1811,21 @@ class OutputMixin:
1822
1811
  qt = self.calc_qt()
1823
1812
  # use +/- and us[0]/us[-1] according to system and phi
1824
1813
  # 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
1814
+ Pi_ext = (
1815
+ -qn * (segments["li"][0] + segments["li"][1]) * np.average(w0)
1816
+ - qn * (L - (segments["li"][0] + segments["li"][1])) * self.tc
1817
+ )
1827
1818
  # Ensure
1828
- if self.system in ['pst-']:
1819
+ if self.system in ["pst-"]:
1829
1820
  ub = us[-1]
1830
- elif self.system in ['-pst']:
1821
+ elif self.system in ["-pst"]:
1831
1822
  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.')
1823
+ Pi_ext += (
1824
+ -qt * (segments["li"][0] + segments["li"][1]) * np.average(us)
1825
+ - qt * (L - (segments["li"][0] + segments["li"][1])) * ub
1826
+ )
1827
+ if self.system not in ["pst-", "-pst"]:
1828
+ print("Input error: Only pst-setup implemented at the moment.")
1836
1829
 
1837
1830
  return Pi_ext
1838
1831
 
@@ -1871,31 +1864,37 @@ class OutputMixin:
1871
1864
 
1872
1865
  # Compute weak layer displacements
1873
1866
  wweak = self.w(zweak)
1874
- uweak = self.u(zweak, z0=self.h/2)
1867
+ uweak = self.u(zweak, z0=self.h / 2)
1875
1868
 
1876
1869
  # Compute stored energy of the slab (monte-carlo integration)
1877
1870
  n = len(xq)
1878
1871
  nweak = len(xweak)
1879
1872
  # 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])
1873
+ Pi_int = (
1874
+ L / 2 / n / self.A11 * np.sum([Ni**2 for Ni in N])
1875
+ + L
1876
+ / 2
1877
+ / n
1878
+ / (self.D11 - self.B11**2 / self.A11)
1879
+ * np.sum([Mi**2 for Mi in M])
1880
+ + L / 2 / n / self.kA55 * np.sum([Vi**2 for Vi in V])
1881
+ + L * self.kn / 2 / nweak * np.sum([wi**2 for wi in wweak])
1882
+ + L * self.kt / 2 / nweak * np.sum([ui**2 for ui in uweak])
1883
+ )
1885
1884
  # 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
1885
+ if self.system in ["pst-"]:
1886
+ Pi_int += 1 / 2 * M[-1] * (self.psi(zq)[-1]) ** 2
1887
+ elif self.system in ["-pst"]:
1888
+ Pi_int += 1 / 2 * M[0] * (self.psi(zq)[0]) ** 2
1890
1889
  else:
1891
- print('Input error: Only pst-setup implemented at the moment.')
1890
+ print("Input error: Only pst-setup implemented at the moment.")
1892
1891
 
1893
1892
  return Pi_int
1894
1893
 
1895
1894
  def total_potential(self, C, phi, L, **segments):
1896
1895
  """
1897
1896
  Returns total differential potential
1898
-
1897
+
1899
1898
  Arguments
1900
1899
  ---------
1901
1900
  C : ndarray
@@ -1920,7 +1919,7 @@ class OutputMixin:
1920
1919
 
1921
1920
  return Pi_int + Pi_ext
1922
1921
 
1923
- def get_weaklayer_shearstress(self, x, z, unit='MPa', removeNaNs=False):
1922
+ def get_weaklayer_shearstress(self, x, z, unit="MPa", removeNaNs=False):
1924
1923
  """
1925
1924
  Compute weak-layer shear stress.
1926
1925
 
@@ -1944,7 +1943,7 @@ class OutputMixin:
1944
1943
  Normal stress (stress unit input).
1945
1944
  """
1946
1945
  # Convert coordinates from mm to cm and stresses from MPa to unit
1947
- x = x/10
1946
+ x = x / 10
1948
1947
  tau = self.tau(z, unit=unit)
1949
1948
  # Filter stresses in unspupported segments
1950
1949
  if removeNaNs:
@@ -1957,7 +1956,7 @@ class OutputMixin:
1957
1956
 
1958
1957
  return x, tau
1959
1958
 
1960
- def get_weaklayer_normalstress(self, x, z, unit='MPa', removeNaNs=False):
1959
+ def get_weaklayer_normalstress(self, x, z, unit="MPa", removeNaNs=False):
1961
1960
  """
1962
1961
  Compute weak-layer normal stress.
1963
1962
 
@@ -1981,7 +1980,7 @@ class OutputMixin:
1981
1980
  Normal stress (stress unit input).
1982
1981
  """
1983
1982
  # Convert coordinates from mm to cm and stresses from MPa to unit
1984
- x = x/10
1983
+ x = x / 10
1985
1984
  sig = self.sig(z, unit=unit)
1986
1985
  # Filter stresses in unspupported segments
1987
1986
  if removeNaNs:
@@ -1994,7 +1993,7 @@ class OutputMixin:
1994
1993
 
1995
1994
  return x, sig
1996
1995
 
1997
- def get_slab_displacement(self, x, z, loc='mid', unit='mm'):
1996
+ def get_slab_displacement(self, x, z, loc="mid", unit="mm"):
1998
1997
  """
1999
1998
  Compute horizontal slab displacement.
2000
1999
 
@@ -2019,15 +2018,15 @@ class OutputMixin:
2019
2018
  Horizontal displacements (unit input).
2020
2019
  """
2021
2020
  # Coordinates (cm)
2022
- x = x/10
2021
+ x = x / 10
2023
2022
  # Locator
2024
- z0 = {'top': -self.h/2, 'mid': 0, 'bot': self.h/2}
2023
+ z0 = {"top": -self.h / 2, "mid": 0, "bot": self.h / 2}
2025
2024
  # Displacement (unit)
2026
2025
  u = self.u(z, z0=z0[loc], unit=unit)
2027
2026
  # Output array
2028
2027
  return x, u
2029
2028
 
2030
- def get_slab_deflection(self, x, z, unit='mm'):
2029
+ def get_slab_deflection(self, x, z, unit="mm"):
2031
2030
  """
2032
2031
  Compute vertical slab displacement.
2033
2032
 
@@ -2050,13 +2049,13 @@ class OutputMixin:
2050
2049
  Vertical deflections (unit input).
2051
2050
  """
2052
2051
  # Coordinates (cm)
2053
- x = x/10
2052
+ x = x / 10
2054
2053
  # Deflection (unit)
2055
2054
  w = self.w(z, unit=unit)
2056
2055
  # Output array
2057
2056
  return x, w
2058
2057
 
2059
- def get_slab_rotation(self, x, z, unit='degrees'):
2058
+ def get_slab_rotation(self, x, z, unit="degrees"):
2060
2059
  """
2061
2060
  Compute slab cross-section rotation angle.
2062
2061
 
@@ -2079,7 +2078,7 @@ class OutputMixin:
2079
2078
  Cross section rotations (unit input).
2080
2079
  """
2081
2080
  # Coordinates (cm)
2082
- x = x/10
2081
+ x = x / 10
2083
2082
  # Cross-section rotation angle (unit)
2084
2083
  psi = self.psi(z, unit=unit)
2085
2084
  # Output array