resqpy 4.5.0__py3-none-any.whl → 4.6.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -68,7 +68,9 @@ class PolylineSet(rql_c._BasePolyline):
68
68
  self.save_polys = False
69
69
  self.boolnotconstant = None
70
70
  self.boolvalue = None
71
+ self.closed_array = None
71
72
  self.crs_uuid = crs_uuid
73
+ self.indices = None
72
74
 
73
75
  super().__init__(model = parent_model,
74
76
  uuid = uuid,
@@ -92,10 +94,11 @@ class PolylineSet(rql_c._BasePolyline):
92
94
  break
93
95
  self.polys = polylines
94
96
  # Setting the title of the first polyline given as the PolylineSet title
95
- if len(polylines) > 1:
96
- self.title = f"{polylines[0].title} + {len(polylines)-1} polylines"
97
- else:
98
- self.title = polylines[0].title
97
+ if not self.title:
98
+ if len(polylines) > 1:
99
+ self.title = f"{polylines[0].title} + {len(polylines)-1} polylines"
100
+ else:
101
+ self.title = polylines[0].title
99
102
 
100
103
  elif irap_file is not None: # Create from an input IRAP file
101
104
  if crs_uuid is None:
@@ -113,6 +116,7 @@ class PolylineSet(rql_c._BasePolyline):
113
116
  root = self.root
114
117
 
115
118
  self.rep_int_root = self.model.referenced_node(rqet.find_tag(root, 'RepresentedInterpretation'))
119
+ self.closed_array = np.empty((0,), dtype = bool)
116
120
 
117
121
  for patch_node in rqet.list_of_tag(root, 'LinePatch'): # Loop over all LinePatches - likely just the one
118
122
  assert patch_node is not None # Required field
@@ -134,7 +138,7 @@ class PolylineSet(rql_c._BasePolyline):
134
138
  closed_array = self.get_bool_array(closed_node)
135
139
 
136
140
  count_node = rqet.find_tag(patch_node, 'NodeCountPerPolyline')
137
- rql_c.load_hdf5_array(self, count_node, 'count_perpol', tag = 'Values')
141
+ rql_c.load_hdf5_array(self, count_node, 'count_perpol', tag = 'Values', dtype = 'int')
138
142
 
139
143
  points_node = rqet.find_tag(geometry_node, 'Points')
140
144
  assert points_node is not None # Required field
@@ -155,12 +159,16 @@ class PolylineSet(rql_c._BasePolyline):
155
159
  # delattr(self,'count_perpol')
156
160
 
157
161
  self.polys.extend(subpolys)
162
+ self.closed_array = np.concatenate((self.closed_array, closed_array))
163
+
164
+ self.bool_array_format()
158
165
 
159
166
  def _set_from_irap(self, irap_file):
160
167
  inpoints = rsl.read_lines(irap_file)
161
168
  self.count_perpol = []
162
169
  closed_array = []
163
- self.title = os.path.basename(irap_file).split(".")[0]
170
+ if not self.title:
171
+ self.title = os.path.basename(irap_file).split(".")[0]
164
172
  for i, poly in enumerate(inpoints):
165
173
  if len(poly) > 1: # Polylines must have at least 2 points
166
174
  self.count_perpol.append(len(poly))
@@ -183,7 +191,8 @@ class PolylineSet(rql_c._BasePolyline):
183
191
  inpoints = f.readlines()
184
192
  self.count_perpol = []
185
193
  closed_array = []
186
- self.title = os.path.basename(charisma_file).split(".")[0]
194
+ if not self.title:
195
+ self.title = os.path.basename(charisma_file).split(".")[0]
187
196
  for i, line in enumerate(inpoints):
188
197
  line = line.split()
189
198
  coord_entry = np.array([[float(line[3]), float(line[4]), float(line[5])]])
@@ -278,32 +287,45 @@ class PolylineSet(rql_c._BasePolyline):
278
287
  pindex.text = '0'
279
288
 
280
289
  if self.boolnotconstant:
290
+
281
291
  # We have mixed data - use a BooleanArrayFromIndexArray
282
292
  closed = rqet.SubElement(patch, ns['resqml2'] + 'ClosedPolylines')
283
293
  closed.set(ns['xsi'] + 'type', ns['xsd'] + 'BooleanArrayFromIndexArray')
284
294
  closed.text = '\n'
285
295
 
286
- bool_val = rqet.SubElement(closed, ns['resqml2'] + 'Value')
296
+ bool_val = rqet.SubElement(closed, ns['resqml2'] + 'IndexIsTrue')
287
297
  bool_val.set(ns['xsi'] + 'type', ns['xsd'] + 'boolean')
288
- bool_val.text = str(self.boolvalue).lower()
289
-
290
- ind_val = rqet.SubElement(closed, ns['resqml2'] + 'Indices')
291
- ind_val.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset')
292
- ind_val.text = '\n'
298
+ bool_val.text = str(not self.boolvalue).lower()
293
299
 
294
300
  count = rqet.SubElement(closed, ns['resqml2'] + 'Count')
295
301
  count.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger')
296
302
  count.text = str(len(self.count_perpol))
297
303
 
298
- self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'indices_patch0', root = ind_val)
304
+ ind_val = rqet.SubElement(closed, ns['resqml2'] + 'Indices')
305
+ ind_val.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntegerHdf5Array')
306
+ ind_val.text = '\n'
307
+
308
+ iv_null = rqet.SubElement(ind_val, ns['resqml2'] + 'NullValue')
309
+ iv_null.set(ns['xsi'] + 'type', ns['xsd'] + 'integer')
310
+ iv_null.text = '-1'
311
+
312
+ iv_values = rqet.SubElement(ind_val, ns['resqml2'] + 'Values')
313
+ iv_values.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset')
314
+ iv_values.text = '\n'
315
+
316
+ self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'indices_patch0', root = iv_values)
317
+
299
318
  else:
