Coverage for src/beamme/four_c/header_functions.py: 90%

89 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-28 15:20 +0000

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2018-2026 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 module defines functions that can be used to add header information to an input 

23file.""" 

24 

25from typing import Any as _Any 

26 

27from beamme.core.conf import bme as _bme 

28from beamme.four_c.input_file import InputFile as _InputFile 

29 

30 

31def _get_segmentation_strategy(segmentation): 

32 """Get the 4C string for a geometry pair strategy.""" 

33 if segmentation: 

34 return "segmentation" 

35 else: 

36 return "gauss_point_projection_without_boundary_segmentation" 

37 

38 

39def set_runtime_output( 

40 input_file, 

41 *, 

42 output_solid=True, 

43 output_stress_strain=False, 

44 btsvmt_output=True, 

45 btss_output=True, 

46 output_triad=True, 

47 every_iteration=False, 

48 absolute_beam_positions=True, 

49 element_owner=True, 

50 element_gid=True, 

51 element_mat_id=True, 

52 output_energy=False, 

53 output_strains=True, 

54): 

55 """Set the basic runtime output options. 

56 

57 Args 

58 ---- 

59 input_file: 

60 Input file that the options will be added to. 

61 output_solid: bool 

62 If the solid output should be written at runtime. 

63 output_stress_strain: bool 

64 If stress and strain output should be written for the solid. 

65 btsvmt_output: bool 

66 If the output for btsvmt should be written. 

67 btss_output: bool 

68 If the output for beam-to-surface coupling should be written. 

69 output_triad: bool 

70 If the triads along the beam should be written. 

71 every_iteration: int 

72 If output at every Newton iteration should be written. 

73 absolute_beam_positions: bool 

74 If the beams should be written at the current position or always at 

75 the reference position. 

76 element_owner: bool 

77 If the owing rank of each element should be output (currently 

78 only affects the solid elements in 4C, beam element owners are 

79 written by default). 

80 element_gid: bool 

81 If the 4C internal GID of each element should be output. 

82 element_mat_id: bool 

83 If the 4C internal material ID of each element should be output. 

84 output_energy: bool 

85 If the energy output from 4C should be activated. 

86 output_strains: bool 

87 If the strains in the Gauss points should be output. 

