weac 2.4.0__py3-none-any.whl → 2.5.0__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
@@ -1,11 +1,12 @@
1
1
  """Mixins for the elastic analysis of layered snow slabs."""
2
- # pylint: disable=invalid-name,too-many-locals,too-many-arguments
2
+ # pylint: disable=invalid-name,too-many-locals,too-many-arguments,too-many-lines
3
3
 
4
4
  # Standard library imports
5
5
  from functools import partial
6
6
 
7
7
  # Third party imports
8
8
  import numpy as np
9
+ from scipy.optimize import brentq
9
10
  from scipy.integrate import romberg, cumulative_trapezoid
10
11
 
11
12
  # Module imports
@@ -485,72 +486,344 @@ class FieldQuantitiesMixin:
485
486
  """
486
487
  return self.dz_dxdx(z, phi)[5, :]
487
488
 
488
- class SolutionMixin:
489
+
490
+ class SlabContactMixin:
489
491
  """
490
- Mixin for the solution of boundary value problems.
492
+ Mixin for handling the touchdown situation in a PST.
491
493
 
492
- Provides methods for the assembly of the system of equations
493
- and for the computation of the free constants.
494
+ Provides Methods for the calculation of substitute spring stiffnesses,
495
+ cracklength-tresholds and element lengths.
494
496
  """
497
+ # pylint: disable=too-many-instance-attributes
495
498
 
496
- def mode_td(self, l=0):
499
+ def set_columnlength(self,L):
497
500
  """
498
- Identify the mode of the pst-boundary.
501
+ Set cracklength.
499
502
 
500
503
  Arguments
501
504
  ---------
502
- l : float, optional
503
- Length of the segment in consideration. Default is zero.
505
+ L : float
506
+ Column length of a PST (mm).
507
+ """
508
+ self.L = L
509
+
510
+ def set_cracklength(self,a):
511
+ """
512
+ Set cracklength.
513
+
514
+ Arguments
515
+ ---------
516
+ a : float
517
+ Cracklength in a PST (mm).
518
+ """
519
+ self.a = a
520
+
521
+ def set_tc(self,cf):
522
+ """
523
+ Set height of the crack.
524
+
525
+ Arguments
526
+ ---------
527
+ cf : float
528
+ Collapse-factor. Ratio of the crack height to the
529
+ uncollapsed weak-layer height.
530
+ """
531
+ # subtract displacement under constact load from collapsed wl height
532
+ qn = self.calc_qn()
533
+ self.tc = cf*self.t - qn/self.kn
534
+
535
+ def set_phi(self,phi):
536
+ """
537
+ Set inclination of the slab.
538
+
539
+ Arguments
540
+ ---------
541
+ phi : float
542
+ Inclination of the slab (°).
543
+ """
544
+ self.phi = phi
545
+
546
+ def set_stiffness_ratio(self, ratio=1000):
547
+ """
548
+ Set ratio between collapsed and uncollapsed weak-layer stiffness.
549
+
550
+ Parameters
551
+ ----------
552
+ ratio : int, optional
553
+ Stiffness ratio between collapsed and uncollapsed weak layer.
554
+ Default is 1000.
555
+ """
556
+ self.ratio = ratio
557
+
558
+ def calc_qn(self):
559
+ """
560
+ Calc total surface normal load.
504
561
 
505
562
  Returns
506
563
  -------
507
- mode : string
508
- Contains the mode for the boundary of the segment:
509
- A - free end, B - intermediate touchdown,
510
- C - full touchdown (maximum clamped end).
564
+ float
565
+ Total surface normal load (N/mm).
511
566
  """
512
- if self.touchdown:
513
- # Classify boundary type by element length
514
- if l <= self.lC:
515
- return 'A'
516
- elif self.lC < l <= self.lS:
517
- return 'B'
518
- elif self.lS < l:
519
- return 'C'
520
- else:
521
- return 'A'
567
+ return self.get_weight_load(self.phi)[0] + self.get_surface_load(self.phi)[0]
522
568
 
523
- def reduce_stiffness(self, l=0, mode='A'):
569
+ def calc_qt(self):
524
570
  """
525
- Determines the reduction factor for a rotational spring.
571
+ Calc total surface normal load.
572
+
573
+ Returns
574
+ -------
575
+ float
576
+ Total surface normal load (N/mm).
577
+ """
578
+ return self.get_weight_load(self.phi)[1] + self.get_surface_load(self.phi)[1]
579
+
580
+ def substitute_stiffness(self, L, support='rested', dof='rot'):
581
+ """
582
+ Calc substitute stiffness for beam on elastic foundation.
526
583
 
