Coverage for src / sdynpy / fileio / sdynpy_uff_datasets / sdynpy_uff_dataset_58.py: 10%

163 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-11 16:22 +0000

1# -*- coding: utf-8 -*- 

2""" 

3Functions at Nodal Degrees of Freedom 

4 

5Defines several types of functions from time histories to spectra, spectral 

6densities, and transfer functions. 

7""" 

8""" 

9Copyright 2022 National Technology & Engineering Solutions of Sandia, 

10LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. 

11Government retains certain rights in this software. 

12 

13This program is free software: you can redistribute it and/or modify 

14it under the terms of the GNU General Public License as published by 

15the Free Software Foundation, either version 3 of the License, or 

16(at your option) any later version. 

17 

18This program is distributed in the hope that it will be useful, 

19but WITHOUT ANY WARRANTY; without even the implied warranty of 

20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21GNU General Public License for more details. 

22 

23You should have received a copy of the GNU General Public License 

24along with this program. If not, see <https://www.gnu.org/licenses/>. 

25""" 

26 

27from ..sdynpy_uff import parse_uff_line, parse_uff_lines, write_uff_line 

28from ...core.sdynpy_coordinate import parse_coordinate_string 

29import numpy as np 

30import struct 

31import warnings 

32 

33def is_abscissa_even(abscissa): 

34 abscissa_inc = np.mean(np.diff(abscissa)) 

35 abscissa_start = abscissa[0] 

36 return np.allclose(abscissa, abscissa_start + abscissa_inc * np.arange(abscissa.size)) 

37 

38 

39class Sdynpy_UFF_Dataset_58: 

40 def __init__(self, idline1, idline2, idline3, idline4, idline5, 

41 function_type, function_id, version_number, load_case, 

42 response_entity_name, response_node, response_direction, 

43 reference_entity_name, reference_node, reference_direction, 

44 abscissa_data_type, abscissa_length_exponent, 

45 abscissa_force_exponent, abscissa_temp_exponent, 

46 abscissa_axis_label, abscissa_units_label, 

47 ordinate_num_data_type, ordinate_num_length_exponent, 

48 ordinate_num_force_exponent, ordinate_num_temp_exponent, 

49 ordinate_num_axis_label, ordinate_num_units_label, 

50 ordinate_den_data_type, ordinate_den_length_exponent, 

51 ordinate_den_force_exponent, ordinate_den_temp_exponent, 

52 ordinate_den_axis_label, ordinate_den_units_label, 

53 zaxis_data_type, zaxis_length_exponent, 

54 zaxis_force_exponent, zaxis_temp_exponent, 

55 zaxis_axis_label, zaxis_units_label, zaxis_value, 

56 abscissa, ordinate 

57 ): 

58 self.idline1 = idline1 

59 self.idline2 = idline2 

60 self.idline3 = idline3 

61 self.idline4 = idline4 

62 self.idline5 = idline5 

63 self.function_type = function_type 

64 self.function_id = function_id 

65 self.version_number = version_number 

66 self.load_case = load_case 

67 self.response_entity_name = response_entity_name 

68 self.response_node = response_node 

69 self.response_direction = response_direction 

70 self.reference_entity_name = reference_entity_name 

71 self.reference_node = reference_node 

72 self.reference_direction = reference_direction 

73 self.abscissa_data_type = abscissa_data_type 

74 self.abscissa_length_exponent = abscissa_length_exponent 

75 self.abscissa_force_exponent = abscissa_force_exponent 

76 self.abscissa_temp_exponent = abscissa_temp_exponent 

77 self.abscissa_axis_label = abscissa_axis_label 

78 self.abscissa_units_label = abscissa_units_label 

79 self.ordinate_num_data_type = ordinate_num_data_type 

80 self.ordinate_num_length_exponent = ordinate_num_length_exponent 

81 self.ordinate_num_force_exponent = ordinate_num_force_exponent 

82 self.ordinate_num_temp_exponent = ordinate_num_temp_exponent 

83 self.ordinate_num_axis_label = ordinate_num_axis_label 

