コード例 #1
0
    def __init__(self,
                 operators,
                 is_non_singular=None,
                 is_self_adjoint=None,
                 is_positive_definite=None,
                 is_square=None,
                 name=None):
        r"""Initialize a `LinearOperatorKronecker`.

    `LinearOperatorKronecker` is initialized with a list of operators
    `[op_1,...,op_J]`.

    Args:
      operators:  Iterable of `LinearOperator` objects, each with
        the same `dtype` and composable shape, representing the Kronecker
        factors.
      is_non_singular:  Expect that this operator is non-singular.
      is_self_adjoint:  Expect that this operator is equal to its hermitian
        transpose.
      is_positive_definite:  Expect that this operator is positive definite,
        meaning the quadratic form `x^H A x` has positive real part for all
        nonzero `x`.  Note that we do not require the operator to be
        self-adjoint to be positive-definite.  See:
        https://en.wikipedia.org/wiki/Positive-definite_matrix\
            #Extension_for_non_symmetric_matrices
      is_square:  Expect that this operator acts like square [batch] matrices.
      name: A name for this `LinearOperator`.  Default is the individual
        operators names joined with `_x_`.

    Raises:
      TypeError:  If all operators do not have the same `dtype`.
      ValueError:  If `operators` is empty.
    """
        parameters = dict(operators=operators,
                          is_non_singular=is_non_singular,
                          is_self_adjoint=is_self_adjoint,
                          is_positive_definite=is_positive_definite,
                          is_square=is_square,
                          name=name)

        # Validate operators.
        check_ops.assert_proper_iterable(operators)
        operators = list(operators)
        if not operators:
            raise ValueError("Expected a list of >=1 operators. Found: %s" %
                             operators)
        self._operators = operators

        # Validate dtype.
        dtype = operators[0].dtype
        for operator in operators:
            if operator.dtype != dtype:
                name_type = (str((o.name, o.dtype)) for o in operators)
                raise TypeError(
                    "Expected all operators to have the same dtype.  Found %s"
                    % "   ".join(name_type))

        # Auto-set and check hints.
        # A Kronecker product is invertible, if and only if all factors are
        # invertible.
        if all(operator.is_non_singular for operator in operators):
            if is_non_singular is False:
                raise ValueError(
                    "The Kronecker product of non-singular operators is always "
                    "non-singular.")
            is_non_singular = True

        if all(operator.is_self_adjoint for operator in operators):
            if is_self_adjoint is False:
                raise ValueError(
                    "The Kronecker product of self-adjoint operators is always "
                    "self-adjoint.")
            is_self_adjoint = True

        # The eigenvalues of a Kronecker product are equal to the products of eigen
        # values of the corresponding factors.
        if all(operator.is_positive_definite for operator in operators):
            if is_positive_definite is False:
                raise ValueError(
                    "The Kronecker product of positive-definite operators "
                    "is always positive-definite.")
            is_positive_definite = True

        # Initialization.
        graph_parents = []
        for operator in operators:
            graph_parents.extend(operator.graph_parents)

        if name is None:
            name = operators[0].name
            for operator in operators[1:]:
                name = name + "_x_" + operator.name
        with ops.name_scope(name, values=graph_parents):
            super(LinearOperatorKronecker,
                  self).__init__(dtype=dtype,
                                 is_non_singular=is_non_singular,
                                 is_self_adjoint=is_self_adjoint,
                                 is_positive_definite=is_positive_definite,
                                 is_square=is_square,
                                 parameters=parameters,
                                 name=name)
        # TODO(b/143910018) Remove graph_parents in V3.
        self._set_graph_parents(graph_parents)
