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

98 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-08 11:03 +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 the classes that are used to create an input file for 

234C.""" 

24 

25import os as _os 

26from datetime import datetime as _datetime 

27from pathlib import Path as _Path 

28from typing import Any as _Any 

29from typing import Callable as _Callable 

30 

31from fourcipp.fourc_input import FourCInput as _FourCInput 

32from fourcipp.fourc_input import sort_by_section_names as _sort_by_section_names 

33from fourcipp.utils.not_set import NOT_SET as _NOT_SET 

34 

35from beamme.core.conf import INPUT_FILE_HEADER as _INPUT_FILE_HEADER 

36from beamme.core.mesh import Mesh as _Mesh 

37from beamme.core.mesh_representation import MeshRepresentation as _MeshRepresentation 

38from beamme.four_c.element_data import FourCElementData as _FourCElementData 

39from beamme.four_c.input_file_dump_functions import ( 

40 dump_mesh_representation_to_input_file_legacy as _dump_mesh_representation_to_input_file_legacy, 

41) 

42from beamme.four_c.input_file_dump_functions import ( 

43 dump_mesh_to_input_file as _dump_mesh_to_input_file, 

44) 

45from beamme.utils.environment import cubitpy_is_available as _cubitpy_is_available 

46from beamme.utils.environment import get_application_path as _get_application_path 

47from beamme.utils.environment import get_git_data as _get_git_data 

48 

49if _cubitpy_is_available(): 

50 import cubitpy as _cubitpy 

51 

52 

53class InputFile: 

54 """An item that represents a complete 4C input file.""" 

55 

56 def __init__(self) -> None: 

57 """Initialize the input file.""" 

58 

59 self.fourc_input = _FourCInput() 

60 

61 # Register converters to directly convert non-primitive types 

62 # to native Python types via the FourCIPP type converter. 

63 self.fourc_input.type_converter.register_numpy_types() 

64 

65 # Contents of NOX xml file. 

66 self.nox_xml_contents = "" 

67 

68 # Mesh representation for this input file. 

69 self.mesh_representation = _MeshRepresentation() 

70 self.element_type_id_to_data: dict[_Any, _FourCElementData] = {} 

71 

72 def __contains__(self, key: str) -> bool: 

73 """Contains function. 

74 

75 Allows to use the `in` operator. 

76 

77 Args: 

78 key: Section name to check if it is set 

79 

80 Returns: 

81 True if section is set 

82 """ 

83 

84 return key in self.fourc_input 

85 

86 def __setitem__(self, key: str, value: _Any) -> None: 

87 """Set section. 

88 

89 Args: 

90 key: Section name 

91 value: Section entry 

92 """ 

93 

94 self.fourc_input[key] = value 

95 

96 def __getitem__(self, key: str) -> _Any: 

97 """Get section of input file. 

98 

99 Allows to use the indexing operator. 

100 

101 Args: 

102 key: Section name to get 

103 

104 Returns: 

105 The section content 

106 """ 

107 

108 return self.fourc_input[key] 

109 

110 @classmethod 

111 def from_4C_yaml( 

112 cls, input_file_path: str | _Path, header_only: bool = False 

113 ) -> "InputFile": 

114 """Load 4C yaml file. 

115 

116 Args: 

117 input_file_path: Path to yaml file 

118 header_only: Only extract header, i.e., all sections except the legacy ones 

119 

120 Returns: 

121 Initialised object 

122 """ 

123 

124 obj = cls() 

125 obj.fourc_input = _FourCInput.from_4C_yaml(input_file_path, header_only) 

126 return obj 

127 

128 @property 

129 def sections(self) -> dict: 

130 """All the set sections. 

131 

132 Returns: 

133 dict: Set sections 

134 """ 

135 

136 return self.fourc_input.sections 

137 

138 def pop(self, key: str, default_value: _Any = _NOT_SET) -> _Any: 

139 """Pop section of input file. 

140 

141 Args: 

142 key: Section name to pop 

143 

144 Returns: 

145 The section content 

146 """ 

147 

148 return self.fourc_input.pop(key, default_value) 

149 

150 def add(self, object_to_add, **kwargs): 

151 """Add a mesh or a dictionary to the input file. 

152 

153 Args: 

154 object: The object to be added. This can be a mesh or a dictionary. 

155 **kwargs: Additional arguments to be passed to the add method. 

156 """ 

157 

158 if isinstance(object_to_add, _Mesh): 

159 _dump_mesh_to_input_file(self, mesh=object_to_add, **kwargs) 

160 

161 else: 

162 self.fourc_input.combine_sections(object_to_add) 

163 

164 def get_fourcipp_input_with_mesh(self) -> _FourCInput: 

165 """Return a copy of the FourCIPP input file with the contents of the 

166 mesh representation dumped to the legacy sections.""" 

167 fourc_input = self.fourc_input.copy() 

168 _dump_mesh_representation_to_input_file_legacy( 

169 fourc_input, 

170 self.mesh_representation, 

171 self.element_type_id_to_data, 

172 ) 

173 return fourc_input 

174 

175 def dump( 

176 self, 

177 input_file_path: str | _Path, 

178 *, 

179 nox_xml_file: str | None = None, 

180 add_header_default: bool = True, 

181 add_header_information: bool = True, 

182 add_footer_application_script: bool = True, 

183 validate=True, 

184 validate_sections_only: bool = False, 

185 sort_function: _Callable[[dict], dict] | None = _sort_by_section_names, 

186 fourcipp_yaml_style: bool = True, 

187 ): 

188 """Write the input file to disk. 

189 

