TPFA_ResSim.grid

Tools for working with model grid coordinates.

Most functions here are barely in use, mostly serving as reference. After all, it is surprisingly hard to remember which direction and index is for x and which is for y.

The index ordering is "C-style" (numpy default). This choice means that x is the 1st coord., y is 2nd, and is hardcoded in the reservoir simulator model code (in what takes place between np.ravel and np.reshape, both of which are configured to use row-major index ordering. "F-style" (column-major) indexing implementation is perfectly possible, but would imply an undue amount hassle). Conveniently, it also means that x and y tend to occur in alphabetic order. Thus, in printing a matrix of a field, the x coordinate corresponds to the row index. By contrast, the plotting module depicts x from left to right, y from bottom to top.

  1"""Tools for working with model grid coordinates.
  2
  3Most functions here are barely in use, mostly serving as reference.
  4After all, it is surprisingly hard to remember
  5which direction and index is for x and which is for y.
  6
  7The index ordering is "C-style" (numpy default).
  8This choice means that `x` is the 1st coord., `y` is 2nd,
  9and is hardcoded in the reservoir simulator model code
 10(in what takes place **between** `np.ravel` and `np.reshape`,
 11both of which are configured to use row-major index ordering.
 12"F-style" (column-major) indexing implementation is perfectly possible,
 13but would imply an undue amount hassle).
 14Conveniently, it also means that `x` and `y` tend to occur in alphabetic order.
 15Thus, in printing a matrix of a field, the `x` coordinate corresponds to the row index.
 16By contrast, the plotting module depicts `x` from left to right, `y` from bottom to top.
 17"""
 18
 19from dataclasses import dataclass
 20
 21import numpy as np
 22
 23
 24@dataclass
 25class Grid2D:
 26    """Defines a 2D rectangular grid.
 27
 28    Example (2 x-nodes, 5 y-nodes):
 29    >>> grid = Grid2D(Lx=6, Ly=10, Nx=3, Ny=5)
 30
 31    The nodes are centered in the cells:
 32    >>> X, Y = grid.mesh
 33    >>> X
 34    array([[1., 1., 1., 1., 1.],
 35           [3., 3., 3., 3., 3.],
 36           [5., 5., 5., 5., 5.]])
 37
 38    You can compute cell boundaries (i.e. non-central nodes) by adding or subtracting
 39    `hx`/2 and `hy`/2 (i.e. you will miss either boundary at 0 or `Lx` or `Ly`).
 40
 41    Test of round-trip capability of grid mapping computations:
 42    >>> ij = (0, 4)
 43    >>> grid.xy2sub(X[ij], Y[ij]) == ij
 44    array([ True,  True])
 45
 46    >>> grid.sub2xy(*ij) == (X[ij], Y[ij])
 47    array([ True,  True])
 48    """
 49
 50    Lx: float = 1.0
 51    """Physical x-length of domain."""
 52    Ly: float = 1.0
 53    """Physical y-length of domain."""
 54    Nx: int = 32
 55    """Number of grid cells (and their centres) in x dir."""
 56    Ny: int = 32
 57    """Number of grid cells (and their centres) in y dir."""
 58
 59    @property
 60    def shape(self):
 61        """`(Nx, Ny)`"""
 62        return self.Nx, self.Ny
 63
 64    @property
 65    def size(self):
 66        """Total number of elements."""
 67        return np.prod(self.shape)
 68
 69    @property
 70    def domain(self):
 71        """`((0, 0), (Lx, Ly))`"""
 72        return ((0, 0), (self.Lx, self.Ly))
 73
 74    @property
 75    def Nxy(self):
 76        """`Nx` * `Ny`"""
 77        return np.prod(self.shape)
 78
 79    @property
 80    def hx(self):
 81        """x-length of cells"""
 82        return self.Lx/self.Nx
 83
 84    @property
 85    def hy(self):
 86        """y-length of cells"""
 87        return self.Ly/self.Ny
 88
 89    @property
 90    def h2(self):
 91        """`hx` * `hy`"""
 92        return self.hx*self.hy
 93
 94    @property
 95    def mesh(self):
 96        """Generate 2D coordinate grid of cell centres."""
 97        xx = np.linspace(0, self.Lx, self.Nx, endpoint=False) + self.hx/2
 98        yy = np.linspace(0, self.Ly, self.Ny, endpoint=False) + self.hy/2
 99        return np.meshgrid(xx, yy, indexing="ij")