コード例 #2
0
def broadcast_matrix_batch_dims(batch_matrices, name=None):
  """Broadcast leading dimensions of zero or more [batch] matrices.

  Example broadcasting one batch dim of two simple matrices.

  ```python
  x = [[1, 2],
       [3, 4]]  # Shape [2, 2], no batch dims

  y = [[[1]]]   # Shape [1, 1, 1], 1 batch dim of shape [1]

  x_bc, y_bc = broadcast_matrix_batch_dims([x, y])

  x_bc
  ==> [[[1, 2],
        [3, 4]]]  # Shape [1, 2, 2], 1 batch dim of shape [1].

  y_bc
  ==> same as y
  ```

  Example broadcasting many batch dims

  ```python
  x = tf.random.normal(shape=(2, 3, 1, 4, 4))
  y = tf.random.normal(shape=(1, 3, 2, 5, 5))
  x_bc, y_bc = broadcast_matrix_batch_dims([x, y])

  tensor_shape.TensorShape(x_bc.shape)
  ==> (2, 3, 2, 4, 4)

  tensor_shape.TensorShape(y_bc.shape)
  ==> (2, 3, 2, 5, 5)
  ```

  Args:
    batch_matrices:  Iterable of `Tensor`s, each having two or more dimensions.
    name:  A string name to prepend to created ops.

  Returns:
    bcast_matrices: List of `Tensor`s, with `bcast_matrices[i]` containing
      the values from `batch_matrices[i]`, with possibly broadcast batch dims.

  Raises:
    ValueError:  If any input `Tensor` is statically determined to have less
      than two dimensions.
  """
  with ops.name_scope(
      name or "broadcast_matrix_batch_dims", values=batch_matrices):
    check_ops.assert_proper_iterable(batch_matrices)
    batch_matrices = list(batch_matrices)

    for i, mat in enumerate(batch_matrices):
      batch_matrices[i] = ops.convert_to_tensor(mat)
      assert_is_batch_matrix(batch_matrices[i])

    if len(batch_matrices) < 2:
      return batch_matrices

    # Try static broadcasting.
    # bcast_batch_shape is the broadcast batch shape of ALL matrices.
    # E.g. if batch_matrices = [x, y], with
    # tensor_shape.TensorShape(x.shape) =    [2, j, k]  (batch shape =    [2])
    # tensor_shape.TensorShape(y.shape) = [3, 1, l, m]  (batch shape = [3, 1])
    # ==> bcast_batch_shape = [3, 2]
    bcast_batch_shape = tensor_shape.TensorShape(batch_matrices[0].shape)[:-2]
    for mat in batch_matrices[1:]:
      bcast_batch_shape = _ops.broadcast_static_shape(
          bcast_batch_shape,
          tensor_shape.TensorShape(mat.shape)[:-2])
    if bcast_batch_shape.is_fully_defined():
      for i, mat in enumerate(batch_matrices):
        if tensor_shape.TensorShape(mat.shape)[:-2] != bcast_batch_shape:
          bcast_shape = array_ops.concat(
              [bcast_batch_shape.as_list(), array_ops.shape(mat)[-2:]], axis=0)
          batch_matrices[i] = _ops.broadcast_to(mat, bcast_shape)
      return batch_matrices

    # Since static didn't work, do dynamic, which always copies data.
    bcast_batch_shape = array_ops.shape(batch_matrices[0])[:-2]
    for mat in batch_matrices[1:]:
      bcast_batch_shape = array_ops.broadcast_dynamic_shape(
          bcast_batch_shape,
          array_ops.shape(mat)[:-2])
    for i, mat in enumerate(batch_matrices):
      batch_matrices[i] = _ops.broadcast_to(
          mat,
          array_ops.concat(
              [bcast_batch_shape, array_ops.shape(mat)[-2:]], axis=0))

    return batch_matrices
