Coverage for src/beamme/four_c/element_beam.py: 89%
74 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 11:30 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 11:30 +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 beam elements for 4C."""
24import warnings as _warnings
26import numpy as _np
28from beamme.core.conf import bme as _bme
29from beamme.core.element_beam import Beam as _Beam
30from beamme.core.element_beam import generate_beam_class as _generate_beam_class
31from beamme.four_c.four_c_types import BeamType as _BeamType
32from beamme.four_c.input_file_mappings import (
33 INPUT_FILE_MAPPINGS as _INPUT_FILE_MAPPINGS,
34)
35from beamme.four_c.material import MaterialEulerBernoulli as _MaterialEulerBernoulli
36from beamme.four_c.material import MaterialKirchhoff as _MaterialKirchhoff
37from beamme.four_c.material import MaterialReissner as _MaterialReissner
38from beamme.four_c.material import (
39 MaterialReissnerElastoplastic as _MaterialReissnerElastoplastic,
40)
43def dump_four_c_beam_to_list(self) -> dict:
44 """Return the dictionary representing this beam element in 4C.
46 Args:
47 self: The beam element to be dumped.
48 """
50 # Check the material.
51 self._check_material()
53 # Gather the element data for the input file.
54 element_data = type(self).four_c_element_data | self.data
55 element_data["MAT"] = self.material
56 if type(self).four_c_triads:
57 element_data["TRIADS"] = [
58 item
59 for i in _INPUT_FILE_MAPPINGS["n_nodes_to_node_ordering"][len(self.nodes)]
60 for item in self.nodes[i].rotation.get_rotation_vector()
61 ]
63 return {
64 "id": self.i_global + 1,
65 "cell": {
66 "type": _INPUT_FILE_MAPPINGS["n_nodes_to_cell_type"][len(self.nodes)],
67 "connectivity": [
68 self.nodes[i]
69 for i in _INPUT_FILE_MAPPINGS["n_nodes_to_node_ordering"][
70 len(self.nodes)
71 ]
72 ],
73 },
74 "data": element_data,
75 }
78def get_four_c_reissner_beam(n_nodes: int, is_hermite_centerline: bool) -> type[_Beam]:
79 """Return a Simo-Reissner beam for 4C."""
81 four_c_element_data = {
82 "type": _INPUT_FILE_MAPPINGS["beam_types"][_BeamType.reissner]
83 }
84 if is_hermite_centerline:
85 # TODO: Move this to the four_c_element_data.
86 four_c_element_data["HERMITE_CENTERLINE"] = is_hermite_centerline
87 coupling_fix_dict = {"NUMDOF": 9, "ONOFF": [1, 1, 1, 1, 1, 1, 0, 0, 0]}
88 coupling_joint_dict = {"NUMDOF": 9, "ONOFF": [1, 1, 1, 0, 0, 0, 0, 0, 0]}
89 else:
90 coupling_fix_dict = {"NUMDOF": 6, "ONOFF": [1, 1, 1, 1, 1, 1]}
91 coupling_joint_dict = {"NUMDOF": 6, "ONOFF": [1, 1, 1, 0, 0, 0]}
93 return type(
94 "BeamFourCSimoReissner",
95 (_generate_beam_class(n_nodes),),
96 {
97 "four_c_beam_type": _BeamType.reissner,
98 "four_c_triads": True,
99 "four_c_element_data": four_c_element_data,
100 "valid_materials": [_MaterialReissner, _MaterialReissnerElastoplastic],
101 "coupling_fix_dict": coupling_fix_dict,
102 "coupling_joint_dict": coupling_joint_dict,
103 "dump_to_list": dump_four_c_beam_to_list,
104 },
105 )
108def get_four_c_kirchhoff_beam(weak=True, rotvec=True, is_fad=True) -> type[_Beam]:
109 """Return a Kirchhoff-Love beam for 4C."""
111 # Set the parameters for this beam.
112 four_c_element_data = {
113 "type": _INPUT_FILE_MAPPINGS["beam_types"][_BeamType.kirchhoff],
114 "WK": weak,
115 "ROTVEC": 1 if rotvec else 0,
116 }
117 # TODO: Move this to the four_c_element_data.
118 if is_fad:
119 four_c_element_data["USE_FAD"] = True
121 # Show warning when not using rotvec.
122 if not rotvec:
123 _warnings.warn(
124 "Use rotvec=False with caution, especially when applying the boundary conditions "
125 "and couplings."
126 )
128 coupling_fix_dict = {"NUMDOF": 7, "ONOFF": [1, 1, 1, 1, 1, 1, 0]}
129 coupling_joint_dict = {"NUMDOF": 7, "ONOFF": [1, 1, 1, 0, 0, 0, 0]}
131 return type(
132 "BeamFourCKirchhoffLove",
133 (_generate_beam_class(3),),
134 {
135 "four_c_beam_type": _BeamType.kirchhoff,
136 "four_c_triads": True,
137 "four_c_element_data": four_c_element_data,
138 "valid_materials": [_MaterialKirchhoff],
139 "coupling_fix_dict": coupling_fix_dict,
140 "coupling_joint_dict": coupling_joint_dict,
141 "dump_to_list": dump_four_c_beam_to_list,
142 },
143 )
146class BeamFourCEulerBernoulli(_generate_beam_class(2)): # type: ignore[misc]
147 """Represents a Euler Bernoulli beam element."""
149 four_c_beam_type = _BeamType.euler_bernoulli
150 four_c_triads = False
151 four_c_element_data = {
152 "type": _INPUT_FILE_MAPPINGS["beam_types"][_BeamType.euler_bernoulli]
153 }
155 valid_materials = [_MaterialEulerBernoulli]
157 def dump_to_list(self):
158 """Return a list with the (single) item representing this element."""
160 # Check the material.
161 self._check_material()
163 # The two rotations must be the same and the x1 vector must point from
164 # the start point to the end point.
165 if not self.nodes[0].rotation == self.nodes[1].rotation:
166 raise ValueError(
167 "The two nodal rotations in Euler Bernoulli beams must be the same, i.e. the beam "
168 "has to be straight!"
169 )
170 direction = self.nodes[1].coordinates - self.nodes[0].coordinates
171 t1 = self.nodes[0].rotation * [1, 0, 0]
172 if _np.linalg.norm(direction / _np.linalg.norm(direction) - t1) >= _bme.eps_pos:
173 raise ValueError(
174 "The rotations do not match the direction of the Euler Bernoulli beam!"
175 )
177 return dump_four_c_beam_to_list(self)
180def get_four_c_beam(
181 beam_type: _BeamType,
182 *,
183 n_nodes: int | None = None,
184 is_hermite_centerline: bool | None = None,
185 **kwargs,
186) -> type[_Beam]:
187 """Return an object that can be used to create beams with 4C."""
189 def _check_arguments(name, value, expected):
190 """Check that if an argument is given, it has the expected value."""
191 if value is not None and not value == expected:
192 raise ValueError(
193 f"Parameter {name} with the value {value} does not match the expected value {expected}"
194 )
196 match beam_type:
197 case _BeamType.reissner:
198 # Set default values for centerline interpolation
199 if n_nodes is None:
200 n_nodes = 3
201 if is_hermite_centerline is None:
202 is_hermite_centerline = True
203 return get_four_c_reissner_beam(
204 n_nodes=n_nodes, is_hermite_centerline=is_hermite_centerline, **kwargs
205 )
206 case _BeamType.kirchhoff:
207 _check_arguments("n_nodes", n_nodes, 3)
208 _check_arguments("is_hermite_centerline", is_hermite_centerline, True)
209 return get_four_c_kirchhoff_beam(**kwargs)
210 case _BeamType.euler_bernoulli:
211 _check_arguments("n_nodes", n_nodes, 2)
212 _check_arguments("is_hermite_centerline", is_hermite_centerline, True)
213 return BeamFourCEulerBernoulli
214 case _:
215 raise ValueError("Got unexpected beam type.")
218# Provide shortcuts for backwards compatibility
219Beam3rHerm2Line3 = get_four_c_beam(
220 _BeamType.reissner, n_nodes=3, is_hermite_centerline=True
221)
222Beam3rLine2Line2 = get_four_c_beam(
223 _BeamType.reissner, n_nodes=2, is_hermite_centerline=False
224)
225Beam3eb = get_four_c_beam(_BeamType.euler_bernoulli)