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.
code: def _shape(self):
      batch_shape = _ops.broadcast_static_shape(
          self.base_operator.batch_shape, self.diag_operator.batch_shape
      )
      batch_shape = _ops.broadcast_static_shape(
          batch_shape, tensor_shape.TensorShape(self.u.shape)[:-2]
      )
      batch_shape = _ops.broadcast_static_shape(
          batch_shape, tensor_shape.TensorShape(self.v.shape)[:-2]
      )
      return batch_shape.concatenate(
          tensor_shape.TensorShape(self.base_operator.shape)[-2:]
      )
  
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 11 for calling the LinearOperatorLowRankUpdate obj's underlying member method code with example usage: obj._shape() and returns: batch_shape