Beispiel #1
0
    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())
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
    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)
Beispiel #5
0
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)
Beispiel #6
0
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)
Beispiel #7
0
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)