コード例 #3
0
    def __init__(self,
                 operators,
                 is_non_singular=None,
                 is_self_adjoint=None,
                 is_positive_definite=None,
                 is_square=None,
                 name=None):
        r"""Initialize a `LinearOperatorComposition`.

    `LinearOperatorComposition` is initialized with a list of operators
    `[op_1,...,op_J]`.  For the `matmul` method to be well defined, the
    composition `op_i.matmul(op_{i+1}(x))` must be defined.  Other methods have
    similar constraints.

    Args:
      operators:  Iterable of `LinearOperator` objects, each with
        the same `dtype` and composable shape.
      is_non_singular:  Expect that this operator is non-singular.
      is_self_adjoint:  Expect that this operator is equal to its hermitian
        transpose.
      is_positive_definite:  Expect that this operator is positive definite,
        meaning the quadratic form `x^H A x` has positive real part for all
        nonzero `x`.  Note that we do not require the operator to be
        self-adjoint to be positive-definite.  See:
        https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices
      is_square:  Expect that this operator acts like square [batch] matrices.
      name: A name for this `LinearOperator`.  Default is the individual
        operators names joined with `_o_`.

    Raises:
      TypeError:  If all operators do not have the same `dtype`.
      ValueError:  If `operators` is empty.
    """
        parameters = dict(operators=operators,
                          is_non_singular=is_non_singular,
                          is_self_adjoint=is_self_adjoint,
                          is_positive_definite=is_positive_definite,
                          is_square=is_square,
                          name=name)

        # Validate operators.
        check_ops.assert_proper_iterable(operators)
        operators = list(operators)
        if not operators:
            raise ValueError(
                "Expected a non-empty list of operators. Found: %s" %
                operators)
        self._operators = operators

        # Validate dtype.
        dtype = operators[0].dtype
        for operator in operators:
            if operator.dtype != dtype:
                name_type = (str((o.name, o.dtype)) for o in operators)
                raise TypeError(
                    "Expected all operators to have the same dtype.  Found %s"
                    % "   ".join(name_type))

        # Auto-set and check hints.
        if all(operator.is_non_singular for operator in operators):
            if is_non_singular is False:
                raise ValueError(
                    "The composition of non-singular operators is always non-singular."
                )
            is_non_singular = True

        # Initialization.
        graph_parents = []
        for operator in operators:
            graph_parents.extend(operator.graph_parents)

        if name is None:
            name = "_o_".join(operator.name for operator in operators)
        with ops.name_scope(name, values=graph_parents):
            super(LinearOperatorComposition,
                  self).__init__(dtype=dtype,
                                 graph_parents=None,
                                 is_non_singular=is_non_singular,
                                 is_self_adjoint=is_self_adjoint,
                                 is_positive_definite=is_positive_definite,
                                 is_square=is_square,
                                 parameters=parameters,
                                 name=name)
        # TODO(b/143910018) Remove graph_parents in V3.
        self._set_graph_parents(graph_parents)
コード例 #4
0
    def __init__(self,
                 operators,
                 is_non_singular=None,
                 is_self_adjoint=None,
                 is_positive_definite=None,
                 is_square=True,
                 name=None):
        r"""Initialize a `LinearOperatorBlockDiag`.

    `LinearOperatorBlockDiag` is initialized with a list of operators
    `[op_1,...,op_J]`.

    Args:
      operators:  Iterable of `LinearOperator` objects, each with
        the same `dtype` and composable shape.
      is_non_singular:  Expect that this operator is non-singular.
      is_self_adjoint:  Expect that this operator is equal to its hermitian
        transpose.
      is_positive_definite:  Expect that this operator is positive definite,
        meaning the quadratic form `x^H A x` has positive real part for all
        nonzero `x`.  Note that we do not require the operator to be
        self-adjoint to be positive-definite.  See:
        https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices
      is_square:  Expect that this operator acts like square [batch] matrices.
        This is true by default, and will raise a `ValueError` otherwise.
      name: A name for this `LinearOperator`.  Default is the individual
        operators names joined with `_o_`.

    Raises:
      TypeError:  If all operators do not have the same `dtype`.
      ValueError:  If `operators` is empty or are non-square.
    """
        # Validate operators.
        check_ops.assert_proper_iterable(operators)
        operators = list(operators)
        if not operators:
            raise ValueError(
                "Expected a non-empty list of operators. Found: %s" %
                operators)
        self._operators = operators

        # Define diagonal operators, for functions that are shared across blockwise
        # `LinearOperator` types.
        self._diagonal_operators = operators

        # Validate dtype.
        dtype = operators[0].dtype
        for operator in operators:
            if operator.dtype != dtype:
                name_type = (str((o.name, o.dtype)) for o in operators)
                raise TypeError(
                    "Expected all operators to have the same dtype.  Found %s"
                    % "   ".join(name_type))

        # Auto-set and check hints.
        if all(operator.is_non_singular for operator in operators):
            if is_non_singular is False:
                raise ValueError(
                    "The direct sum of non-singular operators is always non-singular."
                )
            is_non_singular = True

        if all(operator.is_self_adjoint for operator in operators):
            if is_self_adjoint is False:
                raise ValueError(
                    "The direct sum of self-adjoint operators is always self-adjoint."
                )
            is_self_adjoint = True

        if all(operator.is_positive_definite for operator in operators):
            if is_positive_definite is False:
                raise ValueError(
                    "The direct sum of positive definite operators is always "
                    "positive definite.")
            is_positive_definite = True

        if not (is_square and all(operator.is_square
                                  for operator in operators)):
            raise ValueError(
                "Can only represent a block diagonal of square matrices.")

        # Initialization.
        graph_parents = []
        for operator in operators:
            graph_parents.extend(operator.graph_parents)

        if name is None:
            # Using ds to mean direct sum.
            name = "_ds_".join(operator.name for operator in operators)
        with ops.name_scope(name, values=graph_parents):
            super(LinearOperatorBlockDiag,
                  self).__init__(dtype=dtype,
                                 graph_parents=None,
                                 is_non_singular=is_non_singular,
                                 is_self_adjoint=is_self_adjoint,
                                 is_positive_definite=is_positive_definite,
                                 is_square=True,
                                 name=name)

        # TODO(b/143910018) Remove graph_parents in V3.
        self._set_graph_parents(graph_parents)
