def convex_hull(self): """Return the smallest `IntervalProd` containing this grid. The convex hull of a set is the union of all line segments between points in the set. For a rectilinear grid, it is the interval product given by the extremal coordinates. Returns ------- convex_hull : `IntervalProd` Interval product defined by the minimum and maximum points of the grid. Examples -------- >>> g = RectGrid([-1, 0, 3], [2, 4], [5], [2, 4, 7]) >>> g.convex_hull() IntervalProd([-1., 2., 5., 2.], [ 3., 4., 5., 7.]) """ return IntervalProd(self.min(), self.max())
def _resize_discr(discr, newshp, offset, discr_kwargs): """Return a space based on ``discr`` and ``newshp``. Use the domain of ``discr`` and its partition to create a new uniformly discretized space with ``newshp`` as shape. In axes where ``offset`` is given, it determines the number of added/removed cells to the left. Where ``offset`` is ``None``, the points are distributed evenly to left and right. The ``discr_kwargs`` parameter is passed to `uniform_discr` for further specification of discretization parameters. """ nodes_on_bdry = discr_kwargs.get('nodes_on_bdry', False) if np.shape(nodes_on_bdry) == (): nodes_on_bdry = ([(bool(nodes_on_bdry), bool(nodes_on_bdry))] * discr.ndim) elif discr.ndim == 1 and len(nodes_on_bdry) == 2: nodes_on_bdry = [nodes_on_bdry] elif len(nodes_on_bdry) != discr.ndim: raise ValueError('`nodes_on_bdry` has length {}, expected {}' ''.format(len(nodes_on_bdry), discr.ndim)) dtype = discr_kwargs.pop('dtype', discr.dtype) impl = discr_kwargs.pop('impl', discr.impl) exponent = discr_kwargs.pop('exponent', discr.exponent) interp = discr_kwargs.pop('interp', discr.interp) weighting = discr_kwargs.pop('weighting', discr.weighting) affected = np.not_equal(newshp, discr.shape) ndim = discr.ndim for i in range(ndim): if affected[i] and not discr.is_uniform_byaxis[i]: raise ValueError('cannot resize in non-uniformly discretized ' 'axis {}'.format(i)) grid_min, grid_max = discr.grid.min(), discr.grid.max() cell_size = discr.cell_sides new_minpt, new_maxpt = [], [] for axis, (n_orig, n_new, off, on_bdry) in enumerate( zip(discr.shape, newshp, offset, nodes_on_bdry)): if not affected[axis]: new_minpt.append(discr.min_pt[axis]) new_maxpt.append(discr.max_pt[axis]) continue n_diff = n_new - n_orig if off is None: num_r = n_diff // 2 num_l = n_diff - num_r else: num_r = n_diff - off num_l = off try: on_bdry_l, on_bdry_r = on_bdry except TypeError: on_bdry_l = on_bdry on_bdry_r = on_bdry if on_bdry_l: new_minpt.append(grid_min[axis] - num_l * cell_size[axis]) else: new_minpt.append(grid_min[axis] - (num_l + 0.5) * cell_size[axis]) if on_bdry_r: new_maxpt.append(grid_max[axis] + num_r * cell_size[axis]) else: new_maxpt.append(grid_max[axis] + (num_r + 0.5) * cell_size[axis]) fspace = FunctionSpace(IntervalProd(new_minpt, new_maxpt), out_dtype=dtype) tspace = tensor_space(newshp, dtype=dtype, impl=impl, exponent=exponent, weighting=weighting) # Stack together the (unchanged) nonuniform axes and the (new) uniform # axes in the right order part = uniform_partition([], [], ()) for i in range(ndim): if discr.is_uniform_byaxis[i]: part = part.append( uniform_partition(new_minpt[i], new_maxpt[i], newshp[i], nodes_on_bdry=nodes_on_bdry[i])) else: part = part.append(discr.partition.byaxis[i]) return DiscreteLp(fspace, part, tspace, interp=interp)
def uniform_partition_fromgrid(grid, min_pt=None, max_pt=None): """Return a partition of an interval product based on a given grid. This method is complementary to `uniform_partition_fromintv` in that it infers the set to be partitioned from a given grid and optional parameters for ``min_pt`` and ``max_pt`` of the set. Parameters ---------- grid : `RectGrid` Grid on which the partition is based min_pt, max_pt : float, sequence of floats, or dict, optional Spatial points defining the lower/upper limits of the intervals to be partitioned. The points can be specified in two ways: float or sequence: The values are used directly as ``min_pt`` and/or ``max_pt``. dict: Index-value pairs specifying an axis and a spatial coordinate to be used in that axis. In axes which are not a key in the dictionary, the coordinate for the vector is calculated as:: min_pt = x[0] - (x[1] - x[0]) / 2 max_pt = x[-1] + (x[-1] - x[-2]) / 2 See ``Examples`` below. In general, ``min_pt`` may not be larger than ``grid.min_pt``, and ``max_pt`` not smaller than ``grid.max_pt`` in any component. ``None`` is equivalent to an empty dictionary, i.e. the values are calculated in each dimension. See Also -------- uniform_partition_fromintv Examples -------- Have ``min_pt`` and ``max_pt`` of the bounding box automatically calculated: >>> grid = odl.uniform_grid(0, 1, 3) >>> grid.coord_vectors (array([ 0. , 0.5, 1. ]),) >>> part = odl.uniform_partition_fromgrid(grid) >>> part.cell_boundary_vecs (array([-0.25, 0.25, 0.75, 1.25]),) ``min_pt`` and ``max_pt`` can be given explicitly: >>> part = odl.uniform_partition_fromgrid(grid, min_pt=0, max_pt=1) >>> part.cell_boundary_vecs (array([ 0. , 0.25, 0.75, 1. ]),) Using dictionaries, selective axes can be explicitly set. The keys refer to axes, the values to the coordinates to use: >>> grid = odl.uniform_grid([0, 0], [1, 1], (3, 3)) >>> part = odl.uniform_partition_fromgrid(grid, ... min_pt={0: -1}, max_pt={-1: 3}) >>> part.cell_boundary_vecs[0] array([-1. , 0.25, 0.75, 1.25]) >>> part.cell_boundary_vecs[1] array([-0.25, 0.25, 0.75, 3. ]) """ # Make dictionaries from `min_pt` and `max_pt` and fill with `None` where # no value is given (taking negative indices into account) if min_pt is None: min_pt = {i: None for i in range(grid.ndim)} elif not hasattr(min_pt, 'items'): # array-like min_pt = np.atleast_1d(min_pt) min_pt = {i: float(v) for i, v in enumerate(min_pt)} else: min_pt.update({ i: None for i in range(grid.ndim) if i not in min_pt and i - grid.ndim not in min_pt }) if max_pt is None: max_pt = {i: None for i in range(grid.ndim)} elif not hasattr(max_pt, 'items'): max_pt = np.atleast_1d(max_pt) max_pt = {i: float(v) for i, v in enumerate(max_pt)} else: max_pt.update({ i: None for i in range(grid.ndim) if i not in max_pt and i - grid.ndim not in max_pt }) # Set the values in the vectors by computing (None) or directly from the # given vectors (otherwise). min_pt_vec = np.empty(grid.ndim) for ax, xmin in min_pt.items(): if xmin is None: cvec = grid.coord_vectors[ax] if len(cvec) == 1: raise ValueError('in axis {}: cannot calculate `min_pt` with ' 'only 1 grid point'.format(ax)) min_pt_vec[ax] = cvec[0] - (cvec[1] - cvec[0]) / 2 else: min_pt_vec[ax] = xmin max_pt_vec = np.empty(grid.ndim) for ax, xmax in max_pt.items(): if xmax is None: cvec = grid.coord_vectors[ax] if len(cvec) == 1: raise ValueError('in axis {}: cannot calculate `max_pt` with ' 'only 1 grid point'.format(ax)) max_pt_vec[ax] = cvec[-1] + (cvec[-1] - cvec[-2]) / 2 else: max_pt_vec[ax] = xmax return RectPartition(IntervalProd(min_pt_vec, max_pt_vec), grid)
def __getitem__(self, indices): """Return ``self[indices]``. Parameters ---------- indices : index expression Object determining which parts of the partition to extract. ``None`` (new axis) and empty axes are not supported. Examples -------- >>> intvp = odl.IntervalProd([-1, 1, 4, 2], [3, 6, 5, 7]) >>> grid = odl.RectGrid([-1, 0, 3], [2, 4], [5], [2, 4, 7]) >>> part = odl.RectPartition(intvp, grid) >>> part nonuniform_partition( [-1.0, 0.0, 3.0], [2.0, 4.0], [5.0], [2.0, 4.0, 7.0], min_pt=[-1.0, 1.0, 4.0, 2.0], max_pt=[3.0, 6.0, 5.0, 7.0] ) Take an advanced slice (every second along the first axis, the last in the last axis and everything in between): >>> part[::2, ..., -1] nonuniform_partition( [-1.0, 3.0], [2.0, 4.0], [5.0], [7.0], min_pt=[-1.0, 1.0, 4.0, 5.5], max_pt=[3.0, 6.0, 5.0, 7.0] ) Too few indices are filled up with an ellipsis from the right: >>> part[1] nonuniform_partition( [0.0], [2.0, 4.0], [5.0], [2.0, 4.0, 7.0], min_pt=[-0.5, 1.0, 4.0, 2.0], max_pt=[1.5, 6.0, 5.0, 7.0] ) Colons etc work as expected: >>> part[:] == part True >>> part[:, :, :] == part True >>> part[...] == part True """ # Special case of index list: slice along first axis if isinstance(indices, list): if indices == []: new_min_pt = new_max_pt = [] else: new_min_pt = [self.cell_boundary_vecs[0][:-1][indices][0]] new_max_pt = [self.cell_boundary_vecs[0][1:][indices][-1]] for cvec in self.cell_boundary_vecs[1:]: new_min_pt.append(cvec[0]) new_max_pt.append(cvec[-1]) new_intvp = IntervalProd(new_min_pt, new_max_pt) new_grid = self.grid[indices] return RectPartition(new_intvp, new_grid) indices = normalized_index_expression(indices, self.shape, int_to_slice=True) # Build the new partition new_min_pt, new_max_pt = [], [] for cvec, idx in zip(self.cell_boundary_vecs, indices): # Determine the subinterval min_pt and max_pt vectors. Take the # first min_pt as new min_pt and the last max_pt as new max_pt. sub_min_pt = cvec[:-1][idx] sub_max_pt = cvec[1:][idx] new_min_pt.append(sub_min_pt[0]) new_max_pt.append(sub_max_pt[-1]) new_intvp = IntervalProd(new_min_pt, new_max_pt) new_grid = self.grid[indices] return RectPartition(new_intvp, new_grid)
def nonuniform_partition(*coord_vecs, **kwargs): """Return a partition with un-equally sized cells. Parameters ---------- coord_vecs1, ... coord_vecsN : `array-like` Arrays of coordinates of the mid-points of the partition cells. min_pt, max_pt : float or sequence of floats, optional Vectors defining the lower/upper limits of the intervals in an `IntervalProd` (a rectangular box). ``None`` entries mean "compute the value". nodes_on_bdry : bool or sequence, optional If a sequence is provided, it determines per axis whether to place the last grid point on the boundary (``True``) or shift it by half a cell size into the interior (``False``). In each axis, an entry may consist in a single bool or a 2-tuple of bool. In the latter case, the first tuple entry decides for the left, the second for the right boundary. The length of the sequence must be ``array.ndim``. A single boolean is interpreted as a global choice for all boundaries. Cannot be given with both min_pt and max_pt since they determine the same thing. Default: ``False`` See Also -------- uniform_partition : uniformly spaced points uniform_partition_fromintv : partition an existing set uniform_partition_fromgrid : use an existing grid as basis Examples -------- With uniformly spaced points the result is the same as a uniform partition: >>> odl.nonuniform_partition([0, 1, 2, 3]) uniform_partition(-0.5, 3.5, 4) >>> odl.nonuniform_partition([0, 1, 2, 3], [1, 2]) uniform_partition([-0.5, 0.5], [3.5, 2.5], (4, 2)) If the points are not uniformly spaced, a nonuniform partition is created. Note that the containing interval is calculated by assuming that the points are in the middle of the sub-intervals: >>> odl.nonuniform_partition([0, 1, 3]) nonuniform_partition( [0.0, 1.0, 3.0] ) Higher dimensional partitions are created by specifying the gridpoints along each dimension: >>> odl.nonuniform_partition([0, 1, 3], [1, 2]) nonuniform_partition( [0.0, 1.0, 3.0], [1.0, 2.0] ) If the endpoints should be on the boundary, the ``nodes_on_bdry`` parameter can be used: >>> odl.nonuniform_partition([0, 1, 3], nodes_on_bdry=True) nonuniform_partition( [0.0, 1.0, 3.0], nodes_on_bdry=True ) Users can also manually specify the containing intervals dimensions by using the ``min_pt`` and ``max_pt`` arguments: >>> odl.nonuniform_partition([0, 1, 3], min_pt=-2, max_pt=3) nonuniform_partition( [0.0, 1.0, 3.0], min_pt=-2.0, max_pt=3.0 ) """ # Get parameters from kwargs min_pt = kwargs.pop('min_pt', None) max_pt = kwargs.pop('max_pt', None) nodes_on_bdry = kwargs.pop('nodes_on_bdry', False) # np.size(None) == 1 sizes = [len(coord_vecs)] + [np.size(p) for p in (min_pt, max_pt)] ndim = int(np.max(sizes)) min_pt = normalized_scalar_param_list(min_pt, ndim, param_conv=float, keep_none=True) max_pt = normalized_scalar_param_list(max_pt, ndim, param_conv=float, keep_none=True) nodes_on_bdry = normalized_nodes_on_bdry(nodes_on_bdry, ndim) # Calculate the missing parameters in min_pt, max_pt for i, (xmin, xmax, (bdry_l, bdry_r), coords) in enumerate(zip(min_pt, max_pt, nodes_on_bdry, coord_vecs)): # Check input for redundancy if xmin is not None and bdry_l: raise ValueError('in axis {}: got both `min_pt` and ' '`nodes_on_bdry=True`'.format(i)) if xmax is not None and bdry_r: raise ValueError('in axis {}: got both `max_pt` and ' '`nodes_on_bdry=True`'.format(i)) # Compute boundary position if not given by user if xmin is None: if bdry_l: min_pt[i] = coords[0] else: min_pt[i] = coords[0] - (coords[1] - coords[0]) / 2.0 if xmax is None: if bdry_r: max_pt[i] = coords[-1] else: max_pt[i] = coords[-1] + (coords[-1] - coords[-2]) / 2.0 interval = IntervalProd(min_pt, max_pt) grid = RectGrid(*coord_vecs) return RectPartition(interval, grid)
def uniform_partition(min_pt=None, max_pt=None, shape=None, cell_sides=None, nodes_on_bdry=False): """Return a partition with equally sized cells. Parameters ---------- min_pt, max_pt : float or sequence of float, optional Vectors defining the lower/upper limits of the intervals in an `IntervalProd` (a rectangular box). ``None`` entries mean "compute the value". shape : int or sequence of ints, optional Number of nodes per axis. ``None`` entries mean "compute the value". cell_sides : float or sequence of floats, optional Side length of the partition cells per axis. ``None`` entries mean "compute the value". nodes_on_bdry : bool or sequence, optional If a sequence is provided, it determines per axis whether to place the last grid point on the boundary (``True``) or shift it by half a cell size into the interior (``False``). In each axis, an entry may consist in a single bool or a 2-tuple of bool. In the latter case, the first tuple entry decides for the left, the second for the right boundary. The length of the sequence must be ``array.ndim``. A single boolean is interpreted as a global choice for all boundaries. Notes ----- In each axis, 3 of the 4 possible parameters ``min_pt``, ``max_pt``, ``shape`` and ``cell_sides`` must be given. If all four are provided, they are checked for consistency. See Also -------- uniform_partition_fromintv : partition an existing set uniform_partition_fromgrid : use an existing grid as basis Examples -------- Any combination of three of the four parameters can be used for creation of a partition: >>> part = odl.uniform_partition(min_pt=0, max_pt=2, shape=4) >>> part.cell_boundary_vecs (array([ 0. , 0.5, 1. , 1.5, 2. ]),) >>> part = odl.uniform_partition(min_pt=0, shape=4, cell_sides=0.5) >>> part.cell_boundary_vecs (array([ 0. , 0.5, 1. , 1.5, 2. ]),) >>> part = odl.uniform_partition(max_pt=2, shape=4, cell_sides=0.5) >>> part.cell_boundary_vecs (array([ 0. , 0.5, 1. , 1.5, 2. ]),) >>> part = odl.uniform_partition(min_pt=0, max_pt=2, cell_sides=0.5) >>> part.cell_boundary_vecs (array([ 0. , 0.5, 1. , 1.5, 2. ]),) In higher dimensions, the parameters can be given differently in each axis. Where ``None`` is given, the value will be computed: >>> part = odl.uniform_partition(min_pt=[0, 0], max_pt=[1, 2], ... shape=[4, 2]) >>> part.cell_boundary_vecs (array([ 0. , 0.25, 0.5 , 0.75, 1. ]), array([ 0., 1., 2.])) >>> part = odl.uniform_partition(min_pt=[0, 0], max_pt=[1, 2], ... shape=[None, 2], cell_sides=[0.25, None]) >>> part.cell_boundary_vecs (array([ 0. , 0.25, 0.5 , 0.75, 1. ]), array([ 0., 1., 2.])) >>> part = odl.uniform_partition(min_pt=[0, None], max_pt=[None, 2], ... shape=[4, 2], cell_sides=[0.25, 1]) >>> part.cell_boundary_vecs (array([ 0. , 0.25, 0.5 , 0.75, 1. ]), array([ 0., 1., 2.])) By default, no grid points are placed on the boundary: >>> part = odl.uniform_partition(0, 1, 4) >>> part.nodes_on_bdry False >>> part.cell_boundary_vecs (array([ 0. , 0.25, 0.5 , 0.75, 1. ]),) >>> part.grid.coord_vectors (array([ 0.125, 0.375, 0.625, 0.875]),) This can be changed with the nodes_on_bdry parameter: >>> part = odl.uniform_partition(0, 1, 3, nodes_on_bdry=True) >>> part.nodes_on_bdry True >>> part.cell_boundary_vecs (array([ 0. , 0.25, 0.75, 1. ]),) >>> part.grid.coord_vectors (array([ 0. , 0.5, 1. ]),) We can specify this per axis, too. In this case we choose both in the first axis and only the rightmost in the second: >>> part = odl.uniform_partition([0, 0], [1, 1], (3, 3), ... nodes_on_bdry=(True, (False, True))) ... >>> part.cell_boundary_vecs[0] # first axis, as above array([ 0. , 0.25, 0.75, 1. ]) >>> part.grid.coord_vectors[0] array([ 0. , 0.5, 1. ]) >>> part.cell_boundary_vecs[1] # second, asymmetric axis array([ 0. , 0.4, 0.8, 1. ]) >>> part.grid.coord_vectors[1] array([ 0.2, 0.6, 1. ]) """ # Normalize partition parameters # np.size(None) == 1, so that would screw it for sizes 0 of the rest sizes = [ np.size(p) for p in (min_pt, max_pt, shape, cell_sides) if p is not None ] ndim = int(np.max(sizes)) min_pt = normalized_scalar_param_list(min_pt, ndim, param_conv=float, keep_none=True) max_pt = normalized_scalar_param_list(max_pt, ndim, param_conv=float, keep_none=True) shape = normalized_scalar_param_list(shape, ndim, param_conv=safe_int_conv, keep_none=True) cell_sides = normalized_scalar_param_list(cell_sides, ndim, param_conv=float, keep_none=True) nodes_on_bdry = normalized_nodes_on_bdry(nodes_on_bdry, ndim) # Calculate the missing parameters in min_pt, max_pt, shape for i, (xmin, xmax, n, dx, on_bdry) in enumerate( zip(min_pt, max_pt, shape, cell_sides, nodes_on_bdry)): num_params = sum(p is not None for p in (xmin, xmax, n, dx)) if num_params < 3: raise ValueError('in axis {}: expected at least 3 of the ' 'parameters `min_pt`, `max_pt`, `shape`, ' '`cell_sides`, got {}' ''.format(i, num_params)) # Unpack the tuple if possible, else use bool globally for this axis try: bdry_l, bdry_r = on_bdry except TypeError: bdry_l = bdry_r = on_bdry # For each node on the boundary, we subtract 1/2 from the number of # full cells between min_pt and max_pt. if xmin is None: min_pt[i] = xmax - (n - sum([bdry_l, bdry_r]) / 2.0) * dx elif xmax is None: max_pt[i] = xmin + (n - sum([bdry_l, bdry_r]) / 2.0) * dx elif n is None: # Here we add to n since (e-b)/s gives the reduced number of cells. n_calc = (xmax - xmin) / dx + sum([bdry_l, bdry_r]) / 2.0 n_round = int(round(n_calc)) if abs(n_calc - n_round) > 1e-5: raise ValueError('in axis {}: calculated number of nodes ' '{} = ({} - {}) / {} too far from integer' ''.format(i, n_calc, xmax, xmin, dx)) shape[i] = n_round elif dx is None: pass else: xmax_calc = xmin + (n - sum([bdry_l, bdry_r]) / 2.0) * dx if not np.isclose(xmax, xmax_calc): raise ValueError('in axis {}: calculated endpoint ' '{} = {} + {} * {} too far from given ' 'endpoint {}.' ''.format(i, xmax_calc, xmin, n, dx, xmax)) return uniform_partition_fromintv(IntervalProd(min_pt, max_pt), shape, nodes_on_bdry)
def uniform_grid(min_pt, max_pt, shape, nodes_on_bdry=True): """Return a grid from sampling an implicit interval product uniformly. Parameters ---------- min_pt : float or sequence of float Vectors of lower ends of the intervals in the product. max_pt : float or sequence of float Vectors of upper ends of the intervals in the product. shape : int or sequence of ints Number of nodes per axis. Entries corresponding to degenerate axes must be equal to 1. nodes_on_bdry : bool or sequence, optional If a sequence is provided, it determines per axis whether to place the last grid point on the boundary (``True``) or shift it by half a cell size into the interior (``False``). In each axis, an entry may consist in a single bool or a 2-tuple of bool. In the latter case, the first tuple entry decides for the left, the second for the right boundary. The length of the sequence must be ``array.ndim``. A single boolean is interpreted as a global choice for all boundaries. Returns ------- uniform_grid : `RectGrid` The resulting uniform grid. See Also -------- uniform_grid_fromintv : sample a given interval product odl.discr.partition.uniform_partition : divide implicitly defined interval product into equally sized subsets Examples -------- By default, the min/max points are included in the grid: >>> grid = odl.uniform_grid([-1.5, 2], [-0.5, 3], (3, 3)) >>> grid.coord_vectors (array([-1.5, -1. , -0.5]), array([ 2. , 2.5, 3. ])) If ``shape`` is supposed to refer to small subvolumes, and the grid should be their centers, use the option ``nodes_on_bdry=False``: >>> grid = odl.uniform_grid([-1.5, 2], [-0.5, 3], (2, 2), ... nodes_on_bdry=False) >>> grid.coord_vectors (array([-1.25, -0.75]), array([ 2.25, 2.75])) In 1D, we don't need sequences: >>> grid = odl.uniform_grid(0, 1, 3) >>> grid.coord_vectors (array([ 0. , 0.5, 1. ]),) """ return uniform_grid_fromintv(IntervalProd(min_pt, max_pt), shape, nodes_on_bdry=nodes_on_bdry)