84 self.ordinate_num_units_label = ordinate_num_units_label 

85 self.ordinate_den_data_type = ordinate_den_data_type 

86 self.ordinate_den_length_exponent = ordinate_den_length_exponent 

87 self.ordinate_den_force_exponent = ordinate_den_force_exponent 

88 self.ordinate_den_temp_exponent = ordinate_den_temp_exponent 

89 self.ordinate_den_axis_label = ordinate_den_axis_label 

90 self.ordinate_den_units_label = ordinate_den_units_label 

91 self.zaxis_data_type = zaxis_data_type 

92 self.zaxis_length_exponent = zaxis_length_exponent 

93 self.zaxis_force_exponent = zaxis_force_exponent 

94 self.zaxis_temp_exponent = zaxis_temp_exponent 

95 self.zaxis_axis_label = zaxis_axis_label 

96 self.zaxis_units_label = zaxis_units_label 

97 self.zaxis_value = zaxis_value 

98 self.abscissa = abscissa 

99 self.ordinate = ordinate 

100 

101 @property 

102 def dataset_number(self): 

103 return 58 

104 

105 @classmethod 

106 def from_uff_data_array(cls, block_lines, is_binary, byte_ordering, 

107 floating_point_format, num_ascii_lines_following, 

108 num_bytes_following): 

109 """ 

110 Extract function at nodal DOF - data-set 58. 

111 

112 Returns 

113 ------- 

114 ds_58 

115 Object with attributes as the dataset fields and values containing the 

116 data from the universal file in those dataset fields. 

117 """ 

118 if num_ascii_lines_following is None: 

119 num_ascii_lines_following = 11 

120 split_header = [line.decode('utf-8') for line in block_lines[:num_ascii_lines_following]] 

121 # Record 1: Format(80A1) 

122 # Field 1 - ID Line 1 

123 # 

124 # NOTE 

125 # 

126 # ID Line 1 is generally used for the function 

127 # description. 

128 idline1, = parse_uff_line(split_header[0], ['A80']) 

129# Record 2: Format(80A1) 

130# Field 1 - ID Line 2 

131 idline2, = parse_uff_line(split_header[1], ['A80']) 

132# Record 3: Format(80A1) 

133# Field 1 - ID Line 3 

134# 

135# NOTE 

136# 

137# ID Line 3 is generally used to identify when the 

138# function was created. The date is in the form 

139# DD-MMM-YY, and the time is in the form HH:MM:SS, 

140# with a general Format(9A1,1X,8A1). 

141 idline3, = parse_uff_line(split_header[2], ['A80']) 

142# Record 4: Format(80A1) 

143# Field 1 - ID Line 4 

144 idline4, = parse_uff_line(split_header[3], ['A80']) 

145# Record 5: Format(80A1) 

146# Field 1 - ID Line 5 

147 idline5, = parse_uff_line(split_header[4], ['A80']) 

148# Record 6: Format(2(I5,I10),2(1X,10A1,I10,I4)) 

149# DOF Identification 

150# Field 1 - Function Type 

151# 0 - General or Unknown 

152# 1 - Time Response 

153# 2 - Auto Spectrum 

154# 3 - Cross Spectrum 

155# 4 - Frequency Response Function 

156# 5 - Transmissibility 

157# 6 - Coherence 

158# 7 - Auto Correlation 

159# 8 - Cross Correlation 

160# 9 - Power Spectral Density (PSD) 

161# 10 - Energy Spectral Density (ESD) 

162# 11 - Probability Density Function 

163# 12 - Spectrum 

164# 13 - Cumulative Frequency Distribution 

165# 14 - Peaks Valley 

166# 15 - Stress/Cycles 

167# 16 - Strain/Cycles 

168# 17 - Orbit 

169# 18 - Mode Indicator Function 

170# 19 - Force Pattern 

171# 20 - Partial Power 

172# 21 - Partial Coherence 

173# 22 - Eigenvalue 

174# 23 - Eigenvector 

175# 24 - Shock Response Spectrum 