コード例 #5
0
def add_operators(operators,
                  operator_name=None,
                  addition_tiers=None,
                  name=None):
  """Efficiently add one or more linear operators.

  Given operators `[A1, A2,...]`, this `Op` returns a possibly shorter list of
  operators `[B1, B2,...]` such that

  ```sum_k Ak.matmul(x) = sum_k Bk.matmul(x).```

  The operators `Bk` result by adding some of the `Ak`, as allowed by
  `addition_tiers`.

  Example of efficient adding of diagonal operators.

  ```python
  A1 = LinearOperatorDiag(diag=[1., 1.], name="A1")
  A2 = LinearOperatorDiag(diag=[2., 2.], name="A2")

  # Use two tiers, the first contains an Adder that returns Diag.  Since both
  # A1 and A2 are Diag, they can use this Adder.  The second tier will not be
  # used.
  addition_tiers = [
      [_AddAndReturnDiag()],
      [_AddAndReturnMatrix()]]
  B_list = add_operators([A1, A2], addition_tiers=addition_tiers)

  len(B_list)
  ==> 1

  B_list[0].__class__.__name__
  ==> 'LinearOperatorDiag'

  B_list[0].to_dense()
  ==> [[3., 0.],
       [0., 3.]]

  B_list[0].name
  ==> 'Add/A1__A2/'
  ```

  Args:
    operators:  Iterable of `LinearOperator` objects with same `dtype`, domain
      and range dimensions, and broadcastable batch shapes.
    operator_name:  String name for returned `LinearOperator`.  Defaults to
      concatenation of "Add/A__B/" that indicates the order of addition steps.
    addition_tiers:  List tiers, like `[tier_0, tier_1, ...]`, where `tier_i`
      is a list of `Adder` objects.  This function attempts to do all additions
      in tier `i` before trying tier `i + 1`.
    name:  A name for this `Op`.  Defaults to `add_operators`.

  Returns:
    Subclass of `LinearOperator`.  Class and order of addition may change as new
      (and better) addition strategies emerge.

  Raises:
    ValueError:  If `operators` argument is empty.
    ValueError:  If shapes are incompatible.
  """
  # Default setting
  if addition_tiers is None:
    addition_tiers = _DEFAULT_ADDITION_TIERS

  # Argument checking.
  check_ops.assert_proper_iterable(operators)
  operators = list(reversed(operators))
  if len(operators) < 1:
    raise ValueError(
        f"Argument `operators` must contain at least one operator. "
        f"Received: {operators}.")
  if not all(
      isinstance(op, linear_operator.LinearOperator) for op in operators):
    raise TypeError(
        f"Argument `operators` must contain only LinearOperator instances. "
        f"Received: {operators}.")
  _static_check_for_same_dimensions(operators)
  _static_check_for_broadcastable_batch_shape(operators)

  graph_parents = []
  for operator in operators:
    graph_parents.extend(operator.graph_parents)

  with ops.name_scope(name or "add_operators", values=graph_parents):

    # Additions done in one of the tiers.  Try tier 0, 1,...
    ops_to_try_at_next_tier = list(operators)
    for tier in addition_tiers:
      ops_to_try_at_this_tier = ops_to_try_at_next_tier
      ops_to_try_at_next_tier = []
      while ops_to_try_at_this_tier:
        op1 = ops_to_try_at_this_tier.pop()
        op2, adder = _pop_a_match_at_tier(op1, ops_to_try_at_this_tier, tier)
        if op2 is not None:
          # Will try to add the result of this again at this same tier.
          new_operator = adder.add(op1, op2, operator_name)
          ops_to_try_at_this_tier.append(new_operator)
        else:
          ops_to_try_at_next_tier.append(op1)

    return ops_to_try_at_next_tier
