def __init__(self, col, row, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name="LinearOperatorToeplitz"): r"""Initialize a `LinearOperatorToeplitz`. Args: col: Shape `[B1,...,Bb, N]` `Tensor` with `b >= 0` `N >= 0`. The first column of the operator. Allowed dtypes: `float16`, `float32`, `float64`, `complex64`, `complex128`. Note that the first entry of `col` is assumed to be the same as the first entry of `row`. row: Shape `[B1,...,Bb, N]` `Tensor` with `b >= 0` `N >= 0`. The first row of the operator. Allowed dtypes: `float16`, `float32`, `float64`, `complex64`, `complex128`. Note that the first entry of `row` is assumed to be the same as the first entry of `col`. is_non_singular: Expect that this operator is non-singular. is_self_adjoint: Expect that this operator is equal to its hermitian transpose. If `diag.dtype` is real, this is auto-set to `True`. 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`. """ parameters = dict(col=col, row=row, is_non_singular=is_non_singular, is_self_adjoint=is_self_adjoint, is_positive_definite=is_positive_definite, is_square=is_square, name=name) with ops.name_scope(name, values=[row, col]): self._row = linear_operator_util.convert_nonref_to_tensor( row, name="row") self._col = linear_operator_util.convert_nonref_to_tensor( col, name="col") self._check_row_col(self._row, self._col) if is_square is False: # pylint:disable=g-bool-id-comparison raise ValueError( "Only square Toeplitz operators currently supported.") is_square = True super(LinearOperatorToeplitz, self).__init__(dtype=self._row.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) self._set_graph_parents([self._row, self._col])
def _name_scope(self, name=None): """Helper function to standardize op scope.""" full_name = self.name if name is not None: full_name += "/" + name with ops.name_scope(full_name) as scope: yield scope
def add(self, op1, op2, operator_name, hints=None): """Return new `LinearOperator` acting like `op1 + op2`. Args: op1: `LinearOperator` op2: `LinearOperator`, with `shape` and `dtype` such that adding to `op1` is allowed. operator_name: `String` name to give to returned `LinearOperator` hints: `_Hints` object. Returned `LinearOperator` will be created with these hints. Returns: `LinearOperator` """ updated_hints = _infer_hints_allowing_override(op1, op2, hints) if operator_name is None: operator_name = "Add/" + op1.name + "__" + op2.name + "/" values = op1.graph_parents + op2.graph_parents scope_name = self.name if scope_name.startswith("_"): scope_name = scope_name[1:] with ops.name_scope(scope_name, values=values): return self._add(op1, op2, operator_name, updated_hints)
def __init__(self, tril, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name="LinearOperatorLowerTriangular"): r"""Initialize a `LinearOperatorLowerTriangular`. Args: tril: Shape `[B1,...,Bb, N, N]` with `b >= 0`, `N >= 0`. The lower triangular part of `tril` defines this operator. The strictly upper triangle is ignored. is_non_singular: Expect that this operator is non-singular. This operator is non-singular if and only if its diagonal elements are all non-zero. is_self_adjoint: Expect that this operator is equal to its hermitian transpose. This operator is self-adjoint only if it is diagonal with real-valued diagonal entries. In this case it is advised to use `LinearOperatorDiag`. 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`. Raises: ValueError: If `is_square` is `False`. """ parameters = dict(tril=tril, is_non_singular=is_non_singular, is_self_adjoint=is_self_adjoint, is_positive_definite=is_positive_definite, is_square=is_square, name=name) if is_square is False: raise ValueError( "Only square lower triangular operators supported at this time." ) is_square = True with ops.name_scope(name, values=[tril]): self._tril = linear_operator_util.convert_nonref_to_tensor( tril, name="tril") self._check_tril(self._tril) super(LinearOperatorLowerTriangular, self).__init__(dtype=self._tril.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) self._set_graph_parents([self._tril])
def __init__(self, diag, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name="LinearOperatorDiag"): r"""Initialize a `LinearOperatorDiag`. Args: diag: Shape `[B1,...,Bb, N]` `Tensor` with `b >= 0` `N >= 0`. The diagonal of the operator. Allowed dtypes: `float16`, `float32`, `float64`, `complex64`, `complex128`. is_non_singular: Expect that this operator is non-singular. is_self_adjoint: Expect that this operator is equal to its hermitian transpose. If `diag.dtype` is real, this is auto-set to `True`. 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`. Raises: TypeError: If `diag.dtype` is not an allowed type. ValueError: If `diag.dtype` is real, and `is_self_adjoint` is not `True`. """ with ops.name_scope(name, values=[diag]): self._diag = linear_operator_util.convert_nonref_to_tensor( diag, name="diag") self._check_diag(self._diag) # Check and auto-set hints. if not np.issubdtype(self._diag.dtype, np.complexfloating): if is_self_adjoint is False: raise ValueError( "A real diagonal operator is always self adjoint.") else: is_self_adjoint = True if is_square is False: raise ValueError( "Only square diagonal operators currently supported.") is_square = True super(LinearOperatorDiag, self).__init__(dtype=self._diag.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, name=name) # TODO(b/143910018) Remove graph_parents in V3. self._set_graph_parents([self._diag])
def _max_condition_number_to_be_non_singular(self): """Return the maximum condition number that we consider nonsingular.""" with ops.name_scope("max_nonsingular_condition_number"): dtype_eps = np.finfo(self.dtype.as_numpy_dtype).eps eps = _ops.cast( math_ops.reduce_max([ 100., _ops.cast(self.range_dimension_tensor(), self.dtype), _ops.cast(self.domain_dimension_tensor(), self.dtype) ]), self.dtype) * dtype_eps return 1. / eps
def __init__(self, matrix, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name="LinearOperatorFullMatrix"): r"""Initialize a `LinearOperatorFullMatrix`. Args: matrix: Shape `[B1,...,Bb, M, N]` with `b >= 0`, `M, N >= 0`. Allowed dtypes: `float16`, `float32`, `float64`, `complex64`, `complex128`. 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`. Raises: TypeError: If `diag.dtype` is not an allowed type. """ parameters = dict( matrix=matrix, is_non_singular=is_non_singular, is_self_adjoint=is_self_adjoint, is_positive_definite=is_positive_definite, is_square=is_square, name=name ) with ops.name_scope(name, values=[matrix]): self._matrix = linear_operator_util.convert_nonref_to_tensor( matrix, name="matrix") self._check_matrix(self._matrix) super(LinearOperatorFullMatrix, self).__init__( dtype=self._matrix.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([self._matrix])
def matrix_triangular_solve_with_broadcast(matrix, rhs, lower=True, adjoint=False, name=None): """Solves triangular systems of linear equations with by backsubstitution. Works identically to `tf.linalg.triangular_solve`, but broadcasts batch dims of `matrix` and `rhs` (by replicating) if they are determined statically to be different, or if static shapes are not fully defined. Thus, this may result in an inefficient replication of data. Args: matrix: A Tensor. Must be one of the following types: `float64`, `float32`, `complex64`, `complex128`. Shape is `[..., M, M]`. rhs: A `Tensor`. Must have the same `dtype` as `matrix`. Shape is `[..., M, K]`. lower: An optional `bool`. Defaults to `True`. Indicates whether the innermost matrices in `matrix` are lower or upper triangular. adjoint: An optional `bool`. Defaults to `False`. Indicates whether to solve with matrix or its (block-wise) adjoint. name: A name for the operation (optional). Returns: `Tensor` with same `dtype` as `matrix` and shape `[..., M, K]`. """ with ops.name_scope(name, "MatrixTriangularSolve", [matrix, rhs]): matrix = ops.convert_to_tensor(matrix, name="matrix") rhs = ops.convert_to_tensor(rhs, name="rhs", dtype=matrix.dtype) # If either matrix/rhs has extra dims, we can reshape to get rid of them. matrix, rhs, reshape_inv, still_need_to_transpose = _reshape_for_efficiency( matrix, rhs, adjoint_a=adjoint) # lower indicates whether the matrix is lower triangular. If we have # manually taken adjoint inside _reshape_for_efficiency, it is now upper tri if not still_need_to_transpose and adjoint: lower = not lower # This will broadcast by brute force if we still need to. matrix, rhs = broadcast_matrix_batch_dims([matrix, rhs]) solution = linalg_ops.triangular_solve(matrix, rhs, lower=lower, adjoint=adjoint and still_need_to_transpose) return reshape_inv(solution)
def matrix_solve_with_broadcast(matrix, rhs, adjoint=False, name=None): """Solve systems of linear equations.""" with ops.name_scope(name, "MatrixSolveWithBroadcast", [matrix, rhs]): matrix = ops.convert_to_tensor(matrix, name="matrix") rhs = ops.convert_to_tensor(rhs, name="rhs", dtype=matrix.dtype) # If either matrix/rhs has extra dims, we can reshape to get rid of them. matrix, rhs, reshape_inv, still_need_to_transpose = _reshape_for_efficiency( matrix, rhs, adjoint_a=adjoint) # This will broadcast by brute force if we still need to. matrix, rhs = broadcast_matrix_batch_dims([matrix, rhs]) solution = _linalg.solve( matrix, rhs, adjoint=adjoint and still_need_to_transpose) return reshape_inv(solution)
def assert_no_entries_with_modulus_zero( x, message=None, name="assert_no_entries_with_modulus_zero"): """Returns `Op` that asserts Tensor `x` has no entries with modulus zero. Args: x: Numeric `Tensor`, real, integer, or complex. message: A string message to prepend to failure message. name: A name to give this `Op`. Returns: An `Op` that asserts `x` has no entries with modulus zero. """ with ops.name_scope(name, values=[x]): x = ops.convert_to_tensor(x, name="x") dtype = x.dtype should_be_nonzero = math_ops.abs(x) zero = ops.convert_to_tensor(0, dtype=dtypes.real_dtype(dtype)) return check_ops.assert_less(zero, should_be_nonzero, message=message)
def assert_zero_imag_part(x, message=None, name="assert_zero_imag_part"): """Returns `Op` that asserts Tensor `x` has no non-zero imaginary parts. Args: x: Numeric `Tensor`, real, integer, or complex. message: A string message to prepend to failure message. name: A name to give this `Op`. Returns: An `Op` that asserts `x` has no entries with modulus zero. """ with ops.name_scope(name, values=[x]): x = ops.convert_to_tensor(x, name="x") dtype = x.dtype if dtype.is_floating: return control_flow_ops.no_op() zero = ops.convert_to_tensor(0, dtype=dtypes.real_dtype(dtype)) return check_ops.assert_equal(zero, math_ops.imag(x), message=message)
def adjoint(lin_op_a, name=None): """Get the adjoint associated to lin_op_a. Args: lin_op_a: The LinearOperator to take the adjoint of. name: Name to use for this operation. Returns: A LinearOperator that represents the adjoint of `lin_op_a`. Raises: NotImplementedError: If no Adjoint method is defined for the LinearOperator type of `lin_op_a`. """ adjoint_fn = _registered_adjoint(type(lin_op_a)) if adjoint_fn is None: raise ValueError("No adjoint registered for {}".format(type(lin_op_a))) with ops.name_scope(name, "Adjoint"): return adjoint_fn(lin_op_a)
def inverse(lin_op_a, name=None): """Get the Inverse associated to lin_op_a. Args: lin_op_a: The LinearOperator to decompose. name: Name to use for this operation. Returns: A LinearOperator that represents the inverse of `lin_op_a`. Raises: NotImplementedError: If no Inverse method is defined for the LinearOperator type of `lin_op_a`. """ inverse_fn = _registered_inverse(type(lin_op_a)) if inverse_fn is None: raise ValueError("No inverse registered for {}".format(type(lin_op_a))) with ops.name_scope(name, "Inverse"): return inverse_fn(lin_op_a)
def cholesky(lin_op_a, name=None): """Get the Cholesky factor associated to lin_op_a. Args: lin_op_a: The LinearOperator to decompose. name: Name to use for this operation. Returns: A LinearOperator that represents the lower Cholesky factor of `lin_op_a`. Raises: NotImplementedError: If no Cholesky method is defined for the LinearOperator type of `lin_op_a`. """ cholesky_fn = _registered_cholesky(type(lin_op_a)) if cholesky_fn is None: raise ValueError("No cholesky decomposition registered for {}".format( type(lin_op_a))) with ops.name_scope(name, "Cholesky"): return cholesky_fn(lin_op_a)
def solve(lin_op_a, lin_op_b, name=None): """Compute lin_op_a.solve(lin_op_b). Args: lin_op_a: The LinearOperator on the left. lin_op_b: The LinearOperator on the right. name: Name to use for this operation. Returns: A LinearOperator that represents the solve between `lin_op_a` and `lin_op_b`. Raises: NotImplementedError: If no solve method is defined between types of `lin_op_a` and `lin_op_b`. """ solve_fn = _registered_solve(type(lin_op_a), type(lin_op_b)) if solve_fn is None: raise ValueError("No solve registered for {}.solve({})".format( type(lin_op_a), type(lin_op_b))) with ops.name_scope(name, "Solve"): return solve_fn(lin_op_a, lin_op_b)
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
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)
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)
def __init__(self, spectrum, block_depth, input_output_dtype=dtypes.complex64, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=True, parameters=None, name="LinearOperatorCirculant"): r"""Initialize an `_BaseLinearOperatorCirculant`. Args: spectrum: Shape `[B1,...,Bb, N]` `Tensor`. Allowed dtypes: `float16`, `float32`, `float64`, `complex64`, `complex128`. Type can be different than `input_output_dtype` block_depth: Python integer, either 1, 2, or 3. Will be 1 for circulant, 2 for block circulant, and 3 for nested block circulant. input_output_dtype: `dtype` for input/output. is_non_singular: Expect that this operator is non-singular. is_self_adjoint: Expect that this operator is equal to its hermitian transpose. If `spectrum` is real, this will always be true. 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. parameters: Python `dict` of parameters used to instantiate this `LinearOperator`. name: A name to prepend to all ops created by this class. Raises: ValueError: If `block_depth` is not an allowed value. TypeError: If `spectrum` is not an allowed type. """ allowed_block_depths = [1, 2, 3] self._name = name if block_depth not in allowed_block_depths: raise ValueError( f"Argument `block_depth` must be one of {allowed_block_depths}. " f"Received: {block_depth}.") self._block_depth = block_depth with ops.name_scope(name, values=[spectrum]): self._spectrum = self._check_spectrum_and_return_tensor(spectrum) # Check and auto-set hints. if not np.issubdtype(self.spectrum.dtype, np.complexfloating): if is_self_adjoint is False: raise ValueError( f"A real spectrum always corresponds to a self-adjoint operator. " f"Expected argument `is_self_adjoint` to be True when " f"`np.issubdtype(spectrum.dtype, np.complexfloating)` = True. " f"Received: {is_self_adjoint}.") is_self_adjoint = True if is_square is False: raise ValueError( f"A [[nested] block] circulant operator is always square. " f"Expected argument `is_square` to be True. Received: {is_square}." ) is_square = True super(_BaseLinearOperatorCirculant, self).__init__(dtype=dtypes.as_dtype(input_output_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)
def __init__(self, num_rows, batch_shape=None, dtype=None, is_non_singular=True, is_self_adjoint=True, is_positive_definite=True, is_square=True, assert_proper_shapes=False, name="LinearOperatorIdentity"): r"""Initialize a `LinearOperatorIdentity`. The `LinearOperatorIdentity` is initialized with arguments defining `dtype` and shape. This operator is able to broadcast the leading (batch) dimensions, which sometimes requires copying data. If `batch_shape` is `None`, the operator can take arguments of any batch shape without copying. See examples. Args: num_rows: Scalar non-negative integer `Tensor`. Number of rows in the corresponding identity matrix. batch_shape: Optional `1-D` integer `Tensor`. The shape of the leading dimensions. If `None`, this operator has no leading dimensions. dtype: Data type of the matrix that this operator represents. 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. assert_proper_shapes: Python `bool`. If `False`, only perform static checks that initialization and method arguments have proper shape. If `True`, and static checks are inconclusive, add asserts to the graph. name: A name for this `LinearOperator` Raises: ValueError: If `num_rows` is determined statically to be non-scalar, or negative. ValueError: If `batch_shape` is determined statically to not be 1-D, or negative. ValueError: If any of the following is not `True`: `{is_self_adjoint, is_non_singular, is_positive_definite}`. TypeError: If `num_rows` or `batch_shape` is ref-type (e.g. Variable). """ dtype = dtype or dtypes.float32 self._assert_proper_shapes = assert_proper_shapes with ops.name_scope(name): dtype = dtypes.as_dtype(dtype) if not is_self_adjoint: raise ValueError( "An identity operator is always self adjoint.") if not is_non_singular: raise ValueError( "An identity operator is always non-singular.") if not is_positive_definite: raise ValueError( "An identity operator is always positive-definite.") if not is_square: raise ValueError("An identity operator is always square.") super(LinearOperatorIdentity, 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) linear_operator_util.assert_not_ref_type(num_rows, "num_rows") linear_operator_util.assert_not_ref_type(batch_shape, "batch_shape") self._num_rows = linear_operator_util.shape_tensor(num_rows, name="num_rows") self._num_rows_static = (self._num_rows) self._check_num_rows_possibly_add_asserts() if batch_shape is None: self._batch_shape_arg = None else: self._batch_shape_arg = linear_operator_util.shape_tensor( batch_shape, name="batch_shape_arg") self._batch_shape_static = (self._batch_shape_arg) self._check_batch_shape_possibly_add_asserts()
def __init__(self, num_rows, multiplier, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=True, assert_proper_shapes=False, name="LinearOperatorScaledIdentity"): r"""Initialize a `LinearOperatorScaledIdentity`. The `LinearOperatorScaledIdentity` is initialized with `num_rows`, which determines the size of each identity matrix, and a `multiplier`, which defines `dtype`, batch shape, and scale of each matrix. This operator is able to broadcast the leading (batch) dimensions. Args: num_rows: Scalar non-negative integer `Tensor`. Number of rows in the corresponding identity matrix. multiplier: `Tensor` of shape `[B1,...,Bb]`, or `[]` (a scalar). 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. assert_proper_shapes: Python `bool`. If `False`, only perform static checks that initialization and method arguments have proper shape. If `True`, and static checks are inconclusive, add asserts to the graph. name: A name for this `LinearOperator` Raises: ValueError: If `num_rows` is determined statically to be non-scalar, or negative. """ self._assert_proper_shapes = assert_proper_shapes with ops.name_scope(name, values=[multiplier, num_rows]): self._multiplier = linear_operator_util.convert_nonref_to_tensor( multiplier, name="multiplier") # Check and auto-set hints. if not np.issubdtype(self._multiplier.dtype, np.complexfloating): if is_self_adjoint is False: # pylint: disable=g-bool-id-comparison raise ValueError( "A real diagonal operator is always self adjoint.") else: is_self_adjoint = True if not is_square: raise ValueError("A ScaledIdentity operator is always square.") linear_operator_util.assert_not_ref_type(num_rows, "num_rows") super(LinearOperatorScaledIdentity, self).__init__(dtype=self._multiplier.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) self._num_rows = linear_operator_util.shape_tensor(num_rows, name="num_rows") self._num_rows_static = (self._num_rows) self._check_num_rows_possibly_add_asserts() self._num_rows_cast_to_dtype = _ops.cast(self._num_rows, self.dtype) self._num_rows_cast_to_real_dtype = _ops.cast( self._num_rows, dtypes.real_dtype(self.dtype))
def __init__(self, operator, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name=None): r"""Initialize a `LinearOperatorAdjoint`. `LinearOperatorAdjoint` is initialized with an operator `A`. The `solve` and `matmul` methods effectively flip the `adjoint` argument. E.g. ``` A = MyLinearOperator(...) B = LinearOperatorAdjoint(A) x = [....] # a vector assert A.matvec(x, adjoint=True) == B.matvec(x, adjoint=False) ``` Args: operator: `LinearOperator` object. 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 `operator.name + "_adjoint"`. Raises: ValueError: If `operator.is_non_singular` is False. """ self._operator = operator # The congruency of is_non_singular and is_self_adjoint was checked in the # base operator. combine_hint = ( linear_operator_util.use_operator_or_provided_hint_unless_contradicting) is_square = combine_hint( operator, "is_square", is_square, "An operator is square if and only if its adjoint is square.") is_non_singular = combine_hint( operator, "is_non_singular", is_non_singular, "An operator is non-singular if and only if its adjoint is " "non-singular.") is_self_adjoint = combine_hint( operator, "is_self_adjoint", is_self_adjoint, "An operator is self-adjoint if and only if its adjoint is " "self-adjoint.") is_positive_definite = combine_hint( operator, "is_positive_definite", is_positive_definite, "An operator is positive-definite if and only if its adjoint is " "positive-definite.") # Initialization. if name is None: name = operator.name + "_adjoint" with ops.name_scope(name, values=operator.graph_parents): super(LinearOperatorAdjoint, self).__init__( dtype=operator.dtype, graph_parents=operator.graph_parents, is_non_singular=is_non_singular, is_self_adjoint=is_self_adjoint, is_positive_definite=is_positive_definite, is_square=is_square, name=name)
def __init__(self, operator, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name=None): r"""Initialize a `LinearOperatorInversion`. `LinearOperatorInversion` is initialized with an operator `A`. The `solve` and `matmul` methods are effectively swapped. E.g. ``` A = MyLinearOperator(...) B = LinearOperatorInversion(A) x = [....] # a vector assert A.matvec(x) == B.solvevec(x) ``` Args: operator: `LinearOperator` object. If `operator.is_non_singular == False`, an exception is raised. We do allow `operator.is_non_singular == None`, in which case this operator will have `is_non_singular == None`. Similarly for `is_self_adjoint` and `is_positive_definite`. 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 `operator.name + "_inv"`. Raises: ValueError: If `operator.is_non_singular` is False. """ self._operator = operator # Auto-set and check hints. if operator.is_non_singular is False or is_non_singular is False: raise ValueError( "operator and supplied hints must have `is_non_singular` equal to " "`True` or `None`. Found %s, %s" % (operator.is_non_singular, is_non_singular)) if operator.is_square is False or is_square is False: raise ValueError( "operator and supplied hints must have `is_square` equal to " "`True` or `None`. Found %s, %s" % (operator.is_square, is_square)) # The congruency of is_non_singular and is_self_adjoint was checked in the # base operator. Other hints are, in this special case of inversion, ones # that must be the same for base/derived operator. combine_hint = (linear_operator_util. use_operator_or_provided_hint_unless_contradicting) is_square = combine_hint( operator, "is_square", is_square, "An operator is square if and only if its inverse is square.") is_non_singular = combine_hint( operator, "is_non_singular", is_non_singular, "An operator is non-singular if and only if its inverse is " "non-singular.") is_self_adjoint = combine_hint( operator, "is_self_adjoint", is_self_adjoint, "An operator is self-adjoint if and only if its inverse is " "self-adjoint.") is_positive_definite = combine_hint( operator, "is_positive_definite", is_positive_definite, "An operator is positive-definite if and only if its inverse is " "positive-definite.") # Initialization. if name is None: name = operator.name + "_inv" with ops.name_scope(name, values=operator.graph_parents): super(LinearOperatorInversion, self).__init__(dtype=operator.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, name=name) # TODO(b/143910018) Remove graph_parents in V3. self._set_graph_parents(operator.graph_parents)
def __init__(self, base_operator, u, diag_update=None, v=None, is_diag_update_positive=None, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name="LinearOperatorLowRankUpdate"): """Initialize a `LinearOperatorLowRankUpdate`. This creates a `LinearOperator` of the form `A = L + U D V^H`, with `L` a `LinearOperator`, `U, V` both [batch] matrices, and `D` a [batch] diagonal matrix. If `L` is non-singular, solves and determinants are available. Solves/determinants both involve a solve/determinant of a `K x K` system. In the event that L and D are self-adjoint positive-definite, and U = V, this can be done using a Cholesky factorization. The user should set the `is_X` matrix property hints, which will trigger the appropriate code path. Args: base_operator: Shape `[B1,...,Bb, M, N]`. u: Shape `[B1,...,Bb, M, K]` `Tensor` of same `dtype` as `base_operator`. This is `U` above. diag_update: Optional shape `[B1,...,Bb, K]` `Tensor` with same `dtype` as `base_operator`. This is the diagonal of `D` above. Defaults to `D` being the identity operator. v: Optional `Tensor` of same `dtype` as `u` and shape `[B1,...,Bb, N, K]` Defaults to `v = u`, in which case the perturbation is symmetric. If `M != N`, then `v` must be set since the perturbation is not square. is_diag_update_positive: Python `bool`. If `True`, expect `diag_update > 0`. is_non_singular: Expect that this operator is non-singular. Default is `None`, unless `is_positive_definite` is auto-set to be `True` (see below). is_self_adjoint: Expect that this operator is equal to its hermitian transpose. Default is `None`, unless `base_operator` is self-adjoint and `v = None` (meaning `u=v`), in which case this defaults to `True`. is_positive_definite: Expect that this operator is positive definite. Default is `None`, unless `base_operator` is positive-definite `v = None` (meaning `u=v`), and `is_diag_update_positive`, in which case this defaults to `True`. Note that we say an operator is positive definite when the quadratic form `x^H A x` has positive real part for all nonzero `x`. is_square: Expect that this operator acts like square [batch] matrices. name: A name for this `LinearOperator`. Raises: ValueError: If `is_X` flags are set in an inconsistent way. """ parameters = dict(base_operator=base_operator, u=u, diag_update=diag_update, v=v, is_diag_update_positive=is_diag_update_positive, is_non_singular=is_non_singular, is_self_adjoint=is_self_adjoint, is_positive_definite=is_positive_definite, is_square=is_square, name=name) dtype = base_operator.dtype if diag_update is not None: if is_diag_update_positive and np.issubdtype( dtype, np.complexfloating): logging.warn( "Note: setting is_diag_update_positive with a complex " "dtype means that diagonal is real and positive.") if diag_update is None: if is_diag_update_positive is False: raise ValueError( "Default diagonal is the identity, which is positive. However, " "user set 'is_diag_update_positive' to False.") is_diag_update_positive = True # In this case, we can use a Cholesky decomposition to help us solve/det. self._use_cholesky = (base_operator.is_positive_definite and base_operator.is_self_adjoint and is_diag_update_positive and v is None) # Possibly auto-set some characteristic flags from None to True. # If the Flags were set (by the user) incorrectly to False, then raise. if base_operator.is_self_adjoint and v is None and not np.issubdtype( dtype, np.complexfloating): if is_self_adjoint is False: raise ValueError( "A = L + UDU^H, with L self-adjoint and D real diagonal. Since" " UDU^H is self-adjoint, this must be a self-adjoint operator." ) is_self_adjoint = True # The condition for using a cholesky is sufficient for SPD, and # we no weaker choice of these hints leads to SPD. Therefore, # the following line reads "if hints indicate SPD..." if self._use_cholesky: if (is_positive_definite is False or is_self_adjoint is False or is_non_singular is False): raise ValueError( "Arguments imply this is self-adjoint positive-definite operator." ) is_positive_definite = True is_self_adjoint = True values = base_operator.graph_parents + [u, diag_update, v] with ops.name_scope(name, values=values): # Create U and V. self._u = linear_operator_util.convert_nonref_to_tensor(u, name="u") if v is None: self._v = self._u else: self._v = linear_operator_util.convert_nonref_to_tensor( v, name="v") if diag_update is None: self._diag_update = None else: self._diag_update = linear_operator_util.convert_nonref_to_tensor( diag_update, name="diag_update") # Create base_operator L. self._base_operator = base_operator graph_parents = base_operator.graph_parents + [ self.u, self._diag_update, self.v ] graph_parents = [p for p in graph_parents if p is not None] super(LinearOperatorLowRankUpdate, self).__init__(dtype=self._base_operator.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) self._set_graph_parents(graph_parents) # Create the diagonal operator D. self._set_diag_operators(diag_update, is_diag_update_positive) self._is_diag_update_positive = is_diag_update_positive self._check_shapes()
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)
def __init__(self, reflection_axis, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=None, name="LinearOperatorHouseholder"): r"""Initialize a `LinearOperatorHouseholder`. Args: reflection_axis: Shape `[B1,...,Bb, N]` `Tensor` with `b >= 0` `N >= 0`. The vector defining the hyperplane to reflect about. Allowed dtypes: `float16`, `float32`, `float64`, `complex64`, `complex128`. is_non_singular: Expect that this operator is non-singular. is_self_adjoint: Expect that this operator is equal to its hermitian transpose. This is autoset to true 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 This is autoset to false. is_square: Expect that this operator acts like square [batch] matrices. This is autoset to true. name: A name for this `LinearOperator`. Raises: ValueError: `is_self_adjoint` is not `True`, `is_positive_definite` is not `False` or `is_square` is not `True`. """ parameters = dict( reflection_axis=reflection_axis, is_non_singular=is_non_singular, is_self_adjoint=is_self_adjoint, is_positive_definite=is_positive_definite, is_square=is_square, name=name ) with ops.name_scope(name, values=[reflection_axis]): self._reflection_axis = linear_operator_util.convert_nonref_to_tensor( reflection_axis, name="reflection_axis") self._check_reflection_axis(self._reflection_axis) # Check and auto-set hints. if is_self_adjoint is False: # pylint:disable=g-bool-id-comparison raise ValueError("A Householder operator is always self adjoint.") else: is_self_adjoint = True if is_positive_definite is True: # pylint:disable=g-bool-id-comparison raise ValueError( "A Householder operator is always non-positive definite.") else: is_positive_definite = False if is_square is False: # pylint:disable=g-bool-id-comparison raise ValueError("A Householder operator is always square.") is_square = True super(LinearOperatorHouseholder, self).__init__( dtype=self._reflection_axis.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)
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
def cholesky_solve_with_broadcast(chol, rhs, name=None): """Solve systems of linear equations.""" with ops.name_scope(name, "CholeskySolveWithBroadcast", [chol, rhs]): chol, rhs = broadcast_matrix_batch_dims([chol, rhs]) return linalg_ops.cholesky_solve(chol, rhs)
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)
def __init__(self, spectrum, block_depth, input_output_dtype=dtypes.complex64, is_non_singular=None, is_self_adjoint=None, is_positive_definite=None, is_square=True, name="LinearOperatorCirculant"): r"""Initialize an `_BaseLinearOperatorCirculant`. Args: spectrum: Shape `[B1,...,Bb, N]` `Tensor`. Allowed dtypes: `float16`, `float32`, `float64`, `complex64`, `complex128`. Type can be different than `input_output_dtype` block_depth: Python integer, either 1, 2, or 3. Will be 1 for circulant, 2 for block circulant, and 3 for nested block circulant. input_output_dtype: `dtype` for input/output. is_non_singular: Expect that this operator is non-singular. is_self_adjoint: Expect that this operator is equal to its hermitian transpose. If `spectrum` is real, this will always be true. 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 to prepend to all ops created by this class. Raises: ValueError: If `block_depth` is not an allowed value. TypeError: If `spectrum` is not an allowed type. """ allowed_block_depths = [1, 2, 3] self._name = name if block_depth not in allowed_block_depths: raise ValueError("Expected block_depth to be in %s. Found: %s." % (allowed_block_depths, block_depth)) self._block_depth = block_depth with ops.name_scope(name, values=[spectrum]): self._spectrum = self._check_spectrum_and_return_tensor(spectrum) # Check and auto-set hints. if not np.issubdtype(self.spectrum.dtype, np.complexfloating): if is_self_adjoint is False: raise ValueError( "A real spectrum always corresponds to a self-adjoint operator.") is_self_adjoint = True if is_square is False: raise ValueError( "A [[nested] block] circulant operator is always square.") is_square = True # If _ops.TensorShape(spectrum.shape) = [s0, s1, s2], and block_depth = 2, # block_shape = [s1, s2] s_shape = array_ops.shape(self.spectrum) self._block_shape_tensor = s_shape[-self.block_depth:] # Add common variants of spectrum to the graph. self._spectrum_complex = _to_complex(self.spectrum) self._abs_spectrum = math_ops.abs(self.spectrum) self._conj_spectrum = math_ops.conj(self._spectrum_complex) super(_BaseLinearOperatorCirculant, self).__init__( dtype=dtypes.as_dtype(input_output_dtype), graph_parents=[self.spectrum], is_non_singular=is_non_singular, is_self_adjoint=is_self_adjoint, is_positive_definite=is_positive_definite, is_square=is_square, name=name)