pyuff 2.5.8__tar.gz → 2.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyuff
3
- Version: 2.5.8
3
+ Version: 2.6.1
4
4
  Summary: UFF (Universal File Format) read/write.
5
5
  Project-URL: homepage, https://github.com/ladisk/pyuff
6
6
  Project-URL: documentation, https://pyuff.readthedocs.io/en/latest/
@@ -14,12 +14,11 @@ Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
17
  Classifier: Programming Language :: Python :: 3.13
20
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
21
20
  Classifier: Topic :: Scientific/Engineering
22
- Requires-Python: >=3.11
21
+ Requires-Python: >=3.13
23
22
  Requires-Dist: numpy
24
23
  Provides-Extra: dev
25
24
  Requires-Dist: build; extra == 'dev'
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pyuff"
7
- version = "2.5.8"
7
+ version = "2.6.1"
8
8
  authors = [{name = "Primož Čermelj, Janko Slavič", email = "janko.slavic@fs.uni-lj.si"}]
9
9
  maintainers = [{name = "Janko Slavič et al.", email = "janko.slavic@fs.uni-lj.si"}]
10
10
  license = "MIT"
11
11
  description = "UFF (Universal File Format) read/write."
12
12
  readme = "README.rst"
13
13
  keywords = ['UFF', 'UNV', 'Universal File Format', 'read/write']
14
- requires-python = ">=3.11"
14
+ requires-python = ">=3.13"
15
15
  dependencies = [
16
16
  "numpy",
17
17
  ]
@@ -20,10 +20,9 @@ classifiers = [
20
20
  'Intended Audience :: Developers',
21
21
  'Topic :: Scientific/Engineering',
22
22
  'Programming Language :: Python :: 3',
23
- 'Programming Language :: Python :: 3.11',
24
- 'Programming Language :: Python :: 3.12',
25
23
  'Programming Language :: Python :: 3.13',
26
24
  'Programming Language :: Python :: 3.14',
25
+ 'Programming Language :: Python :: 3.15',
27
26
  "License :: OSI Approved :: MIT License",
28
27
  ]
29
28
 
@@ -2,7 +2,7 @@ import numpy as np
2
2
  import time
3
3
  import os
4
4
 
5
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
5
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
6
6
 
7
7
  def get_structure_151(raw=False):
8
8
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -61,15 +61,18 @@ def _write151(fh, dset):
61
61
  'time_file_written': ts})
62
62
  # write strings to the file
63
63
  fh.write('%6i\n%6i%74s\n' % (-1, 151, ' '))
64
- fh.write('%-80s\n' % dset['model_name'])
65
- fh.write('%-80s\n' % dset['description'])
66
- fh.write('%-80s\n' % dset['db_app'])
67
- fh.write('%-10s%-10s%10s%10s%10s\n' % (dset['date_db_created'],
68
- dset['time_db_created'], dset['version_db1'], dset['version_db2'],
64
+ fh.write('%-80s\n' % _fit_string(dset['model_name'], 80, 'model_name', 151))
65
+ fh.write('%-80s\n' % _fit_string(dset['description'], 80, 'description', 151))
66
+ fh.write('%-80s\n' % _fit_string(dset['db_app'], 80, 'db_app', 151))
67
+ fh.write('%-10s%-10s%10s%10s%10s\n' % (_fit_string(dset['date_db_created'], 10, 'date_db_created', 151),
68
+ _fit_string(dset['time_db_created'], 10, 'time_db_created', 151),
69
+ dset['version_db1'], dset['version_db2'],
69
70
  dset['file_type']))
70
- fh.write('%-10s%-10s\n' % (dset['date_db_saved'], dset['time_db_saved']))
71
- fh.write('%-80s\n' % dset['program'])
72
- fh.write('%-10s%-10s\n' % (dset['date_file_written'], dset['time_file_written']))
71
+ fh.write('%-10s%-10s\n' % (_fit_string(dset['date_db_saved'], 10, 'date_db_saved', 151),
72
+ _fit_string(dset['time_db_saved'], 10, 'time_db_saved', 151)))
73
+ fh.write('%-80s\n' % _fit_string(dset['program'], 80, 'program', 151))
74
+ fh.write('%-10s%-10s\n' % (_fit_string(dset['date_file_written'], 10, 'date_file_written', 151),
75
+ _fit_string(dset['time_file_written'], 10, 'time_file_written', 151)))
73
76
  fh.write('%6i\n' % -1)
74
77
  except KeyError as msg:
75
78
  raise Exception('The required key \'' + msg.args[0] + '\' not present when writing data-set #151')
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  import os
3
3
 
4
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
4
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
5
5
 
6
6
  def get_structure_164(raw=False):
7
7
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -52,7 +52,9 @@ def _write164(fh, dset):
52
52
  'temp_mode': 1})