88 """ 

89 # Set the basic runtime output options. 

90 input_file.add( 

91 { 

92 "IO/RUNTIME VTK OUTPUT": { 

93 "OUTPUT_DATA_FORMAT": "binary", 

94 "INTERVAL_STEPS": 1, 

95 "EVERY_ITERATION": every_iteration, 

96 } 

97 } 

98 ) 

99 

100 # Set the structure runtime output options 

101 input_file.add( 

102 { 

103 "IO/RUNTIME VTK OUTPUT/STRUCTURE": { 

104 "OUTPUT_STRUCTURE": output_solid, 

105 "DISPLACEMENT": True, 

106 "STRESS_STRAIN": output_stress_strain, 

107 "ELEMENT_OWNER": element_owner, 

108 "ELEMENT_GID": element_gid, 

109 "ELEMENT_MAT_ID": element_mat_id, 

110 } 

111 } 

112 ) 

113 

114 # Set the beam runtime output options 

115 input_file.add( 

116 { 

117 "IO/RUNTIME VTK OUTPUT/BEAMS": { 

118 "OUTPUT_BEAMS": True, 

119 "DISPLACEMENT": True, 

120 "USE_ABSOLUTE_POSITIONS": absolute_beam_positions, 

121 "TRIAD_VISUALIZATIONPOINT": output_triad, 

122 "STRAINS_GAUSSPOINT": output_strains, 

123 "ELEMENT_GID": element_gid, 

124 } 

125 } 

126 ) 

127 

128 if btsvmt_output: 

129 # Set the beam to solid volume mesh tying runtime output options. 

130 input_file.add( 

131 { 

132 "BEAM INTERACTION/BEAM TO SOLID VOLUME MESHTYING/RUNTIME VTK OUTPUT": { 

133 "WRITE_OUTPUT": True, 

134 "NODAL_FORCES": True, 

135 "MORTAR_LAMBDA_DISCRET": True, 

136 "MORTAR_LAMBDA_CONTINUOUS": True, 

137 "MORTAR_LAMBDA_CONTINUOUS_SEGMENTS": 5, 

138 "SEGMENTATION": True, 

139 "INTEGRATION_POINTS": True, 

140 } 

141 } 

142 ) 

143 

144 if btss_output: 

145 # Set the beam to solid surface coupling runtime output options. 

146 input_file.add( 

147 { 

148 "BEAM INTERACTION/BEAM TO SOLID SURFACE/RUNTIME VTK OUTPUT": { 

149 "WRITE_OUTPUT": True, 

150 "NODAL_FORCES": True, 

151 "MORTAR_LAMBDA_DISCRET": True, 

152 "MORTAR_LAMBDA_CONTINUOUS": True, 

153 "MORTAR_LAMBDA_CONTINUOUS_SEGMENTS": 5, 

154 "SEGMENTATION": True, 

155 "INTEGRATION_POINTS": True, 

156 "AVERAGED_NORMALS": True, 

157 } 

158 } 

159 ) 

160 

161 if output_energy: 

162 input_file["STRUCTURAL DYNAMIC"]["RESEVERYERGY"] = 1 

163 

164 

165def set_beam_to_solid_meshtying( 

166 input_file, 

167 interaction_type, 

168 *, 

169 contact_discretization=None, 

170 segmentation=True, 

171 segmentation_search_points=2, 

172 couple_restart=False, 

173 mortar_shape="none", 

174 n_gauss_points=6, 

175 n_integration_points_circ=None, 

176 penalty_parameter=None, 

177 coupling_type=None, 

178): 

179 """Set the beam to solid meshtying options. 

180 

181 Args 

182 ---- 

183 input_file: 

184 Input file that the options will be added to. 

185 interaction_type: BoundaryCondition 

186 Type of beam-to-solid interaction. 

187 contact_discretization: str 

188 Type of contact (mortar, Gauss point, ...) 

189 segmentation: bool 

190 If segmentation should be used in the numerical integration. 

191 segmentation_search_points: int 

192 Number of search points for segmentation. 

193 couple_restart: bool 

194 If the restart configuration should be used for the coupling 

195 mortar_shape: str 

196 Type of shape function for mortar discretization. 

197 n_gauss_points: int 

198 Number of Gauss points for numerical integration. 

199 n_integration_points_circ: int 

200 Number of integration points along the circumference of the cross 

201 section. 

202 penalty_parameter: float 

203 Penalty parameter for contact enforcement. 

204 coupling_type: str 

205 Type of coupling for beam-to-surface coupling. 

