Source code for resqpy.surface._mesh

"""Mesh class based on RESQML Grid2dRepresentation class."""

# RMS and ROXAR are registered trademarks of Roxar Software Solutions AS, an Emerson company
# GOCAD is also a trademark of Emerson

import logging

log = logging.getLogger(__name__)

import warnings
import math as maths
import numpy as np

import resqpy.crs as rqc
import resqpy.grid as grr
import resqpy.olio.uuid as bu
import resqpy.olio.vector_utilities as vec
import resqpy.olio.write_hdf5 as rwh5
import resqpy.olio.xml_et as rqet
import resqpy.property as rqp
import resqpy.weights_and_measures as wam
import resqpy.surface
import resqpy.surface._base_surface as rqsb
import resqpy.surface._surface as rqss
from resqpy.olio.xml_namespaces import curly_namespace as ns
from resqpy.olio.zmap_reader import read_mesh


class Mesh(rqsb.BaseSurface):
    """Class covering meshes (lattices: surfaces where points form a 2D grid; RESQML obj_Grid2dRepresentation)."""

    resqml_type = 'Grid2dRepresentation'

[docs] def __init__(self, parent_model, uuid = None, mesh_file = None, mesh_format = None, mesh_flavour = 'explicit', xyz_values = None, nj = None, ni = None, origin = None, dxyz_dij = None, z_values = None, z_supporting_mesh_uuid = None, surface_role = 'map', crs_uuid = None, title = None, originator = None, extra_metadata = None): """Initialises a Mesh object from xml, or a regular mesh from arguments. arguments: parent_model (model.Model object): the model to which this Mesh object will be associated uuid (uuid.UUID, optional): the uuid of an existing RESQML obj_Grid2dRepresentation object from which this resqpy Mesh object is populated mesh_file (string, optional): file name, required if initialising from an RMS text or zmap+ ascii file mesh_format (string, optional): 'rms' or 'zmap', required if initialising from an ascii file mesh_flavour (string, default 'explicit'): required flavour when reading from a mesh file; one of: 'explicit', 'regular' (z values discarded), 'reg&z', 'ref&z' xyz_values (numpy int array of shape (nj, ni, 3), optional): can be used to create an explicit mesh directly from the full array of points nj (int, optional): when generating a regular or 'ref&z' mesh, the number of nodes (NB. not 'cells') in the j axis of the regular mesh ni (int, optional): the number of nodes in the i axis of the regular or ref&z mesh origin (triple float, optional): the xyz origin of the regular mesh; use z value of zero if irrelevant dxyz_dij (numpy float array of shape (2, 3), optional): the xyz increment for each step in i and j axes; use z increments of zero if not applicable; eg. [[50.0, 0.0, 0.0], [0.0, 50.0, 0.0]] for mesh with 50 (m or ft) spacing where the I axis aligns with x axis and the J axis aligns with y axis; first of the two triplets relates to the I axis z_values (numpy int array of shape (nj, ni), optional): z values used when creating a ref&z flavour mesh; z_supporting_mesh_uuid must also be supplied z_supporting_mesh_uuid (uuid.UUID, optional): used to specify the supporting mesh when creating a ref&z or reg&z flavour mesh; z_values must also be supplied surface_role (string, default 'map'): 'map' or 'pick'; ignored if uuid is not None crs_uuid (uuid.Uuid or string, optional): required if generating a regular mesh, the uuid of the crs title (str, optional): the citation title to use for a new mesh; ignored if uuid is not None originator (str, optional): the name of the person creating the mesh, defaults to login id; ignored if uuid is not None extra_metadata (dict, optional): string key, value pairs to add as extra metadata for the mesh; ignored if uuid is not None returns: the newly created Mesh object notes: a mesh is a set of x,y,z (or x,y) points organised into a 2D lattice indexed by j,i; the z values are sometimes not applicable and then can be set to zero; 3 flavours of mesh are supported (the RESQML standard might allow for others): regular, where a constant xyz delta is applied with each step in i and j, starting from an origin, to yield a planar surface; explicit, where the full xyz (or xy) data is held as an array; 'ref & z', where another mesh is referred to for xy data, and z values are held in an array; there are 5 ways to initialise a Mesh object, in order of precedence: 1. pass uuid to initialise from xml 2. pass mesh_file, mesh_format and crs_uuid to load an explicit mesh from an ascii file 3. pass xyz_values and crs_uuid to create an explicit mesh from a numpy array 4. pass nj, ni, origin, dxyz_dij and crs_uuid to initialise a regular mesh directly 5. pass z_values, z_supporting_mesh_uuid and crs_uuid to initialise a 'ref & z' mesh 6. leave all optional arguments as None for an empty Mesh object :meta common: """ assert surface_role in ['map', 'pick'] self.surface_role = surface_role self.ni = None # NB: these are the number of nodes (points) in the mesh, unlike 3D grids self.nj = None self.flavour = None # 'explicit', 'regular', 'ref&z' or 'reg&z' self.full_array = None # loaded on demand, shape (NJ, NI, 3 or 2) being xyz or xy points at each node self.explicit_h5_key_pair = None self.regular_origin = None # xyz of origin of regular mesh self.regular_dxyz_dij = None # numpy array of shape (2, 3) being xyz increment with each step in i, j self.ref_uuid = None self.ref_mesh = None self.ref_z_h5_key_pair = None # note: in this class, z values for ref&z meshes are held in the full_array (xy data will be duplicated in memory) self.crs_uuid = crs_uuid self.represented_interpretation_root = None super().__init__(model = parent_model, uuid = uuid, title = title, originator = originator, extra_metadata = extra_metadata) if self.root is not None: pass elif mesh_file and mesh_format and crs_uuid is not None: self.__load_from_mesh_file(mesh_file, mesh_flavour, mesh_format, crs_uuid, ni, nj) elif xyz_values is not None and crs_uuid is not None: self.__load_from_xyz_values(xyz_values) else: self.__load_from_arguments(ni, nj, origin, dxyz_dij, z_values, z_supporting_mesh_uuid, crs_uuid) assert self.crs_uuid is not None if not self.title: self.title = 'mesh'
[docs] @classmethod def from_regular_grid_column_property(cls, parent_model, grid_uuid, property_uuid): """Creates a reg&z flavoured Mesh from an aligned regular grid column property. arguments: parent_model (Model): the model that the property is part of and the new Mesh will be part of grid_uuid (UUID): the uuid of the RegularGrid object property_uuid (UUID): the uuid of a property whose supporting representation is a RegularGrid and where the indexable elements are columns returns: a new Mesh object with flavour reg&z, the xy regular points are the column centres and the z values are the property values notes: the grid's (constant) dx, dy & dz properties must be avaiable in the model; the property must not be a points property and must have a count of 1; discrete property data will be converted to continuous (float) data; if the property uom is of quantity class length, a suitable crs will be used for the mesh, otherwise the grid's crs will be used and a uom extra metadata item will be added; calling code will need to call the write_hdf5() and create_xml() methods for the mesh """ grid = grr.RegularGrid(parent_model, uuid = grid_uuid) assert grid is not None assert grid.is_aligned prop = rqp.Property(parent_model, uuid = property_uuid, support_uuid = grid_uuid) assert prop is not None assert prop.indexable_element() == 'columns' and prop.count() == 1 and not prop.is_points() prop_array = prop.array_ref().astype(float) assert prop_array.shape == (grid.nj, grid.ni) crs = grid.crs em_uom = None if prop.is_continuous(): uom = prop.uom() if uom != grid.crs.z_units and uom in wam.valid_uoms(quantity = 'length'): # create a copy of the grid's crs with the z units set to the property's uom crs = rqc.Crs(parent_model, uuid = grid.crs_uuid) crs.uuid = bu.new_uuid() crs.z_units = uom crs.create_xml(reuse = True) em_uom = uom else: # use grid's crs and add extra metadata item em_uom = 'Euc' dxyz_dij = np.zeros((2, 3), dtype = float) dxyz_dij[0, 0] = grid.block_dxyz_dkji[2, 0] dxyz_dij[1, 1] = grid.block_dxyz_dkji[1, 1] assert dxyz_dij[0, 0] != 0.0 and dxyz_dij[1, 1] != 0.0 em = prop.extra_metadata if em is None: em = {} if em_uom is not None: em['Uom'] = em_uom mesh = cls(parent_model, mesh_flavour = 'reg&z', nj = grid.nj, ni = grid.ni, origin = (0.5 * dxyz_dij[0, 0], 0.5 * dxyz_dij[1, 1], 0.0), dxyz_dij = dxyz_dij, z_values = prop_array, surface_role = 'map', crs_uuid = crs.uuid, title = prop.title, extra_metadata = em) return mesh
[docs] def set_represented_interpretation_root(self, interp_root): """Makes a note of the xml root of the represented interpretation.""" self.represented_interpretation_root = interp_root
[docs] def full_array_ref(self): """Populates a full 2D(+1) numpy array of shape (nj, ni, 3) with xyz values, caches and returns. note: z values may be zero or not applicable when using the mesh as support for properties. """ if self.full_array is not None: return self.full_array if self.flavour == 'explicit': self.__full_array_ref_explicit() elif self.flavour in ['regular', 'reg&z']: self.__full_array_ref_regular() elif self.flavour == 'ref&z': self.__full_array_ref_refz() else: raise Exception(f'unrecognised mesh flavour when fetching full array: {self.flavour}') return self.full_array
[docs] def surface(self, quad_triangles = False): """Returns a surface object generated from this mesh.""" return rqss.Surface(self.model, crs_uuid = self.crs_uuid, mesh = self, quad_triangles = quad_triangles)
[docs] def write_hdf5(self, file_name = None, mode = 'a', use_xy_only = False): """Create or append to an hdf5 file, writing datasets for the mesh depending on flavour.""" if not file_name: file_name = self.model.h5_file_name() if self.uuid is None: self.uuid = bu.new_uuid() if self.flavour == 'regular': return # NB: arrays must have been set up prior to calling this function h5_reg = rwh5.H5Register(self.model) a = self.full_array_ref() if self.flavour == 'explicit': if use_xy_only: h5_reg.register_dataset(self.uuid, 'points', a[..., :2]) # todo: check what others use here else: h5_reg.register_dataset(self.uuid, 'points', a) elif self.flavour == 'ref&z' or self.flavour == 'reg&z': h5_reg.register_dataset(self.uuid, 'zvalues', a[..., 2]) else: log.error('bad mesh flavour when writing hdf5 array') h5_reg.write(file_name, mode = mode)
[docs] def create_xml(self, ext_uuid = None, use_xy_only = False, add_as_part = True, add_relationships = True, title = None, originator = None): """Creates a grid 2d representation xml node from this mesh object and optionally adds as part of model. arguments: ext_uuid (uuid.UUID, optional): the uuid of the hdf5 external part holding the mesh array use_xy_only (boolean, default False): if True and the flavour of this mesh is explicit, only the xy coordinates are stored in the hdf5 dataset, otherwise xyz are stored add_as_part (boolean, default True): if True, the newly created xml node is added as a part in the model add_relationships (boolean, default True): if True, a relationship xml part is created relating the new grid 2d part to the crs part (and optional interpretation part), and to the represented interpretation if present title (string, optional): used as the citation Title text; should be meaningful to a human originator (string, optional): the name of the human being who created the grid 2d representation part; default is to use the login name returns: the newly created grid 2d representation (mesh) xml node """ if ext_uuid is None and self.flavour != 'regular': ext_uuid = self.model.h5_uuid() g2d_node = super().create_xml(add_as_part = False, title = title, originator = originator) p_node = self.__create_xml_basics(g2d_node) ref_root = None if self.flavour == 'regular': self.__create_xml_regular(p_node) elif self.flavour == 'ref&z': self.__create_xml_refandz(p_node, ext_uuid) elif self.flavour == 'reg&z': self.__create_xml_regandz(p_node, ext_uuid) elif self.flavour == 'explicit': self.__create_xml_explicit(p_node, ext_uuid, use_xy_only) else: log.error('mesh has bad flavour when creating xml') return None if add_as_part: self.__create_xml_add_parts(g2d_node, ref_root, add_relationships, ext_uuid) return g2d_node
def __load_from_arguments(self, ni, nj, origin, dxyz_dij, z_values, z_supporting_mesh_uuid, crs_uuid): assert ni is not None and nj is not None, 'If loading from arguments ni and nj must be provided' if z_values is None: assert origin is not None and dxyz_dij is not None and crs_uuid is not None, 'origin, dxyz_dij and crs_uuid must be provided for regular mesh' self.__regular_mesh_from_arguments(ni, nj, origin, dxyz_dij, z_values) else: if z_supporting_mesh_uuid is not None: self.__refz_from_arguments(ni, nj, z_values, z_supporting_mesh_uuid) else: assert dxyz_dij is not None and crs_uuid is not None and origin is not None, 'dzy_dij, crs_uuid and origin must be provided for regular and z mesh' self.__regz_from_arguments(ni, nj, z_values, origin, dxyz_dij) def __regz_from_arguments(self, ni, nj, z_values, origin, dxyz_dij): # create a reg&z mesh from arguments assert nj > 0 and ni > 0 assert len(origin) == 3 assert dxyz_dij.shape == (2, 3) assert z_values.shape == (nj, ni) or z_values.shape == (nj * ni,) self.nj = nj self.ni = ni self.regular_origin = origin self.regular_dxyz_dij = np.array(dxyz_dij, dtype = float) assert self.crs_uuid is not None, 'crs uuid missing' self.flavour = 'regular' self.full_array = None _ = self.full_array_ref() self.full_array[..., 2] = z_values.reshape(tuple(self.full_array.shape[:-1])) self.flavour = 'reg&z' def __refz_from_arguments(self, ni, nj, z_values, z_supporting_mesh_uuid): # create a ref&z mesh from arguments assert nj > 0 and ni > 0 assert z_values.shape == (nj, ni) or z_values.shape == (nj * ni,) self.flavour = 'ref&z' self.nj = nj self.ni = ni self.ref_uuid = z_supporting_mesh_uuid self.ref_mesh = Mesh(self.model, uuid = z_supporting_mesh_uuid) assert self.ref_mesh is not None assert self.ref_mesh.nj == nj and self.ref_mesh.ni == ni self.full_array = self.ref_mesh.full_array_ref().copy() self.full_array[..., 2] = z_values.reshape(tuple(self.full_array.shape[:-1])) assert self.crs_uuid is not None, 'crs uuid missing' def __regular_mesh_from_arguments(self, ni, nj, origin, dxyz_dij, z_values): # create a regular mesh from arguments assert nj > 0 and ni > 0 assert len(origin) == 3 assert dxyz_dij.shape == (2, 3) self.flavour = 'regular' self.nj = nj self.ni = ni self.regular_origin = origin self.regular_dxyz_dij = np.array(dxyz_dij, dtype = float) assert self.crs_uuid is not None, 'crs uuid missing' def __load_from_xyz_values(self, xyz_values): # create an explicit mesh directly from a numpy array of points assert xyz_values.ndim == 3 and xyz_values.shape[2] == 3 and xyz_values.shape[0] > 1 and xyz_values.shape[1] > 1 self.flavour = 'explicit' self.nj = xyz_values.shape[0] self.ni = xyz_values.shape[1] self.full_array = xyz_values.copy() assert self.crs_uuid is not None, 'crs uuid missing' def __load_from_mesh_file(self, mesh_file, mesh_flavour, mesh_format, crs_uuid, ni, nj): # load an explicit mesh from an ascii file in RMS text or zmap format assert mesh_format in ['rms', 'roxar', 'zmap'] # 'roxar' is treated synonymously with 'rms' assert mesh_flavour in ['explicit', 'regular', 'reg&z', 'ref&z'] x, y, z = read_mesh(mesh_file, format = mesh_format) self.flavour = mesh_flavour self.nj = z.shape[0] self.ni = z.shape[1] assert self.nj > 1 and self.ni > 1 self.full_array = np.stack((x, y, z), axis = -1) if mesh_flavour != 'explicit': min_x = x.flatten()[0] max_x = x.flatten()[-1] min_y = y.flatten()[0] max_y = y.flatten()[-1] dxyz_dij = np.array([[(max_x - min_x) / (self.ni - 1), 0.0, 0.0], [0.0, (max_y - min_y) / (self.nj - 1), 0.0]], dtype = float) if mesh_flavour in ['regular', 'reg&z']: self.regular_origin = (min_x, min_y, 0.0) self.regular_dxyz_dij = dxyz_dij elif mesh_flavour == 'ref&z': self.ref_mesh = Mesh(self.model, ni = self.ni, nj = self.nj, origin = self.regular_origin, dxyz_dij = dxyz_dij, crs_uuid = crs_uuid) assert self.ref_mesh is not None assert self.ref_mesh.nj == nj and self.ref_mesh.ni == ni else: log.critical('code failure') assert self.crs_uuid is not None, 'crs uuid missing' # todo: option to create a regular and ref&z pair instead of an explicit mesh def __load_from_xml_regular(self, point_node): self.flavour = 'regular' origin_node = rqet.find_tag(point_node, 'Origin') assert origin_node is not None, 'origin missing in xml for regular mesh (lattice)' # yapf: disable self.regular_origin = (rqet.find_tag_float(origin_node, 'Coordinate1'), rqet.find_tag_float(origin_node, 'Coordinate2'), rqet.find_tag_float(origin_node, 'Coordinate3')) # yapf: enable offset_nodes = rqet.list_of_tag(point_node, 'Offset') # first occurrence for FastestAxis, ie. I; 2nd for J assert len(offset_nodes) == 2, 'missing (or too many) offset nodes in xml for regular mesh (lattice)' self.regular_dxyz_dij = np.empty((2, 3)) for j_or_i in range(2): # 0 = J, 1 = I axial_offset_node = rqet.find_tag(offset_nodes[j_or_i], 'Offset') assert axial_offset_node is not None, 'missing offset offset node in xml' self.regular_dxyz_dij[1 - j_or_i] = (rqet.find_tag_float(axial_offset_node, 'Coordinate1'), rqet.find_tag_float(axial_offset_node, 'Coordinate2'), rqet.find_tag_float(axial_offset_node, 'Coordinate3')) if not maths.isclose(vec.dot_product(self.regular_dxyz_dij[1 - j_or_i], self.regular_dxyz_dij[1 - j_or_i]), 1.0): log.warning('non-orthogonal axes and/or scaling in xml for regular mesh (lattice)') spacing_node = rqet.find_tag(offset_nodes[j_or_i], 'Spacing') stride = rqet.find_tag_float(spacing_node, 'Value') count = rqet.find_tag_int(spacing_node, 'Count') assert stride is not None and count is not None, 'missing spacing info in xml' assert count == (self.nj, self.ni)[j_or_i] - 1, \ 'unexpected value for count in xml spacing info for regular mesh (lattice)' assert stride > 0.0, 'spacing distance is not positive in xml for regular mesh (lattice)' self.regular_dxyz_dij[1 - j_or_i] *= stride def __load_from_xml_explicit(self, point_node): self.flavour = 'explicit' self.explicit_h5_key_pair = self.model.h5_uuid_and_path_for_node(point_node, tag = 'Coordinates') # load full_array on demand later (see full_array_ref() method) def __load_from_xml_refz(self, support_geom_node): self.flavour = 'ref&z' # assert rqet.node_type(support_geom_node) == 'Point3dFromRepresentationLatticeArray' # only this supported for now self.ref_uuid = rqet.find_nested_tags_text(support_geom_node, ['SupportingRepresentation', 'UUID']) assert self.ref_uuid, 'missing supporting representation info in xml for z-value mesh' self.ref_mesh = Mesh(self.model, uuid = self.ref_uuid) assert self.nj == self.ref_mesh.nj and self.ni == self.ref_mesh.ni # only this supported for now niosr_node = rqet.find_tag(support_geom_node, 'NodeIndicesOnSupportingRepresentation') start_value = rqet.find_tag_int(niosr_node, 'StartValue') assert start_value == 0, 'only full use of supporting mesh catered for at present' offset_nodes = rqet.list_of_tag(niosr_node, 'Offset') # first occurrence for FastestAxis, ie. I; 2nd for J assert len(offset_nodes) == 2, 'missing (or too many) offset nodes in xml for regular mesh (lattice)' for j_or_i in range(2): # 0 = J, 1 = I assert rqet.node_type(offset_nodes[j_or_i]) == 'IntegerConstantArray', 'variable step not catered for' assert rqet.find_tag_int(offset_nodes[j_or_i], 'Value') == 1, 'step other than 1 not catered for' count = rqet.find_tag_int(offset_nodes[j_or_i], 'Count') assert count == (self.nj, self.ni)[j_or_i] - 1, \ 'unexpected value for count in xml spacing info for regular mesh (lattice)' def __load_from_xml_regz(self, support_geom_node): self.flavour = 'reg&z' orig_node = rqet.find_tag(support_geom_node, 'Origin') self.regular_origin = (rqet.find_tag_float(orig_node, 'Coordinate1'), rqet.find_tag_float(orig_node, 'Coordinate2'), rqet.find_tag_float(orig_node, 'Coordinate3')) assert self.regular_origin is not None, 'origin missing in xml for reg&z mesh (lattice)' offset_nodes = rqet.list_of_tag(support_geom_node, 'Offset') # first occurrence for FastestAxis, ie. I; 2nd for J assert len(offset_nodes) == 2, 'missing (or too many) offset nodes in xml for reg&z mesh (lattice)' self.regular_dxyz_dij = np.empty((2, 3)) for j_or_i in range(2): # 0 = J, 1 = I axial_offset_node = rqet.find_tag(offset_nodes[j_or_i], 'Offset') assert axial_offset_node is not None, 'missing offset offset node in xml' self.regular_dxyz_dij[1 - j_or_i] = (rqet.find_tag_float(axial_offset_node, 'Coordinate1'), rqet.find_tag_float(axial_offset_node, 'Coordinate2'), rqet.find_tag_float(axial_offset_node, 'Coordinate3')) if not maths.isclose(vec.dot_product(self.regular_dxyz_dij[1 - j_or_i], self.regular_dxyz_dij[1 - j_or_i]), 1.0): log.warning('non-orthogonal axes and/or scaling in xml for regular mesh (lattice)') spacing_node = rqet.find_tag(offset_nodes[j_or_i], 'Spacing') stride = rqet.find_tag_float(spacing_node, 'Value') count = rqet.find_tag_int(spacing_node, 'Count') assert stride is not None and count is not None, 'missing spacing info in xml' assert count == (self.nj, self.ni)[j_or_i] - 1, \ 'unexpected value for count in xml spacing info for regular mesh (lattice)' assert stride > 0.0, 'spacing distance is not positive in xml for regular mesh (lattice)' self.regular_dxyz_dij[1 - j_or_i] *= stride def __load_from_xml_basics(self): root_node = self.root assert root_node is not None self.surface_role = rqet.find_tag_text(root_node, 'SurfaceRole') ref_node = rqet.find_tag(root_node, 'RepresentedInterpretation') if ref_node is not None: self.represented_interpretation_root = self.model.referenced_node(ref_node) patch_node = rqet.find_tag(root_node, 'Grid2dPatch') assert rqet.find_tag_int(patch_node, 'PatchIndex') == 0 self.ni = rqet.find_tag_int(patch_node, 'FastestAxisCount') self.nj = rqet.find_tag_int(patch_node, 'SlowestAxisCount') assert self.ni is not None and self.nj is not None, 'mesh extent info missing in xml' geom_node = rqet.find_tag(patch_node, 'Geometry') assert geom_node is not None, 'geometry missing in mesh xml' self.crs_uuid = rqet.find_nested_tags_text(geom_node, ['LocalCrs', 'UUID']) assert self.crs_uuid is not None, 'crs reference missing in mesh geometry xml' point_node = rqet.find_tag(geom_node, 'Points') assert point_node is not None, 'missing Points node in mesh geometry xml' flavour = rqet.node_type(point_node) return point_node, flavour def _load_from_xml(self): point_node, flavour = self.__load_from_xml_basics() if flavour == 'Point3dLatticeArray': self.__load_from_xml_regular(point_node) elif flavour == 'Point3dZValueArray': # note: only simple, full use of supporting mesh is handled at present z_ref_node = rqet.find_tag(point_node, 'ZValues') self.ref_z_h5_key_pair = self.model.h5_uuid_and_path_for_node(z_ref_node, tag = 'Values') support_geom_node = rqet.find_tag(point_node, 'SupportingGeometry') if rqet.node_type(support_geom_node) == 'Point3dFromRepresentationLatticeArray': self.__load_from_xml_refz(support_geom_node) elif rqet.node_type(support_geom_node) == 'Point3dLatticeArray': self.__load_from_xml_regz(support_geom_node) elif flavour in ['Point3dHdf5Array', 'Point2dHdf5Array']: self.__load_from_xml_explicit(point_node) else: raise Exception('unrecognised flavour for mesh points') def __full_array_ref_explicit(self): # load array directly from hdf5 points reference; note: could be xyz or xy data assert self.explicit_h5_key_pair is not None, 'h5 key pair not established for mesh' try: self.model.h5_array_element(self.explicit_h5_key_pair, cache_array = True, object = self, array_attribute = 'full_array', dtype = 'float') # todo: could extend with z values of zero if only xy present? except Exception: log.exception('hdf5 points failure for mesh') def __full_array_ref_regular(self): self.full_array = np.empty((self.nj, self.ni, 3)) x_i0 = np.linspace(0.0, (self.nj - 1) * self.regular_dxyz_dij[1, 0], num = self.nj) y_i0 = np.linspace(0.0, (self.nj - 1) * self.regular_dxyz_dij[1, 1], num = self.nj) z_i0 = np.linspace(0.0, (self.nj - 1) * self.regular_dxyz_dij[1, 2], num = self.nj) x_full = np.linspace(x_i0, x_i0 + (self.ni - 1) * self.regular_dxyz_dij[0, 0], num = self.ni, axis = -1) y_full = np.linspace(y_i0, y_i0 + (self.ni - 1) * self.regular_dxyz_dij[0, 1], num = self.ni, axis = -1) z_full = np.linspace(z_i0, z_i0 + (self.ni - 1) * self.regular_dxyz_dij[0, 2], num = self.ni, axis = -1) self.full_array = np.stack((x_full, y_full, z_full), axis = -1) + self.regular_origin if self.flavour == 'reg&z': # overwrite regular z values with explicitly stored z values self.__full_array_ref_regz() def __full_array_ref_regz(self): assert self.ref_z_h5_key_pair is not None, 'h5 key pair missing for mesh z values' try: self.model.h5_array_element(self.ref_z_h5_key_pair, cache_array = True, object = self, array_attribute = 'temp_z', dtype = 'float') except Exception: log.exception('hdf5 failure for mesh z values') self.full_array[..., 2] = self.temp_z delattr(self, 'temp_z') def __full_array_ref_refz(self): # load array from referenced mesh and overwrite z values if self.ref_mesh is None: self.ref_mesh = Mesh(self.model, uuid = self.ref_uuid) assert self.ref_mesh is not None, 'failed to instantiate object for referenced mesh' self.full_array = self.ref_mesh.full_array_ref().copy() assert self.ref_z_h5_key_pair is not None, 'h5 key pair missing for mesh z values' try: self.model.h5_array_element(self.ref_z_h5_key_pair, cache_array = True, object = self, array_attribute = 'temp_z', dtype = 'float') except Exception: log.exception('hdf5 failure for mesh z values') self.full_array[..., 2] = self.temp_z # if np.any(np.isnan(self.temp_z)): log.warning('z values include some NaNs') delattr(self, 'temp_z') def __create_xml_regular(self, p_node): assert self.regular_origin is not None and self.regular_dxyz_dij is not None p_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dLatticeArray') self.model.create_solitary_point3d('Origin', p_node, self.regular_origin) # todo: check xml namespace for j_or_i in range(2): # 0 = J, 1 = I dxyz = self.regular_dxyz_dij[1 - j_or_i].copy() log.debug('dxyz: ' + str(dxyz)) d_value = vec.dot_product(dxyz, dxyz) assert d_value > 0.0 d_value = maths.sqrt(d_value) dxyz /= d_value o_node = rqet.SubElement(p_node, ns['resqml2'] + 'Offset') # note: 1st Offset is for J axis , 2nd for I o_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dOffset') o_node.text = '\n' self.model.create_solitary_point3d('Offset', o_node, dxyz) space_node = rqet.SubElement(o_node, ns['resqml2'] + 'Spacing') space_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'DoubleConstantArray') # nothing else catered for just now space_node.text = '\n' ov_node = rqet.SubElement(space_node, ns['resqml2'] + 'Value') ov_node.set(ns['xsi'] + 'type', ns['xsd'] + 'double') ov_node.text = str(d_value) oc_node = rqet.SubElement(space_node, ns['resqml2'] + 'Count') oc_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') if j_or_i: oc_node.text = str(self.ni - 1) else: oc_node.text = str(self.nj - 1) def __create_xml_refandz(self, p_node, ext_uuid): assert ext_uuid is not None assert self.ref_uuid is not None p_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dZValueArray') sg_node = rqet.SubElement(p_node, ns['resqml2'] + 'SupportingGeometry') sg_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dFromRepresentationLatticeArray') sg_node.text = '\n' niosr_node = rqet.SubElement(sg_node, ns['resqml2'] + 'NodeIndicesOnSupportingRepresentation') niosr_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntegerLatticeArray') niosr_node.text = '\n' sv_node = rqet.SubElement(niosr_node, ns['resqml2'] + 'StartValue') sv_node.set(ns['xsi'] + 'type', ns['xsd'] + 'integer') sv_node.text = '0' # no other possibility cater for at present for j_or_i in range(2): # 0 = J, 1 = I o_node = rqet.SubElement(niosr_node, ns['resqml2'] + 'Offset') o_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntegerConstantArray') # no other possibility cater for at present o_node.text = '\n' ov_node = rqet.SubElement(o_node, ns['resqml2'] + 'Value') ov_node.set(ns['xsi'] + 'type', ns['xsd'] + 'integer') ov_node.text = '1' # no other possibility cater for at present oc_node = rqet.SubElement(o_node, ns['resqml2'] + 'Count') oc_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') if j_or_i: oc_node.text = str(self.ni - 1) else: oc_node.text = str(self.nj - 1) ref_root = self.model.root_for_uuid(self.ref_uuid) self.model.create_ref_node('SupportingRepresentation', rqet.find_nested_tags_text(ref_root, ['Citation', 'Title']), self.ref_uuid, content_type = 'Grid2dRepresentation', root = sg_node) zv_node = rqet.SubElement(p_node, ns['resqml2'] + 'ZValues') zv_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'DoubleHdf5Array') zv_node.text = '\n' v_node = rqet.SubElement(zv_node, ns['resqml2'] + 'Values') v_node.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset') v_node.text = '\n' self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'zvalues', root = v_node) def __create_xml_regandz(self, p_node, ext_uuid): assert ext_uuid is not None p_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dZValueArray') sg_node = rqet.SubElement(p_node, ns['resqml2'] + 'SupportingGeometry') sg_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dLatticeArray') sg_node.text = '\n' assert self.regular_origin is not None and self.regular_dxyz_dij is not None self.model.create_solitary_point3d('Origin', sg_node, self.regular_origin) # todo: check xml namespace for j_or_i in range(2): # 0 = J, 1 = I; ie. J axis info first in xml, followed by I axis dxyz = self.regular_dxyz_dij[1 - j_or_i].copy() log.debug('dxyz: ' + str(dxyz)) d_value = vec.dot_product(dxyz, dxyz) assert d_value > 0.0 d_value = maths.sqrt(d_value) dxyz /= d_value o_node = rqet.SubElement(sg_node, ns['resqml2'] + 'Offset') o_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dOffset') o_node.text = '\n' self.model.create_solitary_point3d('Offset', o_node, dxyz) space_node = rqet.SubElement(o_node, ns['resqml2'] + 'Spacing') space_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'DoubleConstantArray') # nothing else catered for just now space_node.text = '\n' ov_node = rqet.SubElement(space_node, ns['resqml2'] + 'Value') ov_node.set(ns['xsi'] + 'type', ns['xsd'] + 'double') ov_node.text = str(d_value) oc_node = rqet.SubElement(space_node, ns['resqml2'] + 'Count') oc_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') if j_or_i: oc_node.text = str(self.ni - 1) else: oc_node.text = str(self.nj - 1) zv_node = rqet.SubElement(p_node, ns['resqml2'] + 'ZValues') zv_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'DoubleHdf5Array') zv_node.text = '\n' v_node = rqet.SubElement(zv_node, ns['resqml2'] + 'Values') v_node.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset') v_node.text = '\n' self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'zvalues', root = v_node) def __create_xml_explicit(self, p_node, ext_uuid, use_xy_only): assert ext_uuid is not None if use_xy_only: p_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point2dHdf5Array') else: p_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dHdf5Array') coords = rqet.SubElement(p_node, ns['resqml2'] + 'Coordinates') coords.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset') coords.text = '\n' self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'points', root = coords) def __create_xml_basics(self, g2d_node): if self.represented_interpretation_root is not None: interp_root = self.represented_interpretation_root interp_uuid = bu.uuid_from_string(interp_root.attrib['uuid']) interp_part = self.model.part_for_uuid(interp_uuid) self.model.create_ref_node('RepresentedInterpretation', rqet.find_nested_tags_text(interp_root, ['Citation', 'Title']), interp_uuid, content_type = self.model.type_of_part(interp_part), root = g2d_node) role_node = rqet.SubElement(g2d_node, ns['resqml2'] + 'SurfaceRole') role_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'SurfaceRole') role_node.text = self.surface_role patch_node = rqet.SubElement(g2d_node, ns['resqml2'] + 'Grid2dPatch') patch_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Grid2dPatch') patch_node.text = '\n' pi_node = rqet.SubElement(patch_node, ns['resqml2'] + 'PatchIndex') pi_node.set(ns['xsi'] + 'type', ns['xsd'] + 'nonNegativeInteger') pi_node.text = '0' fast_node = rqet.SubElement(patch_node, ns['resqml2'] + 'FastestAxisCount') fast_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') fast_node.text = str(self.ni) slow_node = rqet.SubElement(patch_node, ns['resqml2'] + 'SlowestAxisCount') slow_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') slow_node.text = str(self.nj) geom = rqet.SubElement(patch_node, ns['resqml2'] + 'Geometry') geom.set(ns['xsi'] + 'type', ns['resqml2'] + 'PointGeometry') geom.text = '\n' self.model.create_crs_reference(crs_uuid = self.crs_uuid, root = geom) p_node = rqet.SubElement(geom, ns['resqml2'] + 'Points') p_node.text = '\n' return p_node def __create_xml_add_parts(self, g2d_node, ref_root, add_relationships, ext_uuid): self.model.add_part('obj_Grid2dRepresentation', self.uuid, g2d_node) if add_relationships: crs_root = self.model.root_for_uuid(self.crs_uuid) self.model.create_reciprocal_relationship(g2d_node, 'destinationObject', crs_root, 'sourceObject') if self.represented_interpretation_root is not None: self.model.create_reciprocal_relationship(g2d_node, 'destinationObject', self.represented_interpretation_root, 'sourceObject') if ref_root is not None: # used for ref&z flavour self.model.create_reciprocal_relationship(g2d_node, 'destinationObject', ref_root, 'sourceObject') if self.flavour == 'ref&z' or self.flavour == 'explicit' or self.flavour == 'reg&z': ext_part = rqet.part_name_for_object('obj_EpcExternalPartReference', ext_uuid, prefixed = False) ext_node = self.model.root_for_part(ext_part) self.model.create_reciprocal_relationship(g2d_node, 'mlToExternalPartProxy', ext_node, 'externalPartProxyToMl')