100
101    def sub2ind(self, ix, iy):
102        """Convert index `(ix, iy)` to index in flattened array."""
103        idx = np.ravel_multi_index((ix, iy), self.shape)
104        return idx
105
106    def ind2sub(self, ind):
107        """Inv. of `self.sub2ind`."""
108        ix, iy = np.unravel_index(ind, self.shape)
109        return np.asarray([ix, iy])
110
111    def xy2sub(self, x, y):
112        """Convert physical coordinate tuple to `(ix, iy)`, ix ∈ {0, ..., Nx-1}.
113
114        .. warning:: `xy2sub` and `xy2ind` *round* to nearest cell center.
115            I.e. they are not injective.
116            The alternative would be to return some kind
117            of interpolation weights distributing `(x, y)` over multiple nodes.
118        """
119        x = np.asarray(x)
120        y = np.asarray(y)
121        # Don't silence errors! Validation is useful in optimisation (e.g.)
122        assert np.all(x <= self.Lx)
123        assert np.all(y <= self.Ly)
124        # Set upper border values to slightly interior
125        x = x.clip(max=self.Lx-1e-8)
126        y = y.clip(max=self.Ly-1e-8)
127        ix = np.floor(x / self.Lx * self.Nx).astype(int)
128        iy = np.floor(y / self.Ly * self.Ny).astype(int)
129        return np.asarray([ix, iy])
130
131    def xy2ind(self, x, y):
132        """Convert physical coord to flat indx."""
133        i, j = self.xy2sub(x, y)
134        return self.sub2ind(i, j)
135
136    def sub2xy(self, ix, iy):
137        """Inverse of `self.xy2sub`."""
138        x = (np.asarray(ix) + .5) * self.hx
139        y = (np.asarray(iy) + .5) * self.hy
140        return np.asarray([x, y])
141
142    def ind2xy(self, ind):
143        """Inverse of `self.xy2ind`."""
144        i, j = self.ind2sub(ind)
145        return self.sub2xy(i, j)
@dataclass
class Grid2D:
 25@dataclass
 26class Grid2D:
 27    """Defines a 2D rectangular grid.
 28
 29    Example (2 x-nodes, 5 y-nodes):
 30    >>> grid = Grid2D(Lx=6, Ly=10, Nx=3, Ny=5)
 31
 32    The nodes are centered in the cells:
 33    >>> X, Y = grid.mesh
 34    >>> X
 35    array([[1., 1., 1., 1., 1.],
 36           [3., 3., 3., 3., 3.],
 37           [5., 5., 5., 5., 5.]])
 38
 39    You can compute cell boundaries (i.e. non-central nodes) by adding or subtracting
 40    `hx`/2 and `hy`/2 (i.e. you will miss either boundary at 0 or `Lx` or `Ly`).
 41
 42    Test of round-trip capability of grid mapping computations:
 43    >>> ij = (0, 4)
 44    >>> grid.xy2sub(X[ij], Y[ij]) == ij
 45    array([ True,  True])
 46
 47    >>> grid.sub2xy(*ij) == (X[ij], Y[ij])
 48    array([ True,  True])
 49    """
 50
 51    Lx: float = 1.0
 52    """Physical x-length of domain."""
 53    Ly: float = 1.0
 54    """Physical y-length of domain."""
 55    Nx: int = 32
 56    """Number of grid cells (and their centres) in x dir."""
 57    Ny: int = 32
 58    """Number of grid cells (and their centres) in y dir."""
 59
 60    @property
 61    def shape(self):
 62        """`(Nx, Ny)`"""
 63        return self.Nx, self.Ny
 64
 65    @property
 66    def size(self):
 67        """Total number of elements."""
 68        return np.prod(self.shape)
 69
 70    @property
 71    def domain(self):
 72        """`((0, 0), (Lx, Ly))`"""
 73        return ((0, 0), (self.Lx, self.Ly))
 74
 75    @property
 76    def Nxy(self):
 77        """`Nx` * `Ny`"""
 78        return np.prod(self.shape)
 79
 80    @property
 81    def hx(self):
 82        """x-length of cells"""
 83        return self.Lx/self.Nx
 84
 85    @property
 86    def hy(self):
 87        """y-length of cells"""
 88        return self.Ly/self.Ny
 89
 90    @property
 91    def h2(self):
 92        """`hx` * `hy`"""
 93        return self.hx*self.hy
 94
 95    @property
 96    def mesh(self):
 97        """Generate 2D coordinate grid of cell centres."""
 98        xx = np.linspace(0, self.Lx, self.Nx, endpoint=False) + self.hx/2
 99        yy = np.linspace(0, self.Ly, self.Ny, endpoint=False) + self.hy/2
