Coverage for src / beamme / four_c / material.py: 83%

99 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-21 12:57 +0000

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2018-2025 BeamMe Authors 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy 

6# of this software and associated documentation files (the "Software"), to deal 

7# in the Software without restriction, including without limitation the rights 

8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 

9# copies of the Software, and to permit persons to whom the Software is 

10# furnished to do so, subject to the following conditions: 

11# 

12# The above copyright notice and this permission notice shall be included in 

13# all copies or substantial portions of the Software. 

14# 

15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 

21# THE SOFTWARE. 

22"""This file implements materials for 4C beams and solids.""" 

23 

24import numpy as _np 

25 

26from beamme.core.material import MaterialBeamBase as _MaterialBeamBase 

27from beamme.core.material import MaterialSolidBase as _MaterialSolidBase 

28 

29 

30class MaterialReissner(_MaterialBeamBase): 

31 """Holds material definition for Reissner beams.""" 

32 

33 def __init__( 

34 self, 

35 shear_correction=1.0, 

36 *, 

37 by_modes=False, 

38 scale_axial_rigidity=1.0, 

39 scale_shear_rigidity=1.0, 

40 scale_torsional_rigidity=1.0, 

41 scale_bending_rigidity=1.0, 

42 **kwargs, 

43 ): 

44 if by_modes: 

45 mat_string = "MAT_BeamReissnerElastHyper_ByModes" 

46 else: 

47 mat_string = "MAT_BeamReissnerElastHyper" 

48 

49 super().__init__(material_string=mat_string, **kwargs) 

50 

51 # Shear factor for Reissner beam. 

52 self.shear_correction = shear_correction 

53 

54 self.by_modes = by_modes 

55 

56 # Scaling factors to influence a single stiffness independently 

57 self.scale_axial_rigidity = scale_axial_rigidity 

58 self.scale_shear_rigidity = scale_shear_rigidity 

59 self.scale_torsional_rigidity = scale_torsional_rigidity 

60 self.scale_bending_rigidity = scale_bending_rigidity 

61 

62 if not by_modes and not all( 

63 _np.isclose(x, 1.0) 

64 for x in ( 

65 scale_axial_rigidity, 

66 scale_shear_rigidity, 

67 scale_torsional_rigidity, 

68 scale_bending_rigidity, 

69 ) 

70 ): 

71 raise ValueError( 

72 "Scaling factors are only supported for MAT_BeamReissnerElastHyper_ByModes" 

73 ) 

74 

75 def dump_to_list(self): 

76 """Return a list with the (single) item representing this material.""" 

77 

78 if self.radius is None or self.youngs_modulus is None: 

79 raise ValueError( 

80 "Radius and Young's modulus must be provided for beam materials." 

81 ) 

82 

83 if ( 

84 self.area is None 

85 and self.mom2 is None 

86 and self.mom3 is None 

87 and self.polar is None 

88 ): 

89 area, mom2, mom3, polar = self.calc_area_stiffness() 

90 elif ( 

91 self.area is not None 

92 and self.mom2 is not None 

93 and self.mom3 is not None 

94 and self.polar is not None 

95 ): 

96 area = self.area 

97 mom2 = self.mom2 

98 mom3 = self.mom3 

99 polar = self.polar 

100 else: 

101 raise ValueError( 

102 "Either all relevant material parameters are set " 

103 "by the user, or a circular cross-section will be assumed. " 

104 "A combination is not possible" 

105 ) 

106 

107 if self.by_modes: 

108 shear_modulus = self.youngs_modulus / (2.0 * (1.0 + self.nu)) 

109 

110 data = { 

111 "EA": (self.youngs_modulus * area) * self.scale_axial_rigidity, 

112 "GA2": (shear_modulus * area * self.shear_correction) 

113 * self.scale_shear_rigidity, 

114 "GA3": (shear_modulus * area * self.shear_correction) 

115 * self.scale_shear_rigidity, 

116 "GI_T": (shear_modulus * polar) * self.scale_torsional_rigidity, 

117 "EI2": (self.youngs_modulus * mom2) * self.scale_bending_rigidity, 

118 "EI3": (self.youngs_modulus * mom3) * self.scale_bending_rigidity, 

119 "RhoA": self.density * area, 

120 "MASSMOMINPOL": self.density * (mom2 + mom3), 

121 "MASSMOMIN2": self.density * mom2, 

122 "MASSMOMIN3": self.density * mom3, 

123 } 

124 

125 else: 

126 data = { 

127 "YOUNG": self.youngs_modulus, 

128 "POISSONRATIO": self.nu, 

129 "DENS": self.density, 

130 "CROSSAREA": area, 

131 "SHEARCORR": self.shear_correction, 

132 "MOMINPOL": polar, 

133 "MOMIN2": mom2, 

134 "MOMIN3": mom3, 

135 } 

136 

137 if self.interaction_radius is not None: 

138 data["INTERACTIONRADIUS"] = self.interaction_radius 

139 

140 return {"MAT": self.i_global + 1, self.material_string: data} 

141 

142 

143class MaterialReissnerElastoplastic(MaterialReissner): 

144 """Holds elasto-plastic material definition for Reissner beams.""" 

145 

146 def __init__( 

147 self, 

148 *, 

149 yield_moment=None, 

150 isohardening_modulus_moment=None, 

151 torsion_plasticity=False, 

152 **kwargs, 

153 ): 

