misc.grid.cornerpoint

Common functionality for visualization of corner-point grids.

import pyresito.grid.cornerpoint as cp

  1"""Common functionality for visualization of corner-point grids.
  2
  3import pyresito.grid.cornerpoint as cp
  4"""
  5import itertools as it
  6import logging
  7import numpy as np
  8import numpy.ma as ma
  9
 10
 11# add a valid log in case we are not run through the main program which
 12# sets up one for us
 13log = logging.getLogger(__name__)  # pylint: disable=invalid-name
 14log.addHandler(logging.NullHandler())
 15
 16
 17def scatter(cell_field):
 18    """\
 19    Duplicate all items in every dimension.
 20
 21    Use this method to duplicate values associated with each cell to
 22    each of the corners in the cell.
 23
 24    :param cell_field:   Property per cell in the grid
 25    :type  cell_field:   :class:`numpy.ndarray`, shape = (nk, nj, ni)
 26    :return:             Property per corner in the cube
 27    :rtype:              :class:`numpy.ndarray`, shape = (nk, 2, nj, 2, ni, 2)
 28
 29    >>> scatter(np.array([[[1, 2],
 30                           [3, 4]],
 31                          [[5, 6],
 32                           [7, 8]]]))
 33
 34    array([[[[[[1, 1], [2, 2]],
 35              [[1, 1], [2, 2]]],
 36             [[[3, 3], [4, 4]],
 37              [[3, 3], [4, 4]]]],
 38
 39            [[[[1, 1], [2, 2]],
 40              [[1, 1], [2, 2]]],
 41             [[[3, 3], [4, 4]],
 42              [[3, 3], [4, 4]]]]],
 43
 44           [[[[[5, 5], [6, 6]],
 45              [[5, 5], [6, 6]]],
 46             [[[7, 7], [8, 8]],
 47              [[7, 7], [8, 8]]]],
 48
 49            [[[[5, 5], [6, 6]],
 50              [[5, 5], [6, 6]]],
 51             [[[7, 7], [8, 8]],
 52              [[7, 7], [8, 8]]]]]])
 53    """
 54    # get the dimensions of the cube in easily understood aliases
 55    nk, nj, ni = cell_field.shape  # pylint: disable=invalid-name
 56
 57    # create an extra dimension so that we can duplicate individual values
 58    flat = np.reshape(cell_field, (nk, nj, ni, 1))
 59
 60    # duplicate along each of the axis
 61    dup_i = np.tile(flat, (1, 1, 1, 2))
 62    dup_j = np.tile(dup_i, (1, 1, 2, 1))
 63    dup_k = np.tile(dup_j, (1, 2, 1, 1))
 64
 65    # reformat to a cube with the appropriate dimensions
 66    corn_field = np.reshape(dup_k, (nk, 2, nj, 2, ni, 2))
 67    return corn_field
 68
 69
 70def inner_dup(pillar_field):
 71    """Duplicate all inner items in both dimensions.
 72
 73    Use this method to duplicate values associated with each pillar to
 74    the corners on each side(s) of the pillar; four corners for all
 75    interior pillars, two corners for all pillars on the rim and only
 76    one (element) corner for those pillars that are on the grid corners.
 77
 78    :param pillar_field: Property per pillar in the grid
 79    :type  pillar_field: :class:`numpy.ndarray`, shape=(m+1, n+1)
 80    :returns:            Property per corner in a grid plane
 81    :rtype:              :class:`numpy.ndarray`, shape=(2*m, 2*n))
 82
 83    >>> inner_dup(np.array ([[1, 2, 3],
 84                             [4, 5, 6],
 85                             [7, 8, 9]]))
 86    array([[1, 2, 2, 3],
 87           [4, 5, 5, 6],
 88           [4, 5, 5, 6],
 89           [7, 8, 8, 9]])
 90    """
 91    # extract the inner horizontal part of the plane
 92    horz_part = pillar_field[:, 1:-1]
 93
 94    # tile it to a separate plane
 95    horz_dupd = np.tile(horz_part, (2, 1, 1))
 96
 97    # shift the tile to the end so it can be rolled
 98    # into the second dimension
 99    horz_shft = np.transpose(horz_dupd, (1, 2, 0))