206 """ 

207 # Set the beam contact options. 

208 # check if these keys are already set, otherwise set them 

209 if ( 

210 "BEAM INTERACTION" not in input_file 

211 or input_file["BEAM INTERACTION"].get("REPARTITIONSTRATEGY") != "everydt" 

212 ): 

213 input_file.add({"BEAM INTERACTION": {"REPARTITIONSTRATEGY": "everydt"}}) 

214 

215 # Add the beam to solid volume mesh tying options. 

216 bts_parameters = {} 

217 if interaction_type == _bme.bc.beam_to_solid_volume_meshtying: 

218 bts_section_name = "BEAM INTERACTION/BEAM TO SOLID VOLUME MESHTYING" 

219 elif interaction_type == _bme.bc.beam_to_solid_surface_meshtying: 

220 bts_section_name = "BEAM INTERACTION/BEAM TO SOLID SURFACE MESHTYING" 

221 if coupling_type is not None: 

222 bts_parameters["COUPLING_TYPE"] = coupling_type 

223 else: 

224 raise ValueError( 

225 "Got wrong beam-to-solid mesh tying type. " 

226 f"Got {interaction_type} of type {type(interaction_type)}." 

227 ) 

228 bts_parameters["CONSTRAINT_STRATEGY"] = "penalty" 

229 if penalty_parameter is not None: 

230 bts_parameters["PENALTY_PARAMETER"] = penalty_parameter 

231 bts_parameters["GAUSS_POINTS"] = n_gauss_points 

232 

233 if contact_discretization == "mortar": 

234 bts_parameters["CONTACT_DISCRETIZATION"] = "mortar" 

235 bts_parameters["MORTAR_SHAPE_FUNCTION"] = mortar_shape 

236 segmentation_strategy = _get_segmentation_strategy(segmentation) 

237 elif contact_discretization == "gp": 

238 bts_parameters["CONTACT_DISCRETIZATION"] = "gauss_point_to_segment" 

239 segmentation_strategy = _get_segmentation_strategy(segmentation) 

240 elif contact_discretization == "circ": 

241 bts_parameters["CONTACT_DISCRETIZATION"] = "gauss_point_cross_section" 

242 bts_parameters["INTEGRATION_POINTS_CIRCUMFERENCE"] = n_integration_points_circ 

243 segmentation_strategy = "gauss_point_projection_cross_section" 

244 else: 

245 raise ValueError( 

246 f'Wrong contact_discretization "{contact_discretization}" given!' 

247 ) 

248 

249 bts_parameters["GEOMETRY_PAIR_STRATEGY"] = segmentation_strategy 

250 bts_parameters["GEOMETRY_PAIR_SEGMENTATION_SEARCH_POINTS"] = ( 

251 segmentation_search_points 

252 ) 

253 if interaction_type == _bme.bc.beam_to_solid_volume_meshtying: 

254 bts_parameters["COUPLE_RESTART_STATE"] = couple_restart 

255 

256 input_file.add({bts_section_name: bts_parameters}) 

257 

258 

259def set_header_static( 

260 input_file: _InputFile, 

261 *, 

262 time_step: float | None = None, 

263 n_steps: int | None = None, 

264 total_time: float | None = None, 

265 max_iter: int = 20, 

266 tol_residuum: float = 1e-8, 

267 tol_increment: float = 1e-10, 

268 restart_every: int = 1, 

269 load_lin: bool = False, 

270 write_bin: bool = False, 

271 write_stress: str = "no", 

272 write_strain: str = "no", 

273 predictor: str = "TangDis", 

274 prestress: str = "None", 

275 prestress_time: float = 0, 

276 create_nox_file: bool = True, 

277): 

278 """Set the default parameters for a static structure analysis. 

279 

280 At least two of the three time stepping keyword arguments ["time_step", 

281 "n_steps", "total_time"] have to be set. 

282 

283 Args: 

284 input_file: 

285 Input file that the options will be added to. 

286 time_step: 

287 Time increment per step. 

288 n_steps: 

289 Number of time steps. 

290 total_time: 

291 Total time of simulation 

292 max_iter: 

293 Maximal number of Newton iterations. 

294 tol_residuum: 

295 Tolerance for the convergence of the residuum. 

296 tol_increment: 

297 Tolerance for the convergence of the displacement increment. 

298 load_lin: 

299 If the load_lin option should be set. 

300 write_bin: 

301 If binary output should be written. 

302 restart_every: 

303 Frequency for writing restart output. 

304 write_stress: 

305 If and which stress output to write 

306 write_strain: 

307 If and which strain output to write 

308 predictor: 

309 Type of predictor to be used 

310 prestress: 

311 Type of prestressing strategy to be used 

312 prestress_time: 

313 Prestress Time 

314 create_nox_file: 

315 If the nonlinear solver parameters should be set via a NOX xml file or 

316 directly in the input file. 