53
53
  # write strings to the file
54
54
  fh.write('%6i\n%6i%74s\n' % (-1, 164, ' '))
55
- fh.write('%10i%20s%10i\n' % (dset['units_code'], dset['units_description'], dset['temp_mode']))
55
+ fh.write('%10i%20s%10i\n' % (dset['units_code'],
56
+ _fit_string(dset['units_description'], 20, 'units_description', 164),
57
+ dset['temp_mode']))
56
58
  str = '%25.16e%25.16e%25.16e\n%25.16e\n' % (
57
59
  dset['length'], dset['force'], dset['temp'], dset['temp_offset'])
58
60
  str = str.replace('e+', 'D+')
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
2
 
3
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
3
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
4
4
 
5
5
 
6
6
  def get_structure_18(raw=False):
@@ -60,7 +60,7 @@ def _write18(fh, dset):
60
60
  int(dset['ref_cs_num'][i]),
61
61
  int(dset['color'][i]),
62
62
  int(dset['method'][i])))
63
- fh.write('%-40s\n' % dset['cs_name'][i])
63
+ fh.write('%-40s\n' % _fit_string(dset['cs_name'][i], 40, 'cs_name', 18))
64
64
  fh.write('%13.5e%13.5e%13.5e%13.5e%13.5e%13.5e\n' % (
65
65
  dset['ref_o'][i][0], dset['ref_o'][i][1], dset['ref_o'][i][2],
66
66
  dset['x_point'][i][0], dset['x_point'][i][1], dset['x_point'][i][2]))
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
2
 
3
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
3
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
4
4
 
5
5
  def get_structure_1858(raw=False):
6
6
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -155,9 +155,9 @@ def _write1858(fh, dset):
155
155
  fh.write('%15.7e%15.7e%15.7e%15.7e%15.7e\n' % (0, 0, 0, 0, 0))
156
156
 
157
157
  fh.write('%-4s%2s%-4s\n' % (
158
- dset['response_direction'],
158
+ _fit_string(dset['response_direction'], 4, 'response_direction', 1858),
159
159
  '',
160
- dset['reference_direction'],
160
+ _fit_string(dset['reference_direction'], 4, 'reference_direction', 1858),
161
161
  ))
162
162
 
163
163
  fh.write('%-80s\n' % 'NONE')
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
2
 
3
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
3
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
4
4
 
5
5
 
6
6
  def get_structure_2400(raw=False):
@@ -64,10 +64,11 @@ def _write2400(fh, dset):
64
64
  fh.write('%6i\n%6i%74s\n' % (-1, 2400, ' '))
65
65
  fh.write('%12i%6i%6i%12i\n' % (dset['model_uid'], dset['entity_type'],
66
66
  dset['entity_subtype'], dset['version_number']))
67
- fh.write('%-80s\n' % dset['entity_name'])
68
- fh.write('%-80s\n' % dset['part_number'])
67
+ fh.write('%-80s\n' % _fit_string(dset['entity_name'], 80, 'entity_name', 2400))
68
+ fh.write('%-80s\n' % _fit_string(dset['part_number'], 80, 'part_number', 2400))
69
69
  fh.write('%s\n' % ''.join(['%2i' % v for v in dset['status_mask']]))