100
101    # roll this into the second dimension
102    horz_roll = np.reshape(horz_shft,
103                           (horz_shft.shape[0],
104                            horz_shft.shape[1] * 2))
105
106    # add back the first and last column
107    horz = np.column_stack([pillar_field[:, 0],
108                            horz_roll,
109                            pillar_field[:, -1]])
110
111    # extract the inner vertical part of the plane
112    vert_part = horz[1:-1, :]
113
114    # tile it to a separate plane
115    vert_dupd = np.tile(vert_part, (1, 1, 2))
116
117    # roll this into the first dimension
118    vert_roll = np.reshape(vert_dupd,
119                           (vert_dupd.shape[1] * 2,
120                            vert_dupd.shape[2] // 2))
121
122    # add back the first and last row
123    result = np.vstack([horz[0, :],
124                        vert_roll,
125                        horz[-1, :]])
126    return result
127
128
129def elem_vtcs_ndcs(nk, nj, ni):  # pylint: disable=invalid-name
130    """List zcorn indices used by every element in an nk*nj*ni grid
131
132    :param nk: Number of layers in Z direction
133    :type nk:  int
134    :param nj: Number of elements in the Y direction
135    :type nj:  int
136    :param ni: Number of elements in the X direction
137    :type ni:  int
138    :returns:  Zero-based indices for the hexahedral element corners
139    :rtype:    :class:`numpy.ndarray`, shape=(nk*nj*ni, 8), dtype=int
140    """
141    # hex_perm is the order a hexahedron should be specified to the
142    # gridding engine (it is the same for VTK and Ensight Gold formats)
143    # relative to the order they are in the Eclipse format. the latter
144    # four is listed first because we turn the z order around with a
145    # transform.
146    # local indices:      0, 1, 2, 3, 4, 5, 6, 7
147    hex_perm = np.array([4, 5, 7, 6, 0, 1, 3, 2], dtype=int)
148
149    # kji is the global (k, j, i) index for every element; we tile
150    # this into 8 corners per element, with 3 indices per corner;
151    # this this matrix contains repeats of 8 identical permutations
152    # of the values 0..(ni-1), 0..(nj-1) and 0..(nk-1)
153    kji = np.array(list(it.product(range(nk),
154                                   range(nj),
155                                   range(ni))))
156    kji = np.reshape(np.tile(kji, [1, hex_perm.shape[0]]), [-1, 3])
157
158    # bfr is the local (bottom, front, right) index for every corner;
159    # this is a block of 8 local corners which are picked out of the
160    # zcorn matrix to then correspond with the gridding engines order.
161    # we tile this block for every element
162    bfr = np.array(list(it.product([0, 1], repeat=3)))[hex_perm, :]
163    bfr = np.tile(bfr, [nk*nj*ni, 1])
164
165    # calculate running number for every corner, combining the element
166    # indices kji with the local corner indices bfr. the zcorn hypercube
167    # has dimensions nk * 2 * nj * 2 * ni * 2; the indexing is simply a
168    # flattened view of this hypercube.
169    ndx = (((2*kji[:, 0] + bfr[:, 0]) * 2*nj +
170            (2*kji[:, 1] + bfr[:, 1])) * 2*ni +
171           2*kji[:, 2] + bfr[:, 2])
172
173    # we then collect the indices so that we get 8 of them (all those that
174    # belongs to the same element) on a row
175    ndx = np.reshape(ndx, [-1, hex_perm.shape[0]])
176    return ndx
177
178
179# pylint: disable=invalid-name, multiple-statements, too-many-locals
180def corner_coordinates(coord, zcorn):
181    """Generate (x, y, z) coordinates for each corner-point.
182
183    :param coord: Pillar geometrical information
184    :type coord:  :class:`numpy.ndarray`, shape=(nj+1, ni+1, 2, 3)
185    :param zcorn: Depth values along each pillar
186    :type zcorn:  :class:`numpy.ndarray`, shape=(nk, 2, nj, 2, ni, 2)
187    :returns:     Coordinate values for each corner
188    :rtype:       :class:`numpy.ndarray`, shape=(3, nk*2*nj*2*ni*2)
189    """
190    # indices to treat the pillar matrix as a record
191    X = 0
192    Y = 1
193    Z = 2
194    START = 0
195    END = 1
196
197    # calculate depth derivatives for the pillar slope
198    dx = coord[:, :, END, X] - coord[:, :, START, X]
199    dy = coord[:, :, END, Y] - coord[:, :, START, Y]
200    dz = coord[:, :, END, Z] - coord[:, :, START, Z]
201
202    # make the pillar information compatible with a plane of corners,
203    # having a singleton dimension at the front (instead tiling 2*nk)
204    x0 = inner_dup(coord[:, :, START, X])[None, :, :]
205    y0 = inner_dup(coord[:, :, START, Y])[None, :, :]
206    z0 = inner_dup(coord[:, :, START, Z])[None, :, :]
207    dxdz = inner_dup(dx / dz)[None, :, :]
208    dydz = inner_dup(dy / dz)[None, :, :]
209
210    # infer the grid size from the dimensions of coord
211    nj = coord.shape[0] - 1
212    ni = coord.shape[1] - 1
213
214    # make the formal dimensions of the depths compatible with the plane
215    # of vectors created from the pillars; notice that the first dimension
216    # is not singular
217    z = np.reshape(zcorn, (-1, nj*2, ni*2))
218
219    # calculate the point in the plane where the horizon intersect with the
220    # pillar; we get the (x, y)-coordinates for each corner. notice that the
221    # difference z - z0 is for each point, whereas dz is for the pillar!
222    x = (z - z0) * dxdz + x0
223    y = (z - z0) * dydz + y0
224
225    # reformat the coordinates into the final coordinates
226    xyz = np.vstack([np.reshape(x, (1, -1)),
227                     np.reshape(y, (1, -1)),
228                     np.reshape(z, (1, -1))])
229    return xyz
230
231
232# Enumeration of the dimensions in a point tuple
233Dim = type('Dim', (), {
234    'X': 0,
235    'Y': 1,
236    'Z': 2,
237})
238
239
240# Enumeration of the faces of a hexahedron. The contents are the local
241# indices in each row of 8 corners that are set up for the elements by
242# the elem_vtcs_ndcs function. (The indices here are the numbers that
243# are in the comment "local indices" above the hex_perm selector)
244Face = type('Face', (), {
245    'DOWN':  np.array([0, 1, 2, 3]),
246    'UP':    np.array([4, 5, 6, 7]),
247    'FRONT': np.array([4, 7, 3, 0]),
248    'BACK':  np.array([5, 6, 2, 1]),
249    'LEFT':  np.array([7, 6, 2, 3]),
250    'RIGHT': np.array([4, 5, 1, 0]),
251    'ALL':   np.array([0, 1, 2, 3, 4, 5, 6, 7]),
252})
253
254
255def cp_cells(grid, face):
256    """
257    Make a cell array from a cornerpoint grid. The cells will be in the
258    same order as the Cartesian enumeration of the grid.
259
260    :param grid: Pillar coordinates and corner depths
261    :type grid:  dict, with 'coord' and 'zcorn' properties
262    :param face: Which face that should be extracted, e.g. Face.UP
263    :type face:  Face enum
264    :returns:    Set of geometrical objects that can be sent to rendering
265    :rtype:      dict with 'points', shape (nverts, 3), and 'cells', shape
266                 (nelems, ncorns), where ncorns is either 8 (hexahedron
267                 volume or 4 (quadrilateral face), depending on the face
268                 parameter.
269    """
270    src = {}
271
272    log.debug("Generating points for corner depths")
273    # notice that VTK wants shape (nverts, 3), where as the code sets up
274    # for Ensight format which is (3, nverts).
275    src['points'] = corner_coordinates(grid['COORD'],
276                                       grid['ZCORN']).T.copy()
277
278    # order of the dimensions are different from the structure to the call
279    ni, nj, nk = grid['DIMENS']
280
281    # extract only the columns of the face we want to show. the shape was
282    # originally (nelem, 8). copy is necessary to get a contiguous array
283    # for VTK
284    log.debug("Generating cells")
285    src['cells'] = elem_vtcs_ndcs(nk, nj, ni)[:, face].copy()
286    return src
287
288
289def cell_filter(grid, func):
290    """Create a filter for a specific grid.
291
292    :param grid:   Grid structure that should be filtered
293    :type  grid:   dict
294    :param func:   Lambda function that takes i, j, k indices (one-based)
295                   and returns boolean for whether the cells should be
296                   included or not. The function should be vectorized.
297    :type  func:   Callable[(int, int, int), bool]
298
299    >>> layr = cell_filter (grid, lamba i, j, k: np.greater_equal (k, 56))
300    """
301    # extract the dimensions; notice that the i-dimensions is first
302    # in this list, since it is directly from the grid
303    ni, nj, nk = grid['DIMENS']  # pylint: disable=invalid-name
304
305    # setup a grid of the i, j and k address for each of the cells;
306    # notice that now the order of the dimensions are swapped so that
307    # they fit with the memory layout of a loaded Numpy array
308    kji = np.mgrid[1:(nk+1), 1:(nj+1), 1:(ni+1)]
309
310    # call the filter function on all these addresses, and simply
311    # return the boolean array of those
312    filter_flags = func(kji[2], kji[1], kji[0])
313    masked = filter_flags.astype(np.bool)
314
315    # get the mask of active cells, and combine this with the masked
316    # cells from the filter, giving us a flag for all visible nodes
317    active = grid['ACTNUM']
318    visible = np.logical_and(active, masked)
319
320    return visible
321
322
323def face_coords(grid):
324    """Get (x, y, z) coordinates for each corner-point.
325
326    :param grid:  Cornerpoint-grid
327    :type grid:   dict
328    :returns:     Coordinate values for each corner. Use the Face enum to index
329                  the first dimension, k, j, i coordinates to index the next
330                  three, and Dim enum to index the last dimension.
331                  Note that the first point in a face is not necessarily the
332                  point that is closest to the origin of the grid.
333    :rtype:       :class:`numpy.ndarray`, shape = (8, nk, nj, ni, 3)
334
335    >>> import numpy as np
336    >>> import pyresito.io.ecl as ecl
337    >>> import pyresito.grid.cornerpoint as cp
338    >>> case = ecl.EclipseCase ("FOO")
339    >>> coord_fijkd = cp.face_coords (case.grid ())
340    >>> # get the midpoint of the upper face in each cell
341    >>> up_mid = np.average (coord_fijkd [cp.Face.UP, :, :, :, :], axis = 0)
342    """
343    # get coordinates in native corner-point format
344    xyz = corner_coordinates(grid['COORD'], grid['ZCORN'])
345
346    # get extent of grid
347    ni, nj, nk = grid['DIMENS']
348
349    # break up the latter dimension into a many-dimensional hypercube; this
350    # is an inexpensive operation since it won't have to reshuffle the memory
351    xyz = np.reshape(xyz, (3, nk, 2, nj, 2, ni, 2))
352
353    # move all the dimensions local within an element to the front
354    xyz = np.transpose(xyz, (2, 4, 6, 1, 3, 5, 0))
355
356    # coalesce the local dimensions so that they end up in one dimension
357    # which can be indexed by the Face enum to extract a certain field
358    xyz = np.reshape(xyz, (8, nk, nj, ni, 3))
359
360    return xyz
361
362
363def horizon(grid, layer=0, top=True):
364    """Extract the points that are in a certain horizon and average them so
365    that the result is per cell and not per pillar.
366
367    :param grid:   Grid structure
368    :type grid:    dict
369    :param layer:  The K index of the horizon. Default is the layer at the top.
370    :type layer:   int
371    :param top:    Whether the top face should be exported. If this is False,
372                   then the bottom face is exported instead.
373    :type top:     bool
374    :returns:      Depth at the specified horizon for each cell center
375    :rtype:        :class:`numpy.ma.array`, shape = (nk, nj, ni),
376                   dtype = numpy.float64
377    """
378    # get the dimensions of the grid. notice that this array is always given
379    # in Fortran order (since Eclipse works that way) and not in native order
380    num_i, num_j, _ = grid['DIMENS']
381
382    # zcorn is nk * (up,down) * nj * (front,back) * ni * (left,right). we first
383    # project the particular layer and face that we want to work with, ending
384    # up with four corners for each cell.
385    vertical = 0 if top else 1
386    corners = grid['ZCORN'][layer, vertical, :, :, :, :]
387
388    # move the second and fourth dimension (front/back and left/right) to the
389    # back and collapse them into one dimension, meaning that we get
390    # (nj, ni, 4). then take the average across this last dimension, ending up
391    # with an (nj, ni) grid, which is what we return
392    heights = np.average(np.reshape(np.transpose(
393        corners, axes=(0, 2, 1, 3)), (num_j, num_i, 2*2)), axis=2)
394
395    # if there are any inactive elements, then mask out the elevation at that
396    # spot (it is not really a part of the grid)
397    actnum = grid['ACTNUM'][layer, :, :]
398    return ma.array(data=heights, mask=np.logical_not(actnum))
399
400
401def _reduce_corners(plane, red_op):
402    """
403    :param plane:   Z-coordinates for a specific horizon in the grid
404    :type plane:    :class:`numpy.ndarray`, shape = (nj, 2, ni, 2)
405    :param red_op:  How coordinates are merged (reduction operator): either
406                    numpy.minimum or np.maximum, depending on top or bottom
407    :type red_op:   :class:`numpy.ufunc`
408    :returns:       Extremal value of each point around the corners.
409    :rtype:         :class:`numpy.ndarray`, shape = (nj+1, ni+1)
410
411    >> plane = np.reshape (np.arange (36) + 1, (3, 2, 3, 2))
412    >> _reduce_corners (plane, np.minimum)
413    """
414    # constants
415    NORTH = 1
416    SOUTH = 0
417    EAST = 1
418    WEST = 0
419
420    # get dimensions of the domain
421    nj, _, ni, _ = plane.shape
422
423    # allocate output memory for all pillars
424    out = np.empty((nj + 1, ni + 1), dtype=np.float64)
425
426    # views into the original plane, getting a specific corner in every cell.
427    # these views then have dimensions (nj, ni)
428    nw = plane[:, NORTH, :, WEST]
429    ne = plane[:, NORTH, :, EAST]
430    sw = plane[:, SOUTH, :, WEST]
431    se = plane[:, SOUTH, :, EAST]
432
433    # four corners of the output grid only have a single value. the views have
434    # indices 0..n-1, the output array have indices 0..n
435    out[0,  0] = sw[0,    0]
436    out[nj,  0] = nw[nj-1,    0]
437    out[0, ni] = se[0, ni-1]
438    out[nj, ni] = ne[nj-1, ni-1]
439
440    # four edges contains values from two windows; first two horizontal edges,
441    # (varying along i), then two vertical edges (varying along j). recall that
442    # *ranges* in Python have inclusive start, exclusive end.
443    out[0, 1:ni] = red_op(sw[0, 1:ni], se[0, 0:ni-1])
444    out[nj, 1:ni] = red_op(nw[nj-1, 1:ni], ne[nj-1, 0:ni-1])
445    out[1:nj,    0] = red_op(nw[0:nj-1,    0], sw[1:nj,      0])
446    out[1:nj,   ni] = red_op(ne[0:nj-1, ni-1], se[1:nj,   ni-1])
447
448    # interior have two nested reduction operations
449    out[1:nj, 1:ni] = red_op(
450        red_op(nw[0:nj-1,   1:ni], sw[1:nj,   1:ni]),
451        red_op(ne[0:nj-1, 0:ni-1], se[1:nj, 0:ni-1]))
452
453    return out
454
455
456def horizon_pillars(grid, layer=0, top=True):
457    """Extract heights where a horizon crosses the pillars.
458
459    :param grid:  Grid structure, containing both COORD and ZCORN properties.
460    :type grid:   dict
461    :param layer:  The K index of the horizon. Default is the layer at the top.
462    :type layer:   int
463    :param top:    Whether the top face should be exported. If this false is
464                   False, then the bottom face is exported instead.
465    :type top:     bool
466    :returns:     Heights of the horizon at each pillar, in the same format as
467                  the COORD matrix.
468    :rtype:       :class:`numpy.ndarray`, shape = (nj+1, ni+1, 2, 3)
469    """
470    # NumPy's average operations finds the average within arrays, but we want
471    # an operation that can take the average across two arrays
472    def avg_op(a, b):
473        return 0.5 * (a + b)
474
475    # zcorn is nk * (up,down) * nj * (front,back) * ni * (left,right). we first
476    # project the particular layer and face that we want to work with, ending
477    # up with four corners for each cell. take the average of the heights that
478    # are hinged up on each pillar to get a "smooth" surface. (this will not be
479    # the same surface that the cornerpoint grid was created from).
480    vertical = 0 if top else 1
481    points = _reduce_corners(grid['ZCORN'][layer, vertical, :, :, :, :],
482                             avg_op)
483    return points
484
485
486def snugfit(grid):
487    """Create coordinate pillars that matches exactly the top and bottom
488    horizon of the grid cells.
489
490    This version assumes that the pillars in the grid are all strictly
491    vertical, i.e. the x- and y-coordinates will not be changed.
492
493    :param grid:  Grid structure, containing both COORD and ZCORN properties.
494    :type grid:   dict
495    :returns:     Pillar coordinates, in the same format as the COORD matrix.
496                  Note that a new matrix is returned, the original grid is not
497                  altered/updated.
498    :rtype:       :class:`numpy.ndarray`, shape = (nj+1, ni+1, 2, 3)
499    """
500    # placement in the plane of each pillar is unchanged
501    x = grid['COORD'][:, :, 0, 0]
502    y = grid['COORD'][:, :, 0, 1]
503
504    # get implicit dimension of grid
505    njp1, nip1 = x.shape
506
507    # get new top and bottom from the z-coordinates; smaller z is shallower
508    top = _reduce_corners(grid['ZCORN'][0, 0, :, :, :, :], np.minimum)
509    btm = _reduce_corners(grid['ZCORN'][-1, 1, :, :, :, :], np.maximum)
510
511    # combine coordinates to a pillar grid with new z-coordinates
512    coord = np.reshape(np.dstack((
513        x.ravel(), y.ravel(), top.ravel(),
514        x.ravel(), y.ravel(), btm.ravel())), (njp1, nip1, 2, 3))
515
516    return coord
517
518
519def bounding_box(corn, filtr):
520    """\
521    Bounding box of the grid. This function will always assume that the grid
522    is aligned with the geographical axes.
523
524    :param corn:    Coordinate values for each corner. This matrix can be
525                    constructed with the `corner_coordinates` function.
526    :type  corn:    :class:`numpy.ndarray`, shape=(3, nk*2*nj*2*ni*2)
527    :param filtr:   Active corners; use scatter of ACTNUM if no filtering
528    :type  filtr:   :class:`numpy.ndarray`, shape = (nk, 2, nj, 2, ni, 2),
529                    dtype = numpy.bool
530    :return:        Bottom front right corner and top back left corner. Since
531                    the matrix is returned with C ordering, it is specified the
532                    opposite way of what is natural for mathematical matrices.
533    :rtype:         :class:`numpy.ndarray`, shape = (2, 3), dtype = np.float64
534
535    >>> corn = corner_coordinates(grid['COORD'], grid['ZCORN'])
536    >>> bbox = bounding_box(corn, scatter(grid['ACTNUM']))
537    >>> p, q = bbox[0, :], bbox[1, :]
538    >>> diag = np.sqrt(np.dot(q - p, q - p))
539    """
540    # scatter the filter mask from each cell to its corners
541    mask = np.logical_not(filtr).ravel()
542
543    # build arrays of each separate coordinate based on its inclusion
544    x_vals = ma.array(data=corn[0, :], mask=mask)
545    y_vals = ma.array(data=corn[1, :], mask=mask)
546    z_vals = ma.array(data=corn[2, :], mask=mask)
547
548    # construct bounding box that includes all coordinates in visible grid
549    bbox = np.array([[np.min(x_vals), np.min(y_vals), np.min(z_vals)],
550                     [np.max(x_vals), np.max(y_vals), np.max(z_vals)]],
551                    dtype=corn.dtype)
552    return bbox
553
554
555def mass_center(corn, filtr):
556    """\
557    Mass center of the grid. This function will always assume that the density
558    is equal throughout the field.
559
560    :param corn:    Coordinate values for each corner. This matrix can be
561                    constructed with the `corner_coordinates` function.
562    :type  corn:    :class:`numpy.ndarray`, shape=(3, nk*2*nj*2*ni*2)
563    :param filtr:   Active corners; use scatter of ACTNUM if no filtering
564    :type  filtr:   :class:`numpy.ndarray`, shape = (nk, 2, nj, 2, ni, 2),
565                    dtype = numpy.bool
566    :return:        Center of mass. This should be the focal point of the grid.
567    :rtype:         :class:`numpy.ndarray`, shape = (3,), dtype = np.float64
568
569    >>> corn = cp.corner_coordinates(grid['COORD'], grid['ZCORN'])
570    >>> focal_point = cp.mass_center(corn, cp.scatter(grid['ACTNUM']))
571    """
572    # scatter the filter mask from each cell to its corners
573    mask = np.logical_not(filtr).ravel()
574
575    # build arrays of each separate coordinate based on its inclusion
576    x_vals = ma.array(data=corn[0, :], mask=mask)
577    y_vals = ma.array(data=corn[1, :], mask=mask)
578    z_vals = ma.array(data=corn[2, :], mask=mask)
579
580    # use this as a coarse approximation to find the element center
581    center = np.array([np.average(x_vals),
582                       np.average(y_vals),
583                       np.average(z_vals)], dtype=corn.dtype)
584    return center
log = <Logger misc.grid.cornerpoint (WARNING)>
def scatter(cell_field):
18def scatter(cell_field):
19    """\
20    Duplicate all items in every dimension.
21
22    Use this method to duplicate values associated with each cell to
23    each of the corners in the cell.
24
25    :param cell_field:   Property per cell in the grid
26    :type  cell_field:   :class:`numpy.ndarray`, shape = (nk, nj, ni)
27    :return:             Property per corner in the cube
28    :rtype:              :class:`numpy.ndarray`, shape = (nk, 2, nj, 2, ni, 2)
29
30    >>> scatter(np.array([[[1, 2],
31                           [3, 4]],
32                          [[5, 6],
33                           [7, 8]]]))
34
35    array([[[[[[1, 1], [2, 2]],
36              [[1, 1], [2, 2]]],
37             [[[3, 3], [4, 4]],
38              [[3, 3], [4, 4]]]],
39
40            [[[[1, 1], [2, 2]],
41              [[1, 1], [2, 2]]],
42             [[[3, 3], [4, 4]],
43              [[3, 3], [4, 4]]]]],
44
45           [[[[[5, 5], [6, 6]],
46              [[5, 5], [6, 6]]],
47             [[[7, 7], [8, 8]],
48              [[7, 7], [8, 8]]]],
49
50            [[[[5, 5], [6, 6]],
51              [[5, 5], [6, 6]]],
52             [[[7, 7], [8, 8]],
53              [[7, 7], [8, 8]]]]]])
54    """
55    # get the dimensions of the cube in easily understood aliases
56    nk, nj, ni = cell_field.shape  # pylint: disable=invalid-name
57
58    # create an extra dimension so that we can duplicate individual values
59    flat = np.reshape(cell_field, (nk, nj, ni, 1))
60
61    # duplicate along each of the axis
62    dup_i = np.tile(flat, (1, 1, 1, 2))
63    dup_j = np.tile(dup_i, (1, 1, 2, 1))
64    dup_k = np.tile(dup_j, (1, 2, 1, 1))
65
66    # reformat to a cube with the appropriate dimensions
67    corn_field = np.reshape(dup_k, (nk, 2, nj, 2, ni, 2))
68    return corn_field

Duplicate all items in every dimension.

Use this method to duplicate values associated with each cell to each of the corners in the cell.

Parameters
  • cell_field: Property per cell in the grid
Returns
         Property per corner in the cube
>>> scatter(np.array([[[1, 2],
                       [3, 4]],
                      [[5, 6],
                       [7, 8]]]))

array([[[[[[1, 1], [2, 2]], [[1, 1], [2, 2]]], [[[3, 3], [4, 4]], [[3, 3], [4, 4]]]],

    [[[[1, 1], [2, 2]],
      [[1, 1], [2, 2]]],
     [[[3, 3], [4, 4]],
      [[3, 3], [4, 4]]]]],

   [[[[[5, 5], [6, 6]],
      [[5, 5], [6, 6]]],
     [[[7, 7], [8, 8]],
      [[7, 7], [8, 8]]]],

    [[[[5, 5], [6, 6]],
      [[5, 5], [6, 6]]],
     [[[7, 7], [8, 8]],
      [[7, 7], [8, 8]]]]]])
def inner_dup(pillar_field):
 71def inner_dup(pillar_field):
 72    """Duplicate all inner items in both dimensions.
 73
 74    Use this method to duplicate values associated with each pillar to
 75    the corners on each side(s) of the pillar; four corners for all
 76    interior pillars, two corners for all pillars on the rim and only
 77    one (element) corner for those pillars that are on the grid corners.
 78
 79    :param pillar_field: Property per pillar in the grid
 80    :type  pillar_field: :class:`numpy.ndarray`, shape=(m+1, n+1)
 81    :returns:            Property per corner in a grid plane
 82    :rtype:              :class:`numpy.ndarray`, shape=(2*m, 2*n))
 83
 84    >>> inner_dup(np.array ([[1, 2, 3],
 85                             [4, 5, 6],
 86                             [7, 8, 9]]))
 87    array([[1, 2, 2, 3],
 88           [4, 5, 5, 6],
 89           [4, 5, 5, 6],
 90           [7, 8, 8, 9]])
 91    """
 92    # extract the inner horizontal part of the plane
 93    horz_part = pillar_field[:, 1:-1]
 94
 95    # tile it to a separate plane
 96    horz_dupd = np.tile(horz_part, (2, 1, 1))
 97
 98    # shift the tile to the end so it can be rolled
 99    # into the second dimension
100    horz_shft = np.transpose(horz_dupd, (1, 2, 0))
101
102    # roll this into the second dimension
103    horz_roll = np.reshape(horz_shft,
104                           (horz_shft.shape[0],
105                            horz_shft.shape[1] * 2))
106
107    # add back the first and last column
108    horz = np.column_stack([pillar_field[:, 0],
109                            horz_roll,
110                            pillar_field[:, -1]])
111
112    # extract the inner vertical part of the plane
113    vert_part = horz[1:-1, :]
114
115    # tile it to a separate plane
116    vert_dupd = np.tile(vert_part, (1, 1, 2))
117
118    # roll this into the first dimension
119    vert_roll = np.reshape(vert_dupd,
120                           (vert_dupd.shape[1] * 2,
121                            vert_dupd.shape[2] // 2))
122
123    # add back the first and last row
124    result = np.vstack([horz[0, :],
125                        vert_roll,
126                        horz[-1, :]])
127    return result

Duplicate all inner items in both dimensions.

Use this method to duplicate values associated with each pillar to the corners on each side(s) of the pillar; four corners for all interior pillars, two corners for all pillars on the rim and only one (element) corner for those pillars that are on the grid corners.

Parameters
  • pillar_field: Property per pillar in the grid :returns: Property per corner in a grid plane
>>> inner_dup(np.array ([[1, 2, 3],
                         [4, 5, 6],
                         [7, 8, 9]]))
array([[1, 2, 2, 3],
       [4, 5, 5, 6],
       [4, 5, 5, 6],
       [7, 8, 8, 9]])
def elem_vtcs_ndcs(nk, nj, ni):
130def elem_vtcs_ndcs(nk, nj, ni):  # pylint: disable=invalid-name
131    """List zcorn indices used by every element in an nk*nj*ni grid
132
133    :param nk: Number of layers in Z direction
134    :type nk:  int
135    :param nj: Number of elements in the Y direction
136    :type nj:  int
137    :param ni: Number of elements in the X direction
138    :type ni:  int
139    :returns:  Zero-based indices for the hexahedral element corners
140    :rtype:    :class:`numpy.ndarray`, shape=(nk*nj*ni, 8), dtype=int
141    """
142    # hex_perm is the order a hexahedron should be specified to the
143    # gridding engine (it is the same for VTK and Ensight Gold formats)
144    # relative to the order they are in the Eclipse format. the latter
145    # four is listed first because we turn the z order around with a
146    # transform.
147    # local indices:      0, 1, 2, 3, 4, 5, 6, 7
148    hex_perm = np.array([4, 5, 7, 6, 0, 1, 3, 2], dtype=int)
149
150    # kji is the global (k, j, i) index for every element; we tile
151    # this into 8 corners per element, with 3 indices per corner;
152    # this this matrix contains repeats of 8 identical permutations
153    # of the values 0..(ni-1), 0..(nj-1) and 0..(nk-1)
154    kji = np.array(list(it.product(range(nk),
155                                   range(nj),
156                                   range(ni))))
157    kji = np.reshape(np.tile(kji, [1, hex_perm.shape[0]]), [-1, 3])
158
159    # bfr is the local (bottom, front, right) index for every corner;
160    # this is a block of 8 local corners which are picked out of the
161    # zcorn matrix to then correspond with the gridding engines order.
162    # we tile this block for every element
163    bfr = np.array(list(it.product([0, 1], repeat=3)))[hex_perm, :]
164    bfr = np.tile(bfr, [nk*nj*ni, 1])
165
166    # calculate running number for every corner, combining the element
167    # indices kji with the local corner indices bfr. the zcorn hypercube
168    # has dimensions nk * 2 * nj * 2 * ni * 2; the indexing is simply a
169    # flattened view of this hypercube.
170    ndx = (((2*kji[:, 0] + bfr[:, 0]) * 2*nj +
171            (2*kji[:, 1] + bfr[:, 1])) * 2*ni +
172           2*kji[:, 2] + bfr[:, 2])
173
174    # we then collect the indices so that we get 8 of them (all those that
175    # belongs to the same element) on a row
176    ndx = np.reshape(ndx, [-1, hex_perm.shape[0]])
177    return ndx

List zcorn indices used by every element in an nknjni grid

Parameters
  • nk: Number of layers in Z direction
  • nj: Number of elements in the Y direction
  • ni: Number of elements in the X direction :returns: Zero-based indices for the hexahedral element corners
def corner_coordinates(coord, zcorn):
181def corner_coordinates(coord, zcorn):
182    """Generate (x, y, z) coordinates for each corner-point.
183
184    :param coord: Pillar geometrical information
185    :type coord:  :class:`numpy.ndarray`, shape=(nj+1, ni+1, 2, 3)
186    :param zcorn: Depth values along each pillar
187    :type zcorn:  :class:`numpy.ndarray`, shape=(nk, 2, nj, 2, ni, 2)
188    :returns:     Coordinate values for each corner
189    :rtype:       :class:`numpy.ndarray`, shape=(3, nk*2*nj*2*ni*2)
190    """
191    # indices to treat the pillar matrix as a record
192    X = 0
193    Y = 1
194    Z = 2
195    START = 0
196    END = 1
197
198    # calculate depth derivatives for the pillar slope
199    dx = coord[:, :, END, X] - coord[:, :, START, X]
200    dy = coord[:, :, END, Y] - coord[:, :, START, Y]
201    dz = coord[:, :, END, Z] - coord[:, :, START, Z]
202
203    # make the pillar information compatible with a plane of corners,
204    # having a singleton dimension at the front (instead tiling 2*nk)
205    x0 = inner_dup(coord[:, :, START, X])[None, :, :]
206    y0 = inner_dup(coord[:, :, START, Y])[None, :, :]
207    z0 = inner_dup(coord[:, :, START, Z])[None, :, :]
208    dxdz = inner_dup(dx / dz)[None, :, :]
209    dydz = inner_dup(dy / dz)[None, :, :]
210
211    # infer the grid size from the dimensions of coord
212    nj = coord.shape[0] - 1
213    ni = coord.shape[1] - 1
214
215    # make the formal dimensions of the depths compatible with the plane
216    # of vectors created from the pillars; notice that the first dimension
217    # is not singular
218    z = np.reshape(zcorn, (-1, nj*2, ni*2))
219
220    # calculate the point in the plane where the horizon intersect with the
221    # pillar; we get the (x, y)-coordinates for each corner. notice that the
222    # difference z - z0 is for each point, whereas dz is for the pillar!
223    x = (z - z0) * dxdz + x0
224    y = (z - z0) * dydz + y0
225
226    # reformat the coordinates into the final coordinates
227    xyz = np.vstack([np.reshape(x, (1, -1)),
228                     np.reshape(y, (1, -1)),
229                     np.reshape(z, (1, -1))])
230    return xyz

Generate (x, y, z) coordinates for each corner-point.

Parameters
  • coord: Pillar geometrical information
  • zcorn: Depth values along each pillar :returns: Coordinate values for each corner
class Dim:
X = 0
Y = 1
Z = 2
class Face:
DOWN = array([0, 1, 2, 3])
UP = array([4, 5, 6, 7])
FRONT = array([4, 7, 3, 0])
BACK = array([5, 6, 2, 1])
LEFT = array([7, 6, 2, 3])
RIGHT = array([4, 5, 1, 0])
ALL = array([0, 1, 2, 3, 4, 5, 6, 7])
def cp_cells(grid, face):
256def cp_cells(grid, face):
257    """
258    Make a cell array from a cornerpoint grid. The cells will be in the
259    same order as the Cartesian enumeration of the grid.
260
261    :param grid: Pillar coordinates and corner depths
262    :type grid:  dict, with 'coord' and 'zcorn' properties
263    :param face: Which face that should be extracted, e.g. Face.UP
264    :type face:  Face enum
265    :returns:    Set of geometrical objects that can be sent to rendering
266    :rtype:      dict with 'points', shape (nverts, 3), and 'cells', shape
267                 (nelems, ncorns), where ncorns is either 8 (hexahedron
268                 volume or 4 (quadrilateral face), depending on the face
269                 parameter.
270    """
271    src = {}
272
273    log.debug("Generating points for corner depths")
274    # notice that VTK wants shape (nverts, 3), where as the code sets up
275    # for Ensight format which is (3, nverts).
276    src['points'] = corner_coordinates(grid['COORD'],
277                                       grid['ZCORN']).T.copy()
278
279    # order of the dimensions are different from the structure to the call
280    ni, nj, nk = grid['DIMENS']
281
282    # extract only the columns of the face we want to show. the shape was
283    # originally (nelem, 8). copy is necessary to get a contiguous array
284    # for VTK
285    log.debug("Generating cells")
286    src['cells'] = elem_vtcs_ndcs(nk, nj, ni)[:, face].copy()
287    return src

Make a cell array from a cornerpoint grid. The cells will be in the same order as the Cartesian enumeration of the grid.

Parameters
  • grid: Pillar coordinates and corner depths
  • face: Which face that should be extracted, e.g. Face.UP :returns: Set of geometrical objects that can be sent to rendering
def cell_filter(grid, func):
290def cell_filter(grid, func):
291    """Create a filter for a specific grid.
292
293    :param grid:   Grid structure that should be filtered
294    :type  grid:   dict
295    :param func:   Lambda function that takes i, j, k indices (one-based)
296                   and returns boolean for whether the cells should be
297                   included or not. The function should be vectorized.
298    :type  func:   Callable[(int, int, int), bool]
299
300    >>> layr = cell_filter (grid, lamba i, j, k: np.greater_equal (k, 56))
301    """
302    # extract the dimensions; notice that the i-dimensions is first
303    # in this list, since it is directly from the grid
304    ni, nj, nk = grid['DIMENS']  # pylint: disable=invalid-name
305
306    # setup a grid of the i, j and k address for each of the cells;
307    # notice that now the order of the dimensions are swapped so that
308    # they fit with the memory layout of a loaded Numpy array
309    kji = np.mgrid[1:(nk+1), 1:(nj+1), 1:(ni+1)]
310
311    # call the filter function on all these addresses, and simply
312    # return the boolean array of those
313    filter_flags = func(kji[2], kji[1], kji[0])
314    masked = filter_flags.astype(np.bool)
315
316    # get the mask of active cells, and combine this with the masked
317    # cells from the filter, giving us a flag for all visible nodes
318    active = grid['ACTNUM']
319    visible = np.logical_and(active, masked)
320
321    return visible

Create a filter for a specific grid.

Parameters
  • grid: Grid structure that should be filtered
  • func: Lambda function that takes i, j, k indices (one-based) and returns boolean for whether the cells should be included or not. The function should be vectorized.
>>> layr = cell_filter (grid, lamba i, j, k: np.greater_equal (k, 56))
def face_coords(grid):
324def face_coords(grid):
325    """Get (x, y, z) coordinates for each corner-point.
326
327    :param grid:  Cornerpoint-grid
328    :type grid:   dict
329    :returns:     Coordinate values for each corner. Use the Face enum to index
330                  the first dimension, k, j, i coordinates to index the next
331                  three, and Dim enum to index the last dimension.
332                  Note that the first point in a face is not necessarily the
333                  point that is closest to the origin of the grid.
334    :rtype:       :class:`numpy.ndarray`, shape = (8, nk, nj, ni, 3)
335
336    >>> import numpy as np
337    >>> import pyresito.io.ecl as ecl
338    >>> import pyresito.grid.cornerpoint as cp
339    >>> case = ecl.EclipseCase ("FOO")
340    >>> coord_fijkd = cp.face_coords (case.grid ())
341    >>> # get the midpoint of the upper face in each cell
342    >>> up_mid = np.average (coord_fijkd [cp.Face.UP, :, :, :, :], axis = 0)
343    """
344    # get coordinates in native corner-point format
345    xyz = corner_coordinates(grid['COORD'], grid['ZCORN'])
346
347    # get extent of grid
348    ni, nj, nk = grid['DIMENS']
349
350    # break up the latter dimension into a many-dimensional hypercube; this
351    # is an inexpensive operation since it won't have to reshuffle the memory
352    xyz = np.reshape(xyz, (3, nk, 2, nj, 2, ni, 2))
353
354    # move all the dimensions local within an element to the front
355    xyz = np.transpose(xyz, (2, 4, 6, 1, 3, 5, 0))
356
357    # coalesce the local dimensions so that they end up in one dimension
358    # which can be indexed by the Face enum to extract a certain field
359    xyz = np.reshape(xyz, (8, nk, nj, ni, 3))
360
361    return xyz

Get (x, y, z) coordinates for each corner-point.

Parameters
  • grid: Cornerpoint-grid :returns: Coordinate values for each corner. Use the Face enum to index the first dimension, k, j, i coordinates to index the next three, and Dim enum to index the last dimension. Note that the first point in a face is not necessarily the point that is closest to the origin of the grid.
>>> import numpy as np
>>> import pyresito.io.ecl as ecl
>>> import pyresito.grid.cornerpoint as cp
>>> case = ecl.EclipseCase ("FOO")
>>> coord_fijkd = cp.face_coords (case.grid ())
>>> # get the midpoint of the upper face in each cell
>>> up_mid = np.average (coord_fijkd [cp.Face.UP, :, :, :, :], axis = 0)
def horizon(grid, layer=0, top=True):
364def horizon(grid, layer=0, top=True):
365    """Extract the points that are in a certain horizon and average them so
366    that the result is per cell and not per pillar.
367
368    :param grid:   Grid structure
369    :type grid:    dict
370    :param layer:  The K index of the horizon. Default is the layer at the top.
371    :type layer:   int
372    :param top:    Whether the top face should be exported. If this is False,
373                   then the bottom face is exported instead.
374    :type top:     bool
375    :returns:      Depth at the specified horizon for each cell center
376    :rtype:        :class:`numpy.ma.array`, shape = (nk, nj, ni),
377                   dtype = numpy.float64
378    """
379    # get the dimensions of the grid. notice that this array is always given
380    # in Fortran order (since Eclipse works that way) and not in native order
381    num_i, num_j, _ = grid['DIMENS']
382
383    # zcorn is nk * (up,down) * nj * (front,back) * ni * (left,right). we first
384    # project the particular layer and face that we want to work with, ending
385    # up with four corners for each cell.
386    vertical = 0 if top else 1
387    corners = grid['ZCORN'][layer, vertical, :, :, :, :]
388
389    # move the second and fourth dimension (front/back and left/right) to the
390    # back and collapse them into one dimension, meaning that we get
391    # (nj, ni, 4). then take the average across this last dimension, ending up
392    # with an (nj, ni) grid, which is what we return
393    heights = np.average(np.reshape(np.transpose(
394        corners, axes=(0, 2, 1, 3)), (num_j, num_i, 2*2)), axis=2)
395
396    # if there are any inactive elements, then mask out the elevation at that
397    # spot (it is not really a part of the grid)
398    actnum = grid['ACTNUM'][layer, :, :]
399    return ma.array(data=heights, mask=np.logical_not(actnum))

Extract the points that are in a certain horizon and average them so that the result is per cell and not per pillar.

Parameters
  • grid: Grid structure
  • layer: The K index of the horizon. Default is the layer at the top.
  • top: Whether the top face should be exported. If this is False, then the bottom face is exported instead. :returns: Depth at the specified horizon for each cell center
def horizon_pillars(grid, layer=0, top=True):
457def horizon_pillars(grid, layer=0, top=True):
458    """Extract heights where a horizon crosses the pillars.
459
460    :param grid:  Grid structure, containing both COORD and ZCORN properties.
461    :type grid:   dict
462    :param layer:  The K index of the horizon. Default is the layer at the top.
463    :type layer:   int
464    :param top:    Whether the top face should be exported. If this false is
465                   False, then the bottom face is exported instead.
466    :type top:     bool
467    :returns:     Heights of the horizon at each pillar, in the same format as
468                  the COORD matrix.
469    :rtype:       :class:`numpy.ndarray`, shape = (nj+1, ni+1, 2, 3)
470    """
471    # NumPy's average operations finds the average within arrays, but we want
472    # an operation that can take the average across two arrays
473    def avg_op(a, b):
474        return 0.5 * (a + b)
475
476    # zcorn is nk * (up,down) * nj * (front,back) * ni * (left,right). we first
477    # project the particular layer and face that we want to work with, ending
478    # up with four corners for each cell. take the average of the heights that
479    # are hinged up on each pillar to get a "smooth" surface. (this will not be
480    # the same surface that the cornerpoint grid was created from).
481    vertical = 0 if top else 1
482    points = _reduce_corners(grid['ZCORN'][layer, vertical, :, :, :, :],
483                             avg_op)
484    return points

Extract heights where a horizon crosses the pillars.

Parameters
  • grid: Grid structure, containing both COORD and ZCORN properties.
  • layer: The K index of the horizon. Default is the layer at the top.
  • top: Whether the top face should be exported. If this false is False, then the bottom face is exported instead. :returns: Heights of the horizon at each pillar, in the same format as the COORD matrix.
def snugfit(grid):
487def snugfit(grid):
488    """Create coordinate pillars that matches exactly the top and bottom
489    horizon of the grid cells.
490
491    This version assumes that the pillars in the grid are all strictly
492    vertical, i.e. the x- and y-coordinates will not be changed.
493
494    :param grid:  Grid structure, containing both COORD and ZCORN properties.
495    :type grid:   dict
496    :returns:     Pillar coordinates, in the same format as the COORD matrix.
497                  Note that a new matrix is returned, the original grid is not
498                  altered/updated.
499    :rtype:       :class:`numpy.ndarray`, shape = (nj+1, ni+1, 2, 3)
500    """
501    # placement in the plane of each pillar is unchanged
502    x = grid['COORD'][:, :, 0, 0]
503    y = grid['COORD'][:, :, 0, 1]
504
505    # get implicit dimension of grid
506    njp1, nip1 = x.shape
507
508    # get new top and bottom from the z-coordinates; smaller z is shallower
509    top = _reduce_corners(grid['ZCORN'][0, 0, :, :, :, :], np.minimum)
510    btm = _reduce_corners(grid['ZCORN'][-1, 1, :, :, :, :], np.maximum)
511
512    # combine coordinates to a pillar grid with new z-coordinates
513    coord = np.reshape(np.dstack((
514        x.ravel(), y.ravel(), top.ravel(),
515        x.ravel(), y.ravel(), btm.ravel())), (njp1, nip1, 2, 3))
516
517    return coord

Create coordinate pillars that matches exactly the top and bottom horizon of the grid cells.

This version assumes that the pillars in the grid are all strictly vertical, i.e. the x- and y-coordinates will not be changed.

Parameters
  • grid: Grid structure, containing both COORD and ZCORN properties. :returns: Pillar coordinates, in the same format as the COORD matrix. Note that a new matrix is returned, the original grid is not altered/updated.
def bounding_box(corn, filtr):
520def bounding_box(corn, filtr):
521    """\
522    Bounding box of the grid. This function will always assume that the grid
523    is aligned with the geographical axes.
524
525    :param corn:    Coordinate values for each corner. This matrix can be
526                    constructed with the `corner_coordinates` function.
527    :type  corn:    :class:`numpy.ndarray`, shape=(3, nk*2*nj*2*ni*2)
528    :param filtr:   Active corners; use scatter of ACTNUM if no filtering
529    :type  filtr:   :class:`numpy.ndarray`, shape = (nk, 2, nj, 2, ni, 2),
530                    dtype = numpy.bool
531    :return:        Bottom front right corner and top back left corner. Since
532                    the matrix is returned with C ordering, it is specified the
533                    opposite way of what is natural for mathematical matrices.
534    :rtype:         :class:`numpy.ndarray`, shape = (2, 3), dtype = np.float64
535
536    >>> corn = corner_coordinates(grid['COORD'], grid['ZCORN'])
537    >>> bbox = bounding_box(corn, scatter(grid['ACTNUM']))
538    >>> p, q = bbox[0, :], bbox[1, :]
539    >>> diag = np.sqrt(np.dot(q - p, q - p))
540    """
541    # scatter the filter mask from each cell to its corners
542    mask = np.logical_not(filtr).ravel()
543
544    # build arrays of each separate coordinate based on its inclusion
545    x_vals = ma.array(data=corn[0, :], mask=mask)
546    y_vals = ma.array(data=corn[1, :], mask=mask)
547    z_vals = ma.array(data=corn[2, :], mask=mask)
548
549    # construct bounding box that includes all coordinates in visible grid
550    bbox = np.array([[np.min(x_vals), np.min(y_vals), np.min(z_vals)],
551                     [np.max(x_vals), np.max(y_vals), np.max(z_vals)]],
552                    dtype=corn.dtype)
553    return bbox

Bounding box of the grid. This function will always assume that the grid is aligned with the geographical axes.

Parameters
  • corn: Coordinate values for each corner. This matrix can be constructed with the corner_coordinates function.
  • filtr: Active corners; use scatter of ACTNUM if no filtering
Returns
    Bottom front right corner and top back left corner. Since
            the matrix is returned with C ordering, it is specified the
            opposite way of what is natural for mathematical matrices.
>>> corn = corner_coordinates(grid['COORD'], grid['ZCORN'])
>>> bbox = bounding_box(corn, scatter(grid['ACTNUM']))
>>> p, q = bbox[0, :], bbox[1, :]
>>> diag = np.sqrt(np.dot(q - p, q - p))
def mass_center(corn, filtr):
556def mass_center(corn, filtr):
557    """\
558    Mass center of the grid. This function will always assume that the density
559    is equal throughout the field.
560
561    :param corn:    Coordinate values for each corner. This matrix can be
562                    constructed with the `corner_coordinates` function.
563    :type  corn:    :class:`numpy.ndarray`, shape=(3, nk*2*nj*2*ni*2)
564    :param filtr:   Active corners; use scatter of ACTNUM if no filtering
565    :type  filtr:   :class:`numpy.ndarray`, shape = (nk, 2, nj, 2, ni, 2),
566                    dtype = numpy.bool
567    :return:        Center of mass. This should be the focal point of the grid.
568    :rtype:         :class:`numpy.ndarray`, shape = (3,), dtype = np.float64
569
570    >>> corn = cp.corner_coordinates(grid['COORD'], grid['ZCORN'])
571    >>> focal_point = cp.mass_center(corn, cp.scatter(grid['ACTNUM']))
572    """
573    # scatter the filter mask from each cell to its corners
574    mask = np.logical_not(filtr).ravel()
575
576    # build arrays of each separate coordinate based on its inclusion
577    x_vals = ma.array(data=corn[0, :], mask=mask)
578    y_vals = ma.array(data=corn[1, :], mask=mask)
579    z_vals = ma.array(data=corn[2, :], mask=mask)
580
581    # use this as a coarse approximation to find the element center
582    center = np.array([np.average(x_vals),
583                       np.average(y_vals),
584                       np.average(z_vals)], dtype=corn.dtype)
585    return center

Mass center of the grid. This function will always assume that the density is equal throughout the field.

Parameters
  • corn: Coordinate values for each corner. This matrix can be constructed with the corner_coordinates function.
  • filtr: Active corners; use scatter of ACTNUM if no filtering
Returns
    Center of mass. This should be the focal point of the grid.
>>> corn = cp.corner_coordinates(grid['COORD'], grid['ZCORN'])
>>> focal_point = cp.mass_center(corn, cp.scatter(grid['ACTNUM']))