319
+
300
320
  # All bools are the same - use a BooleanConstantArray
301
321
  closed = rqet.SubElement(patch, ns['resqml2'] + 'ClosedPolylines')
302
322
  closed.set(ns['xsi'] + 'type', ns['resqml2'] + 'BooleanConstantArray')
303
323
  closed.text = '\n'
324
+
304
325
  bool_val = rqet.SubElement(closed, ns['resqml2'] + 'Value')
305
326
  bool_val.set(ns['xsi'] + 'type', ns['xsd'] + 'boolean')
306
327
  bool_val.text = str(self.boolvalue).lower()
328
+
307
329
  count = rqet.SubElement(closed, ns['resqml2'] + 'Count')
308
330
  count.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger')
309
331
  count.text = str(len(self.count_perpol))
@@ -367,7 +389,7 @@ class PolylineSet(rql_c._BasePolyline):
367
389
  if self.uuid is None:
368
390
  self.uuid = bu.new_uuid()
369
391
  self.combine_polylines(self.polys)
370
- self.bool_array_format(self.closed_array)
392
+ self.bool_array_format()
371
393
  self.save_polys = save_polylines
372
394
  if self.save_polys:
373
395
  for poly in self.polys:
@@ -377,29 +399,37 @@ class PolylineSet(rql_c._BasePolyline):
377
399
  h5_reg.register_dataset(self.uuid, 'points_patch0', self.coordinates)
378
400
  h5_reg.register_dataset(self.uuid, 'NodeCountPerPolyline_patch0', self.count_perpol.astype(np.int32))
379
401
  if self.boolnotconstant:
380
- h5_reg.register_dataset(self.uuid, 'indices_patch0', self.indices)
402
+ h5_reg.register_dataset(self.uuid, 'indices_patch0', np.array(self.indices, dtype = np.int32))
381
403
  h5_reg.write(file_name, mode = mode)
382
404
 
383
405
  def get_bool_array(self, closed_node):
384
- # TODO: Check if also defined boolean arrays
385
- """Returns a boolean array using details in the node location.
406
+ # TODO: check for other permissible forms of the abstract boolean array
407
+ """Returns a boolean array using details in the xml node location.
386
408
 
387
- If type of boolean array is BooleanConstantArray, uses the array value and count to generate the array. If type of boolean array is BooleanArrayFromIndexArray, find the "other" value bool and indices of the "other" values, and insert these into an array opposite to the main bool.
409
+ arguments:
410
+ closed_node (xml node): the node under which the boolean array information sits
388
411
 
