def test_is_x_flags(self): # The is_x flags are by default all True. operator = linalg_lib.LinearOperatorIdentity(num_rows=2) self.assertTrue(operator.is_positive_definite) self.assertTrue(operator.is_non_singular) self.assertTrue(operator.is_self_adjoint) # Any of them False raises because the identity is always self-adjoint etc.. with self.assertRaisesRegexp(ValueError, "is always non-singular"): operator = linalg_lib.LinearOperatorIdentity( num_rows=2, is_non_singular=None, )
def test_non_scalar_num_rows_raises_dynamic(self): with self.test_session(): num_rows = array_ops.placeholder(dtypes.int32) operator = linalg_lib.LinearOperatorIdentity( num_rows, assert_proper_shapes=True) with self.assertRaisesOpError("must be a 0-D Tensor"): operator.to_dense().eval(feed_dict={num_rows: [2]})
def test_negative_batch_shape_raises_dynamic(self): with self.test_session(): batch_shape = array_ops.placeholder(dtypes.int32) operator = linalg_lib.LinearOperatorIdentity( num_rows=2, batch_shape=batch_shape, assert_proper_shapes=True) with self.assertRaisesOpError("must be non-negative"): operator.to_dense().eval(feed_dict={batch_shape: [-2]})
def test_broadcast_apply_dynamic_shapes(self): # These cannot be done in the automated (base test class) tests since they # test shapes that tf.batch_matmul cannot handle. # In particular, tf.batch_matmul does not broadcast. with self.test_session() as sess: # Given this x and LinearOperatorIdentity shape of (2, 1, 3, 3), the # broadcast shape of operator and 'x' is (2, 2, 3, 4) x = array_ops.placeholder(dtypes.float32) num_rows = array_ops.placeholder(dtypes.int32) batch_shape = array_ops.placeholder(dtypes.int32) operator = linalg_lib.LinearOperatorIdentity( num_rows, batch_shape=batch_shape) feed_dict = { x: rng.rand(1, 2, 3, 4), num_rows: 3, batch_shape: (2, 1) } # Batch matrix of zeros with the broadcast shape of x and operator. zeros = array_ops.zeros(shape=(2, 2, 3, 4), dtype=x.dtype) # Expected result of apply and solve. expected = x + zeros operator_apply = operator.apply(x) self.assertAllClose( *sess.run([operator_apply, expected], feed_dict=feed_dict))
def test_float16_apply(self): # float16 cannot be tested by base test class because tf.matrix_solve does # not work with float16. with self.test_session(): operator = linalg_lib.LinearOperatorIdentity(num_rows=2, dtype=dtypes.float16) x = rng.randn(2, 3).astype(np.float16) y = operator.apply(x) self.assertAllClose(x, y.eval())
def test_wrong_matrix_dimensions_raises_dynamic(self): num_rows = array_ops.placeholder(dtypes.int32) x = array_ops.placeholder(dtypes.float32) with self.test_session(): operator = linalg_lib.LinearOperatorIdentity( num_rows, assert_proper_shapes=True) y = operator.apply(x) with self.assertRaisesOpError("Incompatible.*dimensions"): y.eval(feed_dict={num_rows: 2, x: rng.rand(3, 3)})
def test_default_batch_shape_broadcasts_with_everything_static(self): # These cannot be done in the automated (base test class) tests since they # test shapes that tf.batch_matmul cannot handle. # In particular, tf.batch_matmul does not broadcast. with self.test_session() as sess: x = random_ops.random_normal(shape=(1, 2, 3, 4)) operator = linalg_lib.LinearOperatorIdentity(num_rows=3, dtype=x.dtype) operator_apply = operator.apply(x) expected = x self.assertAllEqual(operator_apply.get_shape(), expected.get_shape()) self.assertAllClose(*sess.run([operator_apply, expected]))
def test_default_batch_shape_broadcasts_with_everything_dynamic(self): # These cannot be done in the automated (base test class) tests since they # test shapes that tf.batch_matmul cannot handle. # In particular, tf.batch_matmul does not broadcast. with self.test_session() as sess: x = array_ops.placeholder(dtypes.float32) operator = linalg_lib.LinearOperatorIdentity(num_rows=3, dtype=x.dtype) operator_apply = operator.apply(x) expected = x feed_dict = {x: rng.randn(1, 2, 3, 4)} self.assertAllClose( *sess.run([operator_apply, expected], feed_dict=feed_dict))
def _operator_and_mat_and_feed_dict(self, shape, dtype, use_placeholder): shape = list(shape) assert shape[-1] == shape[-2] batch_shape = shape[:-2] num_rows = shape[-1] operator = linalg_lib.LinearOperatorIdentity(num_rows, batch_shape=batch_shape, dtype=dtype) mat = linalg_ops.eye(num_rows, batch_shape=batch_shape, dtype=dtype) # Nothing to feed since LinearOperatorIdentity takes no Tensor args. if use_placeholder: feed_dict = {} else: feed_dict = None return operator, mat, feed_dict
def test_broadcast_apply_static_shapes(self): # These cannot be done in the automated (base test class) tests since they # test shapes that tf.batch_matmul cannot handle. # In particular, tf.batch_matmul does not broadcast. with self.test_session() as sess: # Given this x and LinearOperatorIdentity shape of (2, 1, 3, 3), the # broadcast shape of operator and 'x' is (2, 2, 3, 4) x = random_ops.random_normal(shape=(1, 2, 3, 4)) operator = linalg_lib.LinearOperatorIdentity(num_rows=3, batch_shape=(2, 1), dtype=x.dtype) # Batch matrix of zeros with the broadcast shape of x and operator. zeros = array_ops.zeros(shape=(2, 2, 3, 4), dtype=x.dtype) # Expected result of apply and solve. expected = x + zeros operator_apply = operator.apply(x) self.assertAllEqual(operator_apply.get_shape(), expected.get_shape()) self.assertAllClose(*sess.run([operator_apply, expected]))
def make_diag_scale(loc, scale_diag, scale_identity_multiplier, validate_args, assert_positive, name=None): """Creates a LinOp from `scale_diag`, `scale_identity_multiplier` kwargs.""" def _convert_to_tensor(x, name): return None if x is None else ops.convert_to_tensor(x, name=name) def _maybe_attach_assertion(x): if not validate_args: return x if assert_positive: return control_flow_ops.with_dependencies([ check_ops.assert_positive( x, message="diagonal part must be positive"), ], x) # TODO(b/35157376): Use `assert_none_equal` once it exists. return control_flow_ops.with_dependencies([ check_ops.assert_greater(math_ops.abs(x), array_ops.zeros([], x.dtype), message="diagonal part must be non-zero"), ], x) with ops.name_scope(name, "make_diag_scale", values=[loc, scale_diag, scale_identity_multiplier]): loc = _convert_to_tensor(loc, name="loc") scale_diag = _convert_to_tensor(scale_diag, name="scale_diag") scale_identity_multiplier = _convert_to_tensor( scale_identity_multiplier, name="scale_identity_multiplier") if scale_diag is not None: if scale_identity_multiplier is not None: scale_diag += scale_identity_multiplier[..., array_ops.newaxis] return linalg.LinearOperatorDiag( diag=_maybe_attach_assertion(scale_diag), is_non_singular=True, is_self_adjoint=True, is_positive_definite=assert_positive) # TODO(b/35290280): Consider inferring shape from scale_perturb_factor. if loc is None: raise ValueError( "Cannot infer `event_shape` unless `loc` is specified.") num_rows = dimension_size(loc, -1) if scale_identity_multiplier is None: return linalg.LinearOperatorIdentity( num_rows=num_rows, dtype=loc.dtype.base_dtype, is_self_adjoint=True, is_positive_definite=True, assert_proper_shapes=validate_args) return linalg.LinearOperatorScaledIdentity( num_rows=num_rows, multiplier=_maybe_attach_assertion(scale_identity_multiplier), is_non_singular=True, is_self_adjoint=True, is_positive_definite=assert_positive, assert_proper_shapes=validate_args)
def test_negative_num_rows_raises_static(self): with self.assertRaisesRegexp(ValueError, "must be non-negative"): linalg_lib.LinearOperatorIdentity(num_rows=-2)
def test_non_integer_num_rows_raises_static(self): with self.assertRaisesRegexp(TypeError, "must be integer"): linalg_lib.LinearOperatorIdentity(num_rows=2.)
def test_non_scalar_num_rows_raises_static(self): with self.assertRaisesRegexp(ValueError, "must be a 0-D Tensor"): linalg_lib.LinearOperatorIdentity(num_rows=[2])
def test_wrong_matrix_dimensions_raises_static(self): operator = linalg_lib.LinearOperatorIdentity(num_rows=2) x = rng.randn(3, 3).astype(np.float32) with self.assertRaisesRegexp(ValueError, "Dimensions.*not compatible"): operator.apply(x)
def test_assert_self_adjoint(self): with self.test_session(): operator = linalg_lib.LinearOperatorIdentity(num_rows=2) operator.assert_self_adjoint().run() # Should not fail
def test_assert_positive_definite(self): with self.test_session(): operator = linalg_lib.LinearOperatorIdentity(num_rows=2) operator.assert_positive_definite().run() # Should not fail
def make_diag_scale(loc=None, scale_diag=None, scale_identity_multiplier=None, shape_hint=None, validate_args=False, assert_positive=False, name=None): """Creates a LinOp representing a diagonal matrix. Args: loc: Floating-point `Tensor`. This is used for inferring shape in the case where only `scale_identity_multiplier` is set. scale_diag: Floating-point `Tensor` representing the diagonal matrix. `scale_diag` has shape [N1, N2, ... k], which represents a k x k diagonal matrix. When `None` no diagonal term is added to the LinOp. scale_identity_multiplier: floating point rank 0 `Tensor` representing a scaling done to the identity matrix. When `scale_identity_multiplier = scale_diag = scale_tril = None` then `scale += IdentityMatrix`. Otherwise no scaled-identity-matrix is added to `scale`. shape_hint: scalar integer `Tensor` representing a hint at the dimension of the identity matrix when only `scale_identity_multiplier` is set. validate_args: Python `bool` indicating whether arguments should be checked for correctness. assert_positive: Python `bool` indicating whether LinOp should be checked for being positive definite. name: Python `str` name given to ops managed by this object. Returns: `LinearOperator` representing a lower triangular matrix. Raises: ValueError: If only `scale_identity_multiplier` is set and `loc` and `shape_hint` are both None. """ def _maybe_attach_assertion(x): if not validate_args: return x if assert_positive: return control_flow_ops.with_dependencies([ check_ops.assert_positive( x, message="diagonal part must be positive"), ], x) return control_flow_ops.with_dependencies([ check_ops.assert_none_equal( x, array_ops.zeros([], x.dtype), message="diagonal part must be non-zero") ], x) with ops.name_scope(name, "make_diag_scale", values=[loc, scale_diag, scale_identity_multiplier]): loc = _convert_to_tensor(loc, name="loc") scale_diag = _convert_to_tensor(scale_diag, name="scale_diag") scale_identity_multiplier = _convert_to_tensor( scale_identity_multiplier, name="scale_identity_multiplier") if scale_diag is not None: if scale_identity_multiplier is not None: scale_diag += scale_identity_multiplier[..., array_ops.newaxis] return linalg.LinearOperatorDiag( diag=_maybe_attach_assertion(scale_diag), is_non_singular=True, is_self_adjoint=True, is_positive_definite=assert_positive) if loc is None and shape_hint is None: raise ValueError("Cannot infer `event_shape` unless `loc` or " "`shape_hint` is specified.") if shape_hint is None: shape_hint = loc.shape[-1] if scale_identity_multiplier is None: return linalg.LinearOperatorIdentity( num_rows=shape_hint, dtype=loc.dtype.base_dtype, is_self_adjoint=True, is_positive_definite=True, assert_proper_shapes=validate_args) return linalg.LinearOperatorScaledIdentity( num_rows=shape_hint, multiplier=_maybe_attach_assertion(scale_identity_multiplier), is_non_singular=True, is_self_adjoint=True, is_positive_definite=assert_positive, assert_proper_shapes=validate_args)
def __init__(self, loc=None, scale_tril=None, validate_args=False, allow_nan_stats=True, name="MultivariateNormalTriL"): """Construct Multivariate Normal distribution on `R^k`. The `batch_shape` is the broadcast shape between `loc` and `scale` arguments. The `event_shape` is given by last dimension of the matrix implied by `scale`. The last dimension of `loc` (if provided) must broadcast with this. Recall that `covariance = scale @ scale.T`. A (non-batch) `scale` matrix is: ```none scale = scale_tril ``` where `scale_tril` is lower-triangular `k x k` matrix with non-zero diagonal, i.e., `tf.diag_part(scale_tril) != 0`. Additional leading dimensions (if any) will index batches. Args: loc: Floating-point `Tensor`. If this is set to `None`, `loc` is implicitly `0`. When specified, may have shape `[B1, ..., Bb, k]` where `b >= 0` and `k` is the event size. scale_tril: Floating-point, lower-triangular `Tensor` with non-zero diagonal elements. `scale_tril` has shape `[B1, ..., Bb, k, k]` where `b >= 0` and `k` is the event size. validate_args: Python `bool`, default `False`. When `True` distribution parameters are checked for validity despite possibly degrading runtime performance. When `False` invalid inputs may silently render incorrect outputs. allow_nan_stats: Python `bool`, default `True`. When `True`, statistics (e.g., mean, mode, variance) use the value "`NaN`" to indicate the result is undefined. When `False`, an exception is raised if one or more of the statistic's batch members are undefined. name: Python `str` name prefixed to Ops created by this class. Raises: ValueError: if neither `loc` nor `scale_tril` are specified. """ parameters = locals() def _convert_to_tensor(x, name): return None if x is None else ops.convert_to_tensor(x, name=name) if loc is None and scale_tril is None: raise ValueError( "Must specify one or both of `loc`, `scale_tril`.") with ops.name_scope(name): with ops.name_scope("init", values=[loc, scale_tril]): loc = _convert_to_tensor(loc, name="loc") scale_tril = _convert_to_tensor(scale_tril, name="scale_tril") if scale_tril is None: scale = linalg.LinearOperatorIdentity( num_rows=distribution_util.dimension_size(loc, -1), dtype=loc.dtype, is_self_adjoint=True, is_positive_definite=True, assert_proper_shapes=validate_args) else: # No need to validate that scale_tril is non-singular. # LinearOperatorLowerTriangular has an assert_non_singular # method that is called by the Bijector. scale = linalg.LinearOperatorLowerTriangular( scale_tril, is_non_singular=True, is_self_adjoint=False, is_positive_definite=False) super(MultivariateNormalTriL, self).__init__(loc=loc, scale=scale, validate_args=validate_args, allow_nan_stats=allow_nan_stats, name=name) self._parameters = parameters
def test_non_1d_batch_shape_raises_static(self): with self.assertRaisesRegexp(ValueError, "must be a 1-D"): linalg_lib.LinearOperatorIdentity(num_rows=2, batch_shape=2)