527
584
  Arguments
528
585
  ---------
529
- l : float, optional
530
- Length of the segment in consideration. Default is zero.
531
- mode : string, optional
532
- Contains the mode for the boundary of the segment:
533
- A - free end, B - intermediate touchdown, C - full touchdown.
534
- Default is A.
586
+ L : float
587
+ Total length of the PST-column (mm).
588
+ support : string
589
+ Type of segment foundation. Defaults to 'rested'.
590
+ dof : string
591
+ Type of substitute spring, either 'rot' or 'trans'. Defaults to 'rot'.
535
592
 
536
593
  Returns
537
594
  -------
538
- kf : float
539
- Reduction factor.
595
+ k : stiffness of substitute spring.
596
+ """
597
+ # adjust system to substitute system
598
+ if dof in ['rot']:
599
+ tempsys = self.system
600
+ self.system = 'rot'
601
+ if dof in ['trans']:
602
+ tempsys = self.system
603
+ self.system = 'trans'
604
+
605
+ # Change eigensystem for rested segment
606
+ if support in ['rested']:
607
+ tempkn = self.kn
608
+ tempkt = self.kt
609
+ self.kn = self.ratio*self.kn
610
+ self.kt = self.ratio*self.kt
611
+ self.calc_system_matrix()
612
+ self.calc_eigensystem()
613
+
614
+ # prepare list of segment characteristics
615
+ segments = {'li': np.array([L, 0.]),
616
+ 'mi': np.array([0]),
617
+ 'ki': np.array([True, True])}
618
+ # solve system of equations
619
+ constants = self.assemble_and_solve(phi=0, **segments)
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])
627
+
628
+ # Reset to previous system and eigensystem
629
+ self.system = tempsys
630
+ if support in ['rested']:
631
+ self.kn = tempkn
632
+ self.kt = tempkt
633
+ self.calc_system_matrix()
634
+ self.calc_eigensystem()
635
+
636
+ return k
637
+
638
+ def calc_a1(self):
639
+ """
640
+ Calc transition lengths a1 (aAB).
641
+
642
+ Returns
643
+ -------
644
+ a1 : float
645
+ Length of the crack for transition of stage A to stage B (mm).
646
+ """
647
+ # Unpack variables
648
+ bs = -(self.B11**2/self.A11 - self.D11)
649
+ ss = self.kA55
650
+ L = self.L
651
+ tc = self.tc
652
+ qn = self.calc_qn()
653
+
654
+ # Create polynomial expression
655
+ def polynomial(x):
656
+ # 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
665
+
666
+ # Find root
667
+ a1 = brentq(polynomial, L/1000, 999/1000*L)
668
+
669
+ return a1
670
+
671
+ def calc_a2(self):
540
672
  """
541
- # Reduction to zero for free end bc
542
- if mode in ['A']:
543
- kf = 0
544
- # Reduction factor for touchdown
545
- if mode in ['B', 'C']:
546
- l = l - self.lC
547
- # Beta needs to take into account different weak-layer spring stiffness
548
- beta = self.beta*self.ratio**(1/4)
549
- kf=(np.cos(2*beta*l)+np.cosh(2*beta*l)-2)/(np.sin(2*beta*l)+np.sinh(2*beta*l))
673
+ Calc transition lengths a2 (aBC).
550
674
 