100        return np.meshgrid(xx, yy, indexing="ij")
101
102    def sub2ind(self, ix, iy):
103        """Convert index `(ix, iy)` to index in flattened array."""
104        idx = np.ravel_multi_index((ix, iy), self.shape)
105        return idx
106
107    def ind2sub(self, ind):
108        """Inv. of `self.sub2ind`."""
109        ix, iy = np.unravel_index(ind, self.shape)
110        return np.asarray([ix, iy])
111
112    def xy2sub(self, x, y):
113        """Convert physical coordinate tuple to `(ix, iy)`, ix ∈ {0, ..., Nx-1}.
114
115        .. warning:: `xy2sub` and `xy2ind` *round* to nearest cell center.
116            I.e. they are not injective.
117            The alternative would be to return some kind
118            of interpolation weights distributing `(x, y)` over multiple nodes.
119        """
120        x = np.asarray(x)
121        y = np.asarray(y)
122        # Don't silence errors! Validation is useful in optimisation (e.g.)
123        assert np.all(x <= self.Lx)
124        assert np.all(y <= self.Ly)
125        # Set upper border values to slightly interior
126        x = x.clip(max=self.Lx-1e-8)
127        y = y.clip(max=self.Ly-1e-8)
128        ix = np.floor(x / self.Lx * self.Nx).astype(int)
129        iy = np.floor(y / self.Ly * self.Ny).astype(int)
130        return np.asarray([ix, iy])
131
132    def xy2ind(self, x, y):
133        """Convert physical coord to flat indx."""
134        i, j = self.xy2sub(x, y)
135        return self.sub2ind(i, j)
136
137    def sub2xy(self, ix, iy):
138        """Inverse of `self.xy2sub`."""
139        x = (np.asarray(ix) + .5) * self.hx
140        y = (np.asarray(iy) + .5) * self.hy
141        return np.asarray([x, y])
142
143    def ind2xy(self, ind):
144        """Inverse of `self.xy2ind`."""
145        i, j = self.ind2sub(ind)
146        return self.sub2xy(i, j)

Defines a 2D rectangular grid.

Example (2 x-nodes, 5 y-nodes):

>>> grid = Grid2D(Lx=6, Ly=10, Nx=3, Ny=5)

The nodes are centered in the cells:

>>> X, Y = grid.mesh
>>> X
array([[1., 1., 1., 1., 1.],
       [3., 3., 3., 3., 3.],
       [5., 5., 5., 5., 5.]])

You can compute cell boundaries (i.e. non-central nodes) by adding or subtracting hx/2 and hy/2 (i.e. you will miss either boundary at 0 or Lx or Ly).

Test of round-trip capability of grid mapping computations:

>>> ij = (0, 4)
>>> grid.xy2sub(X[ij], Y[ij]) == ij
array([ True,  True])
>>> grid.sub2xy(*ij) == (X[ij], Y[ij])
array([ True,  True])
Grid2D(Lx: float = 1.0, Ly: float = 1.0, Nx: int = 32, Ny: int = 32)
Lx: float = 1.0

Physical x-length of domain.

Ly: float = 1.0

Physical y-length of domain.

Nx: int = 32

Number of grid cells (and their centres) in x dir.

Ny: int = 32

