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
« 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
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.
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.
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.
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"""
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
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))
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
101 @property
102 def dataset_number(self):
103 return 58
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.
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']))
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# --------------------------------------------------------------
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
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)
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
385 def __repr__(self):
386 return 'Sdynpy_UFF_Dataset_58<{} Elements in Function>'.format(self.ordinate.size)
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
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)
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)