176# 25 - Finite Impulse Response Filter 

177# 26 - Multiple Coherence 

178# 27 - Order Function 

179# 28 - Phase Compensation 

180# Field 2 - Function Identification Number 

181# Field 3 - Version Number, or sequence number 

182# Field 4 - Load Case Identification Number 

183# 0 - Single Point Excitation 

184# Field 5 - Response Entity Name ("NONE" if unused) 

185# Field 6 - Response Node 

186# Field 7 - Response Direction 

187# 0 - Scalar 

188# 1 - +X Translation 4 - +X Rotation 

189# -1 - -X Translation -4 - -X Rotation 

190# 2 - +Y Translation 5 - +Y Rotation 

191# -2 - -Y Translation -5 - -Y Rotation 

192# 3 - +Z Translation 6 - +Z Rotation 

193# -3 - -Z Translation -6 - -Z Rotation 

194# Field 8 - Reference Entity Name ("NONE" if unused) 

195# Field 9 - Reference Node 

196# Field 10 - Reference Direction (same as field 7) 

197# 

198# NOTE 

199# 

200# Fields 8, 9, and 10 are only relevant if field 4 

201# is zero. 

202 (function_type, function_id, version_number, load_case, 

203 response_entity_name, response_node, response_direction, 

204 reference_entity_name, reference_node, reference_direction) = ( 

205 parse_uff_line(split_header[5], 2 * ['I5', 'I10'] + 2 * ['X1', 'A10', 'I10', 'I4'])) 

206 

207# Record 7: Format(3I10,3E13.5) 

208# Data Form 

209# Field 1 - Ordinate Data Type 

210# 2 - real, single precision 

211# 4 - real, double precision 

212# 5 - complex, single precision 

213# 6 - complex, double precision 

214# Field 2 - Number of data pairs for uneven abscissa 

215# spacing, or number of data values for even 

216# abscissa spacing 

217# Field 3 - Abscissa Spacing 

218# 0 - uneven 

219# 1 - even (no abscissa values stored) 

220# Field 4 - Abscissa minimum (0.0 if spacing uneven) 

221# Field 5 - Abscissa increment (0.0 if spacing uneven) 

222# Field 6 - Z-axis value (0.0 if unused) 

223 (ordinate_data_type, num_data, abscissa_spacing, abscissa_minimum, 

224 abscissa_increment, zaxis_value) = ( 

225 parse_uff_line(split_header[6], 3 * ['I10'] + 3 * ['E13.5'])) 

226# Record 8: Format(I10,3I5,2(1X,20A1)) 

227# Abscissa Data Characteristics 

228# Field 1 - Specific Data Type 

229# 0 - unknown 

230# 1 - general 

231# 2 - stress 

232# 3 - strain 

233# 5 - temperature 

234# 6 - heat flux 

235# 8 - displacement 

236# 9 - reaction force 

237# 11 - velocity 

238# 12 - acceleration 

239# 13 - excitation force 

240# 15 - pressure 

241# 16 - mass 

242# 17 - time 

243# 18 - frequency 

244# 19 - rpm 

245# 20 - order 

246# 21 - sound pressure 

247# 22 - sound intensity 

248# 23 - sound power 

249# Field 2 - Length units exponent 

250# Field 3 - Force units exponent 

251# Field 4 - Temperature units exponent 

252# 

253# NOTE 

254# 

255# Fields 2, 3 and 4 are relevant only if the 

256# Specific Data Type is General, or in the case of 

257# ordinates, the response/reference direction is a 

258# scalar, or the functions are being used for 

259# nonlinear connectors in System Dynamics Analysis. 

260# See Addendum 'A' for the units exponent table. 

261# 

262# Field 5 - Axis label ("NONE" if not used) 

263# Field 6 - Axis units label ("NONE" if not used) 

264# 

265# NOTE 

266# 

267# If fields 5 and 6 are supplied, they take 

268# precendence over program generated labels and 

269# units. 

