Help protect the Great Barrier Reef with TensorFlow on Kaggle

# tf.linalg.LinearOperatorBlockLowerTriangular

Combines `LinearOperators` into a blockwise lower-triangular matrix.

Inherits From: `LinearOperator`, `Module`

This operator is initialized with a nested list of linear operators, which are combined into a new `LinearOperator` whose underlying matrix representation is square and has each operator on or below the main diagonal, and zero's elsewhere. Each element of the outer list is a list of `LinearOperators` corresponding to a row-partition of the blockwise structure. The number of `LinearOperator`s in row-partion `i` must be equal to `i`.

For example, a blockwise `3 x 3` `LinearOperatorBlockLowerTriangular` is initialized with the list `[[op_00], [op_10, op_11], [op_20, op_21, op_22]]`, where the `op_ij`, `i < 3, j <= i`, are `LinearOperator` instances. The `LinearOperatorBlockLowerTriangular` behaves as the following blockwise matrix, where `0` represents appropriately-sized [batch] matrices of zeros:

``````[[op_00,     0,     0],
[op_10, op_11,     0],
[op_20, op_21, op_22]]
``````

Each `op_jj` on the diagonal is required to represent a square matrix, and hence will have shape `batch_shape_j + [M_j, M_j]`. `LinearOperator`s in row `j` of the blockwise structure must have `range_dimension` equal to that of `op_jj`, and `LinearOperators` in column `j` must have `domain_dimension` equal to that of `op_jj`.

If each `op_jj` on the diagonal has shape `batch_shape_j + [M_j, M_j]`, then the combined operator has shape `broadcast_batch_shape + [sum M_j, sum M_j]`, where `broadcast_batch_shape` is the mutual broadcast of `batch_shape_j`, `j = 0, 1, ..., J`, assuming the intermediate batch shapes broadcast. Even if the combined shape is well defined, the combined operator's methods may fail due to lack of broadcasting ability in the defining operators' methods.

For example, to create a 4 x 4 linear operator combined of three 2 x 2 operators:

``````>>> operator_0 = tf.linalg.LinearOperatorFullMatrix([[1., 2.], [3., 4.]])
>>> operator_1 = tf.linalg.LinearOperatorFullMatrix([[1., 0.], [0., 1.]])
>>> operator_2 = tf.linalg.LinearOperatorLowerTriangular([[5., 6.], [7., 8]])
>>> operator = LinearOperatorBlockLowerTriangular(
...   [[operator_0], [operator_1, operator_2]])
``````
````operator.to_dense()`
`<tf.Tensor: shape=(4, 4), dtype=float32, numpy=`
`array([[1., 2., 0., 0.],`
`       [3., 4., 0., 0.],`
`       [1., 0., 5., 0.],`
`       [0., 1., 7., 8.]], dtype=float32)>`
```
````operator.shape`
`TensorShape([4, 4])`
```
````operator.log_abs_determinant()`
`<tf.Tensor: shape=(), dtype=float32, numpy=4.3820267>`
```
````x0 = [[1., 6.], [-3., 4.]]`
`x1 = [[0., 2.], [4., 0.]]`
`x = tf.concat([x0, x1], 0)  # Shape [2, 4] Tensor`
`operator.matmul(x)`
`<tf.Tensor: shape=(4, 2), dtype=float32, numpy=`
`array([[-5., 14.],`
`       [-9., 34.],`
`       [ 1., 16.],`
`       [29., 18.]], dtype=float32)>`
```

The above `matmul` is equivalent to:

``````>>> tf.concat([operator_0.matmul(x0),
...   operator_1.matmul(x0) + operator_2.matmul(x1)], axis=0)
<tf.Tensor: shape=(4, 2), dtype=float32, numpy=
array([[-5., 14.],
[-9., 34.],
[ 1., 16.],
[29., 18.]], dtype=float32)>
``````

#### Shape compatibility

This operator acts on [batch] matrix with compatible shape. `x` is a batch matrix with compatible shape for `matmul` and `solve` if

``````operator.shape = [B1,...,Bb] + [M, N],  with b >= 0
x.shape =        [B1,...,Bb] + [N, R],  with R >= 0.
``````

#### For example:

Create a [2, 3] batch of 4 x 4 linear operators:

``````>>> matrix_44 = tf.random.normal(shape=[2, 3, 4, 4])
>>> operator_44 = tf.linalg.LinearOperatorFullMatrix(matrix_44)
``````

Create a [1, 3] batch of 5 x 4 linear operators:

``````>>> matrix_54 = tf.random.normal(shape=[1, 3, 5, 4])
>>> operator_54 = tf.linalg.LinearOperatorFullMatrix(matrix_54)
``````

Create a [1, 3] batch of 5 x 5 linear operators:

``````>>> matrix_55 = tf.random.normal(shape=[1, 3, 5, 5])
>>> operator_55 = tf.linalg.LinearOperatorFullMatrix(matrix_55)
``````

Combine to create a [2, 3] batch of 9 x 9 operators:

``````>>> operator_99 = LinearOperatorBlockLowerTriangular(
...   [[operator_44], [operator_54, operator_55]])
>>> operator_99.shape
TensorShape([2, 3, 9, 9])
``````

Create a shape [2, 1, 9] batch of vectors and apply the operator to it.

``````>>> x = tf.random.normal(shape=[2, 1, 9])
>>> y = operator_99.matvec(x)
>>> y.shape
TensorShape([2, 3, 9])
``````

Create a blockwise list of vectors and apply the operator to it. A blockwise list is returned.

``````>>> x4 = tf.random.normal(shape=[2, 1, 4])
>>> x5 = tf.random.normal(shape=[2, 3, 5])
>>> y_blockwise = operator_99.matvec([x4, x5])
>>> y_blockwise[0].shape
TensorShape([2, 3, 4])
>>> y_blockwise[1].shape
TensorShape([2, 3, 5])
``````

#### Performance

Suppose `operator` is a `LinearOperatorBlockLowerTriangular` consisting of `D` row-partitions and