Coverage for src / beamme / four_c / header_functions.py: 90%
100 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-06 06:24 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-06 06:24 +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 module defines functions that can be used to add header information to
23an input file."""
25from typing import Any as _Any
26from typing import List as _List
27from typing import Union as _Union
29from beamme.core.conf import bme as _bme
30from beamme.four_c.input_file import InputFile as _InputFile
33def _get_segmentation_strategy(segmentation):
34 """Get the 4C string for a geometry pair strategy."""
35 if segmentation:
36 return "segmentation"
37 else:
38 return "gauss_point_projection_without_boundary_segmentation"
41def set_runtime_output(
42 input_file,
43 *,
44 output_solid=True,
45 output_stress_strain=False,
46 btsvmt_output=True,
47 btss_output=True,
48 output_triad=True,
49 every_iteration=False,
50 absolute_beam_positions=True,
51 element_owner=True,
52 element_gid=True,
53 element_mat_id=True,
54 output_energy=False,
55 output_strains=True,
56):
57 """Set the basic runtime output options.
59 Args
60 ----
61 input_file:
62 Input file that the options will be added to.
63 output_solid: bool
64 If the solid output should be written at runtime.
65 output_stress_strain: bool
66 If stress and strain output should be written for the solid.
67 btsvmt_output: bool
68 If the output for btsvmt should be written.
69 btss_output: bool
70 If the output for beam-to-surface coupling should be written.
71 output_triad: bool
72 If the triads along the beam should be written.
73 every_iteration: int
74 If output at every Newton iteration should be written.
75 absolute_beam_positions: bool
76 If the beams should be written at the current position or always at
77 the reference position.
78 element_owner: bool
79 If the owing rank of each element should be output (currently
80 only affects the solid elements in 4C, beam element owners are
81 written by default).
82 element_gid: bool
83 If the 4C internal GID of each element should be output.
84 element_mat_id: bool
85 If the 4C internal material ID of each element should be output.
86 output_energy: bool
87 If the energy output from 4C should be activated.
88 output_strains: bool
89 If the strains in the Gauss points should be output.
90 """
92 # Set the basic runtime output options.
93 input_file.add(
94 {
95 "IO/RUNTIME VTK OUTPUT": {
96 "OUTPUT_DATA_FORMAT": "binary",
97 "INTERVAL_STEPS": 1,
98 "EVERY_ITERATION": every_iteration,
99 }
100 }
101 )
103 # Set the structure runtime output options
104 input_file.add(
105 {
106 "IO/RUNTIME VTK OUTPUT/STRUCTURE": {
107 "OUTPUT_STRUCTURE": output_solid,
108 "DISPLACEMENT": True,
109 "STRESS_STRAIN": output_stress_strain,
110 "ELEMENT_OWNER": element_owner,
111 "ELEMENT_GID": element_gid,
112 "ELEMENT_MAT_ID": element_mat_id,
113 }
114 }
115 )
117 # Set the beam runtime output options
118 input_file.add(
119 {
120 "IO/RUNTIME VTK OUTPUT/BEAMS": {
121 "OUTPUT_BEAMS": True,
122 "DISPLACEMENT": True,
123 "USE_ABSOLUTE_POSITIONS": absolute_beam_positions,
124 "TRIAD_VISUALIZATIONPOINT": output_triad,
125 "STRAINS_GAUSSPOINT": output_strains,
126 "ELEMENT_GID": element_gid,
127 }
128 }
129 )
131 if btsvmt_output:
132 # Set the beam to solid volume mesh tying runtime output options.
133 input_file.add(
134 {
135 "BEAM INTERACTION/BEAM TO SOLID VOLUME MESHTYING/RUNTIME VTK OUTPUT": {
136 "WRITE_OUTPUT": True,
137 "NODAL_FORCES": True,
138 "MORTAR_LAMBDA_DISCRET": True,
139 "MORTAR_LAMBDA_CONTINUOUS": True,
140 "MORTAR_LAMBDA_CONTINUOUS_SEGMENTS": 5,
141 "SEGMENTATION": True,
142 "INTEGRATION_POINTS": True,
143 }
144 }
145 )
147 if btss_output:
148 # Set the beam to solid surface coupling runtime output options.
149 input_file.add(
150 {
151 "BEAM INTERACTION/BEAM TO SOLID SURFACE/RUNTIME VTK OUTPUT": {
152 "WRITE_OUTPUT": True,
153 "NODAL_FORCES": True,
154 "MORTAR_LAMBDA_DISCRET": True,
155 "MORTAR_LAMBDA_CONTINUOUS": True,
156 "MORTAR_LAMBDA_CONTINUOUS_SEGMENTS": 5,
157 "SEGMENTATION": True,
158 "INTEGRATION_POINTS": True,
159 "AVERAGED_NORMALS": True,
160 }
161 }
162 )
164 if output_energy:
165 input_file["STRUCTURAL DYNAMIC"]["RESEVERYERGY"] = 1
168def set_beam_to_solid_meshtying(
169 input_file,
170 interaction_type,
171 *,
172 contact_discretization=None,
173 segmentation=True,
174 segmentation_search_points=2,
175 couple_restart=False,
176 mortar_shape="none",
177 n_gauss_points=6,
178 n_integration_points_circ=None,
179 penalty_parameter=None,
180 coupling_type=None,
181 binning_parameters: dict = {},
182):
183 """Set the beam to solid meshtying options.
185 Args
186 ----
187 input_file:
188 Input file that the options will be added to.
189 interaction_type: BoundaryCondition
190 Type of beam-to-solid interaction.
191 contact_discretization: str
192 Type of contact (mortar, Gauss point, ...)
193 segmentation: bool
194 If segmentation should be used in the numerical integration.
195 segmentation_search_points: int
196 Number of search points for segmentation.
197 couple_restart: bool
198 If the restart configuration should be used for the coupling
199 mortar_shape: str
200 Type of shape function for mortar discretization.
201 n_gauss_points: int
202 Number of Gauss points for numerical integration.
203 n_integration_points_circ: int
204 Number of integration points along the circumference of the cross
205 section.
206 penalty_parameter: float
207 Penalty parameter for contact enforcement.
208 coupling_type: str
209 Type of coupling for beam-to-surface coupling.
210 binning_parameters:
211 Keyword parameters for the binning section
212 """
214 # Set the beam contact options.
215 # check if these keys are already set, otherwise set them
216 if (
217 "BEAM INTERACTION" not in input_file
218 or input_file["BEAM INTERACTION"].get("REPARTITIONSTRATEGY") != "everydt"
219 ):
220 input_file.add({"BEAM INTERACTION": {"REPARTITIONSTRATEGY": "everydt"}})
222 set_binning_strategy_section(
223 input_file,
224 **binning_parameters,
225 )
227 # Add the beam to solid volume mesh tying options.
228 bts_parameters = {}
229 if interaction_type == _bme.bc.beam_to_solid_volume_meshtying:
230 bts_section_name = "BEAM INTERACTION/BEAM TO SOLID VOLUME MESHTYING"
231 elif interaction_type == _bme.bc.beam_to_solid_surface_meshtying:
232 bts_section_name = "BEAM INTERACTION/BEAM TO SOLID SURFACE MESHTYING"
233 if coupling_type is not None:
234 bts_parameters["COUPLING_TYPE"] = coupling_type
235 else:
236 raise ValueError(
237 "Got wrong beam-to-solid mesh tying type. "
238 f"Got {interaction_type} of type {type(interaction_type)}."
239 )
240 bts_parameters["CONSTRAINT_STRATEGY"] = "penalty"
241 if penalty_parameter is not None:
242 bts_parameters["PENALTY_PARAMETER"] = penalty_parameter
243 bts_parameters["GAUSS_POINTS"] = n_gauss_points
245 if contact_discretization == "mortar":
246 bts_parameters["CONTACT_DISCRETIZATION"] = "mortar"
247 bts_parameters["MORTAR_SHAPE_FUNCTION"] = mortar_shape
248 segmentation_strategy = _get_segmentation_strategy(segmentation)
249 elif contact_discretization == "gp":
250 bts_parameters["CONTACT_DISCRETIZATION"] = "gauss_point_to_segment"
251 segmentation_strategy = _get_segmentation_strategy(segmentation)
252 elif contact_discretization == "circ":
253 bts_parameters["CONTACT_DISCRETIZATION"] = "gauss_point_cross_section"
254 bts_parameters["INTEGRATION_POINTS_CIRCUMFERENCE"] = n_integration_points_circ
255 segmentation_strategy = "gauss_point_projection_cross_section"
256 else:
257 raise ValueError(
258 f'Wrong contact_discretization "{contact_discretization}" given!'
259 )
261 bts_parameters["GEOMETRY_PAIR_STRATEGY"] = segmentation_strategy
262 bts_parameters["GEOMETRY_PAIR_SEGMENTATION_SEARCH_POINTS"] = (
263 segmentation_search_points
264 )
265 if interaction_type == _bme.bc.beam_to_solid_volume_meshtying:
266 bts_parameters["COUPLE_RESTART_STATE"] = couple_restart
268 input_file.add({bts_section_name: bts_parameters})
271def set_header_static(
272 input_file: _InputFile,
273 *,
274 time_step: float | None = None,
275 n_steps: int | None = None,
276 total_time: float | None = None,
277 max_iter: int = 20,
278 tol_residuum: float = 1e-8,
279 tol_increment: float = 1e-10,
280 restart_every: int = 1,
281 load_lin: bool = False,
282 write_bin: bool = False,
283 write_stress: str = "no",
284 write_strain: str = "no",
285 predictor: str = "TangDis",
286 prestress: str = "None",
287 prestress_time: float = 0,
288 create_nox_file: bool = True,
289):
290 """Set the default parameters for a static structure analysis.
292 At least two of the three time stepping keyword arguments ["time_step",
293 "n_steps", "total_time"] have to be set.
295 Args:
296 input_file:
297 Input file that the options will be added to.
298 time_step:
299 Time increment per step.
300 n_steps:
301 Number of time steps.
302 total_time:
303 Total time of simulation
304 max_iter:
305 Maximal number of Newton iterations.
306 tol_residuum:
307 Tolerance for the convergence of the residuum.
308 tol_increment:
309 Tolerance for the convergence of the displacement increment.
310 load_lin:
311 If the load_lin option should be set.
312 write_bin:
313 If binary output should be written.
314 restart_every:
315 Frequency for writing restart output.
316 write_stress:
317 If and which stress output to write
318 write_strain:
319 If and which strain output to write
320 predictor:
321 Type of predictor to be used
322 prestress:
323 Type of prestressing strategy to be used
324 prestress_time:
325 Prestress Time
326 create_nox_file:
327 If the nonlinear solver parameters should be set via a NOX xml file or
328 directly in the input file.
329 """
331 input_file_parameters: dict[str, _Any] = {}
333 # Set the parameters for a static analysis.
334 input_file_parameters["PROBLEM TYPE"] = {"PROBLEMTYPE": "Structure"}
335 input_file_parameters["IO"] = {
336 "OUTPUT_BIN": write_bin,
337 "STRUCT_DISP": False,
338 "STRUCT_STRESS": write_stress,
339 "STRUCT_STRAIN": write_strain,
340 "VERBOSITY": "Standard",
341 }
343 # Set the time step parameters
344 given_time_arguments = sum(
345 1 for arg in (time_step, n_steps, total_time) if arg is not None
346 )
347 if given_time_arguments < 2:
348 raise ValueError(
349 'At least two of the following arguments "time_step", "n_steps" or '
350 '"total_time" are required'
351 )
352 elif time_step is None and total_time is not None and n_steps is not None:
353 time_step = total_time / n_steps
354 elif n_steps is None and total_time is not None and time_step is not None:
355 n_steps = round(total_time / time_step)
356 elif total_time is None and time_step is not None and n_steps is not None:
357 total_time = time_step * n_steps
359 input_file_parameters["STRUCTURAL DYNAMIC"] = {
360 "LINEAR_SOLVER": 1,
361 "INT_STRATEGY": "Standard",
362 "DYNAMICTYPE": "Statics",
363 "PREDICT": predictor,
364 "PRESTRESS": prestress,
365 "PRESTRESSTIME": prestress_time,
366 "TIMESTEP": time_step,
367 "NUMSTEP": n_steps,
368 "MAXTIME": total_time,
369 "LOADLIN": load_lin,
370 "RESTARTEVERY": restart_every,
371 }
372 input_file_parameters["SOLVER 1"] = {
373 "NAME": "Structure_Solver",
374 "SOLVER": "Superlu",
375 }
377 # Set the solver parameters.
378 if create_nox_file:
379 # Set the contents of the NOX xml file.
380 nox_xml_contents = f"""
381 <ParameterList name="Status Test">
382 <!-- Outer Status Test: This test is an OR combination of the structural convergence and the maximum number of iterations -->
383 <ParameterList name="Outer Status Test">
384 <Parameter name="Test Type" type="string" value="Combo"/>
385 <Parameter name="Combo Type" type="string" value="OR" />
386 <!-- Structural convergence is an AND combination of the residuum and step update -->
387 <ParameterList name="Test 0">
388 <Parameter name="Test Type" type="string" value="Combo" />
389 <Parameter name="Combo Type" type="string" value="AND" />
390 <!-- BEGIN: Combo AND - Test 0: "NormF" -->
391 <ParameterList name="Test 0">
392 <Parameter name="Test Type" type="string" value="NormF" />
393 <!-- NormF - Quantity 0: Check the right-hand-side norm of the structural quantities -->
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_residuum}" />
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 0: "NormF" -->
403 <!-- BEGIN: Combo AND - Test 1: "NormWRMS" -->
404 <ParameterList name="Test 1">
405 <Parameter name="Test Type" type="string" value="NormUpdate" />
406 <!-- NormWRMS - Quantity 0: Check the increment of the structural displacements -->
407 <ParameterList name="Quantity 0">
408 <Parameter name="Quantity Type" type="string" value="Structure" />
409 <Parameter name="Tolerance Type" type="string" value="Absolute" />
410 <Parameter name="Tolerance" type="double" value="{tol_increment}" />
411 <Parameter name="Norm Type" type="string" value="Two Norm" />
412 <Parameter name="Scale Type" type="string" value="Scaled" />
413 </ParameterList>
414 </ParameterList>
415 <!-- END: Combo AND - Test 1: "NormWRMS" -->
416 </ParameterList>
417 <!-- END: Combo 0 - Test 0: "Combo" -->
418 <!-- BEGIN: Combo OR - Test 1: "MaxIters" -->
419 <ParameterList name="Test 1">
420 <Parameter name="Test Type" type="string" value="MaxIters" />
421 <Parameter name="Maximum Iterations" type="int" value="{max_iter}" />
422 </ParameterList> <!--END: "MaxIters" -->
423 </ParameterList>
424 </ParameterList>
425 """
427 input_file_parameters["STRUCT NOX/Printing"] = {
428 "Error": True,
429 "Inner Iteration": False,
430 "Details": True,
431 "Linear Solver Details": True,
432 "Test Details": True,
433 }
435 # Set the xml content in the input file.
436 input_file.nox_xml_contents = nox_xml_contents
438 else:
439 input_file_parameters["STRUCTURAL DYNAMIC"]["MAXITER"] = max_iter
440 input_file_parameters["STRUCTURAL DYNAMIC"]["TOLRES"] = tol_residuum
441 input_file_parameters["STRUCTURAL DYNAMIC"]["TOLDISP"] = tol_increment
443 input_file.add(input_file_parameters)
446def set_binning_strategy_section(
447 input_file: _InputFile,
448 binning_bounding_box: _Union[_List[int], None] = None,
449 binning_cutoff_radius: _Union[float, None] = None,
450):
451 """Set binning strategy in section of the input file.
453 Args
454 ----
455 input_file:
456 Input file that the options will be added to.
457 binning_bounding_box:
458 List with the limits of the bounding box.
459 binning_cutoff_radius:
460 Maximal influence radius of pair elements.
461 """
463 if binning_bounding_box is not None and binning_cutoff_radius is not None:
464 binning_bounding_box_string = " ".join(
465 [str(val) for val in binning_bounding_box]
466 )
468 input_file.add(
469 {
470 "BINNING STRATEGY": {
471 "BIN_SIZE_LOWER_BOUND": binning_cutoff_radius,
472 "DOMAINBOUNDINGBOX": binning_bounding_box_string,
473 }
474 }
475 )
476 elif [binning_bounding_box, binning_cutoff_radius].count(None) == 2:
477 return
478 else:
479 raise ValueError(
480 f"The variables binning_bounding_box {binning_bounding_box} and binning_cutoff_radius {binning_cutoff_radius} must both be set."
481 )
484def set_beam_interaction_section(
485 input_file: _InputFile,
486 *,
487 repartition_strategy: str = "everydt",
488 search_strategy: str = "bounding_volume_hierarchy",
489):
490 """Set beam interaction section in input file.
492 Args
493 ----
494 input_file:
495 Input file that the options will be added to.
496 repartition_strategy:
497 Type of employed repartitioning strategy
498 Options: "adaptive" or "everydt"
499 search_strategy:
500 Type of search strategy used for finding coupling pairs.
501 Options: "bruteforce_with_binning", "bounding_volume_hierarchy"
502 """
504 input_file.add(
505 {
506 "BEAM INTERACTION": {
507 "REPARTITIONSTRATEGY": repartition_strategy,
508 "SEARCH_STRATEGY": search_strategy,
509 }
510 }
511 )
514def set_beam_contact_runtime_output(
515 input_file: _InputFile, *, every_iteration: bool = False
516):
517 """Output the beam-to-beam contact forces and gaps with runtime output.
519 input_file:
520 Input file that the options will be added to.
521 every_iteration:
522 If output at every Newton iteration should be written.
523 """
525 input_file.add(
526 {
527 "BEAM INTERACTION/BEAM TO BEAM CONTACT/RUNTIME VTK OUTPUT": {
528 "VTK_OUTPUT_BEAM_CONTACT": True,
529 "EVERY_ITERATION": every_iteration,
530 "INTERVAL_STEPS": 1,
531 "CONTACT_FORCES": True,
532 "GAPS": True,
533 }
534 }
535 )
538def set_beam_contact_section(
539 input_file: _InputFile,
540 *,
541 interaction_strategy: str = "penalty",
542 btb_penalty: float = 0,
543 btb_line_penalty: float = 0,
544 per_shift_angle: list[float] = [70, 80],
545 par_shift_angle: list[float] = [70, 80],
546 b_seg_angle: float = 12,
547 num_integration: int = 5,
548 penalty_law: str = "LinPosQuadPen",
549 penalty_regularization_g0: float = 0,
550 penalty_regularization_f0: float = 0,
551 penalty_regularization_c0: float = 0,
552 binning_parameters: dict = {},
553 beam_interaction_parameters: dict = {},
554):
555 """Set default beam contact section, for more and updated details see
556 respective input file within 4C. Parameters for set_binning_strategy and
557 set_beam_interaction may be forwarded as keyword arguments.
559 Args
560 ----
561 input_file:
562 Input file that the options will be added to.
563 interaction_strategy:
564 Type of employed solving strategy
565 Options: "none", "penalty" or "gmshonly"
566 btb_penalty: double
567 Penalty parameter for beam-to-beam point contact
568 btb_line_penalty:
569 Penalty parameter per unit length for beam-to-beam line contact
570 per_shift_angle:
571 Lower and upper shift angle (in degrees) for penalty scaling of large-angle-contact
572 par_shift_angle:
573 Lower and upper shift angle (in degrees) for penalty scaling of small-angle-contact
574 b_seg_angle:
575 Maximal angle deviation allowed for contact search segmentation
576 num_integration:
577 Number of integration intervals per element
578 penalty_law:
579 Penalty Law Options: "LinPen", "QuadPen", "LinNegQuadPen", "LinPosQuadPen", "LinPosCubPen", "LinPosDoubleQuadPen", "LinPosExpPen"
580 penalty_regularization_g0:
581 First penalty regularization parameter G0
582 penalty_regularization_f0:
583 Second penalty regularization parameter F0
584 penalty_regularization_c0:
585 Third penalty regularization parameter C0
586 binning_parameters:
587 Keyword parameters for the binning section
588 beam_interaction_parameters:
589 Keyword parameters for the beam-contact section
590 """
592 if len(per_shift_angle) != 2:
593 raise ValueError(
594 "Please provide lower and upper value of BEAMS_PERPSHIFTANGLE."
595 )
597 if len(par_shift_angle) != 2:
598 raise ValueError("Please provide lower and upper value of BEAMS_PARSHIFTANGLE.")
600 input_file.add(
601 {
602 "BEAM INTERACTION/BEAM TO BEAM CONTACT": {
603 "BEAMS_BTBPENALTYPARAM": btb_penalty,
604 "BEAMS_BTBLINEPENALTYPARAM": btb_line_penalty,
605 "BEAMS_SEGCON": True,
606 "BEAMS_PERPSHIFTANGLE1": per_shift_angle[0],
607 "BEAMS_PERPSHIFTANGLE2": per_shift_angle[1],
608 "BEAMS_PARSHIFTANGLE1": par_shift_angle[0],
609 "BEAMS_PARSHIFTANGLE2": par_shift_angle[1],
610 "BEAMS_SEGANGLE": b_seg_angle,
611 "BEAMS_NUMINTEGRATIONINTERVAL": num_integration,
612 "BEAMS_PENALTYLAW": penalty_law,
613 "BEAMS_PENREGPARAM_G0": penalty_regularization_g0,
614 "BEAMS_PENREGPARAM_F0": penalty_regularization_f0,
615 "BEAMS_PENREGPARAM_C0": penalty_regularization_c0,
616 }
617 }
618 )
620 # beam contact needs a binning strategy
621 set_binning_strategy_section(input_file, **binning_parameters)
623 # beam contact needs interaction strategy
624 set_beam_interaction_section(input_file, **beam_interaction_parameters)
627def add_result_description(
628 input_file: _InputFile,
629 displacements: _List,
630 node_ids: _List[int],
631 *,
632 tol: float = 1e-10,
633):
634 """Add result descriptions for structure problems to the input file.
636 Args:
637 input_file: Input file to add the result description to
638 displacements: Array with the displacements (n_nodes x 3)
639 node_ids: List with the IDs of the nodes to check
640 tol: Tolerance
641 """
642 result_descriptions = []
644 for i_node, node in enumerate(node_ids):
645 for i_dir, direction in enumerate(["x", "y", "z"]):
646 result_descriptions.append(
647 {
648 "STRUCTURE": {
649 "DIS": "structure",
650 "NODE": node,
651 "QUANTITY": f"disp{direction}",
652 "VALUE": displacements[i_node][i_dir],
653 "TOLERANCE": tol,
654 },
655 }
656 )
658 input_file.add({"RESULT DESCRIPTION": result_descriptions})