70
- fh.write('%-10s%-10s%12i%12i%12i\n' % (dset['date'], dset['time'],
70
+ fh.write('%-10s%-10s%12i%12i%12i\n' % (_fit_string(dset['date'], 10, 'date', 2400),
71
+ _fit_string(dset['time'], 10, 'time', 2400),
71
72
  dset['idm_version_id'], dset['idm_item_id'],
72
73
  dset['primary_parent_uid']))
73
74
  fh.write('%12i\n' % dset['optimization_switches'])
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  import math as math
3
3
 
4
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
4
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
5
5
 
6
6
  def get_structure_2414(raw=False):
7
7
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -564,13 +564,13 @@ def _write2414(fh, dset):
564
564
 
565
565
  # Write general fields
566
566
  fh.write('%10i\n' % (dset['analysis_dataset_label'])) #Loadcase number (DS2414_num)
567
- fh.write('%-80s\n' % (dset['analysis_dataset_name'])) #usually with the frequency
567
+ fh.write('%-80s\n' % (_fit_string(dset['analysis_dataset_name'], 80, 'analysis_dataset_name', 2414))) #usually with the frequency
568
568
  fh.write('%10i\n' % (dset['dataset_location']))
569
- fh.write('%-80s\n' % dset['id1'])
570
- fh.write('%-80s\n' % dset['id2'])
571
- fh.write('%-80s\n' % dset['id3']) #usually with the frequency
572
- fh.write('%-80s\n' % dset['id4']) #usually with the loadcase
573
- fh.write('%-80s\n' % dset['id5'])
569
+ fh.write('%-80s\n' % _fit_string(dset['id1'], 80, 'id1', 2414))
570
+ fh.write('%-80s\n' % _fit_string(dset['id2'], 80, 'id2', 2414))
571
+ fh.write('%-80s\n' % _fit_string(dset['id3'], 80, 'id3', 2414)) #usually with the frequency
572
+ fh.write('%-80s\n' % _fit_string(dset['id4'], 80, 'id4', 2414)) #usually with the loadcase
573
+ fh.write('%-80s\n' % _fit_string(dset['id5'], 80, 'id5', 2414))
574
574
 
575
575
  fh.write('%10i%10i%10i%10i%10i%10i\n' % (
576
576
  dset['model_type'],
@@ -668,12 +668,10 @@ def _write2414(fh, dset):
668
668
 
669
669
  # The data and structure of Record 14 and 15 depend on the 'dataset_location'
670
670
  if dset['dataset_location'] == 1:
671
- for index in range(dset['node_nums'].shape[0]):
672
- fh.write('%10i\n' % (dset['node_nums'][index]))
673
- for field in dset['data_at_node'][index]:
674
- # number of values unknown, so loop
675
- fh.write('%13.5e' % (field))
676
- fh.write('\n')
671
+ # the number of values may differ from node to node
672
+ lines = ['%10i\n' % node + '%13.5e' * len(values) % tuple(values) + '\n'
673
+ for node, values in zip(dset['node_nums'], dset['data_at_node'])]
674
+ fh.write(''.join(lines))
677
675
  fh.write('%6i\n' % (-1))
678
676
 
679
677
  elif dset['dataset_location'] == 2:
@@ -788,10 +786,9 @@ def _extract2414(block_data):
788
786
  split_data = split_data.splitlines()
789
787
  if dset['dataset_location'] == 1:
790
788
  # Data at nodes
791
- record14 = np.asarray([str.split() for str in split_data[0::2]], 'U')
792
789
  # record 15 not always same number of fields, so a list of ndarray
793
- record15 = [np.array([float(elem) for elem in item.split()], float) for item in split_data[1::2]]
794
- dset['node_nums'] = np.array(record14[:,0].copy(), dtype=int)
790
+ record15 = [np.array(line.split(), float) for line in split_data[1::2]]
791
+ dset['node_nums'] = np.array([line.split()[0] for line in split_data[0::2]], dtype=int)
795
792
  dset['data_at_node'] = record15
796
793
 
797
794
  elif dset['dataset_location'] == 2:
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
2
 
3
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
3
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
4
4
 
5
5
  def get_structure_2420(raw=False):
6
6
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -58,7 +58,7 @@ def _write2420(fh, dset):
58
58
  dset = _opt_fields(dset, dict)
59
59
  fh.write('%6i\n%6i%74s\n' % (-1, 2420, ' '))
60
60
  fh.write('%10i\n' % (dset['Part_UID']))
61
- fh.write('%-80s\n' % (dset['Part_Name']))
61
+ fh.write('%-80s\n' % (_fit_string(dset['Part_Name'], 80, 'Part_Name', 2420)))
62
62
 
63
63
  # -- Check Dimensions of input arrays
64
64
  num_CS = len(dset['CS_sys_labels'])
@@ -66,7 +66,7 @@ def _write2420(fh, dset):
66
66
  raise IndexError("Values missing for one or more CS")
67
67
  for node in range(num_CS):
68
68
  fh.write('%10i%10i%10i\n' % (dset['CS_sys_labels'][node], dset['CS_types'][node], dset['CS_colors'][node]))
69
- fh.write('%s\n' % dset['CS_names'][node])
69
+ fh.write('%s\n' % _fit_string(dset['CS_names'][node], 80, 'CS_names', 2420))
70
70
  fh.write('%25.16e%25.16e%25.16e\n' % tuple(dset['CS_matrices'][node][0, :]))
71
71
  fh.write('%25.16e%25.16e%25.16e\n' % tuple(dset['CS_matrices'][node][1, :]))
72
72
  fh.write('%25.16e%25.16e%25.16e\n' % tuple(dset['CS_matrices'][node][2, :]))
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  import math
3
3
 
4
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
4
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
5
5
 
6
6
  def get_structure_2429(raw=False):
7
7
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -125,7 +125,7 @@ def _write2429(fh, dset):
125
125
  dset['active_contact_set_no_for_group'],
126
126
  len(dset['entity_type_code'])))
127
127
  # Record 2
128
- fh.write('%-40s\n' % (dset['group_name']))
128
+ fh.write('%-40s\n' % (_fit_string(dset['group_name'], 40, 'group_name', 2429)))
129
129
 
130
130
  # Record 3-N
131
131
  # Write the full lines (which have 4 pairs)
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  import math
3
3
 
4
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
4
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
5
5
 
6
6
  def get_structure_2467(raw=False):
7
7
  """(source: https://github.com/victorsndvg/FEconv/blob/master/source/unv/module_dataset_2467.f90)"""
@@ -76,7 +76,7 @@ def _write2467(fh, dset):
76
76
  group['active_contact_set_no_for_group'],
77
77
  len(group['entity_type_code'])))