551
- return kf
675
+ Returns
676
+ -------
677
+ a2 : float
678
+ Length of the crack for transition of stage B to stage C (mm).
679
+ """
680
+ # Unpack variables
681
+ bs = -(self.B11**2/self.A11 - self.D11)
682
+ ss = self.kA55
683
+ L = self.L
684
+ tc = self.tc
685
+ qn = self.calc_qn()
686
+
687
+ # Create polynomial function
688
+ def polynomial(x):
689
+ # 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
708
+
709
+ # Find root
710
+ a2 = brentq(polynomial, L/1000, 999/1000*L)
711
+
712
+ return a2
713
+
714
+ def calc_lA(self):
715
+ """
716
+ Calculate the length of the touchdown element in mode A.
717
+ """
718
+ lA = self.a
719
+
720
+ return lA
721
+
722
+ def calc_lB(self):
723
+ """
724
+ Calculate the length of the touchdown element in mode B.
725
+ """
726
+ lB = self.a
727
+
728
+ return lB
729
+
730
+ def calc_lC(self):
731
+ """
732
+ Calculate the length of the touchdown element in mode C.
733
+ """
734
+ # Unpack variables
735
+ bs = -(self.B11**2/self.A11 - self.D11)
736
+ ss = self.kA55
737
+ L = self.L
738
+ a = self.a
739
+ tc = self.tc
740
+ qn = self.calc_qn()
741
+
742
+ def polynomial(x):
743
+ # Spring stiffness supported segment
744
+ kRl = self.substitute_stiffness(L-a, 'supported', 'rot')
745
+ kNl = self.substitute_stiffness(L-a, 'supported', 'trans')
746
+ # Spring stiffness rested segment
747
+ kRr = self.substitute_stiffness(a-x, 'rested', 'rot')
748
+ # 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
772
+
773
+ # Find root
774
+ lC = brentq(polynomial, a/1000, 999/1000*a)
775
+
776
+ return lC
777
+
778
+ def set_touchdown_attributes(self, L, a, cf, phi, ratio):
779
+ """Set class attributes for touchdown consideration"""
780
+ self.set_columnlength(L)
781
+ self.set_cracklength(a)
782
+ self.set_tc(cf)
783
+ self.set_phi(phi)
784
+ self.set_stiffness_ratio(ratio)
785
+
786
+ def calc_touchdown_mode(self):
787
+ """Calculate touchdown-mode from thresholds"""
788
+ if self.touchdown:
789
+ # Calculate stage transitions
790
+ a1 = self.calc_a1()
791
+ a2 = self.calc_a2()
792
+ # Assign stage
793
+ if self.a <= a1:
794
+ mode = 'A'
795
+ elif a1 < self.a <= a2:
796
+ mode = 'B'
797
+ elif a2 < self.a:
798
+ mode = 'C'
799
+ self.mode = mode
800
+ else:
801
+ self.mode = 'A'
802
+
803
+ def calc_touchdown_length(self):
804
+ """Calculate touchdown length"""
805
+ if self.mode in ['A']:
806
+ self.td = self.calc_lA()
807
+ elif self.mode in ['B']:
808
+ self.td = self.calc_lB()
809
+ elif self.mode in ['C']:
810
+ self.td = self.calc_lC()
811
+
812
+ def calc_touchdown_system(self, L, a, cf, phi, ratio=1000):
813
+ """Calculate touchdown"""
814
+ self.set_touchdown_attributes(L, a, cf, phi, ratio)
815
+ self.calc_touchdown_mode()
816
+ self.calc_touchdown_length()
817
+
818
+ class SolutionMixin:
819
+ """
820
+ Mixin for the solution of boundary value problems.
821
+
822
+ Provides methods for the assembly of the system of equations
823
+ and for the computation of the free constants.
824
+ """
552
825
 
553
- def bc(self, z, l=0, k=False, pos='mid'):
826
+ def bc(self, z, k=False, pos='mid'):
554
827
  """
555
828
  Provide equations for free (pst) or infinite (skiers) ends.
556
829
 
@@ -574,37 +847,44 @@ class SolutionMixin:
574
847
  bc : ndarray
575
848
  Boundary condition vector (lenght 3) at position x.