389
- args:
390
- closed_node: the node under which the boolean array information sits
412
+ returns:
413
+ 1D numpy bool array set True for those pplylines within the set which are marked as closed
391
414
  """
392
- if rqet.node_type(closed_node) == 'BooleanConstantArray':
415
+ # if type of boolean array is BooleanConstantArray, uses the array value and count to generate
416
+ # the array; if type of boolean array is BooleanArrayFromIndexArray, finds the "other" value
417
+ # bool and indices of the "other" values, and inserts these into an array opposite to the main bool
418
+ flavour = rqet.node_type(closed_node)
419
+ if flavour == 'BooleanConstantArray':
393
420
  count = rqet.find_tag_int(closed_node, 'Count')
394
421
  value = rqet.bool_from_text(rqet.node_text(rqet.find_tag(closed_node, 'Value')))
395
- return np.full((count), value)
396
- elif rqet.node_type(closed_node) == 'BooleanArrayFromIndexArray':
422
+ return np.full((count,), value, dtype = bool)
423
+ elif flavour == 'BooleanArrayFromIndexArray':
397
424
  count = rqet.find_tag_int(closed_node, 'Count')
398
- indices_arr = rql_c.load_hdf5_array(self, closed_node, 'indices_arr', tag = 'Indices')
425
+ indices_node = rqet.find_tag(closed_node, 'Indices')
426
+ assert indices_node is not None
427
+ indices_arr = rql_c.load_hdf5_array(self, indices_node, 'indices_arr', tag = 'Values', dtype = 'int')
399
428
  istrue = rqet.bool_from_text(rqet.node_text(rqet.find_tag(closed_node, 'IndexIsTrue')))
400
- out = np.full((count), not istrue)
429
+ out = np.full((count,), not istrue, dtype = bool)
401
430
  out[indices_arr] = istrue
402
431
  return out
432
+ raise ValueError(f'unrecognised closed array xml node type: {flavour}')
403
433
 
404
434
  def convert_to_polylines(self,
405
435
  closed_array = None,
@@ -496,32 +526,32 @@ class PolylineSet(rql_c._BasePolyline):
496
526
 
497
527
  self.polys = polylines
498
528
 
499
- def bool_array_format(self, closed_array):
500
- """Determines an appropriate output boolean array format from an input array of bools.
529
+ def bool_array_format(self):
530
+ """Determines an appropriate output boolean array format depending on the closed_array bools.
501
531
 
502
532
  self.boolnotconstant - set to True if all are not open or all closed
503
533
  self.boolvalue - value of isclosed for all polylines, or for the majority of polylines if mixed
504
534
  self.indices - array of indices where the values are not self.boolvalue, if the polylines are mixed
505
535
  """
506
536
 
537
+ assert self.closed_array is not None
507
538
  self.indices = []
508
539
  self.boolnotconstant = False
509
- if all(closed_array):
540
+ if all(self.closed_array):
510
541
  self.boolvalue = True
511
- elif not all(closed_array) and not any(closed_array):
542
+ elif not any(self.closed_array):
512
543
  self.boolvalue = False
513
544
  else:
514
- if np.count_nonzero(closed_array) > (len(closed_array) / 2):
545
+ if np.count_nonzero(self.closed_array) > (len(self.closed_array) // 2):
515
546
  self.boolvalue = True
516
- for i, val in enumerate(closed_array):
547
+ for i, val in enumerate(self.closed_array):
517
548
  if not val:
518
549
  self.indices.append(i)
519
550
  else:
520
551
  self.boolvalue = False
521
- for i, val in enumerate(closed_array):
552
+ for i, val in enumerate(self.closed_array):
522
553
  if val:
523
554
  self.indices.append(i)
524
- if len(self.indices) > 0:
525
555
  self.boolnotconstant = True
526
556
 
527
557
  def set_interpretation_root(self, rep_int_root, recursive = True):
resqpy/model/_hdf5.py CHANGED
@@ -208,12 +208,15 @@ def _h5_array_element(model,
208
208
  if object is None:
209
209
  object = model
210
210
 
211
- # Check if attribute has already be cached
211
+ if isinstance(dtype, str) and dtype == 'pack':
212
+ dtype = bool
213
+
214
+ # check if attribute has already been cached
212
215
  if array_attribute is not None:
213
216
  existing_value = getattr(object, array_attribute, None)
214
217
 
215
218
  # Watch out for np.array(None): check existing_value has a valid "shape"
216
- if existing_value is not None and getattr(existing_value, "shape", False):
219
+ if existing_value is not None and getattr(existing_value, 'shape', False):
217
220
  if index is None:
218
221
  return None # this option allows caching of array without actually referring to any element
219
222
  return existing_value[tuple(index)]
@@ -222,14 +225,20 @@ def _h5_array_element(model,
222
225
  if h5_root is None:
223
226
  return None
224
227
  if cache_array:
228
+ str_dtype = str(dtype)
225
229
  shape_tuple = tuple(h5_root[h5_key_pair[1]].shape)
226
- if required_shape is None or shape_tuple == required_shape:
227
- object.__dict__[array_attribute] = np.zeros(shape_tuple, dtype = dtype)
230
+ if required_shape is None:
231
+ required_shape = shape_tuple
232
+ object.__dict__[array_attribute] = np.zeros(required_shape, dtype = dtype)
233
+ if shape_tuple == required_shape:
228
234
  object.__dict__[array_attribute][:] = h5_root[h5_key_pair[1]]
235
+ elif (len(shape_tuple) == len(required_shape) and ('bool' in str_dtype or 'int8' in str_dtype) and
236
+ 8 * (shape_tuple[-1] - 1) < required_shape[-1] <= 8 * shape_tuple[-1]):
237
+ a = np.unpackbits(h5_root[h5_key_pair[1]], axis = -1).astype(bool)
238
+ object.__dict__[array_attribute][:] = a[..., :required_shape[-1]]
229
239
  else:
230
- object.__dict__[array_attribute] = np.zeros(required_shape, dtype = dtype)
231
- object.__dict__[array_attribute][:] = np.array(h5_root[h5_key_pair[1]],
232
- dtype = dtype).reshape(required_shape)
240
+ object.__dict__[array_attribute][:] = \
241
+ np.array(h5_root[h5_key_pair[1]], dtype = dtype).reshape(required_shape)
233
242
  _h5_release(model)
234
243
  if index is None:
235
244
  return None
@@ -241,6 +250,7 @@ def _h5_array_element(model,
241
250
  result = h5_root[h5_key_pair[1]][tuple(index)]
242
251
  else:
243
252
  shape_tuple = tuple(h5_root[h5_key_pair[1]].shape)
253
+ # todo: handle unpacking of a single bit into a bool?
244
254
  if shape_tuple == required_shape:
245
255
  result = h5_root[h5_key_pair[1]][tuple(index)]
246
256
  else:
resqpy/model/_model.py CHANGED
@@ -1344,7 +1344,8 @@ class Model():
1344
1344
  required to cache or access cached array
1345
1345
  dtype (string or data type): the data type of the elements of the array (need not match hdf5 array in precision)
1346
1346
  required_shape (tuple of ints, optional): if not None, the hdf5 array will be reshaped to this shape; if index
1347
- is not None, it is taken to be applicable to the required shape
1347
+ is not None, it is taken to be applicable to the required shape; required if the array is bool data that was
1348
+ written with resqpy specific dtype of 'pack'
1348
1349
 
1349
1350
  returns:
1350
1351
  if index is None, then None;
resqpy/model/_xml.py CHANGED
@@ -153,9 +153,9 @@ def _create_ref_node(model, flavour, title, uuid, content_type = None, root = No
153
153
  else:
154
154
  ct_node.text = 'application/x-resqml+xml;version=2.0;type=' + content_type
155
155
 
156
- if not title:
156
+ if title is None or len(title) == 0:
157
157
  title = model.title(uuid = uuid)
158
- if title is None:
158
+ if title is None or len(title) == 0:
159
159
  title = 'untitled'
160
160
  title_node = rqet.SubElement(ref_node, ns['eml'] + 'Title')
161
161
  title_node.set(ns['xsi'] + 'type', ns['eml'] + 'DescriptionString')
@@ -204,8 +204,8 @@ def _create_rels_part(model):
204
204
  def _create_citation(root = None, title = '', originator = None):
205
205
  """Creates a citation xml node and optionally appends as a child of root."""
206
206
 
207
- if title is None:
208
- title = ''
207
+ if title is None or len(title) == 0:
208
+ title = 'untitled'
209
209
 
210
210
  citation = rqet.Element(ns['eml'] + 'Citation')
211
211
  citation.set(ns['xsi'] + 'type', ns['eml'] + 'Citation')
@@ -80,6 +80,7 @@ def function_multiprocessing(function: Callable,
80
80
  if tmp_dir_path is None:
81
81
  tmp_dir_path = '.'
82
82
  tmp_dir = Path(tmp_dir_path) / f'tmp_{uuid.uuid4()}'
83
+ os.makedirs(tmp_dir)
83
84
  for i, kwargs in enumerate(kwargs_list):
84
85
  kwargs["index"] = i
85
86
  kwargs["parent_tmp_dir"] = str(tmp_dir)
@@ -33,12 +33,15 @@ def find_faces_to_represent_surface_regular_wrapper(
33
33
  trimmed: bool = False,
34
34
  is_curtain = False,
35
35
  extend_fault_representation: bool = False,
36
+ flange_inner_ring = False,
37
+ saucer_parameter = None,
36
38
  retriangulate: bool = False,
37
39
  related_uuid = None,
38
40
  progress_fn: Optional[Callable] = None,
39
41
  extra_metadata = None,
40
42
  return_properties: Optional[List[str]] = None,
41
- raw_bisector: bool = False) -> Tuple[int, bool, str, List[Union[UUID, str]]]:
43
+ raw_bisector: bool = False,
44
+ use_pack: bool = False) -> Tuple[int, bool, str, List[Union[UUID, str]]]:
42
45
  """Multiprocessing wrapper function of find_faces_to_represent_surface_regular_optimised.
43
46
 
44
47
  arguments:
@@ -79,6 +82,8 @@ def find_faces_to_represent_surface_regular_wrapper(
79
82
  the returned dictionary has the passed strings as keys and numpy arrays as values
80
83
  raw_bisector (bool, default False): if True and grid bisector is requested then it is left in a raw
81
84
  form without assessing which side is shallower (True values indicate same side as origin cell)
85
+ use_pack (bool, default False): if True, boolean properties will be stored in numpy packed format,
86
+ which will only be readable by resqpy based applications
82
87
 
83
88
  returns:
84
89
  Tuple containing:
@@ -150,6 +155,8 @@ def find_faces_to_represent_surface_regular_wrapper(
150
155
  convexity_parameter = 2.0,
151
156
  reorient = True,
152
157
  extend_with_flange = extend_fault_representation,
158
+ flange_inner_ring = flange_inner_ring,
159
+ saucer_parameter = saucer_parameter,
153
160
  flange_radial_distance = flange_radius,
154
161
  make_clockwise = False)
155
162
  extended = extend_fault_representation
@@ -181,6 +188,8 @@ def find_faces_to_represent_surface_regular_wrapper(
181
188
  convexity_parameter = 2.0,
182
189
  reorient = True,
183
190
  extend_with_flange = extend_fault_representation,
191
+ flange_inner_ring = flange_inner_ring,
192
+ saucer_parameter = saucer_parameter,
184
193
  flange_radial_distance = flange_radius,
185
194
  make_clockwise = False)
186
195
  del pset
@@ -345,13 +354,13 @@ def find_faces_to_represent_surface_regular_wrapper(
345
354
  raise ValueError(f'unrecognised property name {p_name}')
346
355
  if property_collection.number_of_imports() > 0:
347
356
  # log.debug('writing gcs property hdf5 data')
348
- property_collection.write_hdf5_for_imported_list()
357
+ property_collection.write_hdf5_for_imported_list(use_pack = use_pack)
349
358
  uuids_properties = property_collection.create_xml_for_imported_list_and_add_parts_to_model(
350
359
  find_local_property_kinds = True)
351
360
  uuid_list.extend(uuids_properties)
352
361
  if grid_pc is not None and grid_pc.number_of_imports() > 0:
353
362
  # log.debug('writing grid property (bisector) hdf5 data')
354
- grid_pc.write_hdf5_for_imported_list()
363
+ grid_pc.write_hdf5_for_imported_list(use_pack = use_pack)
355
364
  # log.debug('creating xml for grid property (bisector)')
356
365
  uuids_properties = grid_pc.create_xml_for_imported_list_and_add_parts_to_model(
357
366
  find_local_property_kinds = True)
@@ -76,7 +76,7 @@ def line_triangle_intersect(line_p, line_v, triangle, line_segment = False, l_to
76
76
  return line_p + t * line_v
77
77
 
78
78
 
79
- @njit
79
+ @njit # pragma: no cover
80
80
  def line_triangle_intersect_numba(
81
81
  line_p: np.ndarray,
82
82
  line_v: np.ndarray,
@@ -164,8 +164,7 @@ def line_triangles_intersects(line_p, line_v, triangles, line_segment = False):
164
164
  ts[:] = np.where(np.logical_or(ts < 0.0, ts > 1.0), np.nan, ts)
165
165
  np.divide(np.sum(np.cross(p02s, line_rv) * lp_t0s, axis = 1), denoms, out = us, where = nz)
166
166
  np.divide(np.sum(np.cross(line_rv, p01s) * lp_t0s, axis = 1), denoms, out = vs, where = nz)
167
- ts[:] = np.where(np.logical_or(np.logical_or(us < 0.0, us > 1.0), np.logical_or(vs < 0.0, us + vs > 1.0)), np.nan,
168
- ts)
167
+ ts[np.where(np.logical_or(np.logical_or(us < 0.0, us > 1.0), np.logical_or(vs < 0.0, us + vs > 1.0)))] = np.nan
169
168
 
170
169
  intersects = np.empty((n, 3))
171
170
  intersects[:] = line_v * np.repeat(ts, 3).reshape((n, 3)) + line_p
@@ -13,8 +13,8 @@ import numpy as np
13
13
  import pandas as pd
14
14
 
15
15
 
16
- def load_nexus_fault_mult_table(file_name):
17
- """Reads a Nexus (!) format file containing one or more MULT keywords and returns a dataframe with the MULT rows."""
16
+ def load_nexus_fault_mult_table_from_list(file_as_list):
17
+ """Reads a Nexus (!) format list of file contents containing one or more MULT keywords and returns a dataframe with the MULT rows."""
18
18
 
19
19
  def is_number(s):
20
20
  try:
@@ -41,70 +41,18 @@ def load_nexus_fault_mult_table(file_name):
41
41
 
42
42
  face_dict = {'TX': 'I', 'TY': 'J', 'TZ': 'K', 'TI': 'I', 'TJ': 'J', 'TK': 'K'}
43
43
 
44
- if os.path.isfile(file_name):
45
- chunks = []
46
- with open(file_name) as f:
47
- for line in f:
48
- if len(line.strip()):
49
- if (not line.strip()[0] == '!') & (not line.strip()[0] == 'C'):
50
- line = line.partition('!')[0] # removing trailing comments
51
- # line = line.partition('C')[0] # removing trailing comments
52
- tokens = line.split()
53
- if ISTABLE:
54
- if is_number(tokens[0]):
55
- ISRECORD = True
56
-
57
- if ISRECORD and (not is_number(tokens[0])):
58
- data = chunks[0:]
59
- d_elems = np.array([np.array(data[i].split()) for i in range(len(data))])
60
- # fill empty elements with zero
61
- lens = np.array([len(i) for i in d_elems])
62
- # Mask of valid places in each row
63
- mask = np.arange(lens.max()) < lens[:, None]
64
- # Setup output array and put elements from data into masked positions
65
- outdata = np.zeros(mask.shape, dtype = d_elems.dtype)
66
- outdata[mask] = np.concatenate(d_elems)
67
- df = pd.DataFrame(outdata)
68
- for column in df.columns:
69
- df[column] = pd.to_numeric(df[column], errors = 'ignore')
70
- df.columns = ['i1', 'i2', 'j1', 'j2', 'k1', 'k2', 'mult']
71
- df['grid'] = grid
72
- df['name'] = name
73
- df['face'] = face
74
- dfs.append(df)
75
- num_tables += 1
76
-
77
- ISTABLE = False
78
- ISRECORD = False
79
- chunks = []
80
-
81
- if ISTABLE:
82
- if re.match("(.*)GRID(.*)", tokens[0]):
83
- if len(tokens) > 0:
84
- grid = tokens[1]
85
- elif re.match("(.*)FNAME(.*)", tokens[0]):
86
- if len(tokens) > 0:
87
- name = tokens[1]
88
- else:
89
- if re.match(r"^MULT$", tokens[0]):
90
- ISTABLE = False
91
- ISRECORD = False
92
- chunks = []
93
- else:
94
- chunks.append(line.strip())
95
-
96
- if re.match(r"^MULT$", tokens[0]):
97
- if len(tokens) > 0:
98
- face = face_dict[tokens[1]]
99
- if 'MINUS' in tokens:
100
- face += '-' # indicates data apply to 'negative' faces of specified cells
101
- grid = 'ROOT' # nexus default
102
- name = 'NONE'
103
- ISTABLE = True
104
-
105
- else:
44
+ chunks = []
45
+ for line in file_as_list:
46
+ if len(line.strip()):
47
+ if (not line.strip()[0] == '!') & (not line.strip()[0] == 'C'):
48
+ line = line.partition('!')[0] # removing trailing comments
49
+ # line = line.partition('C')[0] # removing trailing comments
50
+ tokens = line.split()
106
51
  if ISTABLE:
107
- if ISRECORD:
52
+ if is_number(tokens[0]):
53
+ ISRECORD = True
54
+
55
+ if ISRECORD and (not is_number(tokens[0])):
108
56
  data = chunks[0:]
109
57
  d_elems = np.array([np.array(data[i].split()) for i in range(len(data))])
110
58
  # fill empty elements with zero
@@ -128,6 +76,59 @@ def load_nexus_fault_mult_table(file_name):
128
76
  ISRECORD = False
129
77
  chunks = []
130
78
 
79
+ if ISTABLE:
80
+ if re.match("(.*)GRID(.*)", tokens[0]):
81
+ if len(tokens) > 0:
82
+ grid = tokens[1]
83
+ elif re.match("(.*)FNAME(.*)", tokens[0]):
84
+ if len(tokens) > 0:
85
+ name = tokens[1]
86
+ else:
87
+ if re.match(r"^MULT$", tokens[0]):
88
+ ISTABLE = False
89
+ ISRECORD = False
90
+ chunks = []
91
+ else:
92
+ chunks.append(line.strip())
93
+
94
+ if re.match(r"^MULT$", tokens[0]):
95
+ if len(tokens) > 0:
96
+ face = face_dict[tokens[1]]
97
+ if 'MINUS' in tokens:
98
+ face += '-' # indicates data apply to 'negative' faces of specified cells
99
+ grid = 'ROOT' # nexus default
100
+ name = 'NONE'
101
+ ISTABLE = True
102
+
103
+ else:
104
+ if ISTABLE:
105
+ if ISRECORD:
106
+ data = chunks[0:]
107
+ d_elems = np.array([np.array(data[i].split()) for i in range(len(data))])
108
+ # fill empty elements with zero
109
+ lens = np.array([len(i) for i in d_elems])
110
+ # Mask of valid places in each row
111
+ mask = np.arange(lens.max()) < lens[:, None]
112
+ # Setup output array and put elements from data into masked positions
113
+ outdata = np.zeros(mask.shape, dtype = d_elems.dtype)
114
+ outdata[mask] = np.concatenate(d_elems)
115
+ df = pd.DataFrame(outdata)
116
+ for column in df.columns:
117
+ df[column] = pd.to_numeric(df[column], errors = 'ignore')
118
+ df.columns = ['i1', 'i2', 'j1', 'j2', 'k1', 'k2', 'mult']
119
+ df['grid'] = grid
120
+ df['name'] = name
121
+ df['face'] = face
122
+ dfs.append(df)
123
+ num_tables += 1
124
+
125
+ ISTABLE = False
126
+ ISRECORD = False
127
+ chunks = []
128
+
129
+ if not dfs:
130
+ return pd.DataFrame()
131
+
131
132
  fault_df = pd.concat(dfs).reset_index(drop = True)
132
133
 
133
134
  convert_dict = {'i1': int, 'i2': int, 'j1': int, 'j2': int, 'k1': int, 'k2': int, 'mult': float}
@@ -136,7 +137,10 @@ def load_nexus_fault_mult_table(file_name):
136
137
  return fault_df
137
138
 
138
139
 
139
- def load_nexus_fault_mult_table_new(file_name):
140
+ def load_nexus_fault_mult_table(file_name):
140
141
  """Reads a Nexus (!) format file containing one or more MULT keywords and returns a dataframe with the MULT rows."""
141
142
 
142
- return load_nexus_fault_mult_table(file_name)
143
+ with open(file_name) as f:
144
+ file_as_list = f.readlines()
145
+
146
+ return load_nexus_fault_mult_table_from_list(file_as_list)