From 81c40f46579fbf05b70e88d39357eebf58318893 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Sat, 28 Dec 2024 11:30:49 -0500 Subject: [PATCH 01/13] First commit in a while... Adding some docstring text and mainly just dusting off the machinery here. --- src/hyperstruct/fuselage.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 339b88e..9766667 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -486,5 +486,25 @@ def acoustic_fatigue(self, b: float) -> float: return float(t_r) def forced_crippling(self) -> float: - """Thickness from force crippling.""" + """Thickness from forced crippling. + + If the covers on the fuselage structure are allowed to buckle under shear loads, + in the buckled state these loads are supported by diagonal tension stresses. + Axial loads produced by cover tension field are reacted by stiffening elements + (minor frames, stringers) that bound the panel. Since shear loads are maximum at + the midpoint of the side panel, this condition is evaluated for elements on the + side sectors of the shell. + + Basic formulations for the prevention of forced crippling failure due to the + postbuckled design are taken directly from Kuhn[4] and Bruhn [1]. These methods + have been modified to account for the condition where different materials are + selected for cover, longeron, and minor frame design. + + Cover sizing is established to satisfy strength and other criteria within the Cover + class. Shear stress based on this thickness is compared against the critical shear stress + to determine whether the panel is critical for postbuckled strength. At this point + in the sizing process, longeron (stringer) area has not been established. The + longitudinal member area is required to define panel boundary support constraints. + A first approximation is made for longeron area base on vehicle bending loads. + """ return 0.0 From 43a1f3d2b0c8d40d14966a16f89265bf8ae7d0aa Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Sat, 28 Dec 2024 12:47:26 -0500 Subject: [PATCH 02/13] Forced Crippling work. Still building out the forced crippling method. Added a bunch of input arguments, since forced crippling needs so many inputs. This method is still useless but I ran out of time for today. --- src/hyperstruct/fuselage.py | 94 ++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 9766667..30b38e2 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -386,6 +386,9 @@ class MinorFrame(Component): b: float """cap flange width.""" + construction: str + """Construction method. ('stringer' or 'longeron')""" + t_r: float = 0.0 """cap flange thickness.""" @@ -485,7 +488,18 @@ def acoustic_fatigue(self, b: float) -> float: return float(t_r) - def forced_crippling(self) -> float: + def forced_crippling( + self, + L: float, + D: float, + M: float, + Z: float, + sum_z_sq: float, + t_c: float, + RC: float, + f_s: float, + f_scr: float, + ) -> float: """Thickness from forced crippling. If the covers on the fuselage structure are allowed to buckle under shear loads, @@ -506,5 +520,83 @@ def forced_crippling(self) -> float: in the sizing process, longeron (stringer) area has not been established. The longitudinal member area is required to define panel boundary support constraints. A first approximation is made for longeron area base on vehicle bending loads. + + Args: + L: Frame Spacing + D: Fuselage Diameter + M: Bending moment at the cut + Z: coordinate of the extreme fiber (fuselage half-depth at cut) + sum_z_sq: sum of longeron coordinates squared + t_c: Cover thickness + RC: Side panel radius of curvature + f_s: Shear stress in Cover at cut + f_scr: Critical shear buckling strength of Cover + + Returns: + A bunch of floats? + """ + A_s = M * Z / (self.material.F_cy * sum_z_sq) + """Cover sizing is established to satisfy strength and other criteria within the Cover + class. Shear stress based on this thickness is compared against the critical shear stress + to determine whether the panel is critical for postbuckled strength. At this point + in the sizing process, longeron (stringer) area has not been established. The + longitudinal member area is required to define panel boundary support constraints. + A first approximation is made for longeron area base on vehicle bending loads. """ + + # There's two rules here. The dimension ratios are capped at 2, and + # we need to use the larger ratio depending on the convention of construction. + # Typically, for Stringer systems D > L, but for Longerons L > D. + if D >= L: + if D / L > 2: + K = np.tanh((0.5 + 300 * (t_c / RC * 2)) * np.log10(f_s / f_scr)) + else: + K = np.tanh((0.5 + 300 * (t_c / RC * D / L)) * np.log10(f_s / f_scr)) + elif L > D: + if L / D > 2: + K = np.tanh((0.5 + 300 * (t_c / RC * 2)) * np.log10(f_s / f_scr)) + else: + K = np.tanh((0.5 + 300 * (t_c / RC * L / D)) * np.log10(f_s / f_scr)) + else: + raise ValueError( + "Frame Spacing (L) and Fuselage Diameter (D) cannot be properly compared." + ) + + # Angle of the folds is given an initial approximation. + # This is probably good enough for weights sizing, but a more exact solution involves iteration. + # This is the radians equivalent of 45 degrees. + alpha = np.pi / 4 # [rad] + + # The forced crippling method is highly dependant on empirical relations. + # As such, the specific relations we use are dependant on the construction method. + # This method must be chosen from the user, but they can use it as a conceptual + # sizing variable permutation if they wish. + if self.construction == "stringer": + #### + # STRINGER CONSTRUCTION + #### + + pass + + #### + + elif self.construction == "longeron": + #### + # LONGERON CONSTRUCTION + #### + + pass + + #### + + else: + raise AttributeError( + "The MinorFrame attribute 'construction' is not properly defined. Acceptable options are 'stringer' or 'longeron'" + ) + + # Temporary stuff to pass pre-commit checks. Delete this. + _ = A_s + 1 + _ = K + 1 + _ = alpha + 1 + # return 0.0 From 8c1975add77105f15b0e8787d5841b878a5eec02 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Sun, 29 Dec 2024 10:11:43 -0500 Subject: [PATCH 03/13] More Minor Frame Forced Crippling Work. Started the stringer iteration for angle of folds. Very confused here... Not sure how it's actually supposed to iterate. --- src/hyperstruct/fuselage.py | 45 ++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 30b38e2..87cd9d5 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -499,6 +499,9 @@ def forced_crippling( RC: float, f_s: float, f_scr: float, + cover_e: float = None, + long_e: float = None, + frame_e: float = None, ) -> float: """Thickness from forced crippling. @@ -521,6 +524,13 @@ def forced_crippling( longitudinal member area is required to define panel boundary support constraints. A first approximation is made for longeron area base on vehicle bending loads. + Cover sizing is established to satisfy strength and other criteria within the Cover + class. Shear stress based on this thickness is compared against the critical shear stress + to determine whether the panel is critical for postbuckled strength. At this point + in the sizing process, longeron (stringer) area has not been established. The + longitudinal member area is required to define panel boundary support constraints. + A first approximation is made for longeron area base on vehicle bending loads. + Args: L: Frame Spacing D: Fuselage Diameter @@ -535,14 +545,8 @@ def forced_crippling( Returns: A bunch of floats? """ + # Longeron/Stringer area A_s = M * Z / (self.material.F_cy * sum_z_sq) - """Cover sizing is established to satisfy strength and other criteria within the Cover - class. Shear stress based on this thickness is compared against the critical shear stress - to determine whether the panel is critical for postbuckled strength. At this point - in the sizing process, longeron (stringer) area has not been established. The - longitudinal member area is required to define panel boundary support constraints. - A first approximation is made for longeron area base on vehicle bending loads. - """ # There's two rules here. The dimension ratios are capped at 2, and # we need to use the larger ratio depending on the convention of construction. @@ -576,7 +580,30 @@ def forced_crippling( # STRINGER CONSTRUCTION #### - pass + if not cover_e: + cover_e = self.material.E_c + + if not long_e: + long_e = self.material.E_c + + if not frame_e: + frame_e = self.material.E_c + + # Use the same K value as before. + alpha_ratio = K**0.25 + + # A is the curve fit ordinate, Fig. 24 of ADA002867 + A = (D / RC * np.sqrt(cover_e / f_s)) / np.sqrt( + 1 + + 0.5 + * ( + D * t_c * cover_e / (A_s * long_e) + + L * cover_e / (self.area * frame_e) + ) + ) + + alpha_pdt = (np.pi / 4 + 0.1443 * A) / (1 + 0.175622 * A + 0.013411 * A**2) + alpha = alpha_pdt * alpha_ratio #### @@ -595,8 +622,6 @@ def forced_crippling( ) # Temporary stuff to pass pre-commit checks. Delete this. - _ = A_s + 1 - _ = K + 1 _ = alpha + 1 # return 0.0 From e138df455cba7e47d88ab86684f84c149ab6bbf7 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Sun, 29 Dec 2024 11:04:24 -0500 Subject: [PATCH 04/13] I hate Diagonal Tension. Added the stress evaluations for minor frame diagonal tension. --- src/hyperstruct/fuselage.py | 109 ++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 87cd9d5..e232a33 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -501,7 +501,6 @@ def forced_crippling( f_scr: float, cover_e: float = None, long_e: float = None, - frame_e: float = None, ) -> float: """Thickness from forced crippling. @@ -575,53 +574,105 @@ def forced_crippling( # As such, the specific relations we use are dependant on the construction method. # This method must be chosen from the user, but they can use it as a conceptual # sizing variable permutation if they wish. - if self.construction == "stringer": - #### - # STRINGER CONSTRUCTION - #### + if not cover_e: + cover_e = self.material.E_c - if not cover_e: - cover_e = self.material.E_c + if not long_e: + long_e = self.material.E_c - if not long_e: - long_e = self.material.E_c + frame_e = self.material.E_c - if not frame_e: - frame_e = self.material.E_c + # Use the same K value as before. + alpha_ratio = K**0.25 - # Use the same K value as before. - alpha_ratio = K**0.25 + # A is the curve fit ordinate, Fig. 24 of ADA002867 + A = (D / RC * np.sqrt(cover_e / f_s)) / np.sqrt( + 1 + + 0.5 + * (D * t_c * cover_e / (A_s * long_e) + L * cover_e / (self.area * frame_e)) + ) - # A is the curve fit ordinate, Fig. 24 of ADA002867 - A = (D / RC * np.sqrt(cover_e / f_s)) / np.sqrt( - 1 - + 0.5 - * ( - D * t_c * cover_e / (A_s * long_e) - + L * cover_e / (self.area * frame_e) - ) - ) + alpha_pdt = (np.pi / 4 + 0.1443 * A) / (1 + 0.175622 * A + 0.013411 * A**2) + alpha = alpha_pdt * alpha_ratio + + # Secondary ring load: axial load in ring due to shear stress + P_rg = f_s * K * t_c * L * np.tan(alpha) - alpha_pdt = (np.pi / 4 + 0.1443 * A) / (1 + 0.175622 * A + 0.013411 * A**2) - alpha = alpha_pdt * alpha_ratio + # Effective ring area + A_erg = self.area / (1 + (self.c / (2 * self.rho)) ** 2) + if self.construction == "stringer": + #### + # STRINGER CONSTRUCTION #### + # Secondary stringer load: axial load in stringer due to shear stress + P_st = f_s * K * np.tan(alpha) ** (-1) + + # Effecting ring and cover area + A_edt = A_erg + 0.5 * L * t_c * (1 - K) * (cover_e / frame_e) + + # Stress in the ring frame + f_rg = P_rg / A_edt + # Secondary stringer stress: axial load due to shear stress + f_st = P_st / A_edt + elif self.construction == "longeron": #### # LONGERON CONSTRUCTION #### - pass + # Secondary stringer load: axial load in stringer due to shear stress + P_st = f_s * K * t_c * D / 2 * np.tan(alpha) ** (-1) - #### + A_est = A_s / (1 + (self.c / (2 * self.rho)) ** 2) + + # Effecting ring and cover area + A_edt = A_est + 0.5 * D * t_c * (1 - K) * (cover_e / frame_e) + + # Stress in the ring frame + f_rg = P_rg / A_edt + # Secondary longeron stress: axial load in longeron due to shear stress + f_st = P_st / A_edt else: raise AttributeError( "The MinorFrame attribute 'construction' is not properly defined. Acceptable options are 'stringer' or 'longeron'" ) - # Temporary stuff to pass pre-commit checks. Delete this. - _ = alpha + 1 - # + # Max stress in the frame is based on an empirical relation from Bruhn. + # This is dependent on the ratio of frame/longeron spacing. + if L / D <= 1.2: + frgmax_frg = 1 + 0.78 * (1 - K) - 0.65 * L / D * (1 - K) + elif L / D > 1.2: + frgmax_frg = 1.0 + else: + raise ValueError( + "Frame Spacing (L) and Fuselage Diameter (D) cannot be properly compared." + ) + + frgmax = frgmax_frg * f_rg + + # Allowable ring frame stress + if RC <= 151.586: + N = ( + (18695 + 75.238 * RC) + * K ** (2 / 3) + * (self.t_r / t_c) ** (1 / 3) + * (frame_e / cover_e) ** (1 / 9) + ) + elif RC > 151.586: + N = ( + 30100 + * K ** (2 / 3) + * (self.t_r / t_c) ** (1 / 3) + * (frame_e / cover_e) ** (1 / 9) + ) + + G = self.material.F_cy * 1088 - 5 / ( + 5.88 * (self.material.F_cy / frame_e + 0.002) ** 0.5 + ) + + F_RG = N * G + return 0.0 From 803e5beb4dd8576136a2f21cd88a0d4eb00b27da Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Tue, 31 Dec 2024 09:40:00 -0500 Subject: [PATCH 05/13] Finishing the forced crippling method. At some point in the future, this method will need a dedicated branch with textbook examples. Until results are verified against examples, I have zero faith in the accuracy here. More iteration is required, according to reference material, after the thickness is solved for. Additionally, refactoring the method will be required to pass CI. See #71. --- src/hyperstruct/fuselage.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index e232a33..8cd0e30 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -607,7 +607,7 @@ def forced_crippling( #### # Secondary stringer load: axial load in stringer due to shear stress - P_st = f_s * K * np.tan(alpha) ** (-1) + # P_st = f_s * K * np.tan(alpha) ** (-1) # Effecting ring and cover area A_edt = A_erg + 0.5 * L * t_c * (1 - K) * (cover_e / frame_e) @@ -615,7 +615,7 @@ def forced_crippling( # Stress in the ring frame f_rg = P_rg / A_edt # Secondary stringer stress: axial load due to shear stress - f_st = P_st / A_edt + # f_st = P_st / A_edt elif self.construction == "longeron": #### @@ -623,7 +623,7 @@ def forced_crippling( #### # Secondary stringer load: axial load in stringer due to shear stress - P_st = f_s * K * t_c * D / 2 * np.tan(alpha) ** (-1) + # P_st = f_s * K * t_c * D / 2 * np.tan(alpha) ** (-1) A_est = A_s / (1 + (self.c / (2 * self.rho)) ** 2) @@ -633,7 +633,7 @@ def forced_crippling( # Stress in the ring frame f_rg = P_rg / A_edt # Secondary longeron stress: axial load in longeron due to shear stress - f_st = P_st / A_edt + # f_st = P_st / A_edt else: raise AttributeError( @@ -642,6 +642,7 @@ def forced_crippling( # Max stress in the frame is based on an empirical relation from Bruhn. # This is dependent on the ratio of frame/longeron spacing. + # This relation applies for both the applied stress, f, and the allowable stress, F. if L / D <= 1.2: frgmax_frg = 1 + 0.78 * (1 - K) - 0.65 * L / D * (1 - K) elif L / D > 1.2: @@ -673,6 +674,28 @@ def forced_crippling( 5.88 * (self.material.F_cy / frame_e + 0.002) ** 0.5 ) + # Why do we not need the allowable stress, and only the allowable ratio...?? F_RG = N * G + H = A * G - return 0.0 + # Iterating for cap flange thickness, t_r + x_c = 0.5 * (1 - K) * cover_e / self.material.E_c + x_b = (4 * self.b + self.c / 2) / ( + (1 + (self.c / (2 * self.rho)) ** 2) * L * t_c + ) + x_a = ((f_s * np.tan(alpha) / H) * (frgmax / F_RG)) ** 3 * t_c * K + + # An initialization for optimizing + err = 100 + while err > 0.1: + t_r2 = self.t_r - ( + (self.t_r * (self.t_r * x_b + x_c) ** 3 + x_a) + / ( + 3 * x_b * self.t_r * (self.t_r * x_b + x_c) ** 2 + + (self.t_r * x_b + x_c) ** 3 + ) + ) + err = t_r2 - self.t_r + self.t_r = t_r2 + + return t_r2 From 1bc1c52e07a2efa9db9bb41e37f7dade6b789260 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Tue, 31 Dec 2024 11:40:08 -0500 Subject: [PATCH 06/13] Added Longeron class. Added the Longeron class to represent all longitudinal members. --- src/hyperstruct/fuselage.py | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 8cd0e30..9e27c7d 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -699,3 +699,61 @@ def forced_crippling( self.t_r = t_r2 return t_r2 + + +@dataclass +class Longeron(Component): + """Fuselage longitudinal member component. + + Longitudinal Members include both Stringers and Longerons. The Longitudinal Member + sizing is determined to satisfy minimum area, forced crippling, bending strength, + and stiffness requirements. The methods account for differences in Cover, Longeron, + and MinorFrame materials, and the effects of cutouts. + + The flange width is set up to equal the web height, and the thickness is assumed + to be constant across the section. This primarily simplifies + some of the calculations, and should be sufficient for weight estimates. + """ + + b: float + """Web Height, and Flange Width.""" + + t_s: float + """Longeron thickness.""" + + k: float + """Inner flange proportion of web height.""" + + @property + def area(self) -> float: + """Cross-sectional area.""" + return self.t_s * self.b * (3 + self.k) + + @property + def e(self) -> float: + """Eccentricity.""" + return self.b * (0.5 + self.k / (3 + self.k)) + + @property + def i_xx(self) -> float: + """Second moment of area (moment of inertia), strong-axis.""" + return ( + self.t_s + * self.b + * ( + 2 * self.e**2 + + self.b**2 / 12 + + (0.5 * self.b - self.e) ** 2 + + self.k * (self.b - self.e) ** 2 + ) + ) + + @property + def rho(self) -> float: + """Radius of gyration.""" + return np.sqrt(self.i_xx / self.area) + + @property + def area_effective(self) -> float: + """Effective area.""" + return self.area / (1 + (self.e / self.rho) ** 2) From d3de079a971a1f24f81cf23613601df82bc2c89d Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Tue, 31 Dec 2024 15:27:50 -0500 Subject: [PATCH 07/13] Added initial sizing method to Longeron class. bending strength method created. Still mostly a placeholder. --- src/hyperstruct/fuselage.py | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 9e27c7d..671b298 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -757,3 +757,85 @@ def rho(self) -> float: def area_effective(self) -> float: """Effective area.""" return self.area / (1 + (self.e / self.rho) ** 2) + + def bending_strength( + self, + M_ext: float, + t: float, + d: float, + I_t: float, + l_p: float, + rtu: float, + A_s: float, + I_a: float, + ) -> float: + """Thickness required to satisfy bending strength. + + Longitudinal member sizing is dependent on the contribution of all + copmonents that resist bending loads. This method accounts for the difference + in behavior of cover elements under tension load versus the behavior in + compression. Cover material, should it differ from longeron material, can + also have different strength and elastic properties. + + The assumption that plane sections remain plane simplifies the estimating + approach. The practice of using a Design Ultimate Factor of Safety of 1.5 + in analysis and metal deformation characteristics results in stresses at + limit load occurring in the elastic range. + + The stress at any point on the shell is then proportional to the extreme + fiber stress according to the relationship of vertical coordinate versus + extreme fiber coordinate. + + Bending moment is assumed to be reactied by an internal coupled force system. + Thus, in the case of down-bending, the uper half of the shell sustains tension + loads, and the lower half, compression loads; half of the moment is reacted + in each half. Covers are totally effective in the tension sector of the + fuselage. Cutouts eliminate cover contributions; proximity to cutouts degrade + the effectiveness of the cover. The width of cutouts at other synthesis cuts + combined with longitudinal displacement and hsear lag slope of 2 to 1 is used + to determine the apparent effective width. + + Args: + M_ext: External moment at the cut + t: cover thickness + d: fuselage depth + I_t: cover moment of inertia, as a function of thickness + l_p: cover peripheral length + rtu: apparent panel degradation due to cutout proximity + A_s: stringer/longeron area + I_a: side stringer moment of inertia aa a funciton of area + + Returns: + A_l: area that satisfies the bending strength requirement + """ + # Max allowable extreme fiber stresses + # Longeron/Stringer + f_max_l = 0.9 * self.material.F_cy + # Cover + f_max_c = 0.76 * self.material.F_tu + + # Moment reacted by the upper cover in tension + M_c = t * (f_max_c / (d / 2)) * I_t * (l_p - rtu / l_p) + + # Moment reacted by the upper side panel stringers + M_s = A_s * f_max_l * I_a / (0.5 * d) + + # TODO: Secondary side stringer contribution + # Calculated in the same manner as primary side stringers + M_sl = 0.0 + + # Moment reacted by longerons + M_l = 0.5 * M_ext - M_c - M_s - M_sl + + # Longeron area to resist this moment + A_l = M_l * 0.5 * d / (f_max_l * self.i_xx / self.area) + + # Now the compression sector is evaluated + # Effectiveness of cover in compression is based on Peery curved panel buckling + # F_CCR = ( 9*(t_c/R)**(5/3) + 0.16*(t_c/L)**(1.3) + K_c*np.pi**2/(12*(1-nu_c**2)) ) * cover_e + + # ... needs more code ... # + + # + + return A_l From fa2dbac6113c3e530a9ec5a78b4381dee5b39e30 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Tue, 31 Dec 2024 17:09:14 -0500 Subject: [PATCH 08/13] Added the Bulkhead component. Added the initial Bulkhead component class. Nothing much here, just a placeholder with a bunch of reference comments. --- src/hyperstruct/fuselage.py | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 671b298..e8e8018 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -839,3 +839,103 @@ def bending_strength( # return A_l + + +@dataclass +class Bulkhead(Component): + """Fuselage pressure bulkhead component. + + Pressure bulkhead are located at structural synthesis cuts by the user- + determined input. Assumptions used in this approach are: + 1. Construction is stiffened sheet design simply supported around + the periphery. + 2. Strip theory provides an adequate definition of maximum bending + moment. + 3. Stiffeners are of constant cross section basedon the maxium bending + moment, at equal spacings, and oriented parallel to the shortest + bulkhead dimension. + 4. Web thickness base don maximum pressure is constant throughout the + bulkhead surface. + 5. Minor frame material is used for bulkhead construction. + + There are 3 assumed pressure loading types: Uniform, Triangular, and + Trapezoidal. Uniform loading occurs from cabin or equipment compartment + pressurization. Triangular occurs from fuel head. Trapezoidal loadings + result from the combinations of fuel head and vent pressure. Fuel pressure + results from the vehicle maneuver such that both positive and negative + maneuvers are examined. + """ + + duf: float + """Design Ultimate Factor. + + (2.0 for personnel environment, 1.5 for equipment) + """ + + p_1: float + """Triangular pressure.""" + + p_2: float + """Uniform pressure.""" + + L: float + """Height of pressurized surface.""" + + t_w: float + """Web field thickness. + + (Webs are assumed to be milled.) + """ + + t_l: float + """Web land thickness. + + (Webs are assumed to be milled.) + """ + + d: float + """Stiffener spacing.""" + + t_s: float + """Stiffener web thickness.""" + + H: float + """Stiffener cap width. + + (An I-beam cap geometry.) + """ + + def allowable_tensile_stress(self, K_r: float) -> float: + """The design allowable tensile stress. + + Args: + K_r: Fatigue reduction factor (percent of Ftu) + + Returns: + f_t: design allowable tensile stress + """ + return min(self.material.F_tu / self.duf, K_r * self.material.F_tu) + + def max_bending_moment(self) -> float: + """Bending moment per unit width for the pressure loading.""" + x = -self.L * self.p_2 + self.L * np.sqrt( + self.p_2**2 + self.p_2 * self.p_1 + (self.p_1**2) / 3 + ) * self.p_1 ** (-1) + k = x / self.L + M_max = ( + self.p_2 * self.L**2 * 0.5 * (k - k**2) + + self.p_1 * self.L**2 * (k - k**3) / 6 + ) + + return M_max + + def stiffener_area(self) -> float: + """Area of stiffener, including effective web.""" + return 6 * self.t_s * self.H + + def stiffener_inertia(self) -> float: + """Second moment of area of stiffener, including effective web. + + Second order therms of thickness are assumed to be negligible. + """ + return 14 / 3 * self.t_s * self.H**3 From b7af12cff615fde15a0fb4d456268f99731b38e3 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Tue, 31 Dec 2024 17:48:45 -0500 Subject: [PATCH 09/13] Expanded methods in Bulkhead class. Added more boiler plate and the web thickness methods. Started the optimization method for stiffener spacing. --- src/hyperstruct/fuselage.py | 66 ++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index e8e8018..1dd52e9 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -872,6 +872,9 @@ class Bulkhead(Component): (2.0 for personnel environment, 1.5 for equipment) """ + K_r: float + """Fatigue reduction factor (percent of Ftu)""" + p_1: float """Triangular pressure.""" @@ -905,16 +908,13 @@ class Bulkhead(Component): (An I-beam cap geometry.) """ - def allowable_tensile_stress(self, K_r: float) -> float: + def allowable_tensile_stress(self) -> float: """The design allowable tensile stress. - Args: - K_r: Fatigue reduction factor (percent of Ftu) - Returns: f_t: design allowable tensile stress """ - return min(self.material.F_tu / self.duf, K_r * self.material.F_tu) + return min(self.material.F_tu / self.duf, self.K_r * self.material.F_tu) def max_bending_moment(self) -> float: """Bending moment per unit width for the pressure loading.""" @@ -939,3 +939,59 @@ def stiffener_inertia(self) -> float: Second order therms of thickness are assumed to be negligible. """ return 14 / 3 * self.t_s * self.H**3 + + def web_thickness(self) -> tuple: + """Evaluate web thickness. + + Web sizing based on combined bending and diaphragm action between + stiffeners. Webs are assumed to be milled between supports. Thicknesses + are derived by curve-fit approximation of theoretical plots (same + analytical method as Cover diaphragm sizing). + + Returns: + (t_w, t_l): Web field thickness, Web land thickness + """ + t_w = ( + 1.3769 + * self.d + * (self.p_1 + self.p_2) ** 2.484 + * self.material.E**0.394 + / self.allowable_tensile_stress(self.K_r) ** 4.467 + ) + + t_l = ( + 1.646 + * self.d + * (self.p_1 + self.p_2) ** 0.894 + * self.material.E**1.984 + / self.allowable_tensile_stress(self.K_r) ** 1.288 + ) + + return (t_w, t_l) + + def stiffener_spacing( + self, + d_1: float = 2.0, + d_2: float = 12.0, + H_1: float = 1.0, + H_2: float = 5.0, + t_s1: float = 0.025, + ) -> float: + """Stiffener spacing optimization routine. + + Stiffener spacing search is initiated at minimum spacing and + continued at fixed increments where three values of effective thickness + are obtained. A three-point curve fit solution is used to determine + the optimum spacing. + + Args: + d_1: spacing lower bound + d_2: spacing upper bound + H_1: width lower bound + H_2: width upper bound + t_s1: min gauge thickness + + Returns: + d: stiffener spacing + """ + self.t_w, self.t_l = self.web_thickness() From 9c132b0fcdffa33f8ca3bba07ebc5ee4deb5b70c Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Fri, 3 Jan 2025 18:13:08 -0500 Subject: [PATCH 10/13] Finishing Up Initial Bulkhead class. Added a quick test for the stiffener optimization method. The limits seem very sensitive, and should probably have more control in the future. --- src/hyperstruct/__init__.py | 3 ++ src/hyperstruct/fuselage.py | 93 +++++++++++++++++++++++++++++++------ tests/test_fuselage.py | 44 +++++++++++++++--- 3 files changed, 118 insertions(+), 22 deletions(-) diff --git a/src/hyperstruct/__init__.py b/src/hyperstruct/__init__.py index a297012..de8bc9f 100644 --- a/src/hyperstruct/__init__.py +++ b/src/hyperstruct/__init__.py @@ -15,6 +15,9 @@ class Material: It also contains thermomechanical curves for environmental correction factors. """ + rho: float + """Weight density (aka specific weight)""" + E: float """Young's Modulus, tension.""" diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 1dd52e9..88dc3f4 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -10,6 +10,7 @@ from typing import Tuple import numpy as np +from scipy.optimize import minimize_scalar from hyperstruct import Component @@ -908,13 +909,13 @@ class Bulkhead(Component): (An I-beam cap geometry.) """ - def allowable_tensile_stress(self) -> float: + def allowable_tensile_stress(self, K_r) -> float: """The design allowable tensile stress. Returns: f_t: design allowable tensile stress """ - return min(self.material.F_tu / self.duf, self.K_r * self.material.F_tu) + return min(self.material.F_tu / self.duf, K_r * self.material.F_tu) def max_bending_moment(self) -> float: """Bending moment per unit width for the pressure loading.""" @@ -929,18 +930,27 @@ def max_bending_moment(self) -> float: return M_max - def stiffener_area(self) -> float: - """Area of stiffener, including effective web.""" - return 6 * self.t_s * self.H + def stiffener_area(self, t_s: float, H: float) -> float: + """Area of stiffener, including effective web. - def stiffener_inertia(self) -> float: + Args: + t_s: stiffener thickness + H: stiffener cap width + """ + return 6 * t_s * H + + def stiffener_inertia(self, t_s: float, H: float) -> float: """Second moment of area of stiffener, including effective web. Second order therms of thickness are assumed to be negligible. + + Args: + t_s: stiffener thickness + H: stiffener cap width """ - return 14 / 3 * self.t_s * self.H**3 + return 14 / 3 * t_s * H**3 - def web_thickness(self) -> tuple: + def web_thickness(self, d: float) -> tuple: """Evaluate web thickness. Web sizing based on combined bending and diaphragm action between @@ -948,22 +958,25 @@ def web_thickness(self) -> tuple: are derived by curve-fit approximation of theoretical plots (same analytical method as Cover diaphragm sizing). + Args: + d: stiffener spacing + Returns: (t_w, t_l): Web field thickness, Web land thickness """ t_w = ( 1.3769 - * self.d + * d * (self.p_1 + self.p_2) ** 2.484 - * self.material.E**0.394 + * self.material.E**1.984 / self.allowable_tensile_stress(self.K_r) ** 4.467 ) t_l = ( 1.646 - * self.d + * d * (self.p_1 + self.p_2) ** 0.894 - * self.material.E**1.984 + * self.material.E**0.394 / self.allowable_tensile_stress(self.K_r) ** 1.288 ) @@ -976,7 +989,7 @@ def stiffener_spacing( H_1: float = 1.0, H_2: float = 5.0, t_s1: float = 0.025, - ) -> float: + ) -> tuple: """Stiffener spacing optimization routine. Stiffener spacing search is initiated at minimum spacing and @@ -992,6 +1005,56 @@ def stiffener_spacing( t_s1: min gauge thickness Returns: - d: stiffener spacing + t_s: minimum stiffener thickness + d: stiffener spacing for minimum thickness + H: stiffener width for minimum thickness """ - self.t_w, self.t_l = self.web_thickness() + x = np.linspace(d_1, d_2, num=5) + y = [] + + # Flange crippling set to compressive yield stress + # fcc = 0.312*np.sqrt(self.material.F_cy*self.material.E_c)*(4*self.t_s/H_i)**(3/4) + B = self.material.F_cy / ( + 0.312 * np.sqrt(self.material.F_cy * self.material.E_c) + ) + + for d_i in x: + t_w, t_l = self.web_thickness(d_i) + + t_s = ( + 3 + * self.max_bending_moment() + * B ** (8 / 3) + / (224 * self.material.F_cy) + ) ** (1 / 3) + H = 4 * t_s / B ** (4 / 3) + t_bar = ( + t_w + + (1.1 * H * (t_l - t_w) / d_i) + + ((4 * H * t_s + H * (2 * t_s - t_l)) / d_i) + ) + y.append(t_bar) + print(f"{d_i:>4.1f}, {t_s:.4f}, {t_w:.4f}, {t_bar:.3f}") + + # calculate polynomial + z = np.polyfit(x, y, 3) + f = np.poly1d(z) + + # minimum equivalent thickness + res = minimize_scalar(f, bounds=(d_1, d_2), method="bounded") + # The minimum t_bar + t_bar = res.fun + d = res.x + t_s = t_s1 if t_s <= t_s1 else t_s + H = 4 * t_s / B ** (4 / 3) + + if H < H_1: + raise ValueError( + f"Stiffener width of {H:.1f} required for min weight (t_s={t_s:0.3f}), but is outside bounds [{H_1:.1f}, {H_2:.1}]" + ) + elif H > H_2: + raise ValueError( + f"Stiffener width of {H:.1f} required for min weight (t_s={t_s:0.3f}), but is outside bounds [{H_1:.1f}, {H_2:.1}]" + ) + + return (t_s, d, H) diff --git a/tests/test_fuselage.py b/tests/test_fuselage.py index de36b21..df9f288 100644 --- a/tests/test_fuselage.py +++ b/tests/test_fuselage.py @@ -3,6 +3,7 @@ import pytest from hyperstruct import Material +from hyperstruct.fuselage import Bulkhead from hyperstruct.fuselage import Cover @@ -10,16 +11,17 @@ def aluminum() -> Material: """Some basic aluminum.""" material = Material( + rho=0.1, E=10.5e6, E_c=10.6e6, nu=0.33, - F_tu=64, - F_ty=42.1, - F_cy=48, - F_su=41, - F_bru=104, - F_bry=89, - F_en=20, + F_tu=64e3, + F_ty=42.1e3, + F_cy=48.3e3, + F_su=41.0e3, + F_bru=10.04e3, + F_bry=89.0e3, + F_en=20.0e3, db_r=116, ) return material @@ -34,6 +36,25 @@ def unmilled_cover(aluminum: Material) -> Cover: return component +@pytest.fixture +def bulkhead(aluminum: Material) -> Bulkhead: + """Build a Bulkhead component.""" + component = Bulkhead( + material=aluminum, + p_1=30.0, + p_2=6.0, + L=34.0, + K_r=0.53, + duf=1.5, + t_w=0.027, + t_l=0.080, + d=2.0, + t_s=0.090, + H=1.5, + ) + return component + + def test_unmilled_shear_and_net(unmilled_cover: Cover) -> None: """Test an unmilled cover.""" t_c = unmilled_cover.field_thickness_block_shear() @@ -60,3 +81,12 @@ def test_unmilled_acoustic(unmilled_cover: Cover) -> None: t_l, t_c = unmilled_cover.acoustic_fatigue() assert isinstance(t_l, float) assert isinstance(t_c, float) + + +def test_bulkhead_stiffener_spacing(bulkhead: Bulkhead) -> None: + """Test a pressure bulkhead.""" + print(bulkhead.allowable_tensile_stress(0.53)) + print(bulkhead.web_thickness(2.0)) + t_s, d, H = bulkhead.stiffener_spacing() + assert t_s >= 0.025 + assert t_s < 0.500 From 332d54241c2fe8ec8e697a43d07d109b2d442a42 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Fri, 3 Jan 2025 18:13:56 -0500 Subject: [PATCH 11/13] Added scipy as a new dependency. This required dropping support for Python <3.10. --- noxfile.py | 2 +- poetry.lock | 111 ++++++++++++++++++++++++------------------------- pyproject.toml | 3 +- 3 files changed, 57 insertions(+), 59 deletions(-) diff --git a/noxfile.py b/noxfile.py index ff8cea3..7e19ecb 100644 --- a/noxfile.py +++ b/noxfile.py @@ -24,7 +24,7 @@ package = "hyperstruct" -python_versions = ["3.11", "3.10", "3.9"] +python_versions = ["3.12", "3.11", "3.10"] nox.needs_version = ">= 2021.6.6" nox.options.sessions = ( "pre-commit", diff --git a/poetry.lock b/poetry.lock index b2e4631..b745762 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -88,8 +88,6 @@ files = [ {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, ] -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] @@ -113,6 +111,7 @@ stevedore = ">=1.20.0" [package.extras] baseline = ["GitPython (>=3.1.30)"] sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] @@ -745,25 +744,6 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] -[[package]] -name = "importlib-metadata" -version = "7.1.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1388,20 +1368,6 @@ files = [ plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] -[[package]] -name = "pyparsing" -version = "3.1.2" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - [[package]] name = "pytest" version = "8.2.0" @@ -1463,7 +1429,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1678,6 +1643,56 @@ pydantic = "*" ruamel-yaml = ">=0.17.21" typing-extensions = ">=4.7.1" +[[package]] +name = "scipy" +version = "1.14.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "setuptools" version = "69.5.1" @@ -1755,7 +1770,6 @@ babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.22" imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.14" @@ -1936,7 +1950,6 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] @@ -1989,7 +2002,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] @@ -2275,22 +2287,7 @@ tests-binary = ["cmake", "cmake", "ninja", "ninja", "pybind11", "pybind11", "sci tests-binary-strict = ["cmake (==3.21.2)", "cmake (==3.25.0)", "ninja (==1.10.2)", "ninja (==1.11.1)", "pybind11 (==2.10.3)", "pybind11 (==2.7.1)", "scikit-build (==0.11.1)", "scikit-build (==0.16.1)"] tests-strict = ["pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "typing (==3.7.4)"] -[[package]] -name = "zipp" -version = "3.18.1" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - [metadata] lock-version = "2.0" -python-versions = ">=3.9, <4.0" -content-hash = "a6ce81d0fd7b11a582ff6c06f6025918b4b926372fb9fb6e32cf62b8f761d1f1" +python-versions = ">=3.10, <4.0" +content-hash = "a9c5dec35d822b87dc639e343a2c0b97c903be51c85b9b246e0cb7ac13536ae3" diff --git a/pyproject.toml b/pyproject.toml index dd26255..3c95076 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,9 +16,10 @@ classifiers = [ Changelog = "https://github.com/czarified/hyperstruct/releases" [tool.poetry.dependencies] -python = ">=3.9, <4.0" +python = ">=3.10, <4.0" click = ">=8.0.1" numpy = "^1.26.4" +scipy = "^1.14.1" [tool.poetry.dev-dependencies] Pygments = ">=2.10.0" From ec4865e371fdc7cc408757ec350759ee67af4661 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Sat, 4 Jan 2025 12:34:44 -0500 Subject: [PATCH 12/13] Cleaning up some typing. Changed the docs-build version. Started simplifying the MinorFrame.forced_crippling. See #71. --- .readthedocs.yml | 2 +- src/hyperstruct/fuselage.py | 84 ++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 66f2a21..1121f1e 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,7 +2,7 @@ version: 2 build: os: ubuntu-20.04 tools: - python: "3.10" + python: "3.12" sphinx: configuration: docs/conf.py formats: all diff --git a/src/hyperstruct/fuselage.py b/src/hyperstruct/fuselage.py index 88dc3f4..3f6ae86 100644 --- a/src/hyperstruct/fuselage.py +++ b/src/hyperstruct/fuselage.py @@ -489,6 +489,45 @@ def acoustic_fatigue(self, b: float) -> float: return float(t_r) + def ring_allowable_stress( + self, RC: float, K: float, t_c: float, frame_e: float, cover_e: float + ) -> Tuple[float, float]: + """Allowable ring stress for the Forced Crippling method. + + Args: + RC: radius of curvature + K: diagonal tension factor + t_c: cover thickness + frame_e: frame Young's Modulus + cover_e: cover Young's Modulus + + Returns: + (F_RG, G): Frame allowable stress, emiprical factor from Bruhn + """ + # Allowable ring frame stress + if RC <= 151.586: + N = ( + (18695 + 75.238 * RC) + * K ** (2 / 3) + * (self.t_r / t_c) ** (1 / 3) + * (frame_e / cover_e) ** (1 / 9) + ) + elif RC > 151.586: + N = ( + 30100 + * K ** (2 / 3) + * (self.t_r / t_c) ** (1 / 3) + * (frame_e / cover_e) ** (1 / 9) + ) + + G = self.material.F_cy * 1088 - 5 / ( + 5.88 * (self.material.F_cy / frame_e + 0.002) ** 0.5 + ) + + F_RG = N * G + + return (F_RG, G) + def forced_crippling( self, L: float, @@ -500,8 +539,8 @@ def forced_crippling( RC: float, f_s: float, f_scr: float, - cover_e: float = None, - long_e: float = None, + cover_e: float | None = None, + long_e: float | None = None, ) -> float: """Thickness from forced crippling. @@ -655,28 +694,7 @@ def forced_crippling( frgmax = frgmax_frg * f_rg - # Allowable ring frame stress - if RC <= 151.586: - N = ( - (18695 + 75.238 * RC) - * K ** (2 / 3) - * (self.t_r / t_c) ** (1 / 3) - * (frame_e / cover_e) ** (1 / 9) - ) - elif RC > 151.586: - N = ( - 30100 - * K ** (2 / 3) - * (self.t_r / t_c) ** (1 / 3) - * (frame_e / cover_e) ** (1 / 9) - ) - - G = self.material.F_cy * 1088 - 5 / ( - 5.88 * (self.material.F_cy / frame_e + 0.002) ** 0.5 - ) - - # Why do we not need the allowable stress, and only the allowable ratio...?? - F_RG = N * G + F_RG, G = self.ring_allowable_stress(RC, K, t_c, frame_e, cover_e) H = A * G # Iterating for cap flange thickness, t_r @@ -699,7 +717,7 @@ def forced_crippling( err = t_r2 - self.t_r self.t_r = t_r2 - return t_r2 + return float(t_r2) @dataclass @@ -752,7 +770,7 @@ def i_xx(self) -> float: @property def rho(self) -> float: """Radius of gyration.""" - return np.sqrt(self.i_xx / self.area) + return float(np.sqrt(self.i_xx / self.area)) @property def area_effective(self) -> float: @@ -841,6 +859,12 @@ def bending_strength( return A_l + def forced_cripping(self) -> None: + """Not implmented yet. Will be the same as MinorFrames.""" + raise NotImplementedError( + "This will follow similar procedures at the MinorFrame." + ) + @dataclass class Bulkhead(Component): @@ -909,7 +933,7 @@ class Bulkhead(Component): (An I-beam cap geometry.) """ - def allowable_tensile_stress(self, K_r) -> float: + def allowable_tensile_stress(self, K_r: float) -> float: """The design allowable tensile stress. Returns: @@ -928,7 +952,7 @@ def max_bending_moment(self) -> float: + self.p_1 * self.L**2 * (k - k**3) / 6 ) - return M_max + return float(M_max) def stiffener_area(self, t_s: float, H: float) -> float: """Area of stiffener, including effective web. @@ -950,7 +974,7 @@ def stiffener_inertia(self, t_s: float, H: float) -> float: """ return 14 / 3 * t_s * H**3 - def web_thickness(self, d: float) -> tuple: + def web_thickness(self, d: float) -> Tuple[float, float]: """Evaluate web thickness. Web sizing based on combined bending and diaphragm action between @@ -989,7 +1013,7 @@ def stiffener_spacing( H_1: float = 1.0, H_2: float = 5.0, t_s1: float = 0.025, - ) -> tuple: + ) -> Tuple[float, float, float]: """Stiffener spacing optimization routine. Stiffener spacing search is initiated at minimum spacing and From f5c9441d01116f9c79ce409d5be157f0d312d1d9 Mon Sep 17 00:00:00 2001 From: Benjamin Crews Date: Sat, 4 Jan 2025 12:48:19 -0500 Subject: [PATCH 13/13] Trying to update the CI for Python 3.12. Dropping CI runs on 3.9 --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acfee63..d931c0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Upgrade pip run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fc5fd2f..ce47c01 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,19 +12,19 @@ jobs: fail-fast: false matrix: include: - - { python: "3.11", os: "ubuntu-latest", session: "pre-commit" } - - { python: "3.11", os: "ubuntu-latest", session: "safety" } + - { python: "3.12", os: "ubuntu-latest", session: "pre-commit" } + - { python: "3.12", os: "ubuntu-latest", session: "safety" } + - { python: "3.12", os: "ubuntu-latest", session: "mypy" } - { python: "3.11", os: "ubuntu-latest", session: "mypy" } - { python: "3.10", os: "ubuntu-latest", session: "mypy" } - - { python: "3.9", os: "ubuntu-latest", session: "mypy" } + - { python: "3.12", os: "ubuntu-latest", session: "tests" } - { python: "3.11", os: "ubuntu-latest", session: "tests" } - { python: "3.10", os: "ubuntu-latest", session: "tests" } - - { python: "3.9", os: "ubuntu-latest", session: "tests" } - - { python: "3.11", os: "windows-latest", session: "tests" } - - { python: "3.11", os: "macos-latest", session: "tests" } - - { python: "3.11", os: "ubuntu-latest", session: "typeguard" } - - { python: "3.11", os: "ubuntu-latest", session: "xdoctest" } - - { python: "3.11", os: "ubuntu-latest", session: "docs-build" } + - { python: "3.12", os: "windows-latest", session: "tests" } + - { python: "3.12", os: "macos-latest", session: "tests" } + - { python: "3.12", os: "ubuntu-latest", session: "typeguard" } + - { python: "3.12", os: "ubuntu-latest", session: "xdoctest" } + - { python: "3.12", os: "ubuntu-latest", session: "docs-build" } env: NOXSESSION: ${{ matrix.session }} @@ -120,7 +120,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Upgrade pip run: |