576
849
  """
577
- # Check mode for free end
578
- mode = self.mode_td(l=l)
579
- # Get spring stiffness reduction factor
580
- kf = self.reduce_stiffness(l=l, mode=mode)
581
- # Get spring stiffness for collapsed weak-layer
582
- kR = self.calc_rot_spring(collapse=True)
583
850
 
584
851
  # Set boundary conditions for PST-systems
585
852
  if self.system in ['pst-', '-pst']:
586
853
  if not k:
587
- if mode in ['A']:
854
+ if self.mode in ['A']:
588
855
  # Free end
589
- bc = np.array([
590
- self.N(z),
591
- self.M(z),
592
- self.V(z)
593
- ])
594
- elif mode in ['B', 'C'] and pos in ['r', 'right']:
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']:
595
861
  # Touchdown right
596
- bc = np.array([
597
- self.N(z),
598
- self.M(z) + kf*kR*self.psi(z),
599
- self.w(z)
600
- ])
601
- elif mode in ['B', 'C'] and pos in ['l', 'left']:
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
867
+ # 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']:
873
+ # Spring stiffness
874
+ kR = self.substitute_stiffness(self.a - self.td, 'rested', 'rot')
875
+ # 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']:
881
+ # Spring stiffness
882
+ kR = self.substitute_stiffness(self.a - self.td, 'rested', 'rot')
602
883
  # Touchdown left
603
- bc = np.array([
604
- self.N(z),
605
- self.M(z) - kf*kR*self.psi(z),
606
- self.w(z)
607
- ])
884
+ bc = np.array([self.N(z),
885
+ self.M(z) - kR*self.psi(z),
886
+ self.w(z)
887
+ ])
608
888
  else:
609
889
  # Free end
610
890
  bc = np.array([
@@ -626,6 +906,12 @@ class SolutionMixin:
626
906
  self.w(z),
627
907
  self.psi(z)
628
908
  ])
909
+ # 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
+ ])
629
915
  else:
630
916
  raise ValueError(
631
917
  'Boundary conditions not defined for'
@@ -633,7 +919,7 @@ class SolutionMixin:
633
919
 
634
920
  return bc
635
921
 
636
- def eqs(self, zl, zr, l=0, k=False, pos='mid'):
922
+ def eqs(self, zl, zr, k=False, pos='mid'):
637
923
  """
638
924
  Provide boundary or transmission conditions for beam segments.
639
925
 
@@ -643,8 +929,6 @@ class SolutionMixin:
643
929
  Solution vector (6x1) at left end of beam segement.
644
930
  zr : ndarray
645
931
  Solution vector (6x1) at right end of beam segement.
646
- l : float, optional
647
- Length of the segment in consideration. Default is zero.
648
932
  k : boolean
649
933
  Indicates whether segment has foundation(True) or not (False).
650
934
  Default is False.
@@ -664,15 +948,15 @@ class SolutionMixin:
664
948
  """
665
949
  if pos in ('l', 'left'):
666
950
  eqs = np.array([
667
- self.bc(zl, l, k, pos)[0], # Left boundary condition
668
- self.bc(zl, l, k, pos)[1], # Left boundary condition
669
- self.bc(zl, l, k, pos)[2], # Left boundary condition
670
- self.u(zr, z0=0), # ui(xi = li)
671
- self.w(zr), # wi(xi = li)
672
- self.psi(zr), # psii(xi = li)
673
- self.N(zr), # Ni(xi = li)
674
- self.M(zr), # Mi(xi = li)
675
- self.V(zr)]) # Vi(xi = li)
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)
676
960
  elif pos in ('m', 'mid'):
677
961
  eqs = np.array([
678
962
  -self.u(zl, z0=0), # -ui(xi = 0)
@@ -689,15 +973,15 @@ class SolutionMixin:
689
973
  self.V(zr)]) # Vi(xi = li)
690
974
  elif pos in ('r', 'right'):
691
975
  eqs = np.array([
692
- -self.u(zl, z0=0), # -ui(xi = 0)
693
- -self.w(zl), # -wi(xi = 0)
694
- -self.psi(zl), # -psii(xi = 0)
695
- -self.N(zl), # -Ni(xi = 0)
696
- -self.M(zl), # -Mi(xi = 0)
697
- -self.V(zl), # -Vi(xi = 0)
698
- self.bc(zr, l, k, pos)[0], # Right boundary condition
699
- self.bc(zr, l, k, pos)[1], # Right boundary condition
700
- self.bc(zr, l, k, pos)[2]]) # Right boundary condition
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
701
985
  else:
702
986
  raise ValueError(
703
987
  (f'Invalid position argument {pos} given. '
@@ -705,8 +989,19 @@ class SolutionMixin:
705
989
  'or left, mid and right.'))
706
990
  return eqs
707
991
 
708
- def calc_segments(self, tdi=False, li=False, mi=False, ki=False, k0=False,
709
- L=1e4, a=0, m=0, **kwargs):
992
+ 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):
710
1005
  """
711
1006
  Assemble lists defining the segments.
712
1007
 
@@ -739,6 +1034,12 @@ class SolutionMixin:
739
1034
  m : float, optional
740
1035
  Weight of skier (kg) in the axial center of the model.
741
1036
  Used for system 'skier'.
