class_docstr: Perturb a `LinearOperator` with a rank `K` update.
  
  This operator acts like a [batch] matrix `A` with shape
  `[B1,...,Bb, M, N]` for some `b >= 0`.  The first `b` indices index a
  batch member.  For every batch index `(i1,...,ib)`, `A[i1,...,ib, : :]` is
  an `M x N` matrix.
  
  `LinearOperatorLowRankUpdate` represents `A = L + U D V^H`, where
  
  ```
  L, is a LinearOperator representing [batch] M x N matrices
  U, is a [batch] M x K matrix.  Typically K << M.
  D, is a [batch] K x K matrix.
  V, is a [batch] N x K matrix.  Typically K << N.
  V^H is the Hermitian transpose (adjoint) of V.
  ```
  
  If `M = N`, determinants and solves are done using the matrix determinant
  lemma and Woodbury identities, and thus require L and D to be non-singular.
  
  Solves and determinants will be attempted unless the "is_non_singular"
  property of L and D is False.
  
  In the event that L and D are positive-definite, and U = V, solves and
  determinants can be done using a Cholesky factorization.
  
  ```python
  # Create a 3 x 3 diagonal linear operator.
  diag_operator = LinearOperatorDiag(
      diag_update=[1., 2., 3.], is_non_singular=True, is_self_adjoint=True,
      is_positive_definite=True)
  
  # Perturb with a rank 2 perturbation
  operator = LinearOperatorLowRankUpdate(
      operator=diag_operator,
      u=[[1., 2.], [-1., 3.], [0., 0.]],
      diag_update=[11., 12.],
      v=[[1., 2.], [-1., 3.], [10., 10.]])
  
  tensor_shape.TensorShape(operator.shape)
  ==> [3, 3]
  
  operator.log_abs_determinant()
  ==> scalar Tensor
  
  x = ... Shape [3, 4] Tensor
  operator.matmul(x)
  ==> Shape [3, 4] Tensor
  ```
  
  ### Shape compatibility
  
  This operator acts on [batch] matrix with compatible shape.
  `x` is a batch matrix with compatible shape for `matmul` and `solve` if
  
  ```
  tensor_shape.TensorShape(operator.shape) = [B1,...,Bb] + [M, N],  with b >= 0
  tensor_shape.TensorShape(x.shape) =        [B1,...,Bb] + [N, R],  with R >= 0.
  ```
  
  ### Performance
  
  Suppose `operator` is a `LinearOperatorLowRankUpdate` of shape `[M, N]`,
  made from a rank `K` update of `base_operator` which performs `.matmul(x)` on
  `x` having `tensor_shape.TensorShape(x.shape) = [N, R]` with `O(L_matmul*N*R)` complexity (and similarly
  for `solve`, `determinant`.  Then, if `tensor_shape.TensorShape(x.shape) = [N, R]`,
  
  * `operator.matmul(x)` is `O(L_matmul*N*R + K*N*R)`
  
  and if `M = N`,
  
  * `operator.solve(x)` is `O(L_matmul*N*R + N*K*R + K^2*R + K^3)`
  * `operator.determinant()` is `O(L_determinant + L_solve*N*K + K^2*N + K^3)`
  
  If instead `operator` and `x` have shape `[B1,...,Bb, M, N]` and
  `[B1,...,Bb, N, R]`, every operation increases in complexity by `B1*...*Bb`.
  
  #### Matrix property hints
  
  This `LinearOperator` is initialized with boolean flags of the form `is_X`,
  for `X = non_singular`, `self_adjoint`, `positive_definite`,
  `diag_update_positive` and `square`. These have the following meaning:
  
  * If `is_X == True`, callers should expect the operator to have the
    property `X`.  This is a promise that should be fulfilled, but is *not* a
    runtime assert.  For example, finite floating point precision may result
    in these promises being violated.
  * If `is_X == False`, callers should expect the operator to not have `X`.
  * If `is_X == None` (the default), callers should have no expectation either
    way.
docstr: 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.
code: 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
  
      with ops.name_scope(name):
          # 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
  
          super(LinearOperatorLowRankUpdate, self).__init__(
              dtype=self._base_operator.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,
          )
  
          # 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()
  
imports: from tensorflow_probability.python.internal.backend.numpy import ops
  from tensorflow_probability.python.internal.backend.numpy.gen import tensor_shape
  from tensorflow_probability.python.internal.backend.numpy import numpy_array
  from tensorflow_probability.python.internal.backend.numpy import linalg_impl
  from tensorflow_probability.python.internal.backend.numpy import numpy_math
  from tensorflow_probability.python.internal.backend.numpy.gen import linear_operator
  from tensorflow_probability.python.internal.backend.numpy.gen import linear_operator_diag
  from tensorflow_probability.python.internal.backend.numpy.gen import linear_operator_identity
  from tensorflow_probability.python.internal.backend.numpy.gen import linear_operator_util
  from absl import logging
  import numpy
  from tensorflow_probability.python.internal.backend.numpy import linalg_impl
  from tensorflow_probability.python.internal.backend.numpy import ops
  from tensorflow_probability.python.internal.backend.numpy.gen import tensor_shape
  from tensorflow_probability.python.internal.backend.numpy import private
label_desc: Use image node_id 1 to create a new LinearOperatorLowRankUpdate object from inherited base classes: linear_operator with example: obj = LinearOperatorLowRankUpdate(base_operator, u, diag_update, v, is_diag_update_positive, is_non_singular, is__adjoint, is_positive_definite, is_square, name)