270 (abscissa_data_type, abscissa_length_exponent, 

271 abscissa_force_exponent, abscissa_temp_exponent, 

272 abscissa_axis_label, abscissa_units_label) = ( 

273 parse_uff_line(split_header[7], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20'])) 

274# Record 9: Format(I10,3I5,2(1X,20A1)) 

275# Ordinate (or ordinate numerator) Data Characteristics 

276 (ordinate_num_data_type, ordinate_num_length_exponent, 

277 ordinate_num_force_exponent, ordinate_num_temp_exponent, 

278 ordinate_num_axis_label, ordinate_num_units_label) = ( 

279 parse_uff_line(split_header[8], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20'])) 

280# Record 10: Format(I10,3I5,2(1X,20A1)) 

281# Ordinate Denominator Data Characteristics 

282 (ordinate_den_data_type, ordinate_den_length_exponent, 

283 ordinate_den_force_exponent, ordinate_den_temp_exponent, 

284 ordinate_den_axis_label, ordinate_den_units_label) = ( 

285 parse_uff_line(split_header[9], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20'])) 

286# Record 11: Format(I10,3I5,2(1X,20A1)) 

287# Z-axis Data Characteristics 

288 (zaxis_data_type, zaxis_length_exponent, 

289 zaxis_force_exponent, zaxis_temp_exponent, 

290 zaxis_axis_label, zaxis_units_label) = ( 

291 parse_uff_line(split_header[10], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20'])) 

292# Record 12: 

293# Data Values 

294# 

295# Ordinate Abscissa 

296# Case Type Precision Spacing Format 

297# ------------------------------------------------------------- 

298# 1 real single even 6E13.5 

299# 2 real single uneven 6E13.5 

300# 3 complex single even 6E13.5 

301# 4 complex single uneven 6E13.5 

302# 5 real double even 4E20.12 

303# 6 real double uneven 2(E13.5,E20.12) 

304# 7 complex double even 4E20.12 

305# 8 complex double uneven E13.5,2E20.12 

306# -------------------------------------------------------------- 

307 

308 # Number of data points to read per item 

309 read_multiplication_factor = 1 # Base value is just one value 

310 if abscissa_spacing == 0: # If abscissa spacing is uneven 

311 read_multiplication_factor += 1 # Will need to read that too 

312 if ordinate_data_type in [5, 6]: # If complex, 

313 read_multiplication_factor += 1 # Will need to read 2 ordinates 

314 

315 # reading Record 12 if encoded in binary 

316 if is_binary: 

317 split_data = b''.join(block_lines[num_ascii_lines_following:]) 

318 if byte_ordering == 1: 

319 bo = '<' 

320 elif byte_ordering == 2: 

321 bo = '>' 

322 else: 

323 warnings.warn('UFF file does not contain byte_ordering parameter, assuming Little Endian (DEC VMS & ULTRIX, WIN NT)', EncodingWarning, stacklevel=5) 

324 bo = '<' 

325 if (ordinate_data_type in [2, 5]): 

326 # single precision - 4 bytes 

327 ordinate = np.asarray(struct.unpack('%c%sf' % (bo, int(len(split_data) / 4)), split_data), 'd').reshape(-1,read_multiplication_factor) 

328 else: 

329 # double precision - 8 bytes 

330 ordinate = np.asarray(struct.unpack('%c%sd' % (bo, int(len(split_data) / 8)), split_data), 'd').reshape(-1,read_multiplication_factor) 

331 

332 # reading Record 12 if encoded as ascii 

333 else: 

334 if (ordinate_data_type in [2, 5]): # Single, regardless of abscissa or complexity 

335 read_format = 6 * ['E13.5'] 

336 ordinate_data_dtype = 'float32' 

337 elif (ordinate_data_type in [4, 6] # Double precision 

338 and abscissa_spacing == 1): # Even spacing 

339 read_format = 4 * ['E20.12'] 

340 ordinate_data_dtype = 'float64' 

341 elif (ordinate_data_type == 4 # Real double precision 

342 and abscissa_spacing == 0): # Uneven spacing 

343 read_format = 2 * ['E13.5', 'E20.12'] 

344 ordinate_data_dtype = 'float64' 

345 elif (ordinate_data_type == 6 # Complex double precision 

346 and abscissa_spacing == 0): # Uneven spacing 

347 read_format = ['E13.5'] + 2 * ['E20.12'] 

348 ordinate_data_dtype = 'float64' 

349 total_number_of_entries = read_multiplication_factor * num_data 

350 # Now read the data 

351 ordinate = np.array( 

352 parse_uff_lines([line.decode('utf-8') for line in block_lines[num_ascii_lines_following:]], read_format, total_number_of_entries)[0], 

353 dtype=ordinate_data_dtype).reshape(-1, read_multiplication_factor) 

354 # Now parse the data into the right format 

355 if abscissa_spacing == 0: # If abscissa spacing is uneven 

356 abscissa = ordinate[:, 0].astype('float32') 

357 ordinate = ordinate[:, 1:] 

358 else: 

359 abscissa = (abscissa_minimum + abscissa_increment * 

360 np.arange(ordinate.shape[0])).astype('float32') 

361 if ordinate_data_type in [5, 6]: # If complex 

362 ordinate = ordinate[:, 0] + 1j * ordinate[:, 1] 

363 else: 

364 ordinate = ordinate[:, 0] 

365 # Now create the dataset 

366 ds_58 = cls(idline1, idline2, idline3, idline4, idline5, 

367 function_type, function_id, version_number, load_case, 

368 response_entity_name, response_node, response_direction, 

369 reference_entity_name, reference_node, reference_direction, 

370 abscissa_data_type, abscissa_length_exponent, 

371 abscissa_force_exponent, abscissa_temp_exponent, 

372 abscissa_axis_label, abscissa_units_label, 

373 ordinate_num_data_type, ordinate_num_length_exponent, 

374 ordinate_num_force_exponent, ordinate_num_temp_exponent, 

375 ordinate_num_axis_label, ordinate_num_units_label, 

376 ordinate_den_data_type, ordinate_den_length_exponent, 

377 ordinate_den_force_exponent, ordinate_den_temp_exponent, 

378 ordinate_den_axis_label, ordinate_den_units_label, 

379 zaxis_data_type, zaxis_length_exponent, 

380 zaxis_force_exponent, zaxis_temp_exponent, 

381 zaxis_axis_label, zaxis_units_label, zaxis_value, 

382 abscissa, ordinate) 

383 return ds_58 

384 

385 def __repr__(self): 

386 return 'Sdynpy_UFF_Dataset_58<{} Elements in Function>'.format(self.ordinate.size) 

387 

388 def write_string(self): 

389 return_string = '' 

390 return_string += write_uff_line([self.idline1, 

391 self.idline2, 

392 self.idline3, 

393 self.idline4, 

394 self.idline5, 

395 ], ['A80']) 

396 return_string += write_uff_line([self.function_type, 

397 self.function_id, 

398 self.version_number, 

399 self.load_case, 

400 self.response_entity_name, 

401 self.response_node, 

402 self.response_direction, 

403 self.reference_entity_name, 

404 self.reference_node, 

405 self.reference_direction], 

406 2 * ['I5', 'I10'] + 2 * ['X1', 'A10', 'I10', 'I4']) 

407 # Check what type of data 

408 type_size = self.ordinate.dtype.itemsize 

409 if np.iscomplexobj(self.ordinate): # Complex 

410 if type_size == 16: # Double Precision 

411 ordinate_data_type = 6 

412 elif type_size == 8: # Single Precision 

413 ordinate_data_type = 5 

414 else: 

415 raise ValueError( 

416 'Unknown data type to write to UFF file! Should be one of "float32", "float64", "complex64", "complex128".') 

417 ordinate = np.concatenate((self.ordinate.real.reshape(-1, 1), 

418 self.ordinate.imag.reshape(-1, 1)), axis=1) 

419 elif np.isrealobj(self.ordinate): # Real 

420 if type_size == 8: # Double precision 

421 ordinate_data_type = 4 

422 elif type_size == 4: # Single Precision 

423 ordinate_data_type = 2 

424 else: 

425 raise ValueError( 

426 'Unknown data type to write to UFF file! Should be one of "float32", "float64", "complex64", "complex128".') 

427 ordinate = self.ordinate.reshape(-1, 1) 

428 else: 

429 raise ValueError( 

430 'Unknown data type to write to UFF file! Should be one of "float32", "float64", "complex64", "complex128".') 

431 # Check if abscissa is even 

432 if is_abscissa_even(self.abscissa): 

433 abscissa_spacing = 1 

434 abscissa_minimum = self.abscissa[0] 

435 abscissa_increment = np.mean(np.diff(self.abscissa)) 

436 else: 

437 abscissa_spacing = 0 

438 abscissa_minimum = 0 

439 abscissa_increment = 0 

440 ordinate = np.concatenate((self.abscissa.reshape(-1, 1), ordinate), axis=1) 

441 number_data = ordinate.shape[0] 

442 return_string += write_uff_line([ordinate_data_type, number_data, abscissa_spacing, 

443 abscissa_minimum, abscissa_increment, self.zaxis_value], 3 * ['I10'] + 3 * ['E13.5']) 

444 return_string += write_uff_line([self.abscissa_data_type, 

445 self.abscissa_length_exponent, 

446 self.abscissa_force_exponent, 

447 self.abscissa_temp_exponent, 

448 self.abscissa_axis_label, 

449 self.abscissa_units_label], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20']) 

450 return_string += write_uff_line([self.ordinate_num_data_type, 

451 self.ordinate_num_length_exponent, 

452 self.ordinate_num_force_exponent, 

453 self.ordinate_num_temp_exponent, 

454 self.ordinate_num_axis_label, 

455 self.ordinate_num_units_label], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20']) 

456 return_string += write_uff_line([self.ordinate_den_data_type, 

457 self.ordinate_den_length_exponent, 

458 self.ordinate_den_force_exponent, 

459 self.ordinate_den_temp_exponent, 

460 self.ordinate_den_axis_label, 

461 self.ordinate_den_units_label], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20']) 

462 return_string += write_uff_line([self.zaxis_data_type, 

463 self.zaxis_length_exponent, 

464 self.zaxis_force_exponent, 

465 self.zaxis_temp_exponent, 

466 self.zaxis_axis_label, 

467 self.zaxis_units_label], ['I10'] + 3 * ['I5'] + 2 * ['X1', 'A20']) 

468 # Get write format 

469 if (ordinate_data_type in [2, 5]): # Single, regardless of abscissa or complexity 

470 write_format = 6 * ['E13.5'] 

471 elif (ordinate_data_type in [4, 6] # Double precision 

472 and abscissa_spacing == 1): # Even spacing 

473 write_format = 4 * ['E20.12'] 

474 elif (ordinate_data_type == 4 # Real double precision 

475 and abscissa_spacing == 0): # Uneven spacing 

476 write_format = 2 * ['E13.5', 'E20.12'] 

477 elif (ordinate_data_type == 6 # Complex double precision 

478 and abscissa_spacing == 0): # Uneven spacing 

479 write_format = ['E13.5'] + 2 * ['E20.12'] 

480 return_string += write_uff_line(ordinate.flatten(), write_format) 

481 return return_string 

482 

483 def __str__(self): 

484 lines = self.write_string().split('\n') 

485 if len(lines) > 8: 

486 return 'Dataset 58: Data\n ' + '\n '.join(lines[0:5] + ['.', '.', '.']) 

487 else: 

488 return 'Dataset 58: Data\n ' + '\n '.join(lines) 

489 

490 

491def read(data,is_binary=False, byte_ordering = None, floating_point_format = None, 

492 num_ascii_lines_following = None, num_bytes_following = None): 

493 return Sdynpy_UFF_Dataset_58.from_uff_data_array( 

494 data, is_binary, byte_ordering, floating_point_format, 

495 num_ascii_lines_following, num_bytes_following)