1037
+ cf : float, optional
1038
+ Collapse factor. Ratio of the crack height to the uncollapsed
1039
+ weak-layer height. Used for systems 'pst-', '-pst'. Default is 0.5.
1040
+ ratio : float, optional
1041
+ Stiffness ratio between collapsed and uncollapsed weak layer.
1042
+ Default is 1000.
742
1043
 
743
1044
  Returns
744
1045
  -------
@@ -749,12 +1050,9 @@ class SolutionMixin:
749
1050
  """
750
1051
 
751
1052
  _ = kwargs # Unused arguments
752
- # Set unbedded segment length
753
- mode = self.mode_td(l=a)
754
- if mode in ['A', 'B']:
755
- lU = a
756
- if mode in ['C']:
757
- lU = self.lS
1053
+
1054
+ # Precompute touchdown properties
1055
+ self.calc_touchdown_system(L=L, a=a, cf=cf, phi=phi, ratio=ratio)
758
1056
 
759
1057
  # Assemble list defining the segments
760
1058
  if self.system == 'skiers':
@@ -763,12 +1061,12 @@ class SolutionMixin:
763
1061
  ki = np.array(ki) # Crack
764
1062
  k0 = np.array(k0) # No crack
765
1063
  elif self.system == 'pst-':
766
- li = np.array([L - a, lU]) # Segment lengths
1064
+ li = np.array([L - self.a, self.td]) # Segment lengths
767
1065
  mi = np.array([0]) # Skier weights
768
1066
  ki = np.array([True, False]) # Crack
769
1067
  k0 = np.array([True, True]) # No crack
770
1068
  elif self.system == '-pst':
771
- li = np.array([lU, L - a]) # Segment lengths
1069
+ li = np.array([self.td, L - self.a]) # Segment lengths
772
1070
  mi = np.array([0]) # Skier weights
773
1071
  ki = np.array([False, True]) # Crack
774
1072
  k0 = np.array([True, True]) # No crack
@@ -783,8 +1081,8 @@ class SolutionMixin:
783
1081
  ki = np.array([False, True]) # Crack
784
1082
  k0 = np.array([True, True]) # No crack
785
1083
  elif self.system == 'skier':
786
- lb = (L - a)/2 # Half supported length
787
- lf = a/2 # Half free length
1084
+ lb = (L - self.a)/2 # Half bedded length
1085
+ lf = self.a/2 # Half free length
788
1086
  li = np.array([lb, lf, lf, lb]) # Segment lengths
789
1087
  mi = np.array([0, m, 0]) # Skier weights
790
1088
  ki = np.array([True, False, False, True]) # Crack
@@ -841,7 +1139,7 @@ class SolutionMixin:
841
1139
  raise ValueError('Make sure len(li)=N, len(ki)=N, and '
842
1140
  'len(mi)=N-1 for a system of N segments.')
843
1141
 
844
- if self.system not in ['pst-', '-pst', 'vpst-', '-vpst']:
1142
+ if self.system not in ['pst-', '-pst', 'vpst-', '-vpst', 'rot', 'trans']:
845
1143
  # Boundary segments must be on foundation for infinite BCs
846
1144
  if not all([ki[0], ki[-1]]):
847
1145
  raise ValueError('Provide supported boundary segments in '
@@ -856,6 +1154,7 @@ class SolutionMixin:
856
1154
 
857
1155
  # Determine size of linear system of equations
858
1156
  nS = len(li) # Number of beam segments
1157
+
859
1158
  nDOF = 6 # Number of free constants per segment
860
1159
 
861
1160
  # Add dummy segment if only one segment provided
@@ -884,11 +1183,11 @@ class SolutionMixin:
884
1183
  zhi = self.eqs(
885
1184
  zl=self.zh(x=0, l=l, bed=k),
886
1185
  zr=self.zh(x=l, l=l, bed=k),
887
- l=l, k=k, pos=pos)
1186
+ k=k, pos=pos)
888
1187
  zpi = self.eqs(
889
1188
  zl=self.zp(x=0, phi=phi, bed=k),
890
1189
  zr=self.zp(x=l, phi=phi, bed=k),
891
- l=l, k=k, pos=pos)
1190
+ k=k, pos=pos)
892
1191
  # Rows for left-hand side assembly
893
1192
  start = 0 if i == 0 else 3
894
1193
  stop = 6 if i == nS - 1 else 9
@@ -903,7 +1202,7 @@ class SolutionMixin:
903
1202
  # Right-hand side for transmission from segment i-1 to segment i
904
1203
  rhs[6*i:6*i + 3] = np.vstack([Ft, -Ft*self.h/2, Fn])
905
1204
  # Set rhs so that complementary integral vanishes at boundaries
906
- if self.system not in ['pst-', '-pst']:
1205
+ if self.system not in ['pst-', '-pst', 'rested']:
907
1206
  rhs[:3] = self.bc(self.zp(x=0, phi=phi, bed=ki[0]))
908
1207
  rhs[-3:] = self.bc(self.zp(x=li[-1], phi=phi, bed=ki[-1]))
909
1208
 
@@ -921,19 +1220,34 @@ class SolutionMixin:
921
1220
  # Add to right-hand side
922
1221
  rhs[:3] = np.vstack([N, M, V]) # left end
923
1222
  rhs[-3:] = np.vstack([N, M, V]) # right end
924
-
925
- # Set touchdown boundary conditions
926
- elif self.system in ['pst-', '-pst']:
927
- # Loop through segments to set touchdown at rhs
928
- for i in range(nS):
929
- # Length, foundation and position of segment i
930
- l, k, pos = li[i], ki[i], pi[i]
931
- mode = self.mode_td(l=l)
932
- if not k and bool(mode in ['B', 'C']):
933
- if i==0:
934
- rhs[:3] = np.vstack([0,0,self.tc])
935
- if i == (nS - 1):
936
- rhs[-3:] = np.vstack([0,0,self.tc])
1223
+
1224
+ # Loop through segments to set touchdown conditions at rhs
1225
+ for i in range(nS):
1226
+ # Length, foundation and position of segment i
1227
+ l, k, pos = li[i], ki[i], pi[i]
1228
+ # 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])
1232
+ if i == (nS - 1):
1233
+ rhs[-3:] = np.vstack([0,0,self.tc])
1234
+ # 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])
1239
+ if i == (nS - 1):
1240
+ rhs[-3:] = np.vstack([N,0,self.tc])
1241
+
1242
+ # Rhs for substitute spring stiffness
1243
+ if self.system in ['rot']:
1244
+ # apply arbitrary moment of 1 at left boundary
1245
+ rhs = rhs*0
1246
+ rhs[1] = 1
1247
+ if self.system in ['trans']:
1248
+ # apply arbitrary force of 1 at left boundary
1249
+ rhs = rhs*0
1250
+ rhs[2] = 1
937
1251
 
938
1252
  # --- SOLVE -----------------------------------------------------------
939
1253
 
@@ -951,7 +1265,14 @@ class AnalysisMixin:
951
1265
  elastic foundations.
952
1266
  """