190 Args: 

191 input_file_path: 

192 Path to the input file that should be created. 

193 nox_xml_file: 

194 If this is a string, the NOX xml file will be created with this 

195 name. If this is None, the NOX xml file will be created with the 

196 name of the input file with the extension ".nox.xml". 

197 add_header_default: 

198 Prepend the default header comment to the input file. 

199 add_header_information: 

200 If the information header should be exported to the input file 

201 Contains creation date, git details of BeamMe, CubitPy and 

202 original application which created the input file if available. 

203 add_footer_application_script: 

204 Append the application script which creates the input files as a 

205 comment at the end of the input file. 

206 validate: 

207 Validate if the created input file is compatible with 4C with FourCIPP. 

208 validate_sections_only: 

209 Validate each section independently. Required sections are no longer 

210 required, but the sections must be valid. 

211 sort_function: 

212 A function which sorts the sections of the input file. 

213 fourcipp_yaml_style: 

214 If True, the input file is written in the fourcipp yaml style. 

215 """ 

216 

217 # Make sure the given input file is a Path instance. 

218 input_file_path = _Path(input_file_path) 

219 

220 # Create a deep copy of the existing input sections - this function should not alter 

221 # the present instance of InputFile 

222 fourc_input = self.fourc_input.copy() 

223 

224 if self.nox_xml_contents: 

225 if nox_xml_file is None: 

226 nox_xml_file = input_file_path.name.split(".")[0] + ".nox.xml" 

227 

228 fourc_input["STRUCT NOX/Status Test"] = {"XML File": nox_xml_file} 

229 

230 # Write the xml file to the disc. 

231 with open(input_file_path.parent / nox_xml_file, "w") as xml_file: 

232 xml_file.write(self.nox_xml_contents) 

233 

234 # Dump the mesh representation. 

235 _dump_mesh_representation_to_input_file_legacy( 

236 fourc_input, 

237 self.mesh_representation, 

238 self.element_type_id_to_data, 

239 ) 

240 

241 # Add information header to the input file 

242 if add_header_information: 

243 fourc_input.combine_sections({"TITLE": self._get_header()}) 

244 

245 fourc_input.dump( 

246 input_file_path=input_file_path, 

247 validate=validate, 

248 validate_sections_only=validate_sections_only, 

249 convert_to_native_types=False, # conversion already happens during add() 

250 sort_function=sort_function, 

251 use_fourcipp_yaml_style=fourcipp_yaml_style, 

252 ) 

253 

254 if add_header_default or add_footer_application_script: 

255 with open(input_file_path, "r") as input_file: 

256 lines = input_file.readlines() 

257 

258 if add_header_default: 

259 lines = ["# " + line + "\n" for line in _INPUT_FILE_HEADER] + lines 

260 

261 if add_footer_application_script: 

262 application_path = _get_application_path() 

263 if application_path is not None: 

264 lines += self._get_application_script(application_path) 

265 

266 with open(input_file_path, "w") as input_file: 

267 input_file.writelines(lines) 

268 

269 def _get_header(self) -> dict: 

270 """Return the information header for the current BeamMe run. 

271 

272 Returns: 

273 A dictionary with the header information. 

274 """ 

275 

276 header: dict = {"BeamMe": {}} 

277 

278 header["BeamMe"]["creation_date"] = _datetime.now().isoformat( 

279 sep=" ", timespec="seconds" 

280 ) 

281 

282 # application which created the input file 

283 application_path = _get_application_path() 

284 if application_path is not None: 

285 header["BeamMe"]["Application"] = {"path": str(application_path)} 

286 

287 application_git_sha, application_git_date = _get_git_data( 

288 application_path.parent 

289 ) 

290 if application_git_sha is not None and application_git_date is not None: 

291 header["BeamMe"]["Application"].update( 

292 { 

293 "git_sha": application_git_sha, 

294 "git_date": application_git_date, 

295 } 

296 ) 

297 

298 # BeamMe information 

299 beamme_git_sha, beamme_git_date = _get_git_data( 

300 _Path(__file__).resolve().parent 

301 ) 

302 if beamme_git_sha is not None and beamme_git_date is not None: 

303 header["BeamMe"]["BeamMe"] = { 

304 "git_SHA": beamme_git_sha, 

305 "git_date": beamme_git_date, 

306 } 

307 

308 # CubitPy information 

309 if _cubitpy_is_available(): 

310 cubitpy_git_sha, cubitpy_git_date = _get_git_data( 

311 _os.path.dirname(_cubitpy.__file__) 

312 ) 

313 

314 if cubitpy_git_sha is not None and cubitpy_git_date is not None: 

315 header["BeamMe"]["CubitPy"] = { 

316 "git_SHA": cubitpy_git_sha, 

317 "git_date": cubitpy_git_date, 

318 } 

319 

320 return header 

321 

322 def _get_application_script(self, application_path: _Path) -> list[str]: 

323 """Get the script that created this input file. 

324 

325 Args: 

326 application_path: Path to the script that created this input file. 

327 Returns: 

328 A list of strings with the script that created this input file. 

329 """ 

330 

331 application_script_lines = [ 

332 "# Application script which created this input file:\n" 

333 ] 

334 

335 with open(application_path) as script_file: 

336 application_script_lines.extend("# " + line for line in script_file) 

337 

338 return application_script_lines 

339 

340 def contains_external_mesh_based_geometry(self) -> bool: 

341 """Check if the input file contains external mesh-based geometry. 

342 

343 Returns: 

344 True if the input file contains external mesh-based geometry, False otherwise. 

345 """ 

346 

347 return False