317 """ 

318 input_file_parameters: dict[str, _Any] = {} 

319 

320 # Set the parameters for a static analysis. 

321 input_file_parameters["PROBLEM TYPE"] = {"PROBLEMTYPE": "Structure"} 

322 input_file_parameters["IO"] = { 

323 "OUTPUT_BIN": write_bin, 

324 "STRUCT_DISP": False, 

325 "STRUCT_STRESS": write_stress, 

326 "STRUCT_STRAIN": write_strain, 

327 "VERBOSITY": "Standard", 

328 } 

329 

330 # Set the time step parameters 

331 given_time_arguments = sum( 

332 1 for arg in (time_step, n_steps, total_time) if arg is not None 

333 ) 

334 if given_time_arguments < 2: 

335 raise ValueError( 

336 'At least two of the following arguments "time_step", "n_steps" or ' 

337 '"total_time" are required' 

338 ) 

339 elif time_step is None and total_time is not None and n_steps is not None: 

340 time_step = total_time / n_steps 

341 elif n_steps is None and total_time is not None and time_step is not None: 

342 n_steps = round(total_time / time_step) 

343 elif total_time is None and time_step is not None and n_steps is not None: 

344 total_time = time_step * n_steps 

345 

346 input_file_parameters["STRUCTURAL DYNAMIC"] = { 

347 "LINEAR_SOLVER": 1, 

348 "INT_STRATEGY": "Standard", 

349 "DYNAMICTYPE": "Statics", 

350 "PREDICT": predictor, 

351 "PRESTRESS": prestress, 

352 "PRESTRESSTIME": prestress_time, 

353 "TIMESTEP": time_step, 

354 "NUMSTEP": n_steps, 

355 "MAXTIME": total_time, 

356 "LOADLIN": load_lin, 

357 "RESTARTEVERY": restart_every, 

358 } 

359 input_file_parameters["SOLVER 1"] = { 

360 "NAME": "Structure_Solver", 

361 "SOLVER": "Superlu", 

362 } 

363 

364 # Set the solver parameters. 

365 if create_nox_file: 

366 # Set the contents of the NOX xml file. 

367 nox_xml_contents = f""" 

368 <ParameterList name="Status Test"> 

369 <!-- Outer Status Test: This test is an OR combination of the structural convergence and the maximum number of iterations --> 

370 <ParameterList name="Outer Status Test"> 

371 <Parameter name="Test Type" type="string" value="Combo"/> 

372 <Parameter name="Combo Type" type="string" value="OR" /> 

373 <!-- Structural convergence is an AND combination of the residuum and step update --> 

374 <ParameterList name="Test 0"> 

375 <Parameter name="Test Type" type="string" value="Combo" /> 

376 <Parameter name="Combo Type" type="string" value="AND" /> 

377 <!-- BEGIN: Combo AND - Test 0: "NormF" --> 

378 <ParameterList name="Test 0"> 

379 <Parameter name="Test Type" type="string" value="NormF" /> 

380 <!-- NormF - Quantity 0: Check the right-hand-side norm of the structural quantities --> 

381 <ParameterList name="Quantity 0"> 

382 <Parameter name="Quantity Type" type="string" value="Structure" /> 

383 <Parameter name="Tolerance Type" type="string" value="Absolute" /> 

384 <Parameter name="Tolerance" type="double" value="{tol_residuum}" /> 

385 <Parameter name="Norm Type" type="string" value="Two Norm" /> 

386 <Parameter name="Scale Type" type="string" value="Scaled" /> 

387 </ParameterList> 

388 </ParameterList> 

389 <!-- END: Combo AND - Test 0: "NormF" --> 

390 <!-- BEGIN: Combo AND - Test 1: "NormWRMS" --> 

391 <ParameterList name="Test 1"> 

392 <Parameter name="Test Type" type="string" value="NormUpdate" /> 

393 <!-- NormWRMS - Quantity 0: Check the increment of the structural displacements --> 

394 <ParameterList name="Quantity 0"> 

395 <Parameter name="Quantity Type" type="string" value="Structure" /> 

396 <Parameter name="Tolerance Type" type="string" value="Absolute" /> 