コード例 #6
0
    def __init__(self,
                 operators,
                 is_non_singular=None,
                 is_self_adjoint=None,
                 is_positive_definite=None,
                 is_square=None,
                 name="LinearOperatorBlockLowerTriangular"):
        r"""Initialize a `LinearOperatorBlockLowerTriangular`.

    `LinearOperatorBlockLowerTriangular` is initialized with a list of lists of
    operators `[[op_0], [op_1, op_2], [op_3, op_4, op_5],...]`.

    Args:
      operators:  Iterable of iterables of `LinearOperator` objects, each with
        the same `dtype`. Each element of `operators` corresponds to a row-
        partition, in top-to-bottom order. The operators in each row-partition
        are filled in left-to-right. For example,
        `operators = [[op_0], [op_1, op_2], [op_3, op_4, op_5]]` creates a
        `LinearOperatorBlockLowerTriangular` with full block structure
        `[[op_0, 0, 0], [op_1, op_2, 0], [op_3, op_4, op_5]]`. The number of
        operators in the `i`th row must be equal to `i`, such that each operator
        falls on or below the diagonal of the blockwise structure.
        `LinearOperator`s that fall on the diagonal (the last elements of each
        row) must be square. The other `LinearOperator`s must have domain
        dimension equal to the domain dimension of the `LinearOperator`s in the
        same column-partition, and range dimension equal to the range dimension
        of the `LinearOperator`s in the same row-partition.
      is_non_singular:  Expect that this operator is non-singular.
      is_self_adjoint:  Expect that this operator is equal to its hermitian
        transpose.
      is_positive_definite:  Expect that this operator is positive definite,
        meaning the quadratic form `x^H A x` has positive real part for all
        nonzero `x`.  Note that we do not require the operator to be
        self-adjoint to be positive-definite.  See:
        https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices
      is_square:  Expect that this operator acts like square [batch] matrices.
        This will raise a `ValueError` if set to `False`.
      name: A name for this `LinearOperator`.

    Raises:
      TypeError:  If all operators do not have the same `dtype`.
      ValueError:  If `operators` is empty, contains an erroneous number of
        elements, or contains operators with incompatible shapes.
    """
        # Validate operators.
        check_ops.assert_proper_iterable(operators)
        for row in operators:
            check_ops.assert_proper_iterable(row)
        operators = [list(row) for row in operators]

        if not operators:
            raise ValueError(
                "Expected a non-empty list of operators. Found: {}".format(
                    operators))
        self._operators = operators
        self._diagonal_operators = [row[-1] for row in operators]

        dtype = operators[0][0].dtype
        self._validate_dtype(dtype)
        is_non_singular = self._validate_non_singular(is_non_singular)
        self._validate_num_operators()
        self._validate_operator_dimensions()
        is_square = self._validate_square(is_square)
        with ops.name_scope(name):
            super(LinearOperatorBlockLowerTriangular,
                  self).__init__(dtype=dtype,
                                 is_non_singular=is_non_singular,
                                 is_self_adjoint=is_self_adjoint,
                                 is_positive_definite=is_positive_definite,
                                 is_square=is_square,
                                 name=name)