def random_point(self): """Generate the random point belonging to the mesh. The use of this function is mostly limited for writing tests for packages based on `discretisedfield`. Returns ------- tuple (3,) Coordinates of a random point inside that belongs to the mesh :math:`(x_\\text{rand}, y_\\text{rand}, z_\\text{rand})`. Examples -------- 1. Generating a random mesh point. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (200e-9, 200e-9, 1e-9) >>> cell = (1e-9, 1e-9, 0.5e-9) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) >>> mesh.random_point() (...) .. note:: In the example, ellipsis is used instead of an exact tuple because the result differs each time ``random_point`` method is called. """ res = np.add(self.pmin, np.multiply(np.random.random(3), self.l)) return dfu.array2tuple(res)
def centre(self): """Mesh domain centre point. It is computed as the middle point between minimum and maximum coordinates :math:`\\mathbf{p}_\\text{c} = \\frac{1}{2} (\\mathbf{p}_\\text{min} + \\mathbf{p}_\\text{max})` This point does not necessarily coincide with the discretisation cell centre. Returns ------- tuple (3,) Mesh domain centre point :math:`(p_{c}^{x}, p_{c}^{y}, p_{c}^{z})`. Examples -------- 1. Getting the mesh centre point. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (5, 15, 20) >>> n = (5, 15, 20) >>> mesh = df.Mesh(p1=p1, p2=p2, n=n) >>> mesh.centre (2.5, 7.5, 10.0) """ res = np.multiply(np.add(self.pmin, self.pmax), 0.5) return dfu.array2tuple(res)
def l(self): """Mesh domain edge lengths. Edge length in any direction :math:`i` is computed from the points between which the mesh domain spans :math:`l^{i} = |p_{2}^{i} - p_{1}^{i}|`. Returns ------- tuple (3,) Lengths of mesh domain edges :math:`(l_{x}, l_{y}, l_{z})`. Examples -------- 1. Getting the mesh domain edge lengths. >>> import discretisedfield as df ... >>> p1 = (0, 0, -5) >>> p2 = (5, 15, 15) >>> n = (5, 15, 20) >>> mesh = df.Mesh(p1=p1, p2=p2, n=n) >>> mesh.l (5, 15, 20) .. seealso:: :py:func:`~discretisedfield.Mesh.n` """ res = np.abs(np.subtract(self.p1, self.p2)) return dfu.array2tuple(res)
def pmax(self): """Mesh point with maximum coordinates. The :math:`i`-th component of :math:`\\mathbf{p}_\\text{max}` is computed from points :math:`p_{1}` and :math:`p_{2}` between which the mesh domain spans: :math:`p_\\text{min}^{i} = \\text{max}(p_{1}^{i}, p_{2}^{i})`. Returns ------- tuple (3,) Point with maximum mesh coordinates :math:`(p_{x}^\\text{max}, p_{y}^\\text{max}, p_{z}^\\text{max})`. Examples -------- 1. Getting the maximum mesh point. >>> import discretisedfield as df ... >>> p1 = (-1.1, 2.9, 0) >>> p2 = (5, 0, -0.1) >>> cell = (0.1, 0.1, 0.005) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) >>> mesh.pmax (5.0, 2.9, 0.0) .. seealso:: :py:func:`~discretisedfield.Mesh.pmin` """ res = np.maximum(self.p1, self.p2) return dfu.array2tuple(res)
def line(self, *, p1, p2, n): """Line generator. Given two points ``p1`` and ``p2`` line is defined and ``n`` points on that line are generated and yielded in ``n`` iterations: .. math:: \\mathbf{r}_{i} = i\\frac{\\mathbf{p}_{2} - \\mathbf{p}_{1}}{n-1}, \\text{for}\\, i = 0, ..., n-1 Parameters ---------- p1 / p2 : (3,) array_like Points between which the line is defined :math:`\\mathbf{p} = (p_{x}, p_{y}, p_{z})`. n : int Number of points on the line. Yields ------ tuple (3,) :math:`\\mathbf{r}_{i}` Raises ------ ValueError If ``p1`` or ``p2`` is outside the mesh region. Examples -------- 1. Creating line generator. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (2, 2, 2) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) ... >>> line = mesh.line(p1=(0, 0, 0), p2=(2, 0, 0), n=2) >>> list(line) [(0.0, 0.0, 0.0), (2.0, 0.0, 0.0)] .. seealso:: :py:func:`~discretisedfield.Region.plane` """ if p1 not in self.region or p2 not in self.region: msg = f'Point {p1=} or point {p2=} is outside the mesh region.' raise ValueError(msg) dl = np.subtract(p2, p1) / (n - 1) for i in range(n): yield dfu.array2tuple(np.add(p1, i * dl))
def __init__(self, *, region=None, p1=None, p2=None, n=None, cell=None, bc='', subregions=dict()): if region is not None and p1 is None and p2 is None: self.region = region elif region is None and p1 is not None and p2 is not None: self.region = df.Region(p1=p1, p2=p2) else: msg = 'Either region or p1 and p2 can be passed, not both.' raise ValueError(msg) if cell is not None and n is None: self.cell = tuple(cell) n = np.divide(self.region.edges, self.cell).round().astype(int) self.n = dfu.array2tuple(n) elif n is not None and cell is None: self.n = tuple(n) cell = np.divide(self.region.edges, self.n).astype(float) self.cell = dfu.array2tuple(cell) else: msg = 'Either n or cell can be passed, not both.' raise ValueError(msg) # Check if the mesh region is an aggregate of the discretisation cell. tol = 1e-12 # picometre tolerance rem = np.remainder(self.region.edges, self.cell) if np.logical_and(np.greater(rem, tol), np.less(rem, np.subtract(self.cell, tol))).any(): msg = (f'Region cannot be divided into ' f'discretisation cells of size {self.cell=}.') raise ValueError(msg) self.bc = bc.lower() self.subregions = subregions
def __init__(self, p1, p2, n=None, cell=None, pbc=set(), regions={}, name='mesh'): self.p1 = tuple(p1) self.p2 = tuple(p2) self.pbc = pbc self.name = name self.regions = regions # Is any edge length of the domain equal to zero? if np.equal(self.l, 0).any(): msg = 'The length of one of the domain edges is zero.' raise ValueError(msg) # Determine whether cell or n was passed and define them both. if cell is not None and n is None: self.cell = tuple(cell) n = np.divide(self.l, self.cell).round().astype(int) self.n = dfu.array2tuple(n) elif n is not None and cell is None: self.n = tuple(n) cell = np.divide(self.l, self.n).astype(float) self.cell = dfu.array2tuple(cell) else: msg = ('One and only one of the parameters ' '(n or cell) should be defined.') raise ValueError(msg) # Is the mesh domain not an aggregate of discretisation cells? tol = 1e-12 # picometre tolerance rem = np.remainder(self.l, self.cell) if np.logical_and(np.greater(rem, tol), np.less(rem, np.subtract(self.cell, tol))).any(): msg = 'Mesh domain is not an aggregate of the discretisation cell.' raise ValueError(msg)
def line(self, p1, p2, n=100): """Line generator. Given two points :math:`p_{1}` and :math:`p_{2}`, :math:`n` position coordinates are generated. .. math:: \\mathbf{r}_{i} = i\\frac{\\mathbf{p}_{2} - \\mathbf{p}_{1}}{n-1} and this method yields :math:`\\mathbf{r}_{i}` in :math:`n` iterations. Parameters ---------- p1, p2 : (3,) array_like Two points between which the line is generated. n : int Number of points on the line. Yields ------ tuple :math:`\\mathbf{r}_{i}` Raises ------ ValueError If `p1` or `p2` is outside the mesh domain. Examples -------- 1. Creating a line generator. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (2, 2, 2) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) >>> tuple(mesh.line(p1=(0, 0, 0), p2=(2, 0, 0), n=2)) ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0)) """ if p1 not in self or p2 not in self: msg = f'Point {p1} or point {p2} is outside the mesh.' raise ValueError(msg) dl = np.subtract(p2, p1) / (n - 1) for i in range(n): yield dfu.array2tuple(np.add(p1, i * dl))
def point2index(self, point, /): """Convert point to the index of a cell which contains that point. Parameters ---------- point : (3,) array_like Point :math:`\\mathbf{p} = (p_{x}, p_{y}, p_{z})`. Returns ------- (3,) tuple The cell's index :math:`(i_{x}, i_{y}, i_{z})`. Raises ------ ValueError If ``point`` is outside the mesh. Examples -------- 1. Converting point to the cell's index. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (2, 2, 1) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(region=df.Region(p1=p1, p2=p2), cell=cell) >>> mesh.point2index((0.2, 1.7, 0.3)) (0, 1, 0) .. seealso:: :py:func:`~discretisedfield.Mesh.index2point` """ if point not in self.region: msg = f'Point {point=} is outside the mesh region.' raise ValueError(msg) index = np.subtract( np.divide(np.subtract(point, self.region.pmin), self.cell), 0.5).round().astype(int) # If index is rounded to the out-of-range values. index = np.clip(index, 0, np.subtract(self.n, 1)) return dfu.array2tuple(index)
def index2point(self, index, /): """Convert cell's index to its coordinate. Parameters ---------- index : (3,) array_like The cell's index :math:`(i_{x}, i_{y}, i_{z})`. Returns ------- (3,) tuple The cell's coordinate :math:`\\mathbf{p} = (p_{x}, p_{y}, p_{z})`. Raises ------ ValueError If ``index`` is out of range. Examples -------- 1. Converting cell's index to its centre point coordinate. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (2, 2, 1) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) >>> mesh.index2point((0, 0, 0)) (0.5, 0.5, 0.5) >>> mesh.index2point((0, 1, 0)) (0.5, 1.5, 0.5) .. seealso:: :py:func:`~discretisedfield.Mesh.point2index` """ if np.logical_or(np.less(index, 0), np.greater_equal(index, self.n)).any(): msg = f'Index {index=} out of range.' raise ValueError(msg) point = np.add(self.region.pmin, np.multiply(np.add(index, 0.5), self.cell)) return dfu.array2tuple(point)
def point2index(self, point): """Compute the index of a cell to which the point belongs to. Parameters ---------- p : (3,) array_like The point's coordinate :math:`(p_{x}, p_{y}, p_{z})`. Returns ------- The cell's index :math:`(i_{x}, i_{y}, i_{z})`. Raises ------ ValueError If `point` is outside the mesh domain. Examples -------- 1. Converting point's coordinate to the cell index. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (2, 2, 1) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) >>> mesh.point2index((0.2, 1.7, 0.3)) (0, 1, 0) .. seealso:: :py:func:`~discretisedfield.Mesh.index2point` """ if point not in self: msg = f'Point {point} is outside the mesh.' raise ValueError(msg) index = np.subtract( np.divide(np.subtract(point, self.pmin), self.cell), 0.5).round().astype(int) # If index is rounded to the out-of-range values. index = np.clip(index, 0, np.subtract(self.n, 1)) return dfu.array2tuple(index)
def index2point(self, index): """Convert cell's index to the cell's centre coordinate. Parameters ---------- index : (3,) array_like The cell's index :math:`(i_{x}, i_{y}, i_{z})`. Returns ------- The cell's centre point :math:`(p_{x}, p_{y}, p_{z})`. Raises ------ ValueError If the cell's index is out of range. Examples -------- 1. Converting cell's index to its coordinate. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (2, 2, 1) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) >>> mesh.index2point((0, 1, 0)) (0.5, 1.5, 0.5) .. seealso:: :py:func:`~discretisedfield.Mesh.point2index` """ # Does index refer to a cell outside the mesh? if np.logical_or(np.less(index, 0), np.greater_equal(index, self.n)).any(): msg = 'Index out of range.' raise ValueError(msg) res = np.add(self.pmin, np.multiply(np.add(index, 0.5), self.cell)) return dfu.array2tuple(res)
def test_array2tuple(): dfu.array2tuple(np.array([1, 2, 3])) == (1, 2, 3)
def plane(self, *args, n=None, **kwargs): """Extracts plane mesh. If one of the axes (``'x'``, ``'y'``, or ``'z'``) is passed as a string, a plane mesh perpendicular to that axis is extracted, intersecting the mesh region at its centre. Alternatively, if a keyword argument is passed (e.g. ``x=1e-9``), a plane perpendicular to the x-axis (parallel to yz-plane) and intersecting it at ``x=1e-9`` is extracted. The number of points in two dimensions on the plane can be defined using ``n`` tuple (e.g. ``n=(10, 15)``). The resulting mesh has an attribute ``info``, which is a dictionary containing basic information about the plane mesh. Parameters ---------- n : (2,) tuple The number of points on the plane in two dimensions. Returns ------ discretisedfield.Mesh An extracted mesh. Examples -------- 1. Extracting the plane mesh at a specific point (``y=1``). >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (5, 5, 5) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) ... >>> plane_mesh = mesh.plane(y=1) 2. Extracting the xy-plane mesh at the mesh region centre. >>> plane_mesh = mesh.plane('z') 3. Specifying the number of points on the plane. >>> plane_mesh = mesh.plane('z', n=(3, 3)) .. seealso:: :py:func:`~discretisedfield.Region.line` """ if args and not kwargs: if len(args) != 1: msg = f'Multiple args ({args}) passed.' raise ValueError(msg) # Only planeaxis is provided via args and the point is defined as # the centre of the sample. planeaxis = dfu.axesdict[args[0]] point = self.region.centre[planeaxis] elif kwargs and not args: if len(kwargs) != 1: msg = f'Multiple kwargs ({kwargs}) passed.' raise ValueError(msg) planeaxis, point = list(kwargs.items())[0] planeaxis = dfu.axesdict[planeaxis] # Check if point is outside the mesh region. test_point = list(self.region.centre) # make it mutable test_point[planeaxis] = point if test_point not in self.region: msg = f'Point {test_point} is outside the mesh region.' raise ValueError(msg) else: msg = 'Either one arg or one kwarg can be passed, not both.' raise ValueError(msg) # Get indices of in-plane axes. axis1, axis2 = tuple( filter(lambda val: val != planeaxis, dfu.axesdict.values())) if n is None: n = (self.n[axis1], self.n[axis2]) # Build plane-mesh. p1pm, p2pm, npm = np.zeros(3), np.zeros(3), np.zeros(3, dtype=int) ilist = [axis1, axis2, planeaxis] p1pm[ilist] = (self.region.pmin[axis1], self.region.pmin[axis2], point - self.cell[planeaxis] / 2) p2pm[ilist] = (self.region.pmax[axis1], self.region.pmax[axis2], point + self.cell[planeaxis] / 2) npm[ilist] = (*n, 1) plane_mesh = self.__class__(p1=p1pm, p2=p2pm, n=dfu.array2tuple(npm)) # Add info dictionary, so that the mesh can be interpreted easier. info = dict() info['planeaxis'] = planeaxis info['point'] = point info['axis1'], info['axis2'] = axis1, axis2 plane_mesh.info = info return plane_mesh
def plane(self, *args, n=None, **kwargs): """Slices the mesh with a plane. If one of the axes (`'x'`, `'y'`, or `'z'`) is passed as a string, a plane perpendicular to that axis is generated which intersects the mesh at its centre. Alternatively, if a keyword argument is passed (e.g. `x=1`), a plane perpendicular to the x-axis and intersecting it at x=1 is generated. The number of points in two dimensions on the plane can be defined using `n` (e.g. `n=(10, 15)`). Using the generated plane, a new "two-dimensional" mesh is created and returned. Parameters ---------- n : tuple of length 2 The number of points on the plane in two dimensions Returns ------ discretisedfield.Mesh A mesh obtained as an intersection of mesh and the plane. Examples -------- 1. Intersecting the mesh with a plane. >>> import discretisedfield as df ... >>> p1 = (0, 0, 0) >>> p2 = (2, 2, 2) >>> cell = (1, 1, 1) >>> mesh = df.Mesh(p1=p1, p2=p2, cell=cell) >>> mesh.plane(y=1) Mesh(p1=(0.0, 0.5, 0.0), p2=(2.0, 1.5, 2.0), ...) """ info = dfu.plane_info(*args, **kwargs) if info['point'] is None: # Plane info was extracted from args. info['point'] = self.centre[info['planeaxis']] else: # Plane info was extracted from kwargs and should be # tested whether it is inside the mesh. test_point = list(self.centre) test_point[info['planeaxis']] = info['point'] if test_point not in self: msg = f'Point {test_point} is outside the mesh.' raise ValueError(msg) # Determine the n tuple. if n is None: n = (self.n[info['axis1']], self.n[info['axis2']]) # Build a mesh. p1s, p2s, ns = np.zeros(3), np.zeros(3), np.zeros(3) ilist = [info['axis1'], info['axis2'], info['planeaxis']] p1s[ilist] = (self.pmin[info['axis1']], self.pmin[info['axis2']], info['point'] - self.cell[info['planeaxis']] / 2) p2s[ilist] = (self.pmax[info['axis1']], self.pmax[info['axis2']], info['point'] + self.cell[info['planeaxis']] / 2) ns[ilist] = n + (1, ) ns = dfu.array2tuple(ns.astype(int)) plane_mesh = self.__class__(p1=p1s, p2=p2s, n=ns) plane_mesh.info = info # Add info so it can be interpreted easier return plane_mesh