Number of grid cells (and their centres) in y dir.

shape
60    @property
61    def shape(self):
62        """`(Nx, Ny)`"""
63        return self.Nx, self.Ny

(Nx, Ny)

size
65    @property
66    def size(self):
67        """Total number of elements."""
68        return np.prod(self.shape)

Total number of elements.

domain
70    @property
71    def domain(self):
72        """`((0, 0), (Lx, Ly))`"""
73        return ((0, 0), (self.Lx, self.Ly))

((0, 0), (Lx, Ly))

Nxy
75    @property
76    def Nxy(self):
77        """`Nx` * `Ny`"""
78        return np.prod(self.shape)

Nx * Ny

hx
80    @property
81    def hx(self):
82        """x-length of cells"""
83        return self.Lx/self.Nx

x-length of cells

hy
85    @property
86    def hy(self):
87        """y-length of cells"""
88        return self.Ly/self.Ny

y-length of cells

h2
90    @property
91    def h2(self):
92        """`hx` * `hy`"""
93        return self.hx*self.hy

hx * hy

mesh
 95    @property
 96    def mesh(self):
 97        """Generate 2D coordinate grid of cell centres."""
 98        xx = np.linspace(0, self.Lx, self.Nx, endpoint=False) + self.hx/2
 99        yy = np.linspace(0, self.Ly, self.Ny, endpoint=False) + self.hy/2
100        return np.meshgrid(xx, yy, indexing="ij")

Generate 2D coordinate grid of cell centres.

def sub2ind(self, ix, iy):
102    def sub2ind(self, ix, iy):
103        """Convert index `(ix, iy)` to index in flattened array."""
104        idx = np.ravel_multi_index((ix, iy), self.shape)
105        return idx

Convert index (ix, iy) to index in flattened array.

def ind2sub(self, ind):
107    def ind2sub(self, ind):
108        """Inv. of `self.sub2ind`."""
109        ix, iy = np.unravel_index(ind, self.shape)
110        return np.asarray([ix, iy])

Inv. of self.sub2ind.

def xy2sub(self, x, y):
112    def xy2sub(self, x, y):
113        """Convert physical coordinate tuple to `(ix, iy)`, ix ∈ {0, ..., Nx-1}.
114
115        .. warning:: `xy2sub` and `xy2ind` *round* to nearest cell center.
116            I.e. they are not injective.
117            The alternative would be to return some kind
118            of interpolation weights distributing `(x, y)` over multiple nodes.
119        """
120        x = np.asarray(x)
121        y = np.asarray(y)
122        # Don't silence errors! Validation is useful in optimisation (e.g.)
123        assert np.all(x <= self.Lx)
124        assert np.all(y <= self.Ly)
125        # Set upper border values to slightly interior
126        x = x.clip(max=self.Lx-1e-8)
127        y = y.clip(max=self.Ly-1e-8)
128        ix = np.floor(x / self.Lx * self.Nx).astype(int)
129        iy = np.floor(y / self.Ly * self.Ny).astype(int)
130        return np.asarray([ix, iy])

Convert physical coordinate tuple to (ix, iy), ix ∈ {0, ..., Nx-1}.

xy2sub and xy2ind round to nearest cell center.

I.e. they are not injective. The alternative would be to return some kind of interpolation weights distributing (x, y) over multiple nodes.

def xy2ind(self, x, y):
132    def xy2ind(self, x, y):
133        """Convert physical coord to flat indx."""
134        i, j = self.xy2sub(x, y)
135        return self.sub2ind(i, j)

Convert physical coord to flat indx.

def sub2xy(self, ix, iy):
137    def sub2xy(self, ix, iy):
138        """Inverse of `self.xy2sub`."""
139        x = (np.asarray(ix) + .5) * self.hx
140        y = (np.asarray(iy) + .5) * self.hy
141        return np.asarray([x, y])

Inverse of self.xy2sub.

def ind2xy(self, ind):
143    def ind2xy(self, ind):
144        """Inverse of `self.xy2ind`."""
145        i, j = self.ind2sub(ind)
146        return self.sub2xy(i, j)

Inverse of self.xy2ind.