Beispiel #1
0
    def __init__(self, domain, range=None, ran_shp=None, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        domain : uniform `DiscreteLp`
            Uniformly discretized space, the operator can be applied
            to its elements.
        range : uniform `DiscreteLp`, optional
            Uniformly discretized space in which the result of the
            application of this operator lies.
            For the default ``None``, a space with the same attributes
            as ``domain`` is used, except for its shape, which is set
            to ``ran_shp``.
        ran_shp : sequence of ints, optional
            Shape of the range of this operator. This can be provided
            instead of ``range`` and is mandatory if ``range`` is
            ``None``.
        offset : int or sequence of ints, optional
            Number of cells to add to/remove from the left of
            ``domain.partition``. By default, the difference is
            distributed evenly, with preference for left in case of
            ambiguity.
            This option is can only be used together with ``ran_shp``.
        pad_mode : string, optional
            Method to be used to fill in missing values in an enlarged array.

            ``'constant'``: Fill with ``pad_const``.

            ``'symmetric'``: Reflect at the boundaries, not doubling the
            outmost values. This requires left and right padding sizes
            to be strictly smaller than the original array shape.

            ``'periodic'``: Fill in values from the other side, keeping
            the order. This requires left and right padding sizes to be
            at most as large as the original array shape.

            ``'order0'``: Extend constantly with the outmost values
            (ensures continuity).

            ``'order1'``: Extend with constant slope (ensures continuity of
            the first derivative). This requires at least 2 values along
            each axis where padding is applied.

        pad_const : scalar, optional
            Value to be used in the ``'constant'`` padding mode.

        discr_kwargs: dict, optional
            Keyword arguments passed to the `uniform_discr` constructor.

        Examples
        --------
        The simplest way of initializing a resizing operator is by
        providing ``ran_shp`` and, optionally, parameters for the padding
        variant to be used. The range is inferred from ``domain`` and
        the supplied parameters. If no ``offset`` is given, the difference
        in size is evenly distributed to both sides:

        >>> space = odl.uniform_discr([0, 0], [1, 1], (2, 4))
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4))
        >>> resize_op.range
        uniform_discr([-0.5,  0. ], [ 1.5,  1. ], (4, 4))

        Testing different padding methods in the first axis (zero padding
        is the default):

        >>> x = [[1, 2, 3, 4],
        ...      [5, 6, 7, 8]]
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4))
        >>> print(resize_op(x))
        [[ 0.,  0.,  0.,  0.],
         [ 1.,  2.,  3.,  4.],
         [ 5.,  6.,  7.,  8.],
         [ 0.,  0.,  0.,  0.]]
        >>>
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4),
        ...                                  offset=(0, 0),
        ...                                  pad_mode='periodic')
        >>> print(resize_op(x))
        [[ 1.,  2.,  3.,  4.],
         [ 5.,  6.,  7.,  8.],
         [ 1.,  2.,  3.,  4.],
         [ 5.,  6.,  7.,  8.]]
        >>>
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4),
        ...                                  offset=(0, 0),
        ...                                  pad_mode='order0')
        >>> print(resize_op(x))
        [[ 1.,  2.,  3.,  4.],
         [ 5.,  6.,  7.,  8.],
         [ 5.,  6.,  7.,  8.],
         [ 5.,  6.,  7.,  8.]]

        Alternatively, the range of the operator can be provided directly.
        This requires that the partitions match, i.e. that the cell sizes
        are the same and there is no shift:

        >>> # Same space as in the first example, see above
        >>> large_spc = odl.uniform_discr([-0.5, 0], [1.5, 1], (4, 4))
        >>> resize_op = odl.ResizingOperator(space, large_spc,
        ...                                  pad_mode='periodic')
        >>> print(resize_op(x))
        [[ 5.,  6.,  7.,  8.],
         [ 1.,  2.,  3.,  4.],
         [ 5.,  6.,  7.,  8.],
         [ 1.,  2.,  3.,  4.]]
        """
        # Swap names to be able to use the range iterator without worries
        import builtins
        ran, range = range, builtins.range

        if not isinstance(domain, DiscreteLp):
            raise TypeError('`domain` must be a `DiscreteLp` instance, '
                            'got {!r}'.format(domain))

        offset = kwargs.pop('offset', None)
        discr_kwargs = kwargs.pop('discr_kwargs', {})

        if ran is None:
            if ran_shp is None:
                raise ValueError('either `ran` or `ran_shp` must be ' 'given')

            offset = normalized_scalar_param_list(offset,
                                                  domain.ndim,
                                                  param_conv=safe_int_conv,
                                                  keep_none=True)

            ran = _resize_discr(domain, ran_shp, offset, discr_kwargs)
            self.__offset = tuple(_offset_from_spaces(domain, ran))

        elif ran_shp is None:
            if offset is not None:
                raise ValueError('`offset` can only be combined with '
                                 '`ran_shp`')

            for i in range(domain.ndim):
                if (ran.is_uniform_byaxis[i]
                        and domain.is_uniform_byaxis[i] and not np.isclose(
                            ran.cell_sides[i], domain.cell_sides[i])):
                    raise ValueError(
                        'in axis {}: cell sides of domain and range differ '
                        'significantly: (difference {})'
                        ''.format(i, ran.cell_sides[i] - domain.cell_sides[i]))

            self.__offset = _offset_from_spaces(domain, ran)

        else:
            raise ValueError('cannot combine `range` with `ran_shape`')

        pad_mode = kwargs.pop('pad_mode', 'constant')
        pad_mode, pad_mode_in = str(pad_mode).lower(), pad_mode
        if pad_mode not in _SUPPORTED_RESIZE_PAD_MODES:
            raise ValueError("`pad_mode` '{}' not understood"
                             "".format(pad_mode_in))

        self.__pad_mode = pad_mode
        # Store constant in a way that ensures safe casting (one-element array)
        self.__pad_const = np.array(kwargs.pop('pad_const', 0),
                                    dtype=ran.dtype)

        # padding mode 'constant' with `pad_const != 0` is not linear
        linear = (self.pad_mode != 'constant' or self.pad_const == 0.0)

        super(ResizingOperatorBase, self).__init__(domain, ran, linear=linear)
Beispiel #2
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 #3
0
def kaczmarz(ops, x, rhs, niter, omega=1, projection=None,
             callback=None):
    """Optimized implementation of Kaczmarz's method.

    Solves the inverse problem given by the set of equations::

        A_n(x) = rhs_n

    This is also known as the Landweber-Kaczmarz's method, since the method
    coincides with the Landweber method for a single operator.

    Parameters
    ----------
    ops : sequence of `Operator`'s
        Operators in the inverse problem. ``op[i].derivative(x).adjoint`` must
        be well-defined for ``x`` in the operator domain and for all ``i``.
    x : ``op.domain`` element
        Element to which the result is written. Its initial value is
        used as starting point of the iteration, and its values are
        updated in each iteration step.
    rhs : sequence of ``ops[i].range`` elements
        Right-hand side of the equation defining the inverse problem.
    niter : int
        Number of iterations.
    omega : positive float or sequence of positive floats, optional
        Relaxation parameter in the iteration. If a single float is given the
        same step is used for all operators, otherwise separate steps are used.
    projection : callable, optional
        Function that can be used to modify the iterates in each iteration,
        for example enforcing positivity. The function should take one
        argument and modify it in-place.
    callback : callable, optional
        Object executing code per iteration, e.g. plotting each iterate.

    Notes
    -----
    This method calculates an approximate least-squares solution of
    the inverse problem of the first kind

    .. math::
        \mathcal{A}_i (x) = y_i \\quad 1 \\leq i \\leq n,

    for a given :math:`y_n \\in \mathcal{Y}_n`, i.e. an approximate
    solution :math:`x^*` to

    .. math::
        \min_{x\\in \mathcal{X}}
        \\sum_{i=1}^n \| \mathcal{A}_i(x) - y_i \|_{\mathcal{Y}_i}^2

    for a (Frechet-) differentiable operator
    :math:`\mathcal{A}: \mathcal{X} \\to \mathcal{Y}` between Hilbert
    spaces :math:`\mathcal{X}` and :math:`\mathcal{Y}`. The method
    starts from an initial guess :math:`x_0` and uses the
    iteration

    .. math::
        x_{k+1} = x_k - \omega_{[k]} \ \partial \mathcal{A}_{[k]}(x_k)^*
                                 (\mathcal{A}_{[k]}(x_k) - y_{[k]}),

    where :math:`\partial \mathcal{A}_{[k]}(x_k)` is the Frechet derivative
    of :math:`\mathcal{A}_{[k]}` at :math:`x_k`, :math:`\omega_{[k]}` is a
    relaxation parameter and :math:`[k] := k \\text{ mod } n`.

    For linear problems, a choice
    :math:`0 < \omega_i < 2/\\lVert \mathcal{A}_{i}^2\\rVert` guarantees
    convergence, where :math:`\|\mathcal{A}_{i}\|` stands for the
    operator norm of :math:`\mathcal{A}_{i}`.

    This implementation uses a minimum amount of memory copies by
    applying re-usable temporaries and in-place evaluation.

    The method is also described in a
    `Wikipedia article
    <https://en.wikipedia.org/wiki/Kaczmarz_method>`_. and in Natterer, F.
    Mathematical Methods in Image Reconstruction, section 5.3.2.

    See Also
    --------
    landweber
    """
    domain = ops[0].domain
    if any(domain != opi.domain for opi in ops):
        raise ValueError('`opi[i].domain` are not all equal')

    if x not in domain:
        raise TypeError('`x` {!r} is not in the domain of `ops` {!r}'
                        ''.format(x, domain))

    if len(ops) != len(rhs):
        raise ValueError('`number of `ops` {} does not match number of '
                         '`rhs` {}'.format(len(ops), len(rhs)))

    omega = normalized_scalar_param_list(omega, len(ops), param_conv=float)

    # Reusable elements in the range, one per type of space
    ranges = [opi.range for opi in ops]
    unique_ranges = set(ranges)
    tmp_rans = {ran: ran.element() for ran in unique_ranges}

    # Single reusable element in the domain
    tmp_dom = domain.element()

    # Iteratively find solution
    for _ in range(niter):
        for i in range(len(ops)):
            # Find residual
            tmp_ran = tmp_rans[ops[i].range]
            ops[i](x, out=tmp_ran)
            tmp_ran -= rhs[i]

            # Update x
            ops[i].derivative(x).adjoint(tmp_ran, out=tmp_dom)
            x.lincomb(1, x, -omega[i], tmp_dom)

            if projection is not None:
                projection(x)

            if callback is not None:
                callback(x)
Beispiel #4
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 #5
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 #6
0
def uniform_grid_fromintv(intv_prod, shape, nodes_on_bdry=True):
    """Return a grid from sampling an interval product uniformly.

    The resulting grid will by default include ``intv_prod.min_pt`` and
    ``intv_prod.max_pt`` as grid points. If you want a subdivision into
    equally sized cells with grid points in the middle, use
    `uniform_partition` instead.

    Parameters
    ----------
    intv_prod : `IntervalProd`
        Set to be sampled.
    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
    -------
    sampling : `RectGrid`
        Uniform sampling grid for the interval product.

    Examples
    --------
    >>> rbox = odl.IntervalProd([-1.5, 2], [-0.5, 3])
    >>> grid = uniform_grid_fromintv(rbox, (3, 3))
    >>> grid.coord_vectors
    (array([-1.5, -1. , -0.5]), array([ 2. ,  2.5,  3. ]))

    To have the nodes in the "middle", use ``nodes_on_bdry=False``:

    >>> grid = uniform_grid_fromintv(rbox, (2, 2), nodes_on_bdry=False)
    >>> grid.coord_vectors
    (array([-1.25, -0.75]), array([ 2.25,  2.75]))

    See Also
    --------
    uniform_grid : Create a uniform grid directly.
    odl.discr.partition.uniform_partition_fromintv :
        divide interval product into equally sized subsets
    """
    if not isinstance(intv_prod, IntervalProd):
        raise TypeError('{!r} is not an `IntervalProd` instance'
                        ''.format(intv_prod))

    if (np.any(np.isinf(intv_prod.min_pt)) or
            np.any(np.isinf(intv_prod.max_pt))):
        raise ValueError('`intv_prod` must be finite, got {!r}'
                         ''.format('intv_prod'))

    shape = normalized_scalar_param_list(shape, intv_prod.ndim, safe_int_conv)

    if np.shape(nodes_on_bdry) == ():
        nodes_on_bdry = ([(bool(nodes_on_bdry), bool(nodes_on_bdry))] *
                         intv_prod.ndim)
    elif intv_prod.ndim == 1 and len(nodes_on_bdry) == 2:
        nodes_on_bdry = [nodes_on_bdry]
    elif len(nodes_on_bdry) != intv_prod.ndim:
        raise ValueError('`nodes_on_bdry` has length {}, expected {}'
                         ''.format(len(nodes_on_bdry), intv_prod.ndim))
    else:
        shape = tuple(int(n) for n in shape)

    # We need to determine the placement of the grid minimum and maximum
    # points based on the choices in nodes_on_bdry. If in a given axis,
    # and for a given side (left or right), the entry is True, the node lies
    # on the boundary, so this coordinate can simply be taken as-is.
    #
    # Otherwise, the following conditions must be met:
    #
    # 1. The node should be half a stride s away from the boundary
    # 2. Adding or subtracting (n-1)*s should give the other extremal node.
    #
    # If both nodes are to be shifted half a stride inside,
    # the second condition yields
    # a + s/2 + (n-1)*s = b - s/2 => s = (b - a) / n,
    # hence the extremal grid points are
    # gmin = a + s/2 = a + (b - a) / (2 * n),
    # gmax = b - s/2 = b - (b - a) / (2 * n).
    #
    # In the case where one node, say the rightmost, lies on the boundary,
    # the condition 2. reads as
    # a + s/2 + (n-1)*s = b => s = (b - a) / (n - 1/2),
    # thus
    # gmin = a + (b - a) / (2 * n - 1).

    gmin, gmax = [], []
    for n, xmin, xmax, on_bdry in zip(shape, intv_prod.min_pt,
                                      intv_prod.max_pt, nodes_on_bdry):

        # 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

        if bdry_l and bdry_r:
            gmin.append(xmin)
            gmax.append(xmax)
        elif bdry_l and not bdry_r:
            gmin.append(xmin)
            gmax.append(xmax - (xmax - xmin) / (2 * n - 1))
        elif not bdry_l and bdry_r:
            gmin.append(xmin + (xmax - xmin) / (2 * n - 1))
            gmax.append(xmax)
        else:
            gmin.append(xmin + (xmax - xmin) / (2 * n))
            gmax.append(xmax - (xmax - xmin) / (2 * n))

    # Create the grid
    coord_vecs = [np.linspace(mi, ma, num, endpoint=True, dtype=np.float64)
                  for mi, ma, num in zip(gmin, gmax, shape)]
    return RectGrid(*coord_vecs)
Beispiel #7
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
    sizes = [np.size(p) for p in (min_pt, max_pt, shape, cell_sides)]
    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 #8
0
def reciprocal_grid(grid, shift=True, axes=None, halfcomplex=False):
    """Return the reciprocal of the given regular grid.

    This function calculates the reciprocal (Fourier/frequency space)
    grid for a given regular grid defined by the nodes::

        x[k] = x[0] + k * s,

    where ``k = (k[0], ..., k[d-1])`` is a ``d``-dimensional index in
    the range ``0 <= k < N`` (component-wise). The multi-index
    ``N`` is the shape of the input grid.
    This grid's reciprocal is then given by the nodes::

        xi[j] = xi[0] + j * sigma,

    with the reciprocal grid stride ``sigma = 2*pi / (s * N)``.
    The minimum frequency ``xi[0]`` can in principle be chosen
    freely, but usually it is chosen in a such a way that the reciprocal
    grid is centered around zero. For this, there are two possibilities:

    1. Make the grid point-symmetric around 0.

    2. Make the grid "almost" point-symmetric around zero by shifting
       it to the left by half a reciprocal stride.

    In the first case, the minimum frequency (per axis) is given as::

        xi_1[0] = -pi/s + pi/(s*n) = -pi/s + sigma/2.

    For the second case, it is::

        xi_1[0] = -pi / s.

    Note that the zero frequency is contained in case 1 for an odd
    number of points, while for an even size, the second option
    guarantees that 0 is contained.

    If a real-to-complex (half-complex) transform is to be computed,
    the reciprocal grid has the shape ``M[i] = floor(N[i]/2) + 1``
    in the last transform axis ``i``.

    Parameters
    ----------
    grid : uniform `RectGrid`
        Original sampling grid,.
    shift : bool or sequence of bools, optional
        If ``True``, the grid is shifted by half a stride in the negative
        direction. With a sequence, this option is applied separately on
        each axis.
    axes : int or sequence of ints, optional
        Dimensions in which to calculate the reciprocal. The sequence
        must have the same length as ``shift`` if the latter is given
        as a sequence. ``None`` means all axes in ``grid``.
    halfcomplex : bool, optional
        If ``True``, return the half of the grid with last coordinate
        less than zero. This is related to the fact that for real-valued
        functions, the other half is the mirrored complex conjugate of
        the given half and therefore needs not be stored.

    Returns
    -------
    reciprocal_grid : uniform `RectGrid`
        The reciprocal grid.
    """
    if axes is None:
        axes = list(range(grid.ndim))
    else:
        try:
            axes = [int(axes)]
        except TypeError:
            axes = list(axes)

    # List indicating shift or not per "active" axis, same length as axes
    shift_list = normalized_scalar_param_list(shift, length=len(axes),
                                              param_conv=bool)

    # Full-length vectors
    stride = grid.stride
    shape = np.array(grid.shape)
    rmin = grid.min_pt.copy()
    rmax = grid.max_pt.copy()
    rshape = list(shape)

    # Shifted axes (full length to avoid ugly double indexing)
    shifted = np.zeros(grid.ndim, dtype=bool)
    shifted[axes] = shift_list
    rmin[shifted] = -np.pi / stride[shifted]
    # Length min->max increases by double the shift, so we
    # have to compensate by a full stride
    rmax[shifted] = (-rmin[shifted] -
                     2 * np.pi / (stride[shifted] * shape[shifted]))

    # Non-shifted axes
    not_shifted = np.zeros(grid.ndim, dtype=bool)
    not_shifted[axes] = np.logical_not(shift_list)
    rmin[not_shifted] = ((-1.0 + 1.0 / shape[not_shifted]) *
                         np.pi / stride[not_shifted])
    rmax[not_shifted] = -rmin[not_shifted]

    # Change last axis shape and max if halfcomplex
    if halfcomplex:
        rshape[axes[-1]] = shape[axes[-1]] // 2 + 1

        # - Odd and shifted: - stride / 2
        # - Even and not shifted: + stride / 2
        # - Otherwise: 0
        last_odd = shape[axes[-1]] % 2 == 1
        last_shifted = shift_list[-1]
        half_rstride = np.pi / (shape[axes[-1]] * stride[axes[-1]])

        if last_odd and last_shifted:
            rmax[axes[-1]] = -half_rstride
        elif not last_odd and not last_shifted:
            rmax[axes[-1]] = half_rstride
        else:
            rmax[axes[-1]] = 0

    return uniform_grid(rmin, rmax, rshape)
Beispiel #9
0
def dft_postprocess_data(arr, real_grid, recip_grid, shift, axes,
                         interp, sign='-', op='multiply', out=None):
    """Post-process the Fourier-space data after DFT.

    This function multiplies the given data with the separable
    function::

        q(xi) = exp(+- 1j * dot(x[0], xi)) * s * phi_hat(xi_bar)

    where ``x[0]`` and ``s`` are the minimum point and the stride of
    the real-space grid, respectively, and ``phi_hat(xi_bar)`` is the FT
    of the interpolation kernel. The sign of the exponent depends on the
    choice of ``sign``. Note that for ``op='divide'`` the
    multiplication with ``s * phi_hat(xi_bar)`` is replaced by a
    division with the same array.

    In discretized form on the reciprocal grid, the exponential part
    of this function becomes an array::

        q[k] = exp(+- 1j * dot(x[0], xi[k]))

    and the arguments ``xi_bar`` to the interpolation kernel
    are the normalized frequencies::

        for 'shift=True'  : xi_bar[k] = -pi + pi * (2*k) / N
        for 'shift=False' : xi_bar[k] = -pi + pi * (2*k+1) / N

    See [Pre+2007], Section 13.9 "Computing Fourier Integrals Using
    the FFT" for a similar approach.

    Parameters
    ----------
    arr : `array-like`
        Array to be pre-processed. An array with real data type is
        converted to its complex counterpart.
    real_grid : uniform `RectGrid`
        Real space grid in the transform.
    recip_grid : uniform `RectGrid`
        Reciprocal grid in the transform
    shift : bool or sequence of bools
        If ``True``, the grid is shifted by half a stride in the negative
        direction in the corresponding axes. The sequence must have the
        same length as ``axes``.
    axes : int or sequence of ints
        Dimensions along which to take the transform. The sequence must
        have the same length as ``shifts``.
    interp : string or sequence of strings
        Interpolation scheme used in the real-space.
    sign : {'-', '+'}, optional
        Sign of the complex exponent.
    op : {'multiply', 'divide'}, optional
        Operation to perform with the stride times the interpolation
        kernel FT
    out : `numpy.ndarray`, optional
        Array in which the result is stored. If ``out is arr``, an
        in-place modification is performed.

    Returns
    -------
    out : `numpy.ndarray`
        Result of the post-processing. If ``out`` was given, the returned
        object is a reference to it.

    References
    ----------
    [Pre+2007] Press, W H, Teukolsky, S A, Vetterling, W T, and Flannery, B P.
    *Numerical Recipes in C - The Art of Scientific Computing* (Volume 3).
    Cambridge University Press, 2007.
    """
    arr = np.asarray(arr)
    if is_real_floating_dtype(arr.dtype):
        arr = arr.astype(complex_dtype(arr.dtype))
    elif not is_complex_floating_dtype(arr.dtype):
        raise ValueError('array data type {} is not a complex floating point '
                         'data type'.format(dtype_repr(arr.dtype)))

    if out is None:
        out = arr.copy()
    elif out is not arr:
        out[:] = arr

    if axes is None:
        axes = list(range(arr.ndim))
    else:
        try:
            axes = [int(axes)]
        except TypeError:
            axes = list(axes)

    shift_list = normalized_scalar_param_list(shift, length=len(axes),
                                              param_conv=bool)

    if sign == '-':
        imag = -1j
    elif sign == '+':
        imag = 1j
    else:
        raise ValueError("`sign` '{}' not understood".format(sign))

    op, op_in = str(op).lower(), op
    if op not in ('multiply', 'divide'):
        raise ValueError("kernel `op` '{}' not understood".format(op_in))

    # Make a list from interp if that's not the case already
    try:
        # Duck-typed string check
        interp + ''
    except TypeError:
        pass
    else:
        interp = [str(interp).lower()] * arr.ndim

    onedim_arrs = []
    for ax, shift, intp in zip(axes, shift_list, interp):
        x = real_grid.min_pt[ax]
        xi = recip_grid.coord_vectors[ax]

        # First part: exponential array
        onedim_arr = np.exp(imag * x * xi)

        # Second part: interpolation kernel
        len_dft = recip_grid.shape[ax]
        len_orig = real_grid.shape[ax]
        halfcomplex = (len_dft < len_orig)
        odd = len_orig % 2

        fmin = -0.5 if shift else -0.5 + 1.0 / (2 * len_orig)
        if halfcomplex:
            # maximum lies around 0, possibly half a cell left or right of it
            if shift and odd:
                fmax = - 1.0 / (2 * len_orig)
            elif not shift and not odd:
                fmax = 1.0 / (2 * len_orig)
            else:
                fmax = 0.0

        else:  # not halfcomplex
            # maximum lies close to 0.5, half or full cell left of it
            if shift:
                # -0.5 + (N-1)/N = 0.5 - 1/N
                fmax = 0.5 - 1.0 / len_orig
            else:
                # -0.5 + 1/(2*N) + (N-1)/N = 0.5 - 1/(2*N)
                fmax = 0.5 - 1.0 / (2 * len_orig)

        freqs = np.linspace(fmin, fmax, num=len_dft)
        stride = real_grid.stride[ax]

        interp_kernel = _interp_kernel_ft(freqs, intp)
        interp_kernel *= stride

        if op == 'multiply':
            onedim_arr *= interp_kernel
        else:
            onedim_arr /= interp_kernel

        onedim_arrs.append(onedim_arr.astype(out.dtype, copy=False))

    fast_1d_tensor_mult(out, onedim_arrs, axes=axes, out=out)
    return out
Beispiel #10
0
def kaczmarz(ops, x, rhs, niter, omega=1, projection=None, callback=None):
    """Optimized implementation of Kaczmarz's method.

    Solves the inverse problem given by the set of equations::

        A_n(x) = rhs_n

    This is also known as the Landweber-Kaczmarz's method, since the method
    coincides with the Landweber method for a single operator.

    Parameters
    ----------
    ops : sequence of `Operator`'s
        Operators in the inverse problem. ``op[i].derivative(x).adjoint`` must
        be well-defined for ``x`` in the operator domain and for all ``i``.
    x : ``op.domain`` element
        Element to which the result is written. Its initial value is
        used as starting point of the iteration, and its values are
        updated in each iteration step.
    rhs : sequence of ``ops[i].range`` elements
        Right-hand side of the equation defining the inverse problem.
    niter : int
        Number of iterations.
    omega : positive float or sequence of positive floats, optional
        Relaxation parameter in the iteration. If a single float is given the
        same step is used for all operators, otherwise separate steps are used.
    projection : callable, optional
        Function that can be used to modify the iterates in each iteration,
        for example enforcing positivity. The function should take one
        argument and modify it in-place.
    callback : callable, optional
        Object executing code per iteration, e.g. plotting each iterate.

    Notes
    -----
    This method calculates an approximate least-squares solution of
    the inverse problem of the first kind

    .. math::
        \mathcal{A}_i (x) = y_i \\quad 1 \\leq i \\leq n,

    for a given :math:`y_n \\in \mathcal{Y}_n`, i.e. an approximate
    solution :math:`x^*` to

    .. math::
        \min_{x\\in \mathcal{X}}
        \\sum_{i=1}^n \| \mathcal{A}_i(x) - y_i \|_{\mathcal{Y}_i}^2

    for a (Frechet-) differentiable operator
    :math:`\mathcal{A}: \mathcal{X} \\to \mathcal{Y}` between Hilbert
    spaces :math:`\mathcal{X}` and :math:`\mathcal{Y}`. The method
    starts from an initial guess :math:`x_0` and uses the
    iteration

    .. math::
        x_{k+1} = x_k - \omega_{[k]} \ \partial \mathcal{A}_{[k]}(x_k)^*
                                 (\mathcal{A}_{[k]}(x_k) - y_{[k]}),

    where :math:`\partial \mathcal{A}_{[k]}(x_k)` is the Frechet derivative
    of :math:`\mathcal{A}_{[k]}` at :math:`x_k`, :math:`\omega_{[k]}` is a
    relaxation parameter and :math:`[k] := k \\text{ mod } n`.

    For linear problems, a choice
    :math:`0 < \omega_i < 2/\\lVert \mathcal{A}_{i}^2\\rVert` guarantees
    convergence, where :math:`\|\mathcal{A}_{i}\|` stands for the
    operator norm of :math:`\mathcal{A}_{i}`.

    This implementation uses a minimum amount of memory copies by
    applying re-usable temporaries and in-place evaluation.

    The method is also described in a
    `Wikipedia article
    <https://en.wikipedia.org/wiki/Kaczmarz_method>`_. and in Natterer, F.
    Mathematical Methods in Image Reconstruction, section 5.3.2.

    See Also
    --------
    landweber
    """
    domain = ops[0].domain
    if any(domain != opi.domain for opi in ops):
        raise ValueError('`opi[i].domain` are not all equal')

    if x not in domain:
        raise TypeError('`x` {!r} is not in the domain of `ops` {!r}'
                        ''.format(x, domain))

    if len(ops) != len(rhs):
        raise ValueError('`number of `ops` {} does not match number of '
                         '`rhs` {}'.format(len(ops), len(rhs)))

    omega = normalized_scalar_param_list(omega, len(ops), param_conv=float)

    # Reusable elements in the range, one per type of space
    ranges = [opi.range for opi in ops]
    unique_ranges = set(ranges)
    tmp_rans = {ran: ran.element() for ran in unique_ranges}

    # Single reusable element in the domain
    tmp_dom = domain.element()

    # Iteratively find solution
    for _ in range(niter):
        for i in range(len(ops)):
            # Find residual
            tmp_ran = tmp_rans[ops[i].range]
            ops[i](x, out=tmp_ran)
            tmp_ran -= rhs[i]

            # Update x
            ops[i].derivative(x).adjoint(tmp_ran, out=tmp_dom)
            x.lincomb(1, x, -omega[i], tmp_dom)

            if projection is not None:
                projection(x)

            if callback is not None:
                callback(x)
Beispiel #11
0
def dft_preprocess_data(arr, shift=True, axes=None, sign='-', out=None):
    """Pre-process the real-space data before DFT.

    This function multiplies the given data with the separable
    function::

        p(x) = exp(+- 1j * dot(x - x[0], xi[0]))

    where ``x[0]`` and ``xi[0]`` are the minimum coodinates of
    the real-space and reciprocal grids, respectively. The sign of
    the exponent depends on the choice of ``sign``. In discretized
    form, this function becomes an array::

        p[k] = exp(+- 1j * k * s * xi[0])

    If the reciprocal grid is not shifted, i.e. symmetric around 0,
    it is ``xi[0] =  pi/s * (-1 + 1/N)``, hence::

        p[k] = exp(-+ 1j * pi * k * (1 - 1/N))

    For a shifted grid, we have :math:``xi[0] =  -pi/s``, thus the
    array is given by::

        p[k] = (-1)**k

    Parameters
    ----------
    arr : `array-like`
        Array to be pre-processed. If its data type is a real
        non-floating type, it is converted to 'float64'.
    shift : bool or or sequence of bools, optional
        If ``True``, the grid is shifted by half a stride in the negative
        direction. With a sequence, this option is applied separately on
        each axis.
    axes : int or sequence of ints, optional
        Dimensions in which to calculate the reciprocal. The sequence
        must have the same length as ``shift`` if the latter is given
        as a sequence.
        Default: all axes.
    sign : {'-', '+'}, optional
        Sign of the complex exponent.
    out : `numpy.ndarray`, optional
        Array in which the result is stored. If ``out is arr``,
        an in-place modification is performed. For real data type,
        this is only possible for ``shift=True`` since the factors are
        complex otherwise.

    Returns
    -------
    out : `numpy.ndarray`
        Result of the pre-processing. If ``out`` was given, the returned
        object is a reference to it.

    Notes
    -----
    If ``out`` is not specified, the data type of the returned array
    is the same as that of ``arr`` except when ``arr`` has real data
    type and ``shift`` is not ``True``. In this case, the return type
    is the complex counterpart of ``arr.dtype``.
    """
    arr = np.asarray(arr)
    if not is_scalar_dtype(arr.dtype):
        raise ValueError('array has non-scalar data type {}'
                         ''.format(dtype_repr(arr.dtype)))
    elif is_real_dtype(arr.dtype) and not is_real_floating_dtype(arr.dtype):
        arr = arr.astype('float64')

    if axes is None:
        axes = list(range(arr.ndim))
    else:
        try:
            axes = [int(axes)]
        except TypeError:
            axes = list(axes)

    shape = arr.shape
    shift_list = normalized_scalar_param_list(shift, length=len(axes),
                                              param_conv=bool)

    # Make a copy of arr with correct data type if necessary, or copy values.
    if out is None:
        if is_real_dtype(arr.dtype) and not all(shift_list):
            out = np.array(arr, dtype=complex_dtype(arr.dtype), copy=True)
        else:
            out = arr.copy()
    else:
        out[:] = arr

    if is_real_dtype(out.dtype) and not shift:
        raise ValueError('cannot pre-process real input in-place without '
                         'shift')

    if sign == '-':
        imag = -1j
    elif sign == '+':
        imag = 1j
    else:
        raise ValueError("`sign` '{}' not understood".format(sign))

    def _onedim_arr(length, shift):
        if shift:
            # (-1)^indices
            factor = np.ones(length, dtype=out.dtype)
            factor[1::2] = -1
        else:
            factor = np.arange(length, dtype=out.dtype)
            factor *= -imag * np.pi * (1 - 1.0 / length)
            np.exp(factor, out=factor)
        return factor.astype(out.dtype, copy=False)

    onedim_arrs = []
    for axis, shift in zip(axes, shift_list):
        length = shape[axis]
        onedim_arrs.append(_onedim_arr(length, shift))

    fast_1d_tensor_mult(out, onedim_arrs, axes=axes, out=out)
    return out
Beispiel #12
0
def uniform_grid_fromintv(intv_prod, shape, nodes_on_bdry=True):
    """Return a grid from sampling an interval product uniformly.

    The resulting grid will by default include ``intv_prod.min_pt`` and
    ``intv_prod.max_pt`` as grid points. If you want a subdivision into
    equally sized cells with grid points in the middle, use
    `uniform_partition` instead.

    Parameters
    ----------
    intv_prod : `IntervalProd`
        Set to be sampled.
    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
    -------
    sampling : `RectGrid`
        Uniform sampling grid for the interval product.

    Examples
    --------
    >>> rbox = odl.IntervalProd([-1.5, 2], [-0.5, 3])
    >>> grid = uniform_grid_fromintv(rbox, (3, 3))
    >>> grid.coord_vectors
    (array([-1.5, -1. , -0.5]), array([ 2. ,  2.5,  3. ]))

    To have the nodes in the "middle", use ``nodes_on_bdry=False``:

    >>> grid = uniform_grid_fromintv(rbox, (2, 2), nodes_on_bdry=False)
    >>> grid.coord_vectors
    (array([-1.25, -0.75]), array([ 2.25,  2.75]))

    See Also
    --------
    uniform_grid : Create a uniform grid directly.
    odl.discr.partition.uniform_partition_fromintv :
        divide interval product into equally sized subsets
    """
    if not isinstance(intv_prod, IntervalProd):
        raise TypeError('{!r} is not an `IntervalProd` instance'
                        ''.format(intv_prod))

    if (np.any(np.isinf(intv_prod.min_pt)) or
            np.any(np.isinf(intv_prod.max_pt))):
        raise ValueError('`intv_prod` must be finite, got {!r}'
                         ''.format('intv_prod'))

    shape = normalized_scalar_param_list(shape, intv_prod.ndim, safe_int_conv)

    if np.shape(nodes_on_bdry) == ():
        nodes_on_bdry = ([(bool(nodes_on_bdry), bool(nodes_on_bdry))] *
                         intv_prod.ndim)
    elif intv_prod.ndim == 1 and len(nodes_on_bdry) == 2:
        nodes_on_bdry = [nodes_on_bdry]
    elif len(nodes_on_bdry) != intv_prod.ndim:
        raise ValueError('`nodes_on_bdry` has length {}, expected {}'
                         ''.format(len(nodes_on_bdry), intv_prod.ndim))
    else:
        shape = tuple(int(n) for n in shape)

    # We need to determine the placement of the grid minimum and maximum
    # points based on the choices in nodes_on_bdry. If in a given axis,
    # and for a given side (left or right), the entry is True, the node lies
    # on the boundary, so this coordinate can simply be taken as-is.
    #
    # Otherwise, the following conditions must be met:
    #
    # 1. The node should be half a stride s away from the boundary
    # 2. Adding or subtracting (n-1)*s should give the other extremal node.
    #
    # If both nodes are to be shifted half a stride inside,
    # the second condition yields
    # a + s/2 + (n-1)*s = b - s/2 => s = (b - a) / n,
    # hence the extremal grid points are
    # gmin = a + s/2 = a + (b - a) / (2 * n),
    # gmax = b - s/2 = b - (b - a) / (2 * n).
    #
    # In the case where one node, say the rightmost, lies on the boundary,
    # the condition 2. reads as
    # a + s/2 + (n-1)*s = b => s = (b - a) / (n - 1/2),
    # thus
    # gmin = a + (b - a) / (2 * n - 1).

    gmin, gmax = [], []
    for n, xmin, xmax, on_bdry in zip(shape, intv_prod.min_pt,
                                      intv_prod.max_pt, nodes_on_bdry):

        # 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

        if bdry_l and bdry_r:
            gmin.append(xmin)
            gmax.append(xmax)
        elif bdry_l and not bdry_r:
            gmin.append(xmin)
            gmax.append(xmax - (xmax - xmin) / (2 * n - 1))
        elif not bdry_l and bdry_r:
            gmin.append(xmin + (xmax - xmin) / (2 * n - 1))
            gmax.append(xmax)
        else:
            gmin.append(xmin + (xmax - xmin) / (2 * n))
            gmax.append(xmax - (xmax - xmin) / (2 * n))

    # Create the grid
    coord_vecs = [np.linspace(mi, ma, num, endpoint=True, dtype=np.float64)
                  for mi, ma, num in zip(gmin, gmax, shape)]
    return RectGrid(*coord_vecs)
Beispiel #13
0
def reciprocal_grid(grid, shift=True, axes=None, halfcomplex=False):
    """Return the reciprocal of the given regular grid.

    This function calculates the reciprocal (Fourier/frequency space)
    grid for a given regular grid defined by the nodes::

        x[k] = x[0] + k * s,

    where ``k = (k[0], ..., k[d-1])`` is a ``d``-dimensional index in
    the range ``0 <= k < N`` (component-wise). The multi-index
    ``N`` is the shape of the input grid.
    This grid's reciprocal is then given by the nodes::

        xi[j] = xi[0] + j * sigma,

    with the reciprocal grid stride ``sigma = 2*pi / (s * N)``.
    The minimum frequency ``xi[0]`` can in principle be chosen
    freely, but usually it is chosen in a such a way that the reciprocal
    grid is centered around zero. For this, there are two possibilities:

    1. Make the grid point-symmetric around 0.

    2. Make the grid "almost" point-symmetric around zero by shifting
       it to the left by half a reciprocal stride.

    In the first case, the minimum frequency (per axis) is given as::

        xi_1[0] = -pi/s + pi/(s*n) = -pi/s + sigma/2.

    For the second case, it is::

        xi_1[0] = -pi / s.

    Note that the zero frequency is contained in case 1 for an odd
    number of points, while for an even size, the second option
    guarantees that 0 is contained.

    If a real-to-complex (half-complex) transform is to be computed,
    the reciprocal grid has the shape ``M[i] = floor(N[i]/2) + 1``
    in the last transform axis ``i``.

    Parameters
    ----------
    grid : `RegularGrid`
        Original sampling grid
    shift : bool or sequence of bools, optional
        If ``True``, the grid is shifted by half a stride in the negative
        direction. With a sequence, this option is applied separately on
        each axis.
    axes : int or sequence of ints, optional
        Dimensions in which to calculate the reciprocal. The sequence
        must have the same length as ``shift`` if the latter is given
        as a sequence. ``None`` means all axes in ``grid``.
    halfcomplex : bool, optional
        If ``True``, return the half of the grid with last coordinate
        less than zero. This is related to the fact that for real-valued
        functions, the other half is the mirrored complex conjugate of
        the given half and therefore needs not be stored.

    Returns
    -------
    reciprocal_grid : `RegularGrid`
        The reciprocal grid.
    """
    if axes is None:
        axes = list(range(grid.ndim))
    else:
        try:
            axes = [int(axes)]
        except TypeError:
            axes = list(axes)

    # List indicating shift or not per "active" axis, same length as axes
    shift_list = normalized_scalar_param_list(shift, length=len(axes),
                                              param_conv=bool)

    # Full-length vectors
    stride = grid.stride
    shape = np.array(grid.shape)
    rmin = grid.min_pt.copy()
    rmax = grid.max_pt.copy()
    rshape = list(shape)

    # Shifted axes (full length to avoid ugly double indexing)
    shifted = np.zeros(grid.ndim, dtype=bool)
    shifted[axes] = shift_list
    rmin[shifted] = -np.pi / stride[shifted]
    # Length min->max increases by double the shift, so we
    # have to compensate by a full stride
    rmax[shifted] = (-rmin[shifted] -
                     2 * np.pi / (stride[shifted] * shape[shifted]))

    # Non-shifted axes
    not_shifted = np.zeros(grid.ndim, dtype=bool)
    not_shifted[axes] = np.logical_not(shift_list)
    rmin[not_shifted] = ((-1.0 + 1.0 / shape[not_shifted]) *
                         np.pi / stride[not_shifted])
    rmax[not_shifted] = -rmin[not_shifted]

    # Change last axis shape and max if halfcomplex
    if halfcomplex:
        rshape[axes[-1]] = shape[axes[-1]] // 2 + 1

        # - Odd and shifted: - stride / 2
        # - Even and not shifted: + stride / 2
        # - Otherwise: 0
        last_odd = shape[axes[-1]] % 2 == 1
        last_shifted = shift_list[-1]
        half_rstride = np.pi / (shape[axes[-1]] * stride[axes[-1]])

        if last_odd and last_shifted:
            rmax[axes[-1]] = -half_rstride
        elif not last_odd and not last_shifted:
            rmax[axes[-1]] = half_rstride
        else:
            rmax[axes[-1]] = 0

    return RegularGrid(rmin, rmax, rshape)
Beispiel #14
0
def dft_postprocess_data(arr, real_grid, recip_grid, shift, axes,
                         interp, sign='-', op='multiply', out=None):
    """Post-process the Fourier-space data after DFT.

    This function multiplies the given data with the separable
    function::

        q(xi) = exp(+- 1j * dot(x[0], xi)) * s * phi_hat(xi_bar)

    where ``x[0]`` and ``s`` are the minimum point and the stride of
    the real-space grid, respectively, and ``phi_hat(xi_bar)`` is the FT
    of the interpolation kernel. The sign of the exponent depends on the
    choice of ``sign``. Note that for ``op='divide'`` the
    multiplication with ``s * phi_hat(xi_bar)`` is replaced by a
    division with the same array.

    In discretized form on the reciprocal grid, the exponential part
    of this function becomes an array::

        q[k] = exp(+- 1j * dot(x[0], xi[k]))

    and the arguments ``xi_bar`` to the interpolation kernel
    are the normalized frequencies::

        for 'shift=True'  : xi_bar[k] = -pi + pi * (2*k) / N
        for 'shift=False' : xi_bar[k] = -pi + pi * (2*k+1) / N

    See [Pre+2007]_, Section 13.9 "Computing Fourier Integrals Using
    the FFT" for a similar approach.

    Parameters
    ----------
    arr : `array-like`
        Array to be pre-processed. An array with real data type is
        converted to its complex counterpart.
    real_grid : `RegularGrid`
        Real space grid in the transform
    recip_grid : `RegularGrid`
        Reciprocal grid in the transform
    shift : bool or sequence of bools
        If ``True``, the grid is shifted by half a stride in the negative
        direction in the corresponding axes. The sequence must have the
        same length as ``axes``.
    axes : int or sequence of ints
        Dimensions along which to take the transform. The sequence must
        have the same length as ``shifts``.
    interp : string or sequence of strings
        Interpolation scheme used in the real-space.
    sign : {'-', '+'}, optional
        Sign of the complex exponent.
    op : {'multiply', 'divide'}
        Operation to perform with the stride times the interpolation
        kernel FT
    out : `numpy.ndarray`, optional
        Array in which the result is stored. If ``out is arr``, an
        in-place modification is performed.

    Returns
    -------
    out : `numpy.ndarray`
        Result of the post-processing. If ``out`` was given, the returned
        object is a reference to it.
    """
    arr = np.asarray(arr)
    if is_real_floating_dtype(arr.dtype):
        arr = arr.astype(complex_dtype(arr.dtype))
    elif not is_complex_floating_dtype(arr.dtype):
        raise ValueError('array data type {} is not a complex floating point '
                         'data type'.format(dtype_repr(arr.dtype)))

    if out is None:
        out = arr.copy()
    elif out is not arr:
        out[:] = arr

    if axes is None:
        axes = list(range(arr.ndim))
    else:
        try:
            axes = [int(axes)]
        except TypeError:
            axes = list(axes)

    shift_list = normalized_scalar_param_list(shift, length=len(axes),
                                              param_conv=bool)

    if sign == '-':
        imag = -1j
    elif sign == '+':
        imag = 1j
    else:
        raise ValueError("`sign` '{}' not understood".format(sign))

    op, op_in = str(op).lower(), op
    if op not in ('multiply', 'divide'):
        raise ValueError("kernel `op` '{}' not understood".format(op_in))

    # Make a list from interp if that's not the case already
    try:
        # Duck-typed string check
        interp + ''
    except TypeError:
        pass
    else:
        interp = [str(interp).lower()] * arr.ndim

    onedim_arrs = []
    for ax, shift, intp in zip(axes, shift_list, interp):
        x = real_grid.min_pt[ax]
        xi = recip_grid.coord_vectors[ax]

        # First part: exponential array
        onedim_arr = np.exp(imag * x * xi)

        # Second part: interpolation kernel
        len_dft = recip_grid.shape[ax]
        len_orig = real_grid.shape[ax]
        halfcomplex = (len_dft < len_orig)
        odd = len_orig % 2

        fmin = -0.5 if shift else -0.5 + 1.0 / (2 * len_orig)
        if halfcomplex:
            # maximum lies around 0, possibly half a cell left or right of it
            if shift and odd:
                fmax = - 1.0 / (2 * len_orig)
            elif not shift and not odd:
                fmax = 1.0 / (2 * len_orig)
            else:
                fmax = 0.0

        else:  # not halfcomplex
            # maximum lies close to 0.5, half or full cell left of it
            if shift:
                # -0.5 + (N-1)/N = 0.5 - 1/N
                fmax = 0.5 - 1.0 / len_orig
            else:
                # -0.5 + 1/(2*N) + (N-1)/N = 0.5 - 1/(2*N)
                fmax = 0.5 - 1.0 / (2 * len_orig)

        freqs = np.linspace(fmin, fmax, num=len_dft)
        stride = real_grid.stride[ax]

        if op == 'multiply':
            onedim_arr *= stride * _interp_kernel_ft(freqs, intp)
        else:
            onedim_arr /= stride * _interp_kernel_ft(freqs, intp)

        onedim_arrs.append(onedim_arr.astype(out.dtype, copy=False))

    fast_1d_tensor_mult(out, onedim_arrs, axes=axes, out=out)
    return out
Beispiel #15
0
def dft_preprocess_data(arr, shift=True, axes=None, sign='-', out=None):
    """Pre-process the real-space data before DFT.

    This function multiplies the given data with the separable
    function::

        p(x) = exp(+- 1j * dot(x - x[0], xi[0]))

    where ``x[0]`` and ``xi[0]`` are the minimum coodinates of
    the real-space and reciprocal grids, respectively. The sign of
    the exponent depends on the choice of ``sign``. In discretized
    form, this function becomes an array::

        p[k] = exp(+- 1j * k * s * xi[0])

    If the reciprocal grid is not shifted, i.e. symmetric around 0,
    it is ``xi[0] =  pi/s * (-1 + 1/N)``, hence::

        p[k] = exp(-+ 1j * pi * k * (1 - 1/N))

    For a shifted grid, we have :math:``xi[0] =  -pi/s``, thus the
    array is given by::

        p[k] = (-1)**k

    Parameters
    ----------
    arr : `array-like`
        Array to be pre-processed. If its data type is a real
        non-floating type, it is converted to 'float64'.
    shift : bool or or sequence of bools, optional
        If ``True``, the grid is shifted by half a stride in the negative
        direction. With a sequence, this option is applied separately on
        each axis.
    axes : int or sequence of ints, optional
        Dimensions in which to calculate the reciprocal. The sequence
        must have the same length as ``shift`` if the latter is given
        as a sequence.
        Default: all axes.
    sign : {'-', '+'}, optional
        Sign of the complex exponent.
    out : `numpy.ndarray`, optional
        Array in which the result is stored. If ``out is arr``,
        an in-place modification is performed. For real data type,
        this is only possible for ``shift=True`` since the factors are
        complex otherwise.

    Returns
    -------
    out : `numpy.ndarray`
        Result of the pre-processing. If ``out`` was given, the returned
        object is a reference to it.

    Notes
    -----
    If ``out`` is not specified, the data type of the returned array
    is the same as that of ``arr`` except when ``arr`` has real data
    type and ``shift`` is not ``True``. In this case, the return type
    is the complex counterpart of ``arr.dtype``.
    """
    arr = np.asarray(arr)
    if not is_scalar_dtype(arr.dtype):
        raise ValueError('array has non-scalar data type {}'
                         ''.format(dtype_repr(arr.dtype)))
    elif is_real_dtype(arr.dtype) and not is_real_floating_dtype(arr.dtype):
        arr = arr.astype('float64')

    if axes is None:
        axes = list(range(arr.ndim))
    else:
        try:
            axes = [int(axes)]
        except TypeError:
            axes = list(axes)

    shape = arr.shape
    shift_list = normalized_scalar_param_list(shift, length=len(axes),
                                              param_conv=bool)

    # Make a copy of arr with correct data type if necessary, or copy values.
    if out is None:
        if is_real_dtype(arr.dtype) and not all(shift_list):
            out = np.array(arr, dtype=complex_dtype(arr.dtype), copy=True)
        else:
            out = arr.copy()
    else:
        out[:] = arr

    if is_real_dtype(out.dtype) and not shift:
        raise ValueError('cannot pre-process real input in-place without '
                         'shift')

    if sign == '-':
        imag = -1j
    elif sign == '+':
        imag = 1j
    else:
        raise ValueError("`sign` '{}' not understood".format(sign))

    def _onedim_arr(length, shift):
        if shift:
            # (-1)^indices
            factor = np.ones(length, dtype=out.dtype)
            factor[1::2] = -1
        else:
            factor = np.arange(length, dtype=out.dtype)
            factor *= -imag * np.pi * (1 - 1.0 / length)
            np.exp(factor, out=factor)
        return factor.astype(out.dtype, copy=False)

    onedim_arrs = []
    for axis, shift in zip(axes, shift_list):
        length = shape[axis]
        onedim_arrs.append(_onedim_arr(length, shift))

    fast_1d_tensor_mult(out, onedim_arrs, axes=axes, out=out)
    return out
Beispiel #16
0
    def __init__(self, domain, range=None, ran_shp=None, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        domain : uniform `DiscreteLp`
            Uniformly discretized space, the operator can be applied
            to its elements.
        range : uniform `DiscreteLp`, optional
            Uniformly discretized space in which the result of the
            application of this operator lies.
            For the default ``None``, a space with the same attributes
            as ``domain`` is used, except for its shape, which is set
            to ``ran_shp``.
        ran_shp : sequence of ints, optional
            Shape of the range of this operator. This can be provided
            instead of ``range`` and is mandatory if ``range`` is
            ``None``.
        offset : int or sequence of ints, optional
            Number of cells to add to/remove from the left of
            ``domain.partition``. By default, the difference is
            distributed evenly, with preference for left in case of
            ambiguity.
            This option is can only be used together with ``ran_shp``.
        pad_mode : string, optional
            Method to be used to fill in missing values in an enlarged array.

            ``'constant'``: Fill with ``pad_const``.

            ``'symmetric'``: Reflect at the boundaries, not doubling the
            outmost values. This requires left and right padding sizes
            to be strictly smaller than the original array shape.

            ``'periodic'``: Fill in values from the other side, keeping
            the order. This requires left and right padding sizes to be
            at most as large as the original array shape.

            ``'order0'``: Extend constantly with the outmost values
            (ensures continuity).

            ``'order1'``: Extend with constant slope (ensures continuity of
            the first derivative). This requires at least 2 values along
            each axis where padding is applied.

        pad_const : scalar, optional
            Value to be used in the ``'constant'`` padding mode.

        discr_kwargs: dict, optional
            Keyword arguments passed to the `uniform_discr` constructor.

        Examples
        --------
        The simplest way of initializing a resizing operator is by
        providing ``ran_shp`` and, optionally, parameters for the padding
        variant to be used. The range is inferred from ``domain`` and
        the supplied parameters. If no ``offset`` is given, the difference
        in size is evenly distributed to both sides:

        >>> space = odl.uniform_discr([0, 0], [1, 1], (2, 4))
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4))
        >>> resize_op.range
        uniform_discr([-0.5, 0.0], [1.5, 1.0], (4, 4))

        Testing different padding methods in the first axis (zero padding
        is the default):

        >>> x = [[1, 2, 3, 4],
        ...      [5, 6, 7, 8]]
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4))
        >>> print(resize_op(x))
        [[0.0, 0.0, 0.0, 0.0],
         [1.0, 2.0, 3.0, 4.0],
         [5.0, 6.0, 7.0, 8.0],
         [0.0, 0.0, 0.0, 0.0]]
        >>>
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4),
        ...                                  offset=(0, 0),
        ...                                  pad_mode='periodic')
        >>> print(resize_op(x))
        [[1.0, 2.0, 3.0, 4.0],
         [5.0, 6.0, 7.0, 8.0],
         [1.0, 2.0, 3.0, 4.0],
         [5.0, 6.0, 7.0, 8.0]]
        >>>
        >>> resize_op = odl.ResizingOperator(space, ran_shp=(4, 4),
        ...                                  offset=(0, 0),
        ...                                  pad_mode='order0')
        >>> print(resize_op(x))
        [[1.0, 2.0, 3.0, 4.0],
         [5.0, 6.0, 7.0, 8.0],
         [5.0, 6.0, 7.0, 8.0],
         [5.0, 6.0, 7.0, 8.0]]

        Alternatively, the range of the operator can be provided directly.
        This requires that the partitions match, i.e. that the cell sizes
        are the same and there is no shift:

        >>> # Same space as in the first example, see above
        >>> large_spc = odl.uniform_discr([-0.5, 0], [1.5, 1], (4, 4))
        >>> resize_op = odl.ResizingOperator(space, large_spc,
        ...                                  pad_mode='periodic')
        >>> print(resize_op(x))
        [[5.0, 6.0, 7.0, 8.0],
         [1.0, 2.0, 3.0, 4.0],
         [5.0, 6.0, 7.0, 8.0],
         [1.0, 2.0, 3.0, 4.0]]
        """
        if not isinstance(domain, DiscreteLp):
            raise TypeError('`domain` must be a `DiscreteLp` instance, '
                            'got {!r}'.format(domain))

        if not domain.is_uniform:
            raise ValueError('`domain` is not uniformly discretized')

        offset = kwargs.pop('offset', None)
        discr_kwargs = kwargs.pop('discr_kwargs', {})

        if range is None:
            if ran_shp is None:
                raise ValueError('either `range` or `ran_shp` must be '
                                 'given')

            offset = normalized_scalar_param_list(
                offset, domain.ndim, param_conv=safe_int_conv, keep_none=True)

            range = _resize_discr(domain, ran_shp, offset, discr_kwargs)
            self.__offset = tuple(_offset_from_spaces(domain, range))

        elif ran_shp is None:
            if offset is not None:
                raise ValueError('`offset` can only be combined with '
                                 '`ran_shp`')

            if not np.allclose(range.cell_sides, domain.cell_sides):
                raise ValueError(
                    'cell sides of domain and range differ significantly '
                    '(difference {})'
                    ''.format(range.cell_sides - domain.cell_sides))

            self.__offset = _offset_from_spaces(domain, range)

        else:
            raise ValueError('cannot combine `range` with `ran_shape`')

        pad_mode = kwargs.pop('pad_mode', 'constant')
        pad_mode, pad_mode_in = str(pad_mode).lower(), pad_mode
        if pad_mode not in _SUPPORTED_RESIZE_PAD_MODES:
            raise ValueError("`pad_mode` '{}' not understood"
                             "".format(pad_mode_in))

        self.__pad_mode = pad_mode
        # Store constant in a way that ensures safe casting (one-element array)
        self.__pad_const = np.array(kwargs.pop('pad_const', 0),
                                    dtype=range.dtype)

        # padding mode 'constant' with `pad_const != 0` is not linear
        linear = (self.pad_mode != 'constant' or self.pad_const == 0.0)

        super().__init__(domain, range, linear=linear)