953
1267
 
954
- def rasterize_solution(self, C, phi, li, ki, num=250, **kwargs):
1268
+ 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):
955
1276
  """
956
1277
  Compute rasterized solution vector.
957
1278
 
@@ -984,6 +1305,7 @@ class AnalysisMixin:
984
1305
  _ = kwargs
985
1306
 
986
1307
  # Drop zero-length segments
1308
+ li = abs(li)
987
1309
  isnonzero = li > 0
988
1310
  C, ki, li = C[:, isnonzero], ki[isnonzero], li[isnonzero]
989
1311
 
@@ -1003,7 +1325,7 @@ class AnalysisMixin:
1003
1325
  # Loop through segments
1004
1326
  for i, l in enumerate(li):
1005
1327
  # Get local x-coordinates of segment i
1006
- xi = np.linspace(0, l, num=nq[i], endpoint=(i == li.size - 1))
1328
+ xi = np.linspace(0, l, num=nq[i], endpoint=(i == li.size - 1)) # pylint: disable=superfluous-parens
1007
1329
  # Compute start and end coordinates of segment i
1008
1330
  x0 = lic[i]
1009
1331
  # Assemble global coordinate vector
@@ -1466,6 +1788,137 @@ class OutputMixin:
1466
1788
  Provides convenience methods for the assembly of output lists
1467
1789
  such as rasterized displacements or rasterized stresses.
1468
1790
  """