78
78
  # Record 2
79
- fh.write('%-40s\n' % (group['group_name']))
79
+ fh.write('%-40s\n' % (_fit_string(group['group_name'], 40, 'group_name', 2467)))
80
80
 
81
81
  # Record 3-N
82
82
  # Write the full lines (which have 4 pairs)
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
2
 
3
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
3
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
4
4
 
5
5
  def get_structure_55(raw=False):
6
6
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -401,11 +401,11 @@ def _write55(fh, dset):
401
401
  data_type = 2
402
402
  # Write strings to the file
403
403
  fh.write('%6i\n%6i%74s\n' % (-1, 55, ' '))
404
- fh.write('%-80s\n' % dset['id1'])
405
- fh.write('%-80s\n' % dset['id2'])
406
- fh.write('%-80s\n' % dset['id3'])
407
- fh.write('%-80s\n' % dset['id4'])
408
- fh.write('%-80s\n' % dset['id5'])
404
+ fh.write('%-80s\n' % _fit_string(dset['id1'], 80, 'id1', 55))
405
+ fh.write('%-80s\n' % _fit_string(dset['id2'], 80, 'id2', 55))
406
+ fh.write('%-80s\n' % _fit_string(dset['id3'], 80, 'id3', 55))
407
+ fh.write('%-80s\n' % _fit_string(dset['id4'], 80, 'id4', 55))
408
+ fh.write('%-80s\n' % _fit_string(dset['id5'], 80, 'id5', 55))
409
409
  fh.write('%10i%10i%10i%10i%10i%10i\n' %
410
410
  (dset['model_type'], dset['analysis_type'], dset['data_ch'],
411
411
  dset['spec_data_type'], data_type, n_data_per_node))
@@ -426,26 +426,21 @@ def _write55(fh, dset):
426
426
  dset['modal_b'].real, dset['modal_b'].imag))
427
427
  else:
428
428
  raise ValueError('Unsupported analysis type')
429
- n = len(dset['node_nums'])
429
+ # Records 14 and 15 of every node: node number, then its values
430
430
  if data_type == 2:
431
431
  # Real data
432
432
  if n_data_per_node == 3:
433
- for k in range(0, n):
434
- fh.write('%10i\n' % dset['node_nums'][k])
435
- fh.write('%13.5e%13.5e%13.5e\n' % (dset['r1'][k], dset['r2'][k], dset['r3'][k]))
433
+ rows = zip(dset['node_nums'], dset['r1'], dset['r2'], dset['r3'])
434
+ fh.write(''.join('%10i\n%13.5e%13.5e%13.5e\n' % row for row in rows))
436
435
  else:
437
- for k in range(0, n):
438
- fh.write('%10i\n' % dset['node_nums'][k])
439
- fh.write('%13.5e%13.5e%13.5e%13.5e%13.5e%13.5e\n' %
440
- (dset['r1'][k], dset['r2'][k], dset['r3'][k], dset['r4'][k], dset['r5'][k],
441
- dset['r6'][k]))
436
+ rows = zip(dset['node_nums'], dset['r1'], dset['r2'], dset['r3'], dset['r4'], dset['r5'], dset['r6'])
437
+ fh.write(''.join('%10i\n%13.5e%13.5e%13.5e%13.5e%13.5e%13.5e\n' % row for row in rows))
442
438
  elif data_type == 5:
443
439
  # Complex data; n_data_per_node is assumed being 3
444
- for k in range(0, n):
445
- fh.write('%10i\n' % dset['node_nums'][k])
446
- fh.write('%13.5e%13.5e%13.5e%13.5e%13.5e%13.5e\n' %
447
- (dset['r1'][k].real, dset['r1'][k].imag, dset['r2'][k].real, dset['r2'][k].imag,
448
- dset['r3'][k].real, dset['r3'][k].imag))
440
+ rows = zip(dset['node_nums'], dset['r1'], dset['r2'], dset['r3'])
441
+ fh.write(''.join('%10i\n%13.5e%13.5e%13.5e%13.5e%13.5e%13.5e\n' %
442
+ (node, r1.real, r1.imag, r2.real, r2.imag, r3.real, r3.imag)
443
+ for node, r1, r2, r3 in rows))
449
444
  else:
450
445
  raise ValueError('Unsupported data type')
451
446
  fh.write('%6i\n' % -1)
@@ -504,7 +499,9 @@ def _extract55(block_data):
504
499
  # Body
505
500
  split_data = ''.join(split_data[10:])
506
501
  node_nums = np.asarray([int(str) for str in split_data.splitlines(True)[::2]])
507
- node_vals = np.array([[str[i*13:(i+1)*13] for i in range(len(str)//13) ] for str in split_data.splitlines(True)[1::2]], 'd').flatten()
502
+ # values are in fields of 13 characters, which may touch each other
503
+ value_lines = split_data.splitlines()[1::2]
504
+ node_vals = np.array([line[i:i + 13] for line in value_lines for i in range(0, len(line) - 12, 13)], 'd')
508
505
  if dset['data_type'] == 2:
509
506
  # real data
510
507
  if dset['n_data_per_node'] == 3:
@@ -3,7 +3,7 @@ import struct
3
3
  import sys
4
4
  import os
5
5
 
6
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
6
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
7
7
 
8
8
  def get_structure_58(raw=False):
9
9
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -827,7 +827,8 @@ that takes on a binary format in the future.
827
827
  def _write58(fh, dset, mode='add', _filename=None, force_double=True):
828
828
  """Writes function at nodal DOF - data-set 58 - to an open file fh."""
829
829
  try:
830
- if not (dset['func_type'] in [0, 1, 2, 3, 4, 6, 9]):
830
+ # function types 0-27 of the format; the record layout does not depend on the type
831
+ if not (dset['func_type'] in range(28)):
831
832
  raise ValueError('Unsupported function type')
832
833
  # handle optional fields - only those that are not calculated
833
834
  # automatically
@@ -888,18 +889,26 @@ def _write58(fh, dset, mode='add', _filename=None, force_double=True):
888
889
 
889
890
  if dset['ord_data_type'] in [4, 6]: # double precision
890
891
  is_double = True
891
- n_bytes = num_pts * 8
892
+ n_value_bytes = 8
892
893
  elif dset['ord_data_type'] in [2, 5]: # sigle precision
893
894
  is_double = False
894
- n_bytes = num_pts * 4
895
-
896
- if 'n_bytes' in dset.keys():
897
- dset['n_bytes'] = n_bytes
895
+ n_value_bytes = 4
898
896
 
899
897
  ord_data_type = dset['ord_data_type']
900
898
 
901
899
  is_even = bool(dset['abscissa_spacing']) # handling even/uneven abscissa spacing manually
902
900
 
901
+ # number of bytes of the binary data (58b): every value written, i.e. the
902
+ # real and imaginary part for complex data and the abscissa for uneven spacing;
903
+ # the abscissa is always real single precision
904
+ n_ord_values = 1 if is_r else 2
905
+ n_bytes = num_pts * n_ord_values * n_value_bytes
906
+ if not is_even:
907
+ n_bytes += num_pts * 4
908
+
909
+ if 'n_bytes' in dset.keys():
910
+ dset['n_bytes'] = n_bytes
911
+
903
912
  # handling abscissa spacing automatically
904
913
  # is_even = len( set( [ dset['x'][ii]-dset['x'][ii-1] for ii in range(1,len(dset['x'])) ] ) ) == 1
905
914
  dset['abscissa_min'] = dset['x'][0]
@@ -913,34 +922,39 @@ def _write58(fh, dset, mode='add', _filename=None, force_double=True):
913
922
  fh.write('b%6i%6i%12i%12i%6i%6i%12i%12i\n' % (bo, 2, 11, n_bytes, 0, 0, 0, 0))
914
923
  else:
915
924
  fh.write('%74s\n' % ' ')
916
- fh.write('%-80s\n' % dset['id1'])
917
- fh.write('%-80s\n' % dset['id2'])
918
- fh.write('%-80s\n' % dset['id3'])
919
- fh.write('%-80s\n' % dset['id4'])
920
- fh.write('%-80s\n' % dset['id5'])
925
+ fh.write('%-80s\n' % _fit_string(dset['id1'], 80, 'id1', 58))
926
+ fh.write('%-80s\n' % _fit_string(dset['id2'], 80, 'id2', 58))
927
+ fh.write('%-80s\n' % _fit_string(dset['id3'], 80, 'id3', 58))
928
+ fh.write('%-80s\n' % _fit_string(dset['id4'], 80, 'id4', 58))
929
+ fh.write('%-80s\n' % _fit_string(dset['id5'], 80, 'id5', 58))
921
930
  fh.write('%5i%10i%5i%10i %10s%10i%4i %10s%10i%4i\n' %
922
931
  (dset['func_type'], dset['func_id'], dset['ver_num'], dset['load_case_id'],
923
- dset['rsp_ent_name'], dset['rsp_node'], dset['rsp_dir'], dset['ref_ent_name'],
932
+ _fit_string(dset['rsp_ent_name'], 10, 'rsp_ent_name', 58), dset['rsp_node'], dset['rsp_dir'],
933
+ _fit_string(dset['ref_ent_name'], 10, 'ref_ent_name', 58),
924
934
  dset['ref_node'], dset['ref_dir']))
925
935
  fh.write('%10i%10i%10i%13.5e%13.5e%13.5e\n' % (ord_data_type, num_pts, is_even,
926
936
  is_even * dset['abscissa_min'], is_even * dx,
927
937
  dset['z_axis_value']))
928
938
  fh.write('%10i%5i%5i%5i %-20s %-20s\n' % (dset['abscissa_spec_data_type'],
929
939
  dset['abscissa_len_unit_exp'], dset['abscissa_force_unit_exp'],
930
- dset['abscissa_temp_unit_exp'], dset['abscissa_axis_lab'],
931
- dset['abscissa_axis_units_lab']))
940
+ dset['abscissa_temp_unit_exp'],
941
+ _fit_string(dset['abscissa_axis_lab'], 20, 'abscissa_axis_lab', 58),
942
+ _fit_string(dset['abscissa_axis_units_lab'], 20, 'abscissa_axis_units_lab', 58)))
932
943
  fh.write('%10i%5i%5i%5i %-20s %-20s\n' % (dset['ordinate_spec_data_type'],
933
944
  dset['ordinate_len_unit_exp'], dset['ordinate_force_unit_exp'],
934
- dset['ordinate_temp_unit_exp'], dset['ordinate_axis_lab'],
935
- dset['ordinate_axis_units_lab']))
945
+ dset['ordinate_temp_unit_exp'],
946
+ _fit_string(dset['ordinate_axis_lab'], 20, 'ordinate_axis_lab', 58),
947
+ _fit_string(dset['ordinate_axis_units_lab'], 20, 'ordinate_axis_units_lab', 58)))
936
948
  fh.write('%10i%5i%5i%5i %-20s %-20s\n' % (dset['orddenom_spec_data_type'],
937
949
  dset['orddenom_len_unit_exp'], dset['orddenom_force_unit_exp'],
938
- dset['orddenom_temp_unit_exp'], dset['orddenom_axis_lab'],
939
- dset['orddenom_axis_units_lab']))
950
+ dset['orddenom_temp_unit_exp'],
951
+ _fit_string(dset['orddenom_axis_lab'], 20, 'orddenom_axis_lab', 58),
952
+ _fit_string(dset['orddenom_axis_units_lab'], 20, 'orddenom_axis_units_lab', 58)))
940
953
  fh.write('%10i%5i%5i%5i %-20s %-20s\n' % (dset['z_axis_spec_data_type'],
941
954
  dset['z_axis_len_unit_exp'], dset['z_axis_force_unit_exp'],
942
- dset['z_axis_temp_unit_exp'], dset['z_axis_axis_lab'],
943
- dset['z_axis_axis_units_lab']))
955
+ dset['z_axis_temp_unit_exp'],
956
+ _fit_string(dset['z_axis_axis_lab'], 20, 'z_axis_axis_lab', 58),
957
+ _fit_string(dset['z_axis_axis_units_lab'], 20, 'z_axis_axis_units_lab', 58)))
944
958
  if is_r:
945
959
  if is_even:
946
960
  data = dset['data'].copy()
@@ -958,23 +972,30 @@ def _write58(fh, dset, mode='add', _filename=None, force_double=True):
958
972
  data[0:-2:3] = dset['x']
959
973
  data[1:-1:3] = dset['data'].real
960
974
  data[2::3] = dset['data'].imag
961
- # always write data in double precision
962
975
  if dset['binary']:
963
976
  fh.close()
964
- if mode.lower() == 'overwrite':
965
- fh = open(_filename, 'wb')
966
- elif mode.lower() == 'add':
967
- fh = open(_filename, 'ab')
968
- # write data
977
+ # the header was just written to the file: append in both modes
978
+ fh = open(_filename, 'ab')
979
+ # write data in the precision given by the ordinate data type
980
+ if is_double:
981
+ value_format = 'd'
982
+ else:
983
+ value_format = 'f'
969
984
  if bo == 1:
970
- [fh.write(struct.pack('<d', datai)) for datai in data]
985
+ byte_order = '<'
986
+ else:
987
+ byte_order = '>'
988
+ if is_even:
989
+ fh.write(data.astype(byte_order + value_format).tobytes())
971
990
  else:
972
- [fh.write(struct.pack('>d', datai)) for datai in data]
991
+ # uneven spacing: the abscissa is always real single precision
992
+ rows = np.zeros(num_pts, [('x', byte_order + 'f'),
993
+ ('y', byte_order + value_format, (n_ord_values,))])
994
+ rows['x'] = dset['x']
995
+ rows['y'] = data.reshape(num_pts, -1)[:, 1:]
996
+ fh.write(rows.tobytes())
973
997
  fh.close()