154 super().__init__(**kwargs) 

155 self.material_string = "MAT_BeamReissnerElastPlastic" 

156 

157 if yield_moment is None or isohardening_modulus_moment is None: 

158 raise ValueError( 

159 "The yield moment and the isohardening modulus for moments must be specified " 

160 "for plasticity." 

161 ) 

162 

163 self.yield_moment = yield_moment 

164 self.isohardening_modulus_moment = isohardening_modulus_moment 

165 self.torsion_plasticity = torsion_plasticity 

166 

167 def dump_to_list(self): 

168 """Return a list with the (single) item representing this material.""" 

169 super_list = super().dump_to_list() 

170 mat_dict = super_list[self.material_string] 

171 mat_dict["YIELDM"] = self.yield_moment 

172 mat_dict["ISOHARDM"] = self.isohardening_modulus_moment 

173 mat_dict["TORSIONPLAST"] = self.torsion_plasticity 

174 return super_list 

175 

176 

177class MaterialKirchhoff(_MaterialBeamBase): 

178 """Holds material definition for Kirchhoff beams.""" 

179 

180 def __init__(self, is_fad=False, **kwargs): 

181 super().__init__(material_string="MAT_BeamKirchhoffElastHyper", **kwargs) 

182 self.is_fad = is_fad 

183 

184 def dump_to_list(self): 

185 """Return a list with the (single) item representing this material.""" 

186 

187 if self.radius is None or self.youngs_modulus is None: 

188 raise ValueError( 

189 "Radius and Young's modulus must be provided for beam materials." 

190 ) 

191 

192 if ( 

193 self.area is None 

194 and self.mom2 is None 

195 and self.mom3 is None 

196 and self.polar is None 

197 ): 

198 area, mom2, mom3, polar = self.calc_area_stiffness() 

199 elif ( 

200 self.area is not None 

201 and self.mom2 is not None 

202 and self.mom3 is not None 

203 and self.polar is not None 

204 ): 

205 area = self.area 

206 mom2 = self.mom2 

207 mom3 = self.mom3 

208 polar = self.polar 

209 else: 

210 raise ValueError( 

211 "Either all relevant material parameters are set " 

212 "by the user, or a circular cross-section will be assumed. " 

213 "A combination is not possible" 

214 ) 

215 data = { 

216 "YOUNG": self.youngs_modulus, 

217 "SHEARMOD": self.youngs_modulus / (2.0 * (1.0 + self.nu)), 

218 "DENS": self.density, 

219 "CROSSAREA": area, 

220 "MOMINPOL": polar, 

221 "MOMIN2": mom2, 

222 "MOMIN3": mom3, 

223 "FAD": self.is_fad, 

224 } 

225 if self.interaction_radius is not None: 

226 data["INTERACTIONRADIUS"] = self.interaction_radius 

227 return {"MAT": self.i_global + 1, self.material_string: data} 

228 

229 

230class MaterialEulerBernoulli(_MaterialBeamBase): 

231 """Holds material definition for Euler Bernoulli beams.""" 

232 

233 def __init__(self, **kwargs): 

234 super().__init__( 

235 material_string="MAT_BeamKirchhoffTorsionFreeElastHyper", **kwargs 

236 ) 

237 

238 def dump_to_list(self): 

239 """Return a list with the (single) item representing this material.""" 

240 

241 if self.radius is None or self.youngs_modulus is None: 

242 raise ValueError( 

243 "Radius and Young's modulus must be provided for beam materials." 

244 ) 

245 

246 area, mom2, _, _ = self.calc_area_stiffness() 

247 if self.area is None and self.mom2 is None: 

248 area, mom2, _, _ = self.calc_area_stiffness() 

249 elif self.area is not None and self.mom2 is not None: 

250 area = self.area 

251 mom2 = self.mom2 

252 else: 

253 raise ValueError( 

254 "Either all relevant material parameters are set " 

255 "by the user, or a circular cross-section will be assumed. " 

256 "A combination is not possible" 

257 ) 

258 data = { 

259 "YOUNG": self.youngs_modulus, 

260 "DENS": self.density, 

261 "CROSSAREA": area, 

262 "MOMIN": mom2, 

263 } 

264 return {"MAT": self.i_global + 1, self.material_string: data} 

265 

266 

267class MaterialSolid(_MaterialSolidBase): 

268 """Base class for a material for solids.""" 

269 

270 def __init__(self, material_string=None, **kwargs): 

271 """Set the material values for a solid.""" 

272 self.material_string = material_string 

273 super().__init__(**kwargs) 

274 

275 def dump_to_list(self): 

276 """Return a list with the (single) item representing this material.""" 

277 

278 return {"MAT": self.i_global + 1, self.material_string: self.data} 

279 

280 

281class MaterialStVenantKirchhoff(MaterialSolid): 

282 """Holds material definition for StVenant Kirchhoff solids.""" 

283 

284 def __init__(self, youngs_modulus=None, nu=None, density=None): 

285 if youngs_modulus is None or nu is None: 

286 raise ValueError( 

287 "Young's modulus and Poisson's ratio must be provided " 

288 "for StVenant Kirchhoff solid materials." 

289 ) 

290 data = {"YOUNG": youngs_modulus, "NUE": nu} 

291 if density is not None: 

292 data["DENS"] = density 

293 super().__init__( 

294 material_string="MAT_Struct_StVenantKirchhoff", 

295 data=data, 

296 )