397 <Parameter name="Tolerance" type="double" value="{tol_increment}" /> 

398 <Parameter name="Norm Type" type="string" value="Two Norm" /> 

399 <Parameter name="Scale Type" type="string" value="Scaled" /> 

400 </ParameterList> 

401 </ParameterList> 

402 <!-- END: Combo AND - Test 1: "NormWRMS" --> 

403 </ParameterList> 

404 <!-- END: Combo 0 - Test 0: "Combo" --> 

405 <!-- BEGIN: Combo OR - Test 1: "MaxIters" --> 

406 <ParameterList name="Test 1"> 

407 <Parameter name="Test Type" type="string" value="MaxIters" /> 

408 <Parameter name="Maximum Iterations" type="int" value="{max_iter}" /> 

409 </ParameterList> <!--END: "MaxIters" --> 

410 </ParameterList> 

411 </ParameterList> 

412 """ 

413 

414 input_file_parameters["STRUCT NOX/Printing"] = { 

415 "Error": True, 

416 "Inner Iteration": False, 

417 "Details": True, 

418 "Linear Solver Details": True, 

419 "Test Details": True, 

420 } 

421 

422 # Set the xml content in the input file. 

423 input_file.nox_xml_contents = nox_xml_contents 

424 

425 else: 

426 input_file_parameters["STRUCTURAL DYNAMIC"]["MAXITER"] = max_iter 

427 input_file_parameters["STRUCTURAL DYNAMIC"]["TOLRES"] = tol_residuum 

428 input_file_parameters["STRUCTURAL DYNAMIC"]["TOLDISP"] = tol_increment 

429 

430 input_file.add(input_file_parameters) 

431 

432 

433def set_beam_interaction_section( 

434 input_file: _InputFile, *, repartition_strategy: str = "everydt" 

435): 

436 """Set beam interaction section in input file. 

437 

438 Args 

439 ---- 

440 input_file: 

441 Input file that the options will be added to. 

442 repartition_strategy: 

443 Type of employed repartitioning strategy 

444 Options: "adaptive" or "everydt" 

445 search_strategy: 

446 Type of search strategy used for finding coupling pairs. 

447 """ 

448 input_file.add({"BEAM INTERACTION": {"REPARTITIONSTRATEGY": repartition_strategy}}) 

449 

450 

451def set_beam_contact_runtime_output( 

452 input_file: _InputFile, *, every_iteration: bool = False 

453): 

454 """Output the beam-to-beam contact forces and gaps with runtime output. 

455 

456 input_file: 

457 Input file that the options will be added to. 

458 every_iteration: 

459 If output at every Newton iteration should be written. 

460 """ 

461 input_file.add( 

462 { 

463 "BEAM INTERACTION/BEAM TO BEAM CONTACT/RUNTIME VTK OUTPUT": { 

464 "VTK_OUTPUT_BEAM_CONTACT": True, 

465 "EVERY_ITERATION": every_iteration, 

466 "INTERVAL_STEPS": 1, 

467 "CONTACT_FORCES": True, 

468 "GAPS": True, 

469 } 

470 } 

471 ) 

472 

473 

474def set_beam_contact_section( 

475 input_file: _InputFile, 

476 *, 

477 btb_penalty: float = 0, 

478 btb_line_penalty: float = 0, 

479 per_shift_angle: list[float] = [70, 80], 

480 par_shift_angle: list[float] = [70, 80], 

481 b_seg_angle: float = 12, 

482 num_integration: int = 5, 

483 penalty_law: str = "LinPosQuadPen", 

484 penalty_regularization_g0: float = 0, 

485 penalty_regularization_f0: float = 0, 

486 penalty_regularization_c0: float = 0, 

487 beam_interaction_parameters: dict = {}, 

488): 

489 """Set default beam contact section, for more and updated details see respective 

490 input file within 4C. 

491 

492 Parameters for `set_beam_interaction_section` are forwarded as keyword arguments. 

493 

494 Args 

495 ---- 