974
- if mode.lower() == 'overwrite':
975
- fh = open(_filename, 'wt', encoding='utf-8')
976
- elif mode.lower() == 'add':
977
- fh = open(_filename, 'at', encoding='utf-8')
998
+ fh = open(_filename, 'at', encoding='utf-8')
978
999
  else:
979
1000
  if is_double: # write double precision
980
1001
  n4_blocks = len(data) // 4
@@ -1088,6 +1109,15 @@ def _extract58(block_data, header_only=False):
1088
1109
  # single precision - 4 bytes, note: sometimes split_data wrongly contains CR/LF and
1089
1110
  # therefore the length of split_data is not divisible by 4
1090
1111
  values = np.asarray(struct.unpack('%c%sf' % (bo, int(len(split_data) / 4)), split_data[:(len(split_data) // 4)*4]), 'd')
1112
+ elif (dset['abscissa_spacing'] == 0) and \
1113
+ (len(split_data) < dset['num_pts'] * (8 + 8 * (1 if dset['ord_data_type'] == 4 else 2))):
1114
+ # double precision, uneven abscissa: the abscissa is real single precision
1115
+ # (pyuff 2.5.8 and older wrote it in double precision; those records are
1116
+ # longer and are read below)
1117
+ n_ord_values = 1 if dset['ord_data_type'] == 4 else 2
1118
+ rows = np.frombuffer(split_data[:dset['num_pts'] * (4 + 8 * n_ord_values)],
1119
+ [('x', bo + 'f'), ('y', bo + 'd', (n_ord_values,))])
1120
+ values = np.column_stack((rows['x'], rows['y'])).ravel().astype('d')
1091
1121
  else:
1092
1122
  # double precision - 8 bytes, note: sometimes split_data wrongly contains CR/LF and
1093
1123
  # therefore the length of split_data is not divisible by 8
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  import os
3
3
 
4
- from ..tools import _opt_fields, _parse_header_line, check_dict_for_none
4
+ from ..tools import _opt_fields, _parse_header_line, check_dict_for_none, _fit_string
5
5
 
6
6
  def get_structure_82(raw=False):
7
7
  """(source: https://www.ceas3.uc.edu/sdrluff/"""
@@ -57,7 +57,7 @@ def _write82(fh, dset):
57
57
  n_nodes = np.sum((dset['nodes'][1:] - dset['nodes'][:-1]) != 0) + 1
58
58
  fh.write('%6i\n%6i%74s\n' % (-1, 82, ' '))
59
59
  fh.write('%10i%10i%10i\n' % (dset['trace_num'], n_nodes, dset['color']))
60
- fh.write('%-80s\n' % dset['id'])
60
+ fh.write('%-80s\n' % _fit_string(dset['id'], 80, 'id', 82))
61
61
  sl = 0
62
62
  n8_blocks = n_nodes // 8
63
63
  rem_lines = n_nodes % 8
@@ -225,9 +225,11 @@ class UFF:
225
225
  si = self._block_ind[ii, 0]
226
226
  ei = self._block_ind[ii, 1]
227
227
  try:
228
- block_data = data[si:ei + 1].splitlines()
229
- self._set_types[ii] = int(block_data[1][0:6])
230
- if block_data[1][6].lower() == 'b':
228
+ # only the second line (data-set type, 'b' for binary) is
229
+ # needed; it follows the ' -1' line within 200 bytes
230
+ type_line = data[si:min(si + 200, ei + 1)].splitlines()[1]
231
+ self._set_types[ii] = int(type_line[0:6])
232
+ if type_line[6:7].lower() == b'b':
231
233
  self._set_formats[ii] = 1
232
234
  except:
233
235
  # Some non-valid blocks found; ignore the exception
@@ -257,6 +259,11 @@ class UFF:
257
259
  This usefull for large files.
258
260
  """
259
261
  dset = []
262
+ if not self.file_exists():
263
+ raise Exception('Cannot read from a non-existing file: ' + self._filename)
264
+ if not self._refreshed:
265
+ if not self.refresh():
266
+ raise Exception('Cannot read from the file: ' + self._filename)
260
267
  if setn is None:
261
268
  read_range = range(0, self._n_sets)
262
269
  else:
@@ -264,11 +271,6 @@ class UFF:
264
271
  read_range = [setn]
265
272
  else:
266
273
  read_range = setn
267
- if not self.file_exists():
268
- raise Exception('Cannot read from a non-existing file: ' + self._filename)
269
- if not self._refreshed:
270
- if not self.refresh():
271
- raise Exception('Cannot read from the file: ' + self._filename)
272
274
  try:
273
275
  for ii in read_range:
274
276
  dset.append(self._read_set(ii, header_only=header_only))
@@ -297,11 +299,15 @@ class UFF:
297
299
  calculated automatically and the dset is updated - as dset is actually
298
300
  an alias (aka pointer), this is reflected at the caller too.
299
301
 
300
- The required fields are:
301
- dset - dictionary representing the data-set
302
- mode - 'add' or 'overwrite'
303
- force_double - True or False (default True). Single precision should be avoided,
304
- therefore by default data is saved with double precision.
302
+ NaN and infinite values in ``data`` are written as 0 and as the
303
+ largest finite float, respectively, with a warning.
304
+
305
+ Parameters:
306
+
307
+ - ``dsets``: a dictionary, or a list of dictionaries, one per data-set
308
+ - ``mode``: ``'add'`` (default) or ``'overwrite'``
309
+ - ``force_double``: ``True`` (default) or ``False``; single precision should be
310
+ avoided, therefore by default data is saved with double precision.
305
311
  """
306
312
  if (not type(dsets).__name__ == 'list'):
307
313
  dsets = [dsets]
@@ -320,6 +326,7 @@ class UFF:
320
326
  self._write_set(dsets[ii], 'add', force_double=force_double)
321
327
  else:
322
328
  raise Exception('Unknown mode: ' + mode)
329
+ self.refresh()
323
330
 
324
331
  def _read_set(self, n, header_only=False):
325
332
  """
@@ -439,6 +446,13 @@ class UFF:
439
446
  raise Exception('Data-set\'s dictionary is missing the required \'type\' key')
440
447
  # handle nan or inf
441
448
  if 'data' in dset.keys():
449
+ data = np.asarray(dset['data'])
450
+ if np.issubdtype(data.dtype, np.inexact):
451
+ n_not_finite = np.count_nonzero(~np.isfinite(data))
452
+ if n_not_finite:
453
+ warnings.warn('Data-set %s: %i of %i data values are NaN or infinite; '
454
+ 'NaN is written as 0 and infinity as the largest finite '
455
+ 'float.' % (set_type, n_not_finite, data.size), stacklevel=3)
442
456
  dset['data'] = np.nan_to_num(dset['data'])
443
457
 
444
458
  if set_type == 15:
@@ -479,7 +493,9 @@ class UFF:
479
493
  raise # re-raise the last exception
480
494
  else:
481
495
  fh.close()
482
- self.refresh()
496
+ # the file has changed: its index of data-sets is rebuilt when it is
497
+ # needed next, not after every data-set (write_sets writes many)
498
+ self._refreshed = False
483
499
 
484
500
 
485
501
  if __name__ == '__main__':
@@ -1,5 +1,6 @@
1
1
  import pyuff
2
2
  import numpy as np
3
+ import warnings
3
4
 
4
5
  def _opt_fields(dict, fields_dict):
5
6
  """Sets the optional fields of the dict dictionary.
@@ -118,6 +119,24 @@ def _write_record(fh, values, formats, multiline=False, fstring=True):
118
119
 
119
120
  fh.write(to_write)
120
121
 
122
+ def _fit_string(value, width, field, set_type):
123
+ """Returns the value as a string of at most width characters.
124
+
125
+ A longer string does not fit the fixed-width field of the record; it is
126
+ truncated with a warning naming the data-set and the field.
127
+
128
+ :param value: value to be written to a text field
129
+ :param width: width of the field (characters)
130
+ :param field: name of the field (key of the dataset dictionary)
131
+ :param set_type: data-set number
132
+ """
133
+ value = str(value)
134
+ if len(value) > width:
135
+ warnings.warn('Data-set %s: %s is longer than %i characters and is truncated to %r.'
136
+ % (set_type, field, width, value[:width]), stacklevel=5)
137
+ value = value[:width]
138
+ return value
139
+
121
140
  def check_dict_for_none(dataset):
122
141
  dataset1 = {}
123
142
  for k, v in dataset.items():
File without changes
File without changes
File without changes
File without changes
File without changes