1791
+ def external_potential(self, C, phi, L, **segments):
1792
+ """
1793
+ Compute total external potential (pst only).
1794
+
1795
+ Arguments
1796
+ ---------
1797
+ C : ndarray
1798
+ Matrix(6xN) of solution constants for a system of N
1799
+ segements. Columns contain the 6 constants of each segement.
1800
+ phi : float
1801
+ Inclination of the slab (°).
1802
+ L : float, optional
1803
+ Total length of model (mm).
1804
+ segments : dict
1805
+ Dictionary with lists of touchdown booleans (tdi), segement
1806
+ lengths (li), skier weights (mi), and foundation booleans
1807
+ in the cracked (ki) and uncracked (k0) configurations.
1808
+
1809
+ Returns
1810
+ -------
1811
+ Pi_ext : float
1812
+ Total external potential (Nmm).
1813
+ """
1814
+ # Rasterize solution
1815
+ xq, zq, xb = self.rasterize_solution(C=C, phi=phi, **segments)
1816
+ _ = xq, xb
1817
+ # Compute displacements where weight loads are applied
1818
+ w0 = self.w(zq)
1819
+ us = self.u(zq, z0=self.zs)
1820
+ # Get weight loads
1821
+ qn = self.calc_qn()
1822
+ qt = self.calc_qt()
1823
+ # use +/- and us[0]/us[-1] according to system and phi
1824
+ # 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
1827
+ # Ensure
1828
+ if self.system in ['pst-']:
1829
+ ub = us[-1]
1830
+ elif self.system in ['-pst']:
1831
+ 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.')
1836
+
1837
+ return Pi_ext
1838
+
1839
+ def internal_potential(self, C, phi, L, **segments):
1840
+ """
1841
+ Compute total internal potential (pst only).
1842
+
1843
+ Arguments
1844
+ ---------
1845
+ C : ndarray
1846
+ Matrix(6xN) of solution constants for a system of N
1847
+ segements. Columns contain the 6 constants of each segement.
1848
+ phi : float
1849
+ Inclination of the slab (°).
1850
+ L : float, optional
1851
+ Total length of model (mm).
1852
+ segments : dict
1853
+ Dictionary with lists of touchdown booleans (tdi), segement
1854
+ lengths (li), skier weights (mi), and foundation booleans
1855
+ in the cracked (ki) and uncracked (k0) configurations.
1856
+
1857
+ Returns
1858
+ -------
1859
+ Pi_int : float
1860
+ Total internal potential (Nmm).
1861
+ """
1862
+ # Rasterize solution
1863
+ xq, zq, xb = self.rasterize_solution(C=C, phi=phi, **segments)
1864
+
1865
+ # Compute section forces
1866
+ N, M, V = self.N(zq), self.M(zq), self.V(zq)
1867
+
1868
+ # Drop parts of the solution that are not a foundation
1869
+ zweak = zq[:, ~np.isnan(xb)]
1870
+ xweak = xb[~np.isnan(xb)]
1871
+
1872
+ # Compute weak layer displacements
1873
+ wweak = self.w(zweak)
1874
+ uweak = self.u(zweak, z0=self.h/2)
1875
+
1876
+ # Compute stored energy of the slab (monte-carlo integration)
1877
+ n = len(xq)
1878
+ nweak = len(xweak)
1879
+ # 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])
1885
+ # 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
1890
+ else:
1891
+ print('Input error: Only pst-setup implemented at the moment.')
1892
+
1893
+ return Pi_int
1894
+
1895
+ def total_potential(self, C, phi, L, **segments):
1896
+ """
1897
+ Returns total differential potential
1898
+
1899
+ Arguments
1900
+ ---------
1901
+ C : ndarray
1902
+ Matrix(6xN) of solution constants for a system of N
1903
+ segements. Columns contain the 6 constants of each segement.
1904
+ phi : float
1905
+ Inclination of the slab (°).
1906
+ L : float, optional
1907
+ Total length of model (mm).
1908
+ segments : dict
1909
+ Dictionary with lists of touchdown booleans (tdi), segement
1910
+ lengths (li), skier weights (mi), and foundation booleans
1911
+ in the cracked (ki) and uncracked (k0) configurations.
1912
+
1913
+ Returns
1914
+ -------
1915
+ Pi : float
1916
+ Total differential potential (Nmm).
1917
+ """
1918
+ Pi_int = self.internal_potential(C, phi, L, **segments)
1919
+ Pi_ext = self.external_potential(C, phi, L, **segments)
1920
+
1921
+ return Pi_int + Pi_ext
1469
1922
 
1470
1923
  def get_weaklayer_shearstress(self, x, z, unit='MPa', removeNaNs=False):
1471
1924
  """