496 input_file: 

497 Input file that the options will be added to. 

498 btb_penalty: double 

499 Penalty parameter for beam-to-beam point contact 

500 btb_line_penalty: 

501 Penalty parameter per unit length for beam-to-beam line contact 

502 per_shift_angle: 

503 Lower and upper shift angle (in degrees) for penalty scaling of large-angle-contact 

504 par_shift_angle: 

505 Lower and upper shift angle (in degrees) for penalty scaling of small-angle-contact 

506 b_seg_angle: 

507 Maximal angle deviation allowed for contact search segmentation 

508 num_integration: 

509 Number of integration intervals per element 

510 penalty_law: 

511 Penalty Law Options: "LinPen", "QuadPen", "LinNegQuadPen", "LinPosQuadPen", "LinPosCubPen", "LinPosDoubleQuadPen", "LinPosExpPen" 

512 penalty_regularization_g0: 

513 First penalty regularization parameter G0 

514 penalty_regularization_f0: 

515 Second penalty regularization parameter F0 

516 penalty_regularization_c0: 

517 Third penalty regularization parameter C0 

518 beam_interaction_parameters: 

519 Keyword parameters for `set_beam_interaction_section` 

520 """ 

521 if len(per_shift_angle) != 2: 

522 raise ValueError( 

523 "Please provide lower and upper value of BEAMS_PERPSHIFTANGLE." 

524 ) 

525 

526 if len(par_shift_angle) != 2: 

527 raise ValueError("Please provide lower and upper value of BEAMS_PARSHIFTANGLE.") 

528 

529 input_file.add( 

530 { 

531 "BEAM INTERACTION/BEAM TO BEAM CONTACT": { 

532 "BEAMS_BTBPENALTYPARAM": btb_penalty, 

533 "BEAMS_BTBLINEPENALTYPARAM": btb_line_penalty, 

534 "BEAMS_SEGCON": True, 

535 "BEAMS_PERPSHIFTANGLE1": per_shift_angle[0], 

536 "BEAMS_PERPSHIFTANGLE2": per_shift_angle[1], 

537 "BEAMS_PARSHIFTANGLE1": par_shift_angle[0], 

538 "BEAMS_PARSHIFTANGLE2": par_shift_angle[1], 

539 "BEAMS_SEGANGLE": b_seg_angle, 

540 "BEAMS_NUMINTEGRATIONINTERVAL": num_integration, 

541 "BEAMS_PENALTYLAW": penalty_law, 

542 "BEAMS_PENREGPARAM_G0": penalty_regularization_g0, 

543 "BEAMS_PENREGPARAM_F0": penalty_regularization_f0, 

544 "BEAMS_PENREGPARAM_C0": penalty_regularization_c0, 

545 } 

546 } 

547 ) 

548 

549 # beam contact needs interaction strategy 

550 set_beam_interaction_section(input_file, **beam_interaction_parameters) 

551 

552 

553def add_result_description( 

554 input_file: _InputFile, 

555 displacements: list, 

556 node_ids: list[int], 

557 *, 

558 tol: float = 1e-10, 

559): 

560 """Add result descriptions for structure problems to the input file. 

561 

562 Args: 

563 input_file: Input file to add the result description to 

564 displacements: Array with the displacements (n_nodes x 3) 

565 node_ids: List with the IDs of the nodes to check 

566 tol: Tolerance 

567 """ 

568 result_descriptions = [] 

569 

570 for i_node, node in enumerate(node_ids): 

571 for i_dir, direction in enumerate(["x", "y", "z"]): 

572 result_descriptions.append( 

573 { 

574 "STRUCTURE": { 

575 "DIS": "structure", 

576 "NODE": node, 

577 "QUANTITY": f"disp{direction}", 

578 "VALUE": displacements[i_node][i_dir], 

579 "TOLERANCE": tol, 

580 }, 

581 } 

582 ) 

583 

584 input_file.add({"RESULT DESCRIPTION": result_descriptions})