chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
+934
View File
@@ -0,0 +1,934 @@
# Tests of TensorFlow linalg kernels written using the Python API.
load("//tensorflow:tensorflow.default.bzl", "cuda_py_strict_test", "tf_py_strict_test")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:license"],
licenses = ["notice"],
)
cuda_py_strict_test(
name = "cholesky_op_test",
size = "medium",
srcs = ["cholesky_op_test.py"],
shard_count = 5,
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:stateless_random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "determinant_op_test",
size = "medium",
srcs = ["determinant_op_test.py"],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:linalg_ops_gen",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
tf_py_strict_test(
name = "eig_op_test",
size = "medium",
srcs = ["eig_op_test.py"],
data = ["//tensorflow/python/kernel_tests/linalg/testdata:self_adjoint_eig_op_test_files"],
shard_count = 20,
tags = [
"no_windows",
"nomsan", # TODO(b/213581489): MSAN false positives from LAPACK use inside NumPy.
],
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:linalg_ops_gen",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops:sort_ops",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
# b/127344411: xla_enable_strict_auto_jit = True,
)
cuda_py_strict_test(
name = "einsum_op_test",
size = "medium",
srcs = ["einsum_op_test.py"],
shard_count = 4,
xla_tags = [
"no_cuda_asan", # times out
],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:linalg_ops_gen",
"//tensorflow/python/ops:special_math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linalg_grad_test",
size = "medium",
srcs = ["linalg_grad_test.py"],
shard_count = 20,
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:gradients_impl",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops/linalg:linalg_impl",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linalg_ops_test",
size = "medium",
srcs = ["linalg_ops_test.py"],
shard_count = 8,
tags = ["no_windows_gpu"],
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:ops",
"//tensorflow/python/framework:tensor",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
"@absl_py//absl/testing:parameterized",
],
)
cuda_py_strict_test(
name = "linear_operator_addition_test",
size = "small",
srcs = ["linear_operator_addition_test.py"],
deps = [
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_addition",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_adjoint_test",
size = "medium",
srcs = ["linear_operator_adjoint_test.py"],
shard_count = 5,
tags = [
"optonly", # times out
],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_block_diag_test",
size = "medium",
srcs = ["linear_operator_block_diag_test.py"],
shard_count = 8,
tags = ["optonly"],
deps = [
"//tensorflow/python/eager:backprop",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator",
"//tensorflow/python/ops/linalg:linear_operator_block_diag",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/ops/linalg:linear_operator_util",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_block_lower_triangular_test",
size = "medium",
srcs = ["linear_operator_block_lower_triangular_test.py"],
shard_count = 8,
tags = [
"no_gpu", # Seg fault. http://b/365525243
"optonly",
],
deps = [
"//tensorflow/python/eager:backprop",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_block_lower_triangular",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/ops/linalg:linear_operator_util",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_composition_test",
size = "medium",
srcs = ["linear_operator_composition_test.py"],
shard_count = 5,
tags = [
"optonly", # times out
],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_circulant_test",
size = "medium",
srcs = ["linear_operator_circulant_test.py"],
shard_count = 50,
tags = [
"no_cuda11", # TODO(b/197522782): reenable test after fixing.
"optonly", # times out, b/79171797
],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:array_ops_stack",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_circulant",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/ops/signal:fft_ops",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
"@absl_py//absl/testing:parameterized",
],
)
cuda_py_strict_test(
name = "linear_operator_diag_test",
size = "medium",
srcs = ["linear_operator_diag_test.py"],
shard_count = 5,
tags = ["optonly"],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_full_matrix_test",
size = "medium",
srcs = ["linear_operator_full_matrix_test.py"],
shard_count = 5,
tags = ["optonly"],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_householder_test",
size = "medium",
srcs = ["linear_operator_householder_test.py"],
shard_count = 5,
tags = ["optonly"],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_householder",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_identity_test",
size = "medium",
srcs = ["linear_operator_identity_test.py"],
shard_count = 5,
tags = ["optonly"],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_inversion_test",
size = "medium",
srcs = ["linear_operator_inversion_test.py"],
shard_count = 5,
tags = [
"optonly", # times out
],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_kronecker_test",
size = "medium",
srcs = ["linear_operator_kronecker_test.py"],
shard_count = 10,
tags = [
"no_oss", # TODO(b/282984925)
"optonly",
],
xla_enable_strict_auto_jit = False,
deps = [
"//tensorflow/python/eager:backprop",
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator",
"//tensorflow/python/ops/linalg:linear_operator_kronecker",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/ops/linalg:linear_operator_util",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_low_rank_update_test",
size = "medium",
srcs = ["linear_operator_low_rank_update_test.py"],
shard_count = 15,
tags = ["optonly"],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_lower_triangular_test",
size = "medium",
srcs = ["linear_operator_lower_triangular_test.py"],
shard_count = 4,
tags = ["optonly"],
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
"@absl_py//absl/testing:parameterized",
],
)
cuda_py_strict_test(
name = "linear_operator_permutation_test",
size = "medium",
srcs = ["linear_operator_permutation_test.py"],
shard_count = 5,
tags = ["optonly"],
xla_enable_strict_auto_jit = True,
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_permutation",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_test",
size = "small",
srcs = ["linear_operator_test.py"],
deps = [
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/parallel_for:control_flow_ops",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_toeplitz_test",
size = "medium",
srcs = ["linear_operator_toeplitz_test.py"],
shard_count = 5,
tags = [
"no_cuda_on_cpu_tap", # flaky, b/135701551
"no_gpu", # flaky, b/135701551
"optonly", # times out, b/79171797
],
deps = [
"//tensorflow/python/eager:backprop",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/ops/linalg:linear_operator_toeplitz",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "linear_operator_tridiag_test",
size = "medium",
srcs = ["linear_operator_tridiag_test.py"],
shard_count = 10,
tags = [
"no_windows_gpu",
"optonly",
],
# TODO(b/313470344): XLA temporarily disabled due to empty shards on 3.12.
xla_enable_strict_auto_jit = False,
xla_enabled = False,
deps = [
"//tensorflow/python/framework:config",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:array_ops_gen",
"//tensorflow/python/ops:array_ops_stack",
"//tensorflow/python/ops:manip_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "linear_operator_util_test",
size = "medium",
srcs = ["linear_operator_util_test.py"],
shard_count = 5,
tags = ["optonly"],
deps = [
"//tensorflow/python/framework:ops",
"//tensorflow/python/framework:tensor",
"//tensorflow/python/framework:tensor_shape",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_util",
"//tensorflow/python/platform:client_testlib",
"@absl_py//absl/testing:parameterized",
],
)
cuda_py_strict_test(
name = "linear_operator_zeros_test",
size = "medium",
srcs = ["linear_operator_zeros_test.py"],
shard_count = 5,
tags = ["optonly"], # Test is flaky without optimization.
deps = [
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "lu_op_test",
size = "small",
srcs = ["lu_op_test.py"],
xla_tags = [
"no_cuda_asan", # times out
],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:array_ops_stack",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:map_fn",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:stateless_random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
tf_py_strict_test(
name = "matrix_exponential_op_test",
size = "medium",
srcs = ["matrix_exponential_op_test.py"],
shard_count = 16,
tags = ["no_windows_gpu"],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg:linalg_impl",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "matrix_inverse_op_test",
size = "small",
timeout = "moderate",
srcs = ["matrix_inverse_op_test.py"],
tags = ["optonly"],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
tf_py_strict_test(
name = "matrix_logarithm_op_test",
size = "medium",
srcs = ["matrix_logarithm_op_test.py"],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:linalg_ops_gen",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:stateless_random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg:linalg_impl",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "matrix_solve_ls_op_test",
size = "medium",
srcs = ["matrix_solve_ls_op_test.py"],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "matrix_solve_op_test",
size = "medium",
srcs = ["matrix_solve_op_test.py"],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:stateless_random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "matrix_square_root_op_test",
size = "medium",
srcs = ["matrix_square_root_op_test.py"],
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:linalg_ops_gen",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:stateless_random_ops",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "matrix_triangular_solve_op_test",
size = "medium",
srcs = ["matrix_triangular_solve_op_test.py"],
shard_count = 3,
deps = [
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "normalize_op_test",
size = "medium",
srcs = ["normalize_op_test.py"],
shard_count = 20,
tags = ["no_windows_gpu"],
# TODO(b/208263392): Re-enable. tf.Squeeze op after tf.Where op doesn't reshape.
xla_enable_strict_auto_jit = False,
deps = [
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:nn_impl",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "norm_op_test",
size = "medium",
srcs = ["norm_op_test.py"],
shard_count = 20,
tags = ["no_windows_gpu"],
# TODO(b/208263392): Re-enable. tf.Squeeze op after tf.Where op doesn't reshape.
xla_enable_strict_auto_jit = False,
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "qr_op_test",
size = "medium",
srcs = ["qr_op_test.py"],
shard_count = 20,
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/eager:backprop",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:stateless_random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "self_adjoint_eig_op_test",
size = "medium",
srcs = ["self_adjoint_eig_op_test.py"],
data = ["//tensorflow/python/kernel_tests/linalg/testdata:self_adjoint_eig_op_test_files"],
shard_count = 20,
tags = [
"no_windows",
],
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
# TODO(b/127344411): This test passes because XLA does not actually cluster
# the self_adjoint_eig op.
)
cuda_py_strict_test(
name = "slicing_test",
size = "small",
srcs = ["slicing_test.py"],
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg:linear_operator_test_util",
"//tensorflow/python/ops/linalg:slicing",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "svd_op_test",
size = "medium",
srcs = ["svd_op_test.py"],
shard_count = 30,
tags = [
"no_oss", # b/117185141.
],
xla_tags = [
"no_cuda_asan", # times out
],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:gradient_checker",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:gradients_impl",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:stateless_random_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
# TODO(b/127344411): This test passes because XLA does not actually cluster
# the svd op.
)
cuda_py_strict_test(
name = "tridiagonal_matmul_op_test",
size = "medium",
srcs = ["tridiagonal_matmul_op_test.py"],
shard_count = 5,
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:array_ops_stack",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:gradient_checker_v2",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg:linalg_impl",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
cuda_py_strict_test(
name = "tridiagonal_solve_op_test",
size = "medium",
srcs = ["tridiagonal_solve_op_test.py"],
shard_count = 12,
tags = [
"no_oss", # TODO(b/142818120): Re-enable.
],
deps = [
"//tensorflow/python/client:session",
"//tensorflow/python/eager:backprop",
"//tensorflow/python/eager:context",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:for_generated_wrappers",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:control_flow_ops",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:variables",
"//tensorflow/python/ops/linalg:linalg_impl",
"//tensorflow/python/platform:benchmark",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
],
)
@@ -0,0 +1,15 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Kernel tests for tf.linalg."""
@@ -0,0 +1,388 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.tf.Cholesky."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes as dtypes_lib
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import stateless_random_ops
from tensorflow.python.ops import variables
from tensorflow.python.ops.linalg import linalg
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
# Different gradient implementations for benchmark purposes
def _GradWithInverseL(l, l_inverse, grad):
middle = math_ops.matmul(l, grad, adjoint_a=True)
middle = array_ops.matrix_set_diag(middle,
0.5 * array_ops.matrix_diag_part(middle))
middle = array_ops.matrix_band_part(middle, -1, 0)
grad_a = math_ops.matmul(
math_ops.matmul(l_inverse, middle, adjoint_a=True), l_inverse)
grad_a += math_ops.conj(array_ops.matrix_transpose(grad_a))
return grad_a * 0.5
def TriAngSolveCompositeGrad(l, grad):
# Gradient is l^{-H} @ ((l^{H} @ grad) * (tril(ones)-1/2*eye)) @ l^{-1}
# Compute ((l^{H} @ grad) * (tril(ones)-1/2*eye)) = middle
middle = math_ops.matmul(l, grad, adjoint_a=True)
middle = array_ops.matrix_set_diag(middle,
0.5 * array_ops.matrix_diag_part(middle))
middle = array_ops.matrix_band_part(middle, -1, 0)
# Compute l^{-H} @ middle = z
l_inverse_middle = linalg_ops.matrix_triangular_solve(l, middle, adjoint=True)
# We need to compute z @ l^{-1}. With matrix_triangular_solve we
# actually compute l^{-H} @ z^{H} = grad. Since we later add grad^{H}
# we can ommit the conjugate transpose here.
z_h = math_ops.conj(array_ops.matrix_transpose(l_inverse_middle))
grad_a = linalg_ops.matrix_triangular_solve(l, z_h, adjoint=True)
grad_a += linalg.adjoint(grad_a)
return grad_a * 0.5
def MatrixInverseCompositeGrad(l, grad):
l_inverse = linalg_ops.matrix_inverse(l)
return _GradWithInverseL(l, l_inverse, grad)
def TriAngInvCompositeGrad(l, grad):
num_rows = array_ops.shape(l)[-1]
batch_shape = array_ops.shape(l)[:-2]
l_inverse = linalg_ops.matrix_triangular_solve(l,
linalg_ops.eye(
num_rows,
batch_shape=batch_shape,
dtype=l.dtype))
return _GradWithInverseL(l, l_inverse, grad)
class CholeskyOpTest(test.TestCase):
def _verifyCholeskyBase(self, x, chol, verification):
chol_np, verification_np = self.evaluate([chol, verification])
self.assertAllClose(x, verification_np)
self.assertShapeEqual(x, chol)
# Check that the cholesky is lower triangular, and has positive diagonal
# elements.
if chol_np.shape[-1] > 0:
chol_reshaped = np.reshape(chol_np, (-1, chol_np.shape[-2],
chol_np.shape[-1]))
for chol_matrix in chol_reshaped:
self.assertAllClose(chol_matrix, np.tril(chol_matrix))
self.assertTrue((np.diag(chol_matrix) > 0.0).all())
def _verifyCholesky(self, x):
# Verify that LL^T == x.
chol = linalg_ops.cholesky(x)
verification = test_util.matmul_without_tf32(chol, chol, adjoint_b=True)
self._verifyCholeskyBase(x, chol, verification)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testBasic(self):
data = np.array([[4., -1., 2.], [-1., 6., 0], [2., 0., 5.]])
for dtype in (np.float32, np.float64):
with self.subTest(dtype=dtype):
self._verifyCholesky(data.astype(dtype))
for dtype in (np.complex64, np.complex128):
with self.subTest(dtype=dtype):
complex_data = np.tril(1j * data, -1).astype(dtype)
complex_data += np.triu(-1j * data, 1).astype(dtype)
complex_data += data
self._verifyCholesky(complex_data)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testBatch(self):
simple_array = np.array([[[1., 0.], [0., 5.]]]) # shape (1, 2, 2)
self._verifyCholesky(simple_array)
self._verifyCholesky(np.vstack((simple_array, simple_array)))
odd_sized_array = np.array([[[4., -1., 2.], [-1., 6., 0], [2., 0., 5.]]])
self._verifyCholesky(np.vstack((odd_sized_array, odd_sized_array)))
# Generate random positive-definite matrices.
matrices = np.random.rand(10, 5, 5)
for i in range(10):
with self.subTest(i=i):
matrices[i] = np.dot(matrices[i].T, matrices[i])
self._verifyCholesky(matrices)
# Generate random complex valued positive-definite matrices.
matrices = np.random.rand(10, 5, 5) + 1j * np.random.rand(10, 5, 5)
for i in range(10):
with self.subTest(i=i):
matrices[i] = np.dot(matrices[i].T.conj(), matrices[i])
self._verifyCholesky(matrices)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testNonSquareMatrix(self):
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
linalg_ops.cholesky(np.array([[1., 2., 3.], [3., 4., 5.]]))
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
linalg_ops.cholesky(
np.array([[[1., 2., 3.], [3., 4., 5.]], [[1., 2., 3.], [3., 4., 5.]]
]))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testWrongDimensions(self):
tensor3 = constant_op.constant([1., 2.])
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
linalg_ops.cholesky(tensor3)
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
linalg_ops.cholesky(tensor3)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testNotInvertibleCpu(self):
# Non-invertible inputs result in lower-triangular NaNs.
x = constant_op.constant([[1., -1., 0.], [-1., 1., -1.], [0., -1., 1.]])
chol = linalg_ops.cholesky(x)
# Extract the lower-triangular elements.
lower_mask = array_ops.matrix_band_part(
constant_op.constant(True, shape=x.shape), -1, 0)
chol_lower = array_ops.boolean_mask(chol, lower_mask)
# Assert all NaN.
all_nan = self.evaluate(
math_ops.reduce_all(math_ops.reduce_all(math_ops.is_nan(chol_lower))))
self.assertTrue(all_nan)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testEmpty(self):
self._verifyCholesky(np.empty([0, 2, 2]))
self._verifyCholesky(np.empty([2, 0, 0]))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testConcurrentExecutesWithoutError(self):
seed = [42, 24]
matrix_shape = [5, 5]
matrix1 = stateless_random_ops.stateless_random_normal(matrix_shape, seed)
matrix2 = stateless_random_ops.stateless_random_normal(matrix_shape, seed)
matrix1 = math_ops.matmul(matrix1, matrix1, adjoint_a=True)
matrix2 = math_ops.matmul(matrix2, matrix2, adjoint_a=True)
c1 = linalg_ops.cholesky(matrix1)
c2 = linalg_ops.cholesky(matrix2)
c1_val, c2_val = self.evaluate([c1, c2])
self.assertAllClose(c1_val, c2_val)
class CholeskyGradTest(test.TestCase):
_backprop_block_size = 16
def getShapes(self, shapeList):
return ((elem, int(np.floor(1.2 * elem))) for elem in shapeList)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testSmallMatrices(self):
np.random.seed(0)
shapes = self.getShapes([1, 2, 10])
self.runFiniteDifferences(
shapes, dtypes=(dtypes_lib.float32, dtypes_lib.float64))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testSmallMatricesComplex(self):
np.random.seed(0)
shapes = self.getShapes([1, 2, 10])
self.runFiniteDifferences(
shapes, dtypes=(dtypes_lib.complex64, dtypes_lib.complex128))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testOneBlockMatrices(self):
np.random.seed(0)
shapes = self.getShapes([self._backprop_block_size + 1])
self.runFiniteDifferences(
shapes,
dtypes=(dtypes_lib.float32, dtypes_lib.float64),
scalar_test=True)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testTwoBlockMatrixFloat(self):
np.random.seed(0)
shapes = self.getShapes([2 * self._backprop_block_size + 1])
self.runFiniteDifferences(
shapes, dtypes=(dtypes_lib.float32,), scalar_test=True)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testTwoBlockMatrixDouble(self):
np.random.seed(0)
shapes = self.getShapes([2 * self._backprop_block_size + 1])
self.runFiniteDifferences(
shapes, dtypes=(dtypes_lib.float64,), scalar_test=True)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testTwoBlockMatrixComplexFloat(self):
np.random.seed(0)
shapes = self.getShapes([2 * self._backprop_block_size + 1])
self.runFiniteDifferences(
shapes, dtypes=(dtypes_lib.complex64,), scalar_test=True)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testTwoBlockMatrixComplexDouble(self):
np.random.seed(0)
shapes = self.getShapes([2 * self._backprop_block_size + 1])
self.runFiniteDifferences(
shapes, dtypes=(dtypes_lib.complex128,), scalar_test=True)
def _runOneTest(self, shape, dtype, batch, scalar_test):
if dtype == dtypes_lib.float64:
tol = 1e-5
elif dtype == dtypes_lib.complex128:
tol = 5e-5
else:
tol = 5e-3
epsilon = np.finfo(dtype.as_numpy_dtype).eps
delta = epsilon**(1.0 / 3.0)
def RandomInput():
a = np.random.randn(shape[0], shape[1]).astype(dtype.as_numpy_dtype)
if dtype.is_complex:
a += 1j * np.random.randn(shape[0], shape[1]).astype(
dtype.as_numpy_dtype)
return a
def Compute(x):
# Turn the random matrix x into a Hermitian matrix by
# computing the quadratic form x * x^H.
a = test_util.matmul_without_tf32(
x, math_ops.conj(array_ops.matrix_transpose(x))) / shape[0]
if batch:
a = array_ops.tile(array_ops.expand_dims(a, 0), [2, 1, 1])
# Finally take the cholesky decomposition of the Hermitian matrix.
c = linalg_ops.cholesky(a)
if scalar_test:
# Reduce to a single scalar output to speed up test.
c = math_ops.reduce_mean(c)
return c
theoretical, numerical = gradient_checker_v2.compute_gradient(
Compute, [RandomInput()], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
def runFiniteDifferences(self,
shapes,
dtypes=(dtypes_lib.float32, dtypes_lib.float64,
dtypes_lib.complex64, dtypes_lib.complex128),
scalar_test=False):
for shape_ in shapes:
for dtype_ in dtypes:
for batch_ in False, True:
self._runOneTest(shape_, dtype_, batch_, scalar_test)
class CholeskyBenchmark(test.Benchmark):
shapes = [
(4, 4),
(10, 10),
(16, 16),
(101, 101),
(256, 256),
(1000, 1000),
(1024, 1024),
(2048, 2048),
(513, 2, 2),
(513, 8, 8),
(513, 256, 256),
(4, 513, 2, 2),
]
def _GenerateMatrix(self, shape):
batch_shape = shape[:-2]
shape = shape[-2:]
assert shape[0] == shape[1]
n = shape[0]
matrix = np.ones(shape).astype(np.float32) / (
2.0 * n) + np.diag(np.ones(n).astype(np.float32))
return np.tile(matrix, batch_shape + (1, 1))
def benchmarkCholeskyOp(self):
for shape in self.shapes:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix = variables.Variable(self._GenerateMatrix(shape))
l = linalg_ops.cholesky(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(
l,),
min_iters=25,
name="cholesky_cpu_{shape}".format(shape=shape))
if test.is_gpu_available(True):
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/device:GPU:0"):
matrix = variables.Variable(self._GenerateMatrix(shape))
l = linalg_ops.cholesky(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(
l,),
min_iters=25,
name="cholesky_gpu_{shape}".format(shape=shape))
def benchmarkGradVariants(self):
def _BenchmarkGrad(grad_fn, name, device):
for shape in self.shapes:
matrix = self._GenerateMatrix(shape)
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device(device):
l = variables.Variable(np.linalg.cholesky(matrix))
grad_matrix = variables.Variable(
np.random.randn(*matrix.shape).astype(np.float32))
grad = grad_fn(l, grad_matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(
grad,),
min_iters=25,
name="{name}_{dev}_{shape}".format(
name=name, dev=grad.device, shape=shape))
if test.is_gpu_available(True):
_BenchmarkGrad(MatrixInverseCompositeGrad, "composite_matrix_inverse",
"/device:GPU:0")
_BenchmarkGrad(TriAngInvCompositeGrad, "composite_tri_ang_inverse",
"/device:GPU:0")
_BenchmarkGrad(TriAngSolveCompositeGrad, "composite_triangular_solve",
"/device:GPU:0")
_BenchmarkGrad(MatrixInverseCompositeGrad, "composite_matrix_inverse",
"/cpu:0")
_BenchmarkGrad(TriAngInvCompositeGrad, "composite_tri_ang_inverse",
"/cpu:0")
_BenchmarkGrad(TriAngSolveCompositeGrad, "composite_triangular_solve",
"/cpu:0")
if __name__ == "__main__":
test.main()
@@ -0,0 +1,263 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.tf.MatrixDeterminant."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import gen_linalg_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
class DeterminantOpTest(test.TestCase):
def _compareDeterminantBase(self, matrix_x, tf_ans, expected=None):
out = self.evaluate(tf_ans)
shape = matrix_x.shape
if shape[-1] == 0 and shape[-2] == 0:
np_ans = np.ones(shape[:-2]).astype(matrix_x.dtype)
elif expected is None:
np_ans = np.array(np.linalg.det(matrix_x)).astype(matrix_x.dtype)
else:
np_ans = expected
self.assertShapeEqual(np_ans, tf_ans)
self.assertAllClose(np_ans, out, atol=5e-5)
def _compareLogDeterminantBase(self, matrix_x, tf_ans, expected=None):
sign_tf, abs_log_det_tf = tf_ans
shape = matrix_x.shape
if shape[-1] == 0 or shape[-2] == 0:
np_sign, np_ans = (1.0, np.zeros(shape[:-2]).astype(matrix_x.dtype))
elif expected is None:
np_sign, np_ans = np.linalg.slogdet(matrix_x)
np_ans = np_ans.astype(matrix_x.dtype)
else:
np_ans = np.log(np.abs(expected))
np_sign = np.sign(expected)
self.assertShapeEqual(np_ans, abs_log_det_tf)
sign_tf_val = self.evaluate(sign_tf)
abs_log_det_tf_val = self.evaluate(abs_log_det_tf)
self.assertAllClose(
sign_tf_val * np.exp(abs_log_det_tf_val),
np_sign * np.exp(np_ans),
atol=5e-5)
def _compareDeterminant(self, matrix_x, expected=None):
with test_util.use_gpu():
self._compareDeterminantBase(
matrix_x, linalg_ops.matrix_determinant(matrix_x), expected
)
self._compareLogDeterminantBase(
matrix_x, gen_linalg_ops.log_matrix_determinant(matrix_x), expected
)
def testBasic(self):
# 2x2 matrices
self._compareDeterminant(np.array([[2., 3.], [3., 4.]]).astype(np.float32))
self._compareDeterminant(np.array([[0., 0.], [0., 0.]]).astype(np.float32))
# 5x5 matrices (Eigen forces LU decomposition)
self._compareDeterminant(
np.array([[2., 3., 4., 5., 6.], [3., 4., 9., 2., 0.], [
2., 5., 8., 3., 8.
], [1., 6., 7., 4., 7.], [2., 3., 4., 5., 6.]]).astype(np.float32))
# A multidimensional batch of 2x2 matrices
self._compareDeterminant(np.random.rand(3, 4, 5, 2, 2).astype(np.float32))
def testBasicDouble(self):
# 2x2 matrices
self._compareDeterminant(np.array([[2., 3.], [3., 4.]]).astype(np.float64))
self._compareDeterminant(np.array([[0., 0.], [0., 0.]]).astype(np.float64))
# 5x5 matrices (Eigen forces LU decomposition)
self._compareDeterminant(
np.array([[2., 3., 4., 5., 6.], [3., 4., 9., 2., 0.], [
2., 5., 8., 3., 8.
], [1., 6., 7., 4., 7.], [2., 3., 4., 5., 6.]]).astype(np.float64))
# A multidimensional batch of 2x2 matrices
self._compareDeterminant(np.random.rand(3, 4, 5, 2, 2).astype(np.float64))
def testBasicComplex64(self):
# 2x2 matrices
self._compareDeterminant(
np.array([[2., 3.], [3., 4.]]).astype(np.complex64))
self._compareDeterminant(
np.array([[0., 0.], [0., 0.]]).astype(np.complex64))
self._compareDeterminant(
np.array([[1. + 1.j, 1. - 1.j], [-1. + 1.j, -1. - 1.j]]).astype(
np.complex64))
# 5x5 matrices (Eigen forces LU decomposition)
self._compareDeterminant(
np.array([[2., 3., 4., 5., 6.], [3., 4., 9., 2., 0.], [
2., 5., 8., 3., 8.
], [1., 6., 7., 4., 7.], [2., 3., 4., 5., 6.]]).astype(np.complex64))
# A multidimensional batch of 2x2 matrices
self._compareDeterminant(np.random.rand(3, 4, 5, 2, 2).astype(np.complex64))
def testBasicComplex128(self):
# 2x2 matrices
self._compareDeterminant(
np.array([[2., 3.], [3., 4.]]).astype(np.complex128))
self._compareDeterminant(
np.array([[0., 0.], [0., 0.]]).astype(np.complex128))
self._compareDeterminant(
np.array([[1. + 1.j, 1. - 1.j], [-1. + 1.j, -1. - 1.j]]).astype(
np.complex128))
# 5x5 matrices (Eigen forces LU decomposition)
self._compareDeterminant(
np.array([[2., 3., 4., 5., 6.], [3., 4., 9., 2., 0.], [
2., 5., 8., 3., 8.
], [1., 6., 7., 4., 7.], [2., 3., 4., 5., 6.]]).astype(np.complex128))
# A multidimensional batch of 2x2 matrices
self._compareDeterminant(
np.random.rand(3, 4, 5, 2, 2).astype(np.complex128))
def testInfiniteDeterminant(self):
max_double = np.finfo("d").max
huge_matrix = np.array([[max_double, 0.0], [0.0, max_double]])
self._compareDeterminant(huge_matrix)
@test_util.run_v1_only("b/120545219")
def testNonSquareMatrix(self):
# When the determinant of a non-square matrix is attempted we should return
# an error
with self.assertRaises(ValueError):
linalg_ops.matrix_determinant(
np.array([[1., 2., 3.], [3., 5., 4.]]).astype(np.float32))
@test_util.run_v1_only("b/120545219")
def testWrongDimensions(self):
# The input to the determinant should be a 2-dimensional tensor.
tensor1 = constant_op.constant([1., 2.])
with self.assertRaises(ValueError):
linalg_ops.matrix_determinant(tensor1)
def testEmpty(self):
self._compareDeterminant(np.empty([0, 2, 2]))
self._compareDeterminant(np.empty([2, 0, 0]))
@test_util.run_v1_only("b/120545219")
def testConcurrentExecutesWithoutError(self):
with self.session():
matrix1 = random_ops.random_normal([5, 5], seed=42)
matrix2 = random_ops.random_normal([5, 5], seed=42)
det1 = linalg_ops.matrix_determinant(matrix1)
det2 = linalg_ops.matrix_determinant(matrix2)
det1_val, det2_val = self.evaluate([det1, det2])
self.assertEqual(det1_val, det2_val)
def testInfAndNans(self):
# 2x2 matrices
np_inf = np.float64(np.inf)
np_nan = np.float64(np.nan)
self._compareDeterminant(
np.array([[np.inf, 1], [1, 1]]).astype(np.float32), expected=np_inf
)
self._compareDeterminant(
np.array([[np.inf, np.inf], [1, 1]]).astype(np.float32), expected=np_nan
)
self._compareDeterminant(
np.array([[np.inf, -np.inf], [1, 1]]).astype(np.float32),
expected=np_nan,
)
self._compareDeterminant(
np.array([[np.nan, 1], [1, 1]]).astype(np.float32), expected=np_nan
)
# 5x5 matrices (Eigen forces LU decomposition)
self._compareDeterminant(
np.array([
[np.inf, 3.0, 4.0, 5.0, 6.0],
[3.0, 4.0, 9.0, 2.0, 0.0],
[2.0, 5.0, 8.0, 3.0, 8.0],
[1.0, 6.0, 7.0, 4.0, 7.0],
[2.0, 3.0, 4.0, 5.0, 6.0],
]).astype(np.float32),
expected=-np_inf,
)
self._compareDeterminant(
np.array([
[np.nan, 3.0, 4.0, 5.0, 6.0],
[3.0, 4.0, 9.0, 2.0, 0.0],
[2.0, 5.0, 8.0, 3.0, 8.0],
[1.0, 6.0, 7.0, 4.0, 7.0],
[2.0, 3.0, 4.0, 5.0, 6.0],
]).astype(np.float32),
expected=np_nan,
)
class MatrixDeterminantBenchmark(test.Benchmark):
shapes = [
(4, 4),
(10, 10),
(16, 16),
(101, 101),
(256, 256),
(1000, 1000),
(1024, 1024),
(2048, 2048),
(513, 4, 4),
(513, 16, 16),
(513, 256, 256),
]
def _GenerateMatrix(self, shape):
batch_shape = shape[:-2]
shape = shape[-2:]
assert shape[0] == shape[1]
n = shape[0]
matrix = np.ones(shape).astype(np.float32) / (
2.0 * n) + np.diag(np.ones(n).astype(np.float32))
return variables.Variable(np.tile(matrix, batch_shape + (1, 1)))
def benchmarkMatrixDeterminantOp(self):
for shape in self.shapes:
with ops.Graph().as_default(), session.Session(
config=benchmark.benchmark_config()) as sess, ops.device("/cpu:0"):
matrix = self._GenerateMatrix(shape)
d = linalg_ops.matrix_determinant(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(
d,),
min_iters=25,
name="matrix_determinant_cpu_{shape}".format(shape=shape))
if test.is_gpu_available(True):
with ops.Graph().as_default(), session.Session(
config=benchmark.benchmark_config()) as sess, ops.device("/gpu:0"):
matrix = self._GenerateMatrix(shape)
d = linalg_ops.matrix_determinant(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(
d,),
min_iters=25,
name="matrix_determinant_gpu_{shape}".format(shape=shape))
if __name__ == "__main__":
test.main()
@@ -0,0 +1,282 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.linalg_ops.eig."""
import numpy as np
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes as dtypes_lib
from tensorflow.python.framework import errors
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gen_linalg_ops
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import sort_ops
from tensorflow.python.platform import test
def _AddTest(test_class, op_name, testcase_name, fn):
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test_class, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test_class, test_name, fn)
class EigTest(test.TestCase):
@test_util.run_deprecated_v1
def testWrongDimensions(self):
# The input to self_adjoint_eig should be a tensor of
# at least rank 2.
scalar = constant_op.constant(1.)
with self.assertRaises(ValueError):
linalg_ops.eig(scalar)
vector = constant_op.constant([1., 2.])
with self.assertRaises(ValueError):
linalg_ops.eig(vector)
@test_util.run_deprecated_v1
def testConcurrentExecutesWithoutError(self):
all_ops = []
with self.session():
for compute_v_ in True, False:
matrix1 = random_ops.random_normal([5, 5], seed=42)
matrix2 = random_ops.random_normal([5, 5], seed=42)
if compute_v_:
e1, v1 = linalg_ops.eig(matrix1)
e2, v2 = linalg_ops.eig(matrix2)
all_ops += [e1, v1, e2, v2]
else:
e1 = linalg_ops.eigvals(matrix1)
e2 = linalg_ops.eigvals(matrix2)
all_ops += [e1, e2]
val = self.evaluate(all_ops)
self.assertAllEqual(val[0], val[2])
# The algorithm is slightly different for compute_v being True and False,
# so require approximate equality only here.
self.assertAllClose(val[2], val[4])
self.assertAllEqual(val[4], val[5])
self.assertAllEqual(val[1], val[3])
def testMatrixThatFailsWhenFlushingDenormsToZero(self):
# Test a 32x32 matrix which is known to fail if denorm floats are flushed to
# zero.
matrix = np.genfromtxt(
test.test_src_dir_path(
"python/kernel_tests/linalg/testdata/"
"self_adjoint_eig_fail_if_denorms_flushed.txt")).astype(np.float32)
self.assertEqual(matrix.shape, (32, 32))
matrix_tensor = constant_op.constant(matrix)
with self.session() as _:
(e, v) = self.evaluate(linalg_ops.self_adjoint_eig(matrix_tensor))
self.assertEqual(e.size, 32)
self.assertAllClose(
np.matmul(v, v.transpose()), np.eye(32, dtype=np.float32), atol=2e-3)
self.assertAllClose(matrix,
np.matmul(np.matmul(v, np.diag(e)), v.transpose()))
def testMismatchedDtypes(self):
tensor = constant_op.constant([[0, 1], [2, 3]], dtype=dtypes_lib.float32)
with self.assertRaisesRegex((ValueError, errors.InvalidArgumentError),
"Invalid output dtype"):
self.evaluate(
gen_linalg_ops.eig(
input=tensor,
Tout=dtypes_lib.complex128, # Expected dtype: complex64.
compute_v=True))
def SortEigenValues(e):
perm = np.argsort(e.real + e.imag, -1)
return np.take(e, perm, -1)
def SortEigenDecomposition(e, v):
if v.ndim < 2:
return e, v
perm = np.argsort(e.real + e.imag, -1)
return np.take(e, perm, -1), np.take(v, perm, -1)
def EquilibrateEigenVectorPhases(x, y):
"""Equilibrate the phase of the Eigenvectors in the columns of `x` and `y`.
Eigenvectors are only unique up to an arbitrary phase. This function rotates x
such that it matches y. Precondition: The columns of x and y differ by a
multiplicative complex phase factor only.
Args:
x: `np.ndarray` with Eigenvectors
y: `np.ndarray` with Eigenvectors
Returns:
`np.ndarray` containing an equilibrated version of x.
"""
phases = np.sum(np.conj(x) * y, -2, keepdims=True)
phases /= np.abs(phases)
return phases * x
def _GetEigTest(dtype_, shape_, compute_v_):
def CompareEigenVectors(self, x, y, tol):
x = EquilibrateEigenVectorPhases(x, y)
self.assertAllClose(x, y, atol=tol)
def CompareEigenDecompositions(self, x_e, x_v, y_e, y_v, tol):
num_batches = int(np.prod(x_e.shape[:-1]))
n = x_e.shape[-1]
x_e = np.reshape(x_e, [num_batches] + [n])
x_v = np.reshape(x_v, [num_batches] + [n, n])
y_e = np.reshape(y_e, [num_batches] + [n])
y_v = np.reshape(y_v, [num_batches] + [n, n])
for i in range(num_batches):
x_ei, x_vi = SortEigenDecomposition(x_e[i, :], x_v[i, :, :])
y_ei, y_vi = SortEigenDecomposition(y_e[i, :], y_v[i, :, :])
self.assertAllClose(x_ei, y_ei, atol=tol, rtol=tol)
CompareEigenVectors(self, x_vi, y_vi, tol)
def Test(self):
np.random.seed(1)
n = shape_[-1]
batch_shape = shape_[:-2]
np_dtype = dtype_.as_numpy_dtype
def RandomInput():
# Most matrices are diagonalizable
a = np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
if dtype_.is_complex:
a += 1j * np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
a = np.tile(a, batch_shape + (1, 1))
return a
if dtype_ in (dtypes_lib.float32, dtypes_lib.complex64):
atol = 1e-4
else:
atol = 1e-12
a = RandomInput()
np_e, np_v = np.linalg.eig(a)
with self.session():
if compute_v_:
tf_e, tf_v = linalg_ops.eig(constant_op.constant(a))
# Check that V*diag(E)*V^(-1) is close to A.
a_ev = math_ops.matmul(
math_ops.matmul(tf_v, array_ops.matrix_diag(tf_e)),
linalg_ops.matrix_inverse(tf_v))
self.assertAllClose(self.evaluate(a_ev), a, atol=atol)
# Compare to numpy.linalg.eig.
CompareEigenDecompositions(self, np_e, np_v, self.evaluate(tf_e),
self.evaluate(tf_v), atol)
else:
tf_e = linalg_ops.eigvals(constant_op.constant(a))
self.assertAllClose(
SortEigenValues(np_e),
SortEigenValues(self.evaluate(tf_e)),
atol=atol)
return Test
class EigGradTest(test.TestCase):
pass # Filled in below
def _GetEigGradTest(dtype_, shape_, compute_v_):
def Test(self):
np.random.seed(1)
n = shape_[-1]
batch_shape = shape_[:-2]
np_dtype = dtype_.as_numpy_dtype
def RandomInput():
# Most matrices are diagonalizable
a = np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
if dtype_.is_complex:
a += 1j * np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
a = np.tile(a, batch_shape + (1, 1))
return a
# Optimal stepsize for central difference is O(epsilon^{1/3}).
epsilon = np.finfo(np_dtype).eps
delta = 0.1 * epsilon**(1.0 / 3.0)
# tolerance obtained by looking at actual differences using
# np.linalg.norm(theoretical-numerical, np.inf) on -mavx build
# after discarding one random input sample
_ = RandomInput()
if dtype_ in (dtypes_lib.float32, dtypes_lib.complex64):
tol = 1e-2
else:
tol = 1e-7
with self.session():
def Compute(x):
e, v = linalg_ops.eig(x)
# We sort eigenvalues by e.real+e.imag to have consistent
# order between runs
b_dims = len(e.shape) - 1
idx = sort_ops.argsort(math_ops.real(e) + math_ops.imag(e), axis=-1)
e = array_ops.gather(e, idx, batch_dims=b_dims)
v = array_ops.gather(v, idx, batch_dims=b_dims)
# (complex) Eigenvectors are only unique up to an arbitrary phase
# We normalize the vectors such that the first component has phase 0.
top_rows = v[..., 0:1, :]
angle = -math_ops.angle(top_rows)
phase = math_ops.complex(math_ops.cos(angle), math_ops.sin(angle))
v *= phase
return e, v
if compute_v_:
funcs = [lambda x: Compute(x)[0], lambda x: Compute(x)[1]]
else:
funcs = [linalg_ops.eigvals]
for f in funcs:
theoretical, numerical = gradient_checker_v2.compute_gradient(
f, [RandomInput()], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
return Test
if __name__ == "__main__":
dtypes_to_test = [
dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.complex64,
dtypes_lib.complex128
]
for compute_v in True, False:
for dtype in dtypes_to_test:
for size in 1, 2, 5, 10:
for batch_dims in [(), (3,)] + [(3, 2)] * (max(size, size) < 10):
shape = batch_dims + (size, size)
name = "%s_%s_%s" % (dtype.name, "_".join(map(str, shape)), compute_v)
_AddTest(EigTest, "Eig", name, _GetEigTest(dtype, shape, compute_v))
if dtype not in [dtypes_lib.float32, dtypes_lib.float64]:
_AddTest(EigGradTest, "EigGrad", name,
_GetEigGradTest(dtype, shape, compute_v))
test.main()
@@ -0,0 +1,448 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.Einsum."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gen_linalg_ops
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import special_math_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
@test_util.run_all_without_tensor_float_32(
'Tests einsum, which sometimes does a matmul with cuBLAS')
class EinsumOpTest(test.TestCase):
def _check(self, s, *input_shapes, **kwargs):
dtype = kwargs.pop('dtype', np.float32)
r = np.random.RandomState(0)
inputs = []
for shape in input_shapes:
with self.subTest(s=s, shape=shape):
arr = np.array(r.randn(*shape)).astype(dtype)
if dtype == np.complex64 or dtype == np.complex128:
arr += 1j * np.array(r.randn(*shape)).astype(dtype)
inputs.append(arr)
input_tensors = [constant_op.constant(x, shape=x.shape) for x in inputs]
a = np.einsum(s, *inputs)
b = self.evaluate(gen_linalg_ops.einsum(input_tensors, s))
self.assertAllClose(a, b, atol=1e-4, rtol=1e-4)
def testUnary(self):
self._check('->', ())
self._check('ab->', (3, 3))
self._check('ab->ab', (3, 3))
self._check('abc->b', (3, 4, 5))
self._check('abc->ca', (3, 4, 5))
self._check('abc->cab', (3, 4, 5))
def testUnaryWithRepeatedLabels(self):
self._check('aa->', (3, 3))
self._check('aa->a', (3, 3))
self._check('aaa->', (3, 3, 3))
self._check('aaa->a', (3, 3, 3))
self._check('aab->a', (3, 3, 4))
self._check('aabcc->a', (3, 3, 5, 4, 4))
self._check('aabcc->ac', (3, 3, 5, 4, 4))
self._check('aabcd->ad', (3, 3, 5, 4, 4))
def testUnaryEllipsis(self):
# Unary cases with ellipsis.
# Edge cases.
self._check('...->...', ())
self._check('...->', ())
self._check('->...', ())
# Tests from dask
self._check('a...a->a...', (2, 2))
self._check('a...a->', (2, 2))
self._check('a...a->...', (2, 5, 1, 2))
self._check('a...a->a...', (2, 1, 2))
self._check('a...a->a...', (2, 3, 4, 5, 2))
# Regular cases.
self._check('...ijk->...ki', (3, 4, 5))
self._check('...ijk->...ki', (1, 3, 4, 5))
self._check('...ijk->...ki', (2, 2, 3, 4, 5))
# Repeated indices.
self._check('i...ii->...i', (3, 2, 3, 3))
def testBinarySimple(self):
# Binary cases in XLA mode must have either (a) each index appearing exactly
# once in both the inputs (batch or contraction index), or (b) appearing
# exactly once in an input and in the output (free index).
self._check(',->', (), ())
self._check('a,a->', (3,), (3,))
self._check('a,a->a', (3,), (3,))
self._check('ab,b->a', (3, 4), (4,))
self._check('ab,ab->', (3, 4), (3, 4))
self._check('ab,bc->ac', (3, 4), (4, 5))
self._check('nij,jk->nik', (5, 2, 3), (3, 4))
self._check('abc,bad->abcd', (1, 2, 3), (2, 1, 4))
# Based on https://github.com/google/jax/issues/37#issuecomment-448572187
self._check('sa,shb->shab', (2, 1), (2, 3, 4))
def testReducedIndices(self):
self._check('ba,b->', (3, 2), (3,))
self._check('ab,ab->', (3, 4), (3, 4))
self._check('abce,badf->abcd', (1, 2, 3, 4), (2, 1, 4, 3))
def testRepeatedIndices(self):
# Repeated indices.
self._check('ijj,k->ik', (2, 3, 3), (4,))
self._check('aba,a->b', (3, 4, 3), (3,))
# From https://github.com/dask/dask/pull/3412#discussion_r182413444
self._check('aab,bc->ac', (2, 2, 3), (3, 4))
self._check('aab,bcc->ac', (2, 2, 3), (3, 4, 4))
def testEllipsis(self):
# Batch matmul with ellipsis but without broadcasting.
self._check('...mk,...kn->...mn', (5, 1, 2, 3), (5, 1, 3, 4))
# Empty batch dimensions.
self._check('...mk,...kn->...mn', (2, 3), (3, 4))
# Tensor contraction with transpose.
self._check('...ija,aijb...->ba...ij', (1, 2, 2, 3, 1), (1, 2, 3, 4, 1, 2))
# Output subscripts may omit ellipsis when batch shape is empty.
self._check('...mk,...kn->mn', (2, 3), (3, 4))
self._check('...mk,kn->mn', (2, 3), (3, 4))
self._check('mk,...kn->mn', (2, 3), (3, 4))
def testBroadcasting(self):
# Batch matmul with broadcasting.
self._check('...ij,...jk->...ik', (1, 2, 3), (3, 5))
self._check('...ij,...jk->...ik', (2, 3), (1, 3, 5))
self._check('...ij,...jk->...ik', (5, 2, 3), (3, 5))
self._check('...ij,...jk->...ik', (2, 3), (5, 3, 5))
self._check('...ij,...jk->...ik', (3, 1, 2, 3), (1, 1, 7, 3, 5))
self._check('i...j,j...k->...ik', (2, 1, 3, 1, 3), (3, 1, 7, 5))
# Following 2 from https://stackoverflow.com/a/19203475/1611416
self._check('...abc,...abcd->...d', (1, 1, 2, 3, 4), (5, 2, 3, 4, 6))
self._check('ab...,b->ab...', (2, 3, 1, 1, 5), (3,))
self._check('i...j,j...k->i...k', (3, 1, 2, 2), (2, 2, 3, 1, 4))
def testBroadcastingWithRepeatedIndices(self):
# Broadcasting with repeated indices.
self._check('ij,jk...k->i...', (3, 2), (2, 4, 1, 4))
self._check('ij,jk...k->...i', (3, 2), (2, 4, 5, 4))
self._check('ijj,jk...k->i...', (3, 2, 2), (2, 4, 1, 4))
self._check('i...jj,jk...k->i...', (3, 3, 1, 2, 2), (2, 4, 1, 5, 4))
def testDtypes(self):
bfloat16 = dtypes.bfloat16.as_numpy_dtype
def check(dtype):
r = np.random.RandomState(0)
equation = 'ij,jk->ik'
input_shapes = [(2, 2), (2, 2)]
inputs = []
for shape in input_shapes:
with self.subTest(dtype=dtype, shape=shape):
arr = np.array(r.randn(*shape)).astype(dtype)
if dtype == np.complex64 or dtype == np.complex128:
arr += 1j * np.array(r.randn(*shape)).astype(dtype)
inputs.append(arr)
input_tensors = [constant_op.constant(x) for x in inputs]
if dtype == bfloat16:
# np.einsum doesn't support bfloat16.
a = np.einsum(equation,
*[x.astype(np.float32) for x in inputs]).astype(dtype)
else:
a = np.einsum(equation, *inputs)
b = self.evaluate(gen_linalg_ops.einsum(input_tensors, equation))
tol = 1e-2 if dtype == bfloat16 else 1e-4
self.assertAllClose(a, b, atol=tol, rtol=tol)
for dtype in [
bfloat16, np.float32, np.float64, np.complex64, np.complex128, np.int32,
np.int64
]:
check(dtype)
@test_util.disable_xla('b/131919749')
@test_util.run_in_graph_and_eager_modes
def testInvalid(self):
r = np.random.RandomState(0)
cases = [
# incorrect rank.
('ij,jk->ik', r.randn(1, 2, 3), r.randn(3, 4)),
('...ij,jk->ik', r.randn(3), r.randn(3, 4)),
# inconsistent dimensions.
('ij,jk->ik', r.randn(2, 3), r.randn(4, 4)),
# broadcasting is invalid
('...ij,...jk->...ik', r.randn(5, 2, 3), r.randn(7, 3, 4)),
# output should have ellipsis when broadcasting shape is
# non-empty.
('...ij,...jk->ik', r.randn(2, 2, 3), r.randn(3, 4)),
]
for args in cases:
with self.subTest(args=args):
with self.assertRaises((ValueError, errors.InvalidArgumentError)):
_ = self.evaluate(gen_linalg_ops.einsum(args[1:], args[0]))
placeholders = [
array_ops.placeholder_with_default(x, shape=None) for x in args[1:]
]
with self.assertRaises((ValueError, errors.InvalidArgumentError)):
_ = self.evaluate(gen_linalg_ops.einsum(placeholders, args[0]))
@test_util.run_in_graph_and_eager_modes
def testPlaceholder(self):
def check(equation, *input_and_placeholder_shapes):
r = np.random.RandomState(0)
inputs = []
input_placeholders = []
for actual_shape, placeholder_shape in input_and_placeholder_shapes:
with self.subTest(equation=equation, actual_shape=actual_shape,
placeholder_shape=placeholder_shape):
input_np = np.array(r.randn(*actual_shape))
inputs.append(input_np)
input_placeholders.append(
array_ops.placeholder_with_default(input_np, placeholder_shape))
a = np.einsum(equation, *inputs)
b = self.evaluate(gen_linalg_ops.einsum(input_placeholders, equation))
self.assertAllClose(a, b, atol=1e-4, rtol=1e-4)
check('bijl,bjkm->bik', ((9, 2, 3, 5), (None, None, None, 5)),
((9, 3, 4, 7), (None, None, 4, None)))
check('bijl,bjkm->bik', ((9, 2, 3, 5), None), ((9, 3, 4, 7), None))
check('...ij,...->...i', ((4, 3, 1, 2), (None, 3, None, 2)),
((4, 3), (None, 3)))
check('...ij,...jk->...ik', ((3, 1, 2, 3), None), ((1, 7, 3, 4), None))
def testOutputRepeatedLabels(self):
# This is the reverse operation of generalized traces, to be used for
# computing symbolic gradients of einsum. Note: this operation is not
# supported by np.einsum as it's only required for gradients.
r = np.random.RandomState(0)
a = r.randn(2, 2)
s = 'a->aa'
diag_a = np.diag(np.diag(a))
b = self.evaluate(gen_linalg_ops.einsum([np.diag(a)], s))
self.assertAllClose(diag_a, b, atol=1e-4, rtol=1e-4)
def testEmpty(self):
def check(equation, input_shapes, output_shape):
# All these cases result in an output filled with zeros, so we don't call
# np.einsum. Also np.einsum doesn't support generalized diagonals which
# are needed for EinsumOp gradients.
r = np.random.RandomState(0)
inputs = [np.array(r.randn(*shape)) for shape in input_shapes]
output = self.evaluate(gen_linalg_ops.einsum(inputs, equation))
self.assertAllClose(output, np.zeros(output_shape), atol=1e-4, rtol=1e-4)
# Contractions along zero-sized dimensions.
check('ab,bc->ac', [(0, 10), (10, 10)], (0, 10))
# From transformer xl.
check('ibnd,ijbn->jnd', [(1, 0, 5, 10), (1, 1, 0, 5)], (1, 5, 10))
def testEmptyWithRepeatedLabels(self):
def check(equation, input_shapes, output_shape):
# All these cases result in an output filled with zeros, so we don't call
# np.einsum. Also np.einsum doesn't support generalized diagonals which
# are needed for EinsumOp gradients.
r = np.random.RandomState(0)
inputs = [np.array(r.randn(*shape)) for shape in input_shapes]
output = self.evaluate(gen_linalg_ops.einsum(inputs, equation))
self.assertAllClose(output, np.zeros(output_shape), atol=1e-4, rtol=1e-4)
# Generalized traces with zero-sized dimensions.
check('aab,bc->ac', [(0, 0, 10), (10, 10)], (0, 10))
check('aaab,bc->c', [(0, 0, 0, 3), (3, 4)], (4,))
# Generalized diagonals along with contraction.
check('ab,bc->aaca', [(0, 10), (10, 5)], (0, 0, 5, 0))
check('ab,bc->aaa', [(0, 10), (10, 5)], (0, 0, 0))
check('ab,bc->cc', [(0, 10), (10, 5)], (5, 5))
check('ab,ab->aaa', [(0, 5), (0, 5)], (0, 0, 0))
@test_util.run_all_in_graph_and_eager_modes
@test_util.run_all_without_tensor_float_32(
"Tests einsum's gradient, which sometimes does a matmul with cuBLAS")
class EinsumGradTest(test.TestCase):
def _check_gradient(self, s, *input_shapes):
with self.cached_session():
r = np.random.RandomState(seed=0)
for dtype in (np.float32, np.float64, np.complex64, np.complex128):
with self.subTest(s=s, dtype=dtype):
tol = 10 * np.sqrt(np.finfo(dtype).resolution)
if dtype in (np.complex64, np.complex128):
inputs = [
np.array(r.randn(*shape), dtype) +
1j * np.array(r.randn(*shape), dtype) for shape in input_shapes
]
else:
inputs = [
np.array(r.randn(*shape), dtype) for shape in input_shapes]
input_tensors = [
constant_op.constant(x, shape=x.shape) for x in inputs]
analytical, numerical = gradient_checker_v2.compute_gradient(
lambda *xs: gen_linalg_ops.einsum(xs, s), input_tensors)
self.assertLess(
gradient_checker_v2.max_error(analytical, numerical), tol)
def testUnary(self):
# Unary cases.
self._check_gradient('->', ())
self._check_gradient('aaa->a', (3, 3, 3))
self._check_gradient('aabcd->ad', (3, 3, 5, 4, 4))
self._check_gradient('aabcd->add', (3, 3, 5, 4, 4))
self._check_gradient('abcd->da', (3, 5, 4, 2))
def testUnaryEllipsis(self):
self._check_gradient('...->...', ())
self._check_gradient('...->', ())
self._check_gradient('->...', ())
# Tests from dask
self._check_gradient('a...a->a...', (2, 2))
self._check_gradient('a...a->', (2, 2))
self._check_gradient('a...a->...', (2, 5, 1, 2))
self._check_gradient('a...a->a...', (2, 1, 2))
self._check_gradient('a...a->a...', (2, 3, 4, 5, 2))
self._check_gradient('...ijk->...ki', (3, 4, 5))
self._check_gradient('...ijk->...ki', (1, 3, 4, 5))
self._check_gradient('...ijk->...ki', (2, 2, 3, 4, 5))
self._check_gradient('ab...cd->da...', (3, 5, 2, 3, 4, 2))
def testBinarySimple(self):
# Binary cases in XLA mode must have either (a) each index appearing exactly
# once in both the inputs (batch or contraction index), or (b) appearing
# exactly once in an input and in the output (free index).
self._check_gradient(',->', (), ())
self._check_gradient('a,a->', (3,), (3,))
self._check_gradient('a,a->a', (3,), (3,))
self._check_gradient('ab,b->a', (3, 4), (4,))
self._check_gradient('ab,ab->', (3, 4), (3, 4))
self._check_gradient('ab,bc->ac', (3, 4), (4, 5))
self._check_gradient('nij,jk->nik', (5, 2, 3), (3, 4))
self._check_gradient('abc,bad->abcd', (1, 2, 3), (2, 1, 4))
# Based on https://github.com/google/jax/issues/37#issuecomment-448572187
self._check_gradient('sa,shb->shab', (2, 1), (2, 3, 4))
def testEmpty(self):
# From Transformer XL.
self._check_gradient('ibnd,ijbn->jnd', (1, 0, 5, 10), (1, 1, 0, 5))
def testReducedIndices(self):
self._check_gradient('ba,b->', (3, 2), (3,))
self._check_gradient('ab,ab->', (3, 4), (3, 4))
self._check_gradient('ijkm,ijln->ijmn', (2, 3, 3, 4), (2, 3, 3, 2))
self._check_gradient('abce,badf->abcd', (1, 2, 3, 4), (2, 1, 4, 3))
def testReducedIndicesWithRepeatedLabels(self):
self._check_gradient('abce,badf->bcba', (1, 2, 3, 4), (2, 1, 4, 3))
def testRepeatedLabels(self):
# Repeated indices.
self._check_gradient('aba,a->b', (3, 4, 3), (3,))
self._check_gradient('ijj,k->ik', (2, 3, 3), (4,))
self._check_gradient('ill,k->ik', (2, 3, 3), (4,))
# From https://github.com/dask/dask/pull/3412#discussion_r182413444
self._check_gradient('aab,bc->ac', (1, 1, 3), (3, 4))
self._check_gradient('aab,bcc->ac', (2, 2, 3), (3, 4, 4))
def testEmptyWithRepeatedLabels(self):
self._check_gradient('aab,bc->ac', (0, 0, 10), (10, 10))
self._check_gradient('aab,bc->ac', (1, 1, 0), (0, 10))
self._check_gradient('aaab,bc->c', (0, 0, 0, 3), (3, 4))
def testBroadcasting(self):
self._check_gradient('...ij,...jk->...ik', (3, 2), (2, 4))
self._check_gradient('ij...,jk...->ik...', (3, 2, 1), (2, 4))
self._check_gradient('...ij,...jk->...ik', (3, 1, 3, 2), (1, 5, 2, 4))
self._check_gradient('i...j,j...k->i...k', (3, 1, 2, 2), (2, 2, 3, 1, 4))
def testBroadcastingWithRepeatedLabels(self):
self._check_gradient('ij,jk...k->i...', (3, 2), (2, 4, 1, 4))
self._check_gradient('aab,b...c->a...c', (1, 1, 3), (3, 1, 1, 4))
class EinsumBenchmark(test.Benchmark):
cases = [
# Unary cases.
['ijk->i', 100],
['ijk->kji', 100],
# Regular matmul or batch matmul.
['ij,jk->ik', 1000],
['ji,kj->ik', 1000],
['ab,ab->', 100],
['ab,ba->', 100],
['abc,abc->', 100],
['abc,bac->', 100],
['abc,cba->', 100],
['bij,bjk->bik', 100],
['bji,bjk->bki', 100],
['ikl,kji->kl', 100],
['klj,lki->ij', 100],
['ijk,ilj->kli', 100],
['kij,mkb->ijmb', 100],
['abcd,ad->bc', 40],
# Larger binary contractions.
['ijk,jklm->il', 40],
['efabc,eabcd->efd', 30],
['fabec,abcde->fde', 30],
['efabc,edabc->efd', 30],
['eadbf,dfebc->ecfad', 30],
['abcdef,bcdfg->abcdeg', 30],
]
def benchmarkEinsum(self):
for equation, dim in self.cases:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device('/cpu:0'):
r = np.random.RandomState(0)
input_subscripts = equation.split('->')[0].split(',')
input_vars = []
for subscript in input_subscripts:
input_shape = (dim,) * len(subscript)
input_vars.append(
variables.Variable(np.array(r.randn(*input_shape), np.float32)))
self.evaluate(variables.global_variables_initializer())
# Call einsum_v1.
self.run_op_benchmark(
sess,
special_math_ops.einsum(equation, *input_vars),
min_iters=50,
name='einsum_v1_cpu_({})_{}'.format(equation, dim))
# Call gen_linalg_ops.einsum.
self.run_op_benchmark(
sess,
gen_linalg_ops.einsum(input_vars, equation),
min_iters=50,
name='einsum_v2_cpu_({})_{}'.format(equation, dim))
if __name__ == '__main__':
test.main()
@@ -0,0 +1,294 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.linalg_grad."""
import numpy as np
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import gradients_impl
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.platform import test as test_lib
def _AddTest(test, op_name, testcase_name, fn):
test_name = '_'.join(['test', op_name, testcase_name])
if hasattr(test, test_name):
raise RuntimeError('Test %s defined more than once' % test_name)
setattr(test, test_name, fn)
class ShapeTest(test_lib.TestCase):
@test_util.run_deprecated_v1
def testBatchGradientUnknownSize(self):
with self.cached_session():
batch_size = constant_op.constant(3)
matrix_size = constant_op.constant(4)
batch_identity = array_ops.tile(
array_ops.expand_dims(
array_ops.diag(array_ops.ones([matrix_size])), 0),
[batch_size, 1, 1])
determinants = linalg_ops.matrix_determinant(batch_identity)
reduced = math_ops.reduce_sum(determinants)
sum_grad = gradients_impl.gradients(reduced, batch_identity)[0]
self.assertAllClose(batch_identity, self.evaluate(sum_grad))
class MatrixUnaryFunctorGradientTest(test_lib.TestCase):
pass # Filled in below
# TODO(b/417809163): re-enable this test when upstream issues are resolved
# see commit msg for details
# def _GetMatrixUnaryFunctorGradientTest(functor_, dtype_, shape_, **kwargs_):
#
# @test_util.enable_control_flow_v2
# @test_util.run_in_graph_and_eager_modes(use_gpu=True)
# @test_util.run_without_tensor_float_32(
# 'Tests `tf.linalg.expm`, which call matmul. Additionally, calls ops '
# 'which do matmul in their gradient, such as MatrixSolve.')
# def Test(self):
# def RandomInput():
# np.random.seed(1)
# return np.random.uniform(
# low=-1.0, high=1.0,
# size=np.prod(shape_)).reshape(shape_).astype(dtype_)
# if functor_.__name__ == 'matrix_square_root':
# # Square the input matrix to ensure that its matrix square root exists
# f = lambda x: functor_(math_ops.matmul(x, x), **kwargs_)
# else:
# f = functor_
# # Optimal stepsize for central difference is O(epsilon^{1/3}).
# epsilon = np.finfo(dtype_).eps
# delta = epsilon**(1.0 / 3.0)
# tolerance obtained by looking at actual differences using
# np.linalg.norm(theoretical-numerical, np.inf) on -mavx build
# tol = 1e-6 if dtype_ == np.float64 else 0.05
# theoretical, numerical = gradient_checker_v2.compute_gradient(
# f, [RandomInput()], delta=delta)
# self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
# return Test
class MatrixBinaryFunctorGradientTest(test_lib.TestCase):
pass # Filled in below
def _GetMatrixBinaryFunctorGradientTest(functor_,
dtype_,
shape_,
float32_tol_fudge=1.0,
**kwargs_):
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
@test_util.run_without_tensor_float_32(
'Tests `tf.linalg.lstsq`, which call matmul. Additionally, calls ops '
'which do matmul in their gradient, such as MatrixSolveLs.')
# TODO(b/164254522): With TensorFloat-32, some tests fails with extremely high
# absolute and relative differences when calling assertAllClose. For example,
# the test test_MatrixSolveLsGradient_float32_10_10_1e-06 of class
# MatrixBinaryFunctorGradientTest fails with a max absolute difference of
# 0.883 and a max relative difference of 736892. We should consider disabling
# TensorFloat-32 within `tf.linalg.lstsq and perhaps other linear algebra
# functions, even if TensorFloat-32 is allowed globally.
def Test(self):
def RandomInput():
np.random.seed(1)
return np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(shape_)).reshape(shape_).astype(dtype_)
fixed = RandomInput()
# Optimal stepsize for central difference is O(epsilon^{1/3}).
epsilon = np.finfo(dtype_).eps
delta = epsilon**(1.0 / 3.0)
# tolerance obtained by looking at actual differences using
# np.linalg.norm(theoretical-numerical, np.inf) on -mavx build
tol = 1e-6 if dtype_ == np.float64 else float32_tol_fudge * 0.05
# check gradient w.r.t. left argument.
theoretical, numerical = gradient_checker_v2.compute_gradient(
lambda x: functor_(x, fixed, **kwargs_), [RandomInput()], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
# check gradient w.r.t. right argument.
theoretical, numerical = gradient_checker_v2.compute_gradient(
lambda y: functor_(fixed, y, **kwargs_), [RandomInput()], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
return Test
def _GetBandedTriangularSolveGradientTest(
functor_,
dtype_,
shape_,
float32_tol_fudge=1.0, # pylint: disable=redefined-outer-name
**kwargs_):
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def Test(self):
n = shape_[-1]
np.random.seed(1)
# Make sure invertible.
a_np = np.random.uniform(low=1.0, high=2.0, size=shape_).astype(dtype_)
a = constant_op.constant(a_np)
b_np = np.random.uniform(low=-1.0, high=1.0, size=[n, n]).astype(dtype_)
b = constant_op.constant(b_np)
epsilon = np.finfo(dtype_).eps
delta = epsilon**(1.0 / 3.0)
# tolerance obtained by looking at actual differences using
# np.linalg.norm(theoretical-numerical, np.inf) on -mavx build
tol = 1e-6 if dtype_ == np.float64 else float32_tol_fudge * 0.05
# check gradient w.r.t. left argument.
theoretical, numerical = gradient_checker_v2.compute_gradient(
lambda x: functor_(x, b, **kwargs_), [a], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
# check gradient w.r.t. right argument.
theoretical, numerical = gradient_checker_v2.compute_gradient(
lambda y: functor_(a, y, **kwargs_), [b], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
return Test
if __name__ == '__main__':
# Tests for gradients of binary matrix operations.
for dtype in np.float32, np.float64:
for size in 2, 5, 10:
# We skip the rank 4, size 10 case: it is slow and conceptually covered
# by the other cases.
for extra in [(), (2,), (3,)] + [(3, 2)] * (size < 10):
for adjoint in False, True:
shape = extra + (size, size)
name = '%s_%s_adj_%s' % (dtype.__name__, '_'.join(map(
str, shape)), str(adjoint))
_AddTest(
MatrixBinaryFunctorGradientTest, 'MatrixSolveGradient', name,
_GetMatrixBinaryFunctorGradientTest(
linalg_ops.matrix_solve, dtype, shape, adjoint=adjoint))
for lower in True, False:
name = '%s_low_%s' % (name, lower)
_AddTest(
MatrixBinaryFunctorGradientTest,
'MatrixTriangularSolveGradient', name,
_GetMatrixBinaryFunctorGradientTest(
linalg_ops.matrix_triangular_solve,
dtype,
shape,
float32_tol_fudge=4.0,
adjoint=adjoint,
lower=lower))
band_shape = extra + (size // 2 + 1, size)
name = '%s_%s_adj_%s_low_%s' % (dtype.__name__, '_'.join(
map(str, band_shape)), str(adjoint), lower)
_AddTest(
MatrixBinaryFunctorGradientTest,
'BandedTriangularSolveGradient', name,
_GetBandedTriangularSolveGradientTest(
linalg_ops.banded_triangular_solve,
dtype,
band_shape,
float32_tol_fudge=4.0,
adjoint=adjoint,
lower=lower))
# Tests for gradients of unary matrix operations.
for dtype in np.float32, np.float64:
for size in 2, 5, 10:
# We skip the rank 4, size 10 case: it is slow and conceptually covered
# by the other cases.
for extra in [(), (2,), (3,)] + [(3, 2)] * (size < 10):
shape = extra + (size, size)
name = '%s_%s' % (dtype.__name__, '_'.join(map(str, shape)))
# _AddTest(
# MatrixUnaryFunctorGradientTest, 'MatrixInverseGradient', name,
# _GetMatrixUnaryFunctorGradientTest(linalg_ops.matrix_inverse,
# dtype, shape))
# _AddTest(
# MatrixUnaryFunctorGradientTest,
# 'MatrixAdjointInverseGradient', name,
# _GetMatrixUnaryFunctorGradientTest(
# lambda x: linalg_ops.matrix_inverse(x, adjoint=True),
# dtype, shape))
# if True: # not test_lib.is_built_with_rocm():
# TODO(b/417809163):
# re-enable this test when upstream issues are resolved
# see commit msg for details
# _AddTest(
# MatrixUnaryFunctorGradientTest, 'MatrixExponentialGradient', name,
# _GetMatrixUnaryFunctorGradientTest(linalg_impl.matrix_exponential,
# dtype, shape))
# _AddTest(
# MatrixUnaryFunctorGradientTest,
# 'MatrixDeterminantGradient', name,
# _GetMatrixUnaryFunctorGradientTest(linalg_ops.matrix_determinant,
# dtype, shape))
# _AddTest(
# MatrixUnaryFunctorGradientTest,
# 'LogMatrixDeterminantGradient',
# name,
# _GetMatrixUnaryFunctorGradientTest(lambda x:
# linalg_ops.log_matrix_determinant(x)[1], dtype, shape))
# The numerical Jacobian is consistently invalid for these four shapes
# because the matrix square root of the perturbed input doesn't exist
if shape in {(2, 5, 5), (3, 5, 5), (3, 10, 10), (3, 2, 5, 5)}:
# Alternative shape that consistently produces a valid numerical
# Jacobian
shape = extra + (size + 1, size + 1)
name = '%s_%s' % (dtype.__name__, '_'.join(map(str, shape)))
# _AddTest(
# MatrixUnaryFunctorGradientTest, 'MatrixSquareRootGradient', name,
# _GetMatrixUnaryFunctorGradientTest(linalg_ops.matrix_square_root,
# dtype, shape))
# Tests for gradients of matrix_solve_ls
for dtype in np.float32, np.float64:
for rows in 2, 5, 10:
for cols in 2, 5, 10:
for l2_regularization in 1e-6, 0.001, 1.0:
shape = (rows, cols)
name = '%s_%s_%s' % (dtype.__name__, '_'.join(map(
str, shape)), l2_regularization)
float32_tol_fudge = 5.1 if l2_regularization == 1e-6 else 4.0
_AddTest(
MatrixBinaryFunctorGradientTest,
'MatrixSolveLsGradient',
name,
# pylint: disable=long-lambda,g-long-lambda
_GetMatrixBinaryFunctorGradientTest(
(lambda a, b, l=l2_regularization: linalg_ops.matrix_solve_ls(
a, b, l)), dtype, shape, float32_tol_fudge))
test_lib.main()
@@ -0,0 +1,690 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.python.ops.linalg_ops."""
import itertools
from absl.testing import parameterized
import numpy as np
import scipy.linalg
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops.linalg import linalg
from tensorflow.python.platform import test
def _RandomPDMatrix(n, rng, dtype=np.float64):
"""Random positive definite matrix."""
temp = rng.randn(n, n).astype(dtype)
if dtype in [np.complex64, np.complex128]:
temp.imag = rng.randn(n, n)
return np.conj(temp).dot(temp.T)
class CholeskySolveTest(test.TestCase):
def setUp(self):
self.rng = np.random.RandomState(0)
@test_util.run_deprecated_v1
def test_works_with_five_different_random_pos_def_matrices(self):
for n in range(1, 6):
for np_type, atol in [(np.float32, 0.05), (np.float64, 1e-5)]:
with self.session():
# Create 2 x n x n matrix
array = np.array(
[_RandomPDMatrix(n, self.rng),
_RandomPDMatrix(n, self.rng)]).astype(np_type)
chol = linalg_ops.cholesky(array)
for k in range(1, 3):
with self.subTest(n=n, np_type=np_type, atol=atol, k=k):
rhs = self.rng.randn(2, n, k).astype(np_type)
x = linalg_ops.cholesky_solve(chol, rhs)
self.assertAllClose(rhs, math_ops.matmul(array, x), atol=atol)
class LogdetTest(test.TestCase):
def setUp(self):
self.rng = np.random.RandomState(42)
@test_util.run_deprecated_v1
def test_works_with_five_different_random_pos_def_matrices(self):
for n in range(1, 6):
for np_dtype, atol in [(np.float32, 0.05), (np.float64, 1e-5),
(np.complex64, 0.05), (np.complex128, 1e-5)]:
with self.subTest(n=n, np_dtype=np_dtype, atol=atol):
matrix = _RandomPDMatrix(n, self.rng, np_dtype)
_, logdet_np = np.linalg.slogdet(matrix)
with self.session():
# Create 2 x n x n matrix
# matrix = np.array(
# [_RandomPDMatrix(n, self.rng, np_dtype),
# _RandomPDMatrix(n, self.rng, np_dtype)]).astype(np_dtype)
logdet_tf = linalg.logdet(matrix)
self.assertAllClose(logdet_np, self.evaluate(logdet_tf), atol=atol)
def test_works_with_underflow_case(self):
for np_dtype, atol in [(np.float32, 0.05), (np.float64, 1e-5),
(np.complex64, 0.05), (np.complex128, 1e-5)]:
with self.subTest(np_dtype=np_dtype, atol=atol):
matrix = (np.eye(20) * 1e-6).astype(np_dtype)
_, logdet_np = np.linalg.slogdet(matrix)
with self.session():
logdet_tf = linalg.logdet(matrix)
self.assertAllClose(logdet_np, self.evaluate(logdet_tf), atol=atol)
class SlogdetTest(test.TestCase):
def setUp(self):
self.rng = np.random.RandomState(42)
@test_util.run_deprecated_v1
def test_works_with_five_different_random_pos_def_matrices(self):
for n in range(1, 6):
for np_dtype, atol in [(np.float32, 0.05), (np.float64, 1e-5),
(np.complex64, 0.05), (np.complex128, 1e-5)]:
with self.subTest(n=n, np_dtype=np_dtype, atol=atol):
matrix = _RandomPDMatrix(n, self.rng, np_dtype)
sign_np, log_abs_det_np = np.linalg.slogdet(matrix)
with self.session():
sign_tf, log_abs_det_tf = linalg.slogdet(matrix)
self.assertAllClose(
log_abs_det_np, self.evaluate(log_abs_det_tf), atol=atol)
self.assertAllClose(sign_np, self.evaluate(sign_tf), atol=atol)
def test_works_with_underflow_case(self):
for np_dtype, atol in [(np.float32, 0.05), (np.float64, 1e-5),
(np.complex64, 0.05), (np.complex128, 1e-5)]:
with self.subTest(np_dtype=np_dtype, atol=atol):
matrix = (np.eye(20) * 1e-6).astype(np_dtype)
sign_np, log_abs_det_np = np.linalg.slogdet(matrix)
with self.session():
sign_tf, log_abs_det_tf = linalg.slogdet(matrix)
self.assertAllClose(
log_abs_det_np, self.evaluate(log_abs_det_tf), atol=atol)
self.assertAllClose(sign_np, self.evaluate(sign_tf), atol=atol)
class AdjointTest(test.TestCase):
def test_compare_to_numpy(self):
for dtype in np.float64, np.float64, np.complex64, np.complex128:
with self.subTest(dtype=dtype):
matrix_np = np.array([[1 + 1j, 2 + 2j, 3 + 3j], [4 + 4j, 5 + 5j,
6 + 6j]]).astype(dtype)
expected_transposed = np.conj(matrix_np.T)
with self.session():
matrix = ops.convert_to_tensor(matrix_np)
transposed = linalg.adjoint(matrix)
self.assertEqual((3, 2), transposed.get_shape())
self.assertAllEqual(expected_transposed, self.evaluate(transposed))
class EyeTest(parameterized.TestCase, test.TestCase):
def testShapeInferenceNoBatch(self):
self.assertEqual((2, 2), linalg_ops.eye(num_rows=2).shape)
self.assertEqual((2, 3), linalg_ops.eye(num_rows=2, num_columns=3).shape)
def testShapeInferenceStaticBatch(self):
batch_shape = (2, 3)
self.assertEqual(
(2, 3, 2, 2),
linalg_ops.eye(num_rows=2, batch_shape=batch_shape).shape)
self.assertEqual(
(2, 3, 2, 3),
linalg_ops.eye(
num_rows=2, num_columns=3, batch_shape=batch_shape).shape)
@parameterized.named_parameters(
("DynamicRow",
lambda: array_ops.placeholder_with_default(2, shape=None),
lambda: None),
("DynamicRowStaticColumn",
lambda: array_ops.placeholder_with_default(2, shape=None),
lambda: 3),
("StaticRowDynamicColumn",
lambda: 2,
lambda: array_ops.placeholder_with_default(3, shape=None)),
("DynamicRowDynamicColumn",
lambda: array_ops.placeholder_with_default(2, shape=None),
lambda: array_ops.placeholder_with_default(3, shape=None)))
def testShapeInferenceStaticBatchWith(self, num_rows_fn, num_columns_fn):
num_rows = num_rows_fn()
num_columns = num_columns_fn()
batch_shape = (2, 3)
identity_matrix = linalg_ops.eye(
num_rows=num_rows,
num_columns=num_columns,
batch_shape=batch_shape)
self.assertEqual(4, identity_matrix.shape.ndims)
self.assertEqual((2, 3), identity_matrix.shape[:2])
if num_rows is not None and not isinstance(num_rows, tensor.Tensor):
self.assertEqual(2, identity_matrix.shape[-2])
if num_columns is not None and not isinstance(num_columns, tensor.Tensor):
self.assertEqual(3, identity_matrix.shape[-1])
@parameterized.parameters(
itertools.product(
# num_rows
[0, 1, 2, 5],
# num_columns
[None, 0, 1, 2, 5],
# batch_shape
[None, [], [2], [2, 3]],
# dtype
[
dtypes.int32,
dtypes.int64,
dtypes.float32,
dtypes.float64,
dtypes.complex64,
dtypes.complex128
])
)
def test_eye_no_placeholder(self, num_rows, num_columns, batch_shape, dtype):
eye_np = np.eye(num_rows, M=num_columns, dtype=dtype.as_numpy_dtype)
if batch_shape is not None:
eye_np = np.tile(eye_np, batch_shape + [1, 1])
eye_tf = self.evaluate(linalg_ops.eye(
num_rows,
num_columns=num_columns,
batch_shape=batch_shape,
dtype=dtype))
self.assertAllEqual(eye_np, eye_tf)
@parameterized.parameters(
itertools.product(
# num_rows
[0, 1, 2, 5],
# num_columns
[0, 1, 2, 5],
# batch_shape
[[], [2], [2, 3]],
# dtype
[
dtypes.int32,
dtypes.int64,
dtypes.float32,
dtypes.float64,
dtypes.complex64,
dtypes.complex128
])
)
@test_util.run_deprecated_v1
def test_eye_with_placeholder(
self, num_rows, num_columns, batch_shape, dtype):
eye_np = np.eye(num_rows, M=num_columns, dtype=dtype.as_numpy_dtype)
eye_np = np.tile(eye_np, batch_shape + [1, 1])
num_rows_placeholder = array_ops.placeholder(
dtypes.int32, name="num_rows")
num_columns_placeholder = array_ops.placeholder(
dtypes.int32, name="num_columns")
batch_shape_placeholder = array_ops.placeholder(
dtypes.int32, name="batch_shape")
eye = linalg_ops.eye(
num_rows_placeholder,
num_columns=num_columns_placeholder,
batch_shape=batch_shape_placeholder,
dtype=dtype)
with self.session() as sess:
eye_tf = sess.run(
eye,
feed_dict={
num_rows_placeholder: num_rows,
num_columns_placeholder: num_columns,
batch_shape_placeholder: batch_shape
})
self.assertAllEqual(eye_np, eye_tf)
class _MatrixRankTest(object):
def test_batch_default_tolerance(self):
x_ = np.array(
[
[
[2, 3, -2], # = row2+row3
[-1, 1, -2],
[3, 2, 0]
],
[
[0, 2, 0], # = 2*row2
[0, 1, 0],
[0, 3, 0]
], # = 3*row2
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]
],
self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
self.assertAllEqual([2, 1, 3], self.evaluate(linalg.matrix_rank(x)))
def test_custom_tolerance_broadcasts(self):
q = linalg.qr(random_ops.random_uniform([3, 3], dtype=self.dtype))[0]
e = constant_op.constant([0.1, 0.2, 0.3], dtype=self.dtype)
a = linalg.solve(q, linalg.transpose(a=e * q), adjoint=True)
self.assertAllEqual([3, 2, 1, 0],
self.evaluate(
linalg.matrix_rank(
a, tol=[[0.09], [0.19], [0.29], [0.31]])))
def test_nonsquare(self):
x_ = np.array(
[
[
[2, 3, -2, 2], # = row2+row3
[-1, 1, -2, 4],
[3, 2, 0, -2]
],
[
[0, 2, 0, 6], # = 2*row2
[0, 1, 0, 3],
[0, 3, 0, 9]
]
], # = 3*row2
self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
self.assertAllEqual([2, 1], self.evaluate(linalg.matrix_rank(x)))
@test_util.run_all_in_graph_and_eager_modes
class MatrixRankStatic32Test(test.TestCase, _MatrixRankTest):
dtype = np.float32
use_static_shape = True
@test_util.run_all_in_graph_and_eager_modes
class MatrixRankDynamic64Test(test.TestCase, _MatrixRankTest):
dtype = np.float64
use_static_shape = False
class _PinvTest(object):
def expected_pinv(self, a, rcond):
"""Calls `np.linalg.pinv` but corrects its broken batch semantics."""
if a.ndim < 3:
return np.linalg.pinv(a, rcond)
if rcond is None:
rcond = 10. * max(a.shape[-2], a.shape[-1]) * np.finfo(a.dtype).eps
s = np.concatenate([a.shape[:-2], [a.shape[-1], a.shape[-2]]])
a_pinv = np.zeros(s, dtype=a.dtype)
for i in np.ndindex(a.shape[:(a.ndim - 2)]):
a_pinv[i] = np.linalg.pinv(
a[i], rcond=rcond if isinstance(rcond.tolist(), float) else rcond[i])
return a_pinv
def test_symmetric(self):
a_ = self.dtype([[1., .4, .5], [.4, .2, .25], [.5, .25, .35]])
a_ = np.stack([a_ + 1., a_], axis=0) # Batch of matrices.
a = array_ops.placeholder_with_default(
a_, shape=a_.shape if self.use_static_shape else None)
if self.use_default_rcond:
rcond = None
else:
rcond = self.dtype([0., 0.01]) # Smallest 1 component is forced to zero.
expected_a_pinv_ = self.expected_pinv(a_, rcond)
a_pinv = linalg.pinv(a, rcond, validate_args=True)
a_pinv_ = self.evaluate(a_pinv)
self.assertAllClose(expected_a_pinv_, a_pinv_, atol=2e-5, rtol=2e-5)
if not self.use_static_shape:
return
self.assertAllEqual(expected_a_pinv_.shape, a_pinv.shape)
def test_nonsquare(self):
a_ = self.dtype([[1., .4, .5, 1.], [.4, .2, .25, 2.], [.5, .25, .35, 3.]])
a_ = np.stack([a_ + 0.5, a_], axis=0) # Batch of matrices.
a = array_ops.placeholder_with_default(
a_, shape=a_.shape if self.use_static_shape else None)
if self.use_default_rcond:
rcond = None
else:
# Smallest 2 components are forced to zero.
rcond = self.dtype([0., 0.25])
expected_a_pinv_ = self.expected_pinv(a_, rcond)
a_pinv = linalg.pinv(a, rcond, validate_args=True)
a_pinv_ = self.evaluate(a_pinv)
self.assertAllClose(expected_a_pinv_, a_pinv_, atol=1e-5, rtol=1e-4)
if not self.use_static_shape:
return
self.assertAllEqual(expected_a_pinv_.shape, a_pinv.shape)
@test_util.run_all_in_graph_and_eager_modes
class PinvTestDynamic32DefaultRcond(test.TestCase, _PinvTest):
dtype = np.float32
use_static_shape = False
use_default_rcond = True
@test_util.run_all_in_graph_and_eager_modes
class PinvTestStatic64DefaultRcond(test.TestCase, _PinvTest):
dtype = np.float64
use_static_shape = True
use_default_rcond = True
@test_util.run_all_in_graph_and_eager_modes
class PinvTestDynamic32CustomtRcond(test.TestCase, _PinvTest):
dtype = np.float32
use_static_shape = False
use_default_rcond = False
@test_util.run_all_in_graph_and_eager_modes
class PinvTestStatic64CustomRcond(test.TestCase, _PinvTest):
dtype = np.float64
use_static_shape = True
use_default_rcond = False
def make_tensor_hiding_attributes(value, hide_shape, hide_value=True):
if not hide_value:
return ops.convert_to_tensor(value)
shape = None if hide_shape else getattr(value, "shape", None)
return array_ops.placeholder_with_default(value, shape=shape)
class _LUReconstruct(object):
dtype = np.float32
use_static_shape = True
def test_non_batch(self):
x_ = np.array([[3, 4], [1, 2]], dtype=self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
y = linalg.lu_reconstruct(*linalg.lu(x), validate_args=True)
y_ = self.evaluate(y)
if self.use_static_shape:
self.assertAllEqual(x_.shape, y.shape)
self.assertAllClose(x_, y_, atol=0., rtol=1e-3)
def test_batch(self):
x_ = np.array([
[[3, 4], [1, 2]],
[[7, 8], [3, 4]],
], dtype=self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
y = linalg.lu_reconstruct(*linalg.lu(x), validate_args=True)
y_ = self.evaluate(y)
if self.use_static_shape:
self.assertAllEqual(x_.shape, y.shape)
self.assertAllClose(x_, y_, atol=0., rtol=1e-3)
@test_util.run_all_in_graph_and_eager_modes
class LUReconstructStatic(test.TestCase, _LUReconstruct):
use_static_shape = True
@test_util.run_all_in_graph_and_eager_modes
class LUReconstructDynamic(test.TestCase, _LUReconstruct):
use_static_shape = False
class _LUMatrixInverse(object):
dtype = np.float32
use_static_shape = True
def test_non_batch(self):
x_ = np.array([[1, 2], [3, 4]], dtype=self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
y = linalg.lu_matrix_inverse(*linalg.lu(x), validate_args=True)
y_ = self.evaluate(y)
if self.use_static_shape:
self.assertAllEqual(x_.shape, y.shape)
self.assertAllClose(np.linalg.inv(x_), y_, atol=0., rtol=1e-3)
def test_batch(self):
x_ = np.array([
[[1, 2], [3, 4]],
[[7, 8], [3, 4]],
[[0.25, 0.5], [0.75, -2.]],
],
dtype=self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
y = linalg.lu_matrix_inverse(*linalg.lu(x), validate_args=True)
y_ = self.evaluate(y)
if self.use_static_shape:
self.assertAllEqual(x_.shape, y.shape)
self.assertAllClose(np.linalg.inv(x_), y_, atol=0., rtol=1e-3)
@test_util.run_all_in_graph_and_eager_modes
class LUMatrixInverseStatic(test.TestCase, _LUMatrixInverse):
use_static_shape = True
@test_util.run_all_in_graph_and_eager_modes
class LUMatrixInverseDynamic(test.TestCase, _LUMatrixInverse):
use_static_shape = False
class _LUSolve(object):
dtype = np.float32
use_static_shape = True
def test_non_batch(self):
x_ = np.array([[1, 2], [3, 4]], dtype=self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
rhs_ = np.array([[1, 1]], dtype=self.dtype).T
rhs = array_ops.placeholder_with_default(
rhs_, shape=rhs_.shape if self.use_static_shape else None)
lower_upper, perm = linalg.lu(x)
y = linalg.lu_solve(lower_upper, perm, rhs, validate_args=True)
y_, perm_ = self.evaluate([y, perm])
self.assertAllEqual([1, 0], perm_)
expected_ = np.linalg.solve(x_, rhs_)
if self.use_static_shape:
self.assertAllEqual(expected_.shape, y.shape)
self.assertAllClose(expected_, y_, atol=0., rtol=1e-3)
def test_batch_broadcast(self):
x_ = np.array([
[[1, 2], [3, 4]],
[[7, 8], [3, 4]],
[[0.25, 0.5], [0.75, -2.]],
],
dtype=self.dtype)
x = array_ops.placeholder_with_default(
x_, shape=x_.shape if self.use_static_shape else None)
rhs_ = np.array([[1, 1]], dtype=self.dtype).T
rhs = array_ops.placeholder_with_default(
rhs_, shape=rhs_.shape if self.use_static_shape else None)
lower_upper, perm = linalg.lu(x)
y = linalg.lu_solve(lower_upper, perm, rhs, validate_args=True)
y_, perm_ = self.evaluate([y, perm])
self.assertAllEqual([[1, 0], [0, 1], [1, 0]], perm_)
expected_ = np.linalg.solve(x_, rhs_[np.newaxis])
if self.use_static_shape:
self.assertAllEqual(expected_.shape, y.shape)
self.assertAllClose(expected_, y_, atol=0., rtol=1e-3)
@test_util.run_all_in_graph_and_eager_modes
class LUSolveStatic(test.TestCase, _LUSolve):
use_static_shape = True
@test_util.run_all_in_graph_and_eager_modes
class LUSolveDynamic(test.TestCase, _LUSolve):
use_static_shape = False
@test_util.run_all_in_graph_and_eager_modes
class EighTridiagonalTest(test.TestCase, parameterized.TestCase):
def check_residual(self, matrix, eigvals, eigvectors, atol):
# Test that A*eigvectors is close to eigvectors*diag(eigvals).
l = math_ops.cast(linalg.diag(eigvals), dtype=eigvectors.dtype)
av = math_ops.matmul(matrix, eigvectors)
vl = math_ops.matmul(eigvectors, l)
self.assertAllClose(av, vl, atol=atol)
def check_orthogonality(self, eigvectors, tol):
# Test that eigenvectors are orthogonal.
k = array_ops.shape(eigvectors)[1]
vtv = math_ops.matmul(
eigvectors, eigvectors, adjoint_a=True) - linalg.eye(
k, dtype=eigvectors.dtype)
self.assertAllLess(math_ops.abs(vtv), tol)
def run_test(self, alpha, beta, eigvals_only=True):
n = alpha.shape[0]
matrix = np.diag(alpha) + np.diag(beta, 1) + np.diag(np.conj(beta), -1)
# scipy.linalg.eigh_tridiagonal doesn't support complex inputs, so for
# this we call the slower numpy.linalg.eigh.
if np.issubdtype(alpha.dtype, np.complexfloating):
eigvals_expected, _ = np.linalg.eigh(matrix)
else:
eigvals_expected = scipy.linalg.eigh_tridiagonal(
alpha, beta, eigvals_only=True)
eigvals = linalg.eigh_tridiagonal(alpha, beta, eigvals_only=eigvals_only)
if not eigvals_only:
eigvals, eigvectors = eigvals
eps = np.finfo(alpha.dtype).eps
atol = n * eps * np.amax(np.abs(eigvals_expected))
self.assertAllClose(eigvals_expected, eigvals, atol=atol)
if not eigvals_only:
self.check_orthogonality(eigvectors, 2 * np.sqrt(n) * eps)
self.check_residual(matrix, eigvals, eigvectors, atol)
@parameterized.parameters((np.float32), (np.float64), (np.complex64),
(np.complex128))
def test_small(self, dtype):
for n in [1, 2, 3]:
alpha = np.ones([n], dtype=dtype)
beta = np.ones([n - 1], dtype=dtype)
if np.issubdtype(alpha.dtype, np.complexfloating):
beta += 1j * beta
self.run_test(alpha, beta)
@parameterized.parameters((np.float32), (np.float64), (np.complex64),
(np.complex128))
def test_toeplitz(self, dtype):
n = 8
for a, b in [[2, -1], [1, 0], [0, 1], [-1e10, 1e10], [-1e-10, 1e-10]]:
alpha = a * np.ones([n], dtype=dtype)
beta = b * np.ones([n - 1], dtype=dtype)
if np.issubdtype(alpha.dtype, np.complexfloating):
beta += 1j * beta
self.run_test(alpha, beta)
@parameterized.parameters((np.float32), (np.float64), (np.complex64),
(np.complex128))
def test_random_uniform(self, dtype):
for n in [8, 50]:
alpha = np.random.uniform(size=(n,)).astype(dtype)
beta = np.random.uniform(size=(n - 1,)).astype(dtype)
if np.issubdtype(beta.dtype, np.complexfloating):
beta += 1j * np.random.uniform(size=(n - 1,)).astype(dtype)
self.run_test(alpha, beta)
@parameterized.parameters((np.float32), (np.float64), (np.complex64),
(np.complex128))
def test_select(self, dtype):
n = 4
alpha = np.random.uniform(size=(n,)).astype(dtype)
beta = np.random.uniform(size=(n - 1,)).astype(dtype)
eigvals_all = linalg.eigh_tridiagonal(alpha, beta, select="a")
eps = np.finfo(alpha.dtype).eps
atol = 2 * n * eps
for first in range(n - 1):
for last in range(first + 1, n - 1):
# Check that we get the expected eigenvalues by selecting by
# index range.
eigvals_index = linalg.eigh_tridiagonal(
alpha, beta, select="i", select_range=(first, last))
self.assertAllClose(
eigvals_all[first:(last + 1)], eigvals_index, atol=atol)
# Check that we get the expected eigenvalues by selecting by
# value range.
eigvals_value = linalg.eigh_tridiagonal(
alpha,
beta,
select="v",
select_range=(eigvals_all[first], eigvals_all[last]))
self.assertAllClose(
eigvals_all[first:(last + 1)], eigvals_value, atol=atol)
@parameterized.parameters((np.float32), (np.float64), (np.complex64),
(np.complex128))
def test_extreme_eigenvalues_test(self, dtype):
huge = 0.33 * np.finfo(dtype).max
tiny = 3 * np.finfo(dtype).tiny
for (a, b) in [(tiny, tiny), (huge, np.sqrt(huge))]:
alpha = np.array([-a, -np.sqrt(a), np.sqrt(a), a]).astype(dtype)
beta = b * np.ones([3], dtype=dtype)
if np.issubdtype(alpha.dtype, np.complexfloating):
beta += 1j * beta
@parameterized.parameters((np.float32), (np.float64), (np.complex64),
(np.complex128))
def test_eigenvectors(self, dtype):
if test.is_gpu_available(cuda_only=True) or test_util.is_xla_enabled():
# cuda and XLA do not yet expose the stabilized tridiagonal solver
# needed for inverse iteration.
return
n = 8
alpha = np.random.uniform(size=(n,)).astype(dtype)
beta = np.random.uniform(size=(n - 1,)).astype(dtype)
if np.issubdtype(beta.dtype, np.complexfloating):
beta += 1j * np.random.uniform(size=(n - 1,)).astype(dtype)
self.run_test(alpha, beta, eigvals_only=False)
# Test that we can correctly generate an orthogonal basis for
# a fully degenerate matrix.
eps = np.finfo(dtype).eps
alpha = np.ones(n).astype(dtype)
beta = 0.01 * np.sqrt(eps) * np.ones((n - 1)).astype(dtype)
self.run_test(alpha, beta, eigvals_only=False)
if __name__ == "__main__":
test.main()
@@ -0,0 +1,415 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.framework import test_util
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_addition
from tensorflow.python.platform import test
linalg = linalg_lib
rng = np.random.RandomState(0)
add_operators = linear_operator_addition.add_operators
# pylint: disable=unused-argument
class _BadAdder(linear_operator_addition._Adder):
"""Adder that will fail if used."""
def can_add(self, op1, op2):
raise AssertionError("BadAdder.can_add called!")
def _add(self, op1, op2, operator_name, hints):
raise AssertionError("This line should not be reached")
# pylint: enable=unused-argument
class LinearOperatorAdditionCorrectnessTest(test.TestCase):
"""Tests correctness of addition with combinations of a few Adders.
Tests here are done with the _DEFAULT_ADDITION_TIERS, which means
add_operators should reduce all operators resulting in one single operator.
This shows that we are able to correctly combine adders using the tiered
system. All Adders should be tested separately, and there is no need to test
every Adder within this class.
"""
def test_one_operator_is_returned_unchanged(self):
op_a = linalg.LinearOperatorDiag([1., 1.])
op_sum = add_operators([op_a])
self.assertEqual(1, len(op_sum))
self.assertIs(op_sum[0], op_a)
def test_at_least_one_operators_required(self):
with self.assertRaisesRegex(ValueError, "must contain at least one"):
add_operators([])
def test_attempting_to_add_numbers_raises(self):
with self.assertRaisesRegex(TypeError, "contain only LinearOperator"):
add_operators([1, 2])
@test_util.run_deprecated_v1
def test_two_diag_operators(self):
op_a = linalg.LinearOperatorDiag(
[1., 1.], is_positive_definite=True, name="A")
op_b = linalg.LinearOperatorDiag(
[2., 2.], is_positive_definite=True, name="B")
with self.cached_session():
op_sum = add_operators([op_a, op_b])
self.assertEqual(1, len(op_sum))
op = op_sum[0]
self.assertIsInstance(op, linalg_lib.LinearOperatorDiag)
self.assertAllClose([[3., 0.], [0., 3.]], op.to_dense())
# Adding positive definite operators produces positive def.
self.assertTrue(op.is_positive_definite)
# Real diagonal ==> self-adjoint.
self.assertTrue(op.is_self_adjoint)
# Positive definite ==> non-singular
self.assertTrue(op.is_non_singular)
# Enforce particular name for this simple case
self.assertEqual("Add/B__A/", op.name)
@test_util.run_deprecated_v1
def test_three_diag_operators(self):
op1 = linalg.LinearOperatorDiag(
[1., 1.], is_positive_definite=True, name="op1")
op2 = linalg.LinearOperatorDiag(
[2., 2.], is_positive_definite=True, name="op2")
op3 = linalg.LinearOperatorDiag(
[3., 3.], is_positive_definite=True, name="op3")
with self.cached_session():
op_sum = add_operators([op1, op2, op3])
self.assertEqual(1, len(op_sum))
op = op_sum[0]
self.assertTrue(isinstance(op, linalg_lib.LinearOperatorDiag))
self.assertAllClose([[6., 0.], [0., 6.]], op.to_dense())
# Adding positive definite operators produces positive def.
self.assertTrue(op.is_positive_definite)
# Real diagonal ==> self-adjoint.
self.assertTrue(op.is_self_adjoint)
# Positive definite ==> non-singular
self.assertTrue(op.is_non_singular)
@test_util.run_deprecated_v1
def test_diag_tril_diag(self):
op1 = linalg.LinearOperatorDiag(
[1., 1.], is_non_singular=True, name="diag_a")
op2 = linalg.LinearOperatorLowerTriangular(
[[2., 0.], [0., 2.]],
is_self_adjoint=True,
is_non_singular=True,
name="tril")
op3 = linalg.LinearOperatorDiag(
[3., 3.], is_non_singular=True, name="diag_b")
with self.cached_session():
op_sum = add_operators([op1, op2, op3])
self.assertEqual(1, len(op_sum))
op = op_sum[0]
self.assertIsInstance(op, linalg_lib.LinearOperatorLowerTriangular)
self.assertAllClose([[6., 0.], [0., 6.]], op.to_dense())
# The diag operators will be self-adjoint (because real and diagonal).
# The TriL operator has the self-adjoint hint set.
self.assertTrue(op.is_self_adjoint)
# Even though op1/2/3 are non-singular, this does not imply op is.
# Since no custom hint was provided, we default to None (unknown).
self.assertEqual(None, op.is_non_singular)
@test_util.run_deprecated_v1
def test_matrix_diag_tril_diag_uses_custom_name(self):
op0 = linalg.LinearOperatorFullMatrix(
[[-1., -1.], [-1., -1.]], name="matrix")
op1 = linalg.LinearOperatorDiag([1., 1.], name="diag_a")
op2 = linalg.LinearOperatorLowerTriangular(
[[2., 0.], [1.5, 2.]], name="tril")
op3 = linalg.LinearOperatorDiag([3., 3.], name="diag_b")
with self.cached_session():
op_sum = add_operators([op0, op1, op2, op3], operator_name="my_operator")
self.assertEqual(1, len(op_sum))
op = op_sum[0]
self.assertIsInstance(op, linalg_lib.LinearOperatorFullMatrix)
self.assertAllClose([[5., -1.], [0.5, 5.]], op.to_dense())
self.assertEqual("my_operator", op.name)
def test_incompatible_domain_dimensions_raises(self):
op1 = linalg.LinearOperatorFullMatrix(rng.rand(2, 3))
op2 = linalg.LinearOperatorDiag(rng.rand(2, 4))
with self.assertRaisesRegex(ValueError, "must.*same `domain_dimension`"):
add_operators([op1, op2])
def test_incompatible_range_dimensions_raises(self):
op1 = linalg.LinearOperatorFullMatrix(rng.rand(2, 3))
op2 = linalg.LinearOperatorDiag(rng.rand(3, 3))
with self.assertRaisesRegex(ValueError, "must.*same `range_dimension`"):
add_operators([op1, op2])
def test_non_broadcastable_batch_shape_raises(self):
op1 = linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3))
op2 = linalg.LinearOperatorDiag(rng.rand(4, 3, 3))
with self.assertRaisesRegex(ValueError, "Incompatible shapes"):
add_operators([op1, op2])
class LinearOperatorOrderOfAdditionTest(test.TestCase):
"""Test that the order of addition is done as specified by tiers."""
def test_tier_0_additions_done_in_tier_0(self):
diag1 = linalg.LinearOperatorDiag([1.])
diag2 = linalg.LinearOperatorDiag([1.])
diag3 = linalg.LinearOperatorDiag([1.])
addition_tiers = [
[linear_operator_addition._AddAndReturnDiag()],
[_BadAdder()],
]
# Should not raise since all were added in tier 0, and tier 1 (with the
# _BadAdder) was never reached.
op_sum = add_operators([diag1, diag2, diag3], addition_tiers=addition_tiers)
self.assertEqual(1, len(op_sum))
self.assertIsInstance(op_sum[0], linalg.LinearOperatorDiag)
def test_tier_1_additions_done_by_tier_1(self):
diag1 = linalg.LinearOperatorDiag([1.])
diag2 = linalg.LinearOperatorDiag([1.])
tril = linalg.LinearOperatorLowerTriangular([[1.]])
addition_tiers = [
[linear_operator_addition._AddAndReturnDiag()],
[linear_operator_addition._AddAndReturnTriL()],
[_BadAdder()],
]
# Should not raise since all were added by tier 1, and the
# _BadAdder) was never reached.
op_sum = add_operators([diag1, diag2, tril], addition_tiers=addition_tiers)
self.assertEqual(1, len(op_sum))
self.assertIsInstance(op_sum[0], linalg.LinearOperatorLowerTriangular)
def test_tier_1_additions_done_by_tier_1_with_order_flipped(self):
diag1 = linalg.LinearOperatorDiag([1.])
diag2 = linalg.LinearOperatorDiag([1.])
tril = linalg.LinearOperatorLowerTriangular([[1.]])
addition_tiers = [
[linear_operator_addition._AddAndReturnTriL()],
[linear_operator_addition._AddAndReturnDiag()],
[_BadAdder()],
]
# Tier 0 could convert to TriL, and this converted everything to TriL,
# including the Diags.
# Tier 1 was never used.
# Tier 2 was never used (therefore, _BadAdder didn't raise).
op_sum = add_operators([diag1, diag2, tril], addition_tiers=addition_tiers)
self.assertEqual(1, len(op_sum))
self.assertIsInstance(op_sum[0], linalg.LinearOperatorLowerTriangular)
@test_util.run_deprecated_v1
def test_cannot_add_everything_so_return_more_than_one_operator(self):
diag1 = linalg.LinearOperatorDiag([1.])
diag2 = linalg.LinearOperatorDiag([2.])
tril5 = linalg.LinearOperatorLowerTriangular([[5.]])
addition_tiers = [
[linear_operator_addition._AddAndReturnDiag()],
]
# Tier 0 (the only tier) can only convert to Diag, so it combines the two
# diags, but the TriL is unchanged.
# Result should contain two operators, one Diag, one TriL.
op_sum = add_operators([diag1, diag2, tril5], addition_tiers=addition_tiers)
self.assertEqual(2, len(op_sum))
found_diag = False
found_tril = False
with self.cached_session():
for op in op_sum:
if isinstance(op, linalg.LinearOperatorDiag):
found_diag = True
self.assertAllClose([[3.]], op.to_dense())
if isinstance(op, linalg.LinearOperatorLowerTriangular):
found_tril = True
self.assertAllClose([[5.]], op.to_dense())
self.assertTrue(found_diag and found_tril)
def test_intermediate_tier_is_not_skipped(self):
diag1 = linalg.LinearOperatorDiag([1.])
diag2 = linalg.LinearOperatorDiag([1.])
tril = linalg.LinearOperatorLowerTriangular([[1.]])
addition_tiers = [
[linear_operator_addition._AddAndReturnDiag()],
[_BadAdder()],
[linear_operator_addition._AddAndReturnTriL()],
]
# tril cannot be added in tier 0, and the intermediate tier 1 with the
# BadAdder will catch it and raise.
with self.assertRaisesRegex(AssertionError, "BadAdder.can_add called"):
add_operators([diag1, diag2, tril], addition_tiers=addition_tiers)
class AddAndReturnScaledIdentityTest(test.TestCase):
def setUp(self):
self._adder = linear_operator_addition._AddAndReturnScaledIdentity()
@test_util.run_deprecated_v1
def test_identity_plus_identity(self):
id1 = linalg.LinearOperatorIdentity(num_rows=2)
id2 = linalg.LinearOperatorIdentity(num_rows=2, batch_shape=[3])
hints = linear_operator_addition._Hints(
is_positive_definite=True, is_non_singular=True)
self.assertTrue(self._adder.can_add(id1, id2))
operator = self._adder.add(id1, id2, "my_operator", hints)
self.assertIsInstance(operator, linalg.LinearOperatorScaledIdentity)
with self.cached_session():
self.assertAllClose(2 * linalg_ops.eye(num_rows=2, batch_shape=[3]),
operator.to_dense())
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertEqual("my_operator", operator.name)
@test_util.run_deprecated_v1
def test_identity_plus_scaled_identity(self):
id1 = linalg.LinearOperatorIdentity(num_rows=2, batch_shape=[3])
id2 = linalg.LinearOperatorScaledIdentity(num_rows=2, multiplier=2.2)
hints = linear_operator_addition._Hints(
is_positive_definite=True, is_non_singular=True)
self.assertTrue(self._adder.can_add(id1, id2))
operator = self._adder.add(id1, id2, "my_operator", hints)
self.assertIsInstance(operator, linalg.LinearOperatorScaledIdentity)
with self.cached_session():
self.assertAllClose(3.2 * linalg_ops.eye(num_rows=2, batch_shape=[3]),
operator.to_dense())
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertEqual("my_operator", operator.name)
@test_util.run_deprecated_v1
def test_scaled_identity_plus_scaled_identity(self):
id1 = linalg.LinearOperatorScaledIdentity(
num_rows=2, multiplier=[2.2, 2.2, 2.2])
id2 = linalg.LinearOperatorScaledIdentity(num_rows=2, multiplier=-1.0)
hints = linear_operator_addition._Hints(
is_positive_definite=True, is_non_singular=True)
self.assertTrue(self._adder.can_add(id1, id2))
operator = self._adder.add(id1, id2, "my_operator", hints)
self.assertIsInstance(operator, linalg.LinearOperatorScaledIdentity)
with self.cached_session():
self.assertAllClose(1.2 * linalg_ops.eye(num_rows=2, batch_shape=[3]),
operator.to_dense())
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertEqual("my_operator", operator.name)
class AddAndReturnDiagTest(test.TestCase):
def setUp(self):
self._adder = linear_operator_addition._AddAndReturnDiag()
@test_util.run_deprecated_v1
def test_identity_plus_identity_returns_diag(self):
id1 = linalg.LinearOperatorIdentity(num_rows=2)
id2 = linalg.LinearOperatorIdentity(num_rows=2, batch_shape=[3])
hints = linear_operator_addition._Hints(
is_positive_definite=True, is_non_singular=True)
self.assertTrue(self._adder.can_add(id1, id2))
operator = self._adder.add(id1, id2, "my_operator", hints)
self.assertIsInstance(operator, linalg.LinearOperatorDiag)
with self.cached_session():
self.assertAllClose(2 * linalg_ops.eye(num_rows=2, batch_shape=[3]),
operator.to_dense())
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertEqual("my_operator", operator.name)
@test_util.run_deprecated_v1
def test_diag_plus_diag(self):
diag1 = rng.rand(2, 3, 4)
diag2 = rng.rand(4)
op1 = linalg.LinearOperatorDiag(diag1)
op2 = linalg.LinearOperatorDiag(diag2)
hints = linear_operator_addition._Hints(
is_positive_definite=True, is_non_singular=True)
self.assertTrue(self._adder.can_add(op1, op2))
operator = self._adder.add(op1, op2, "my_operator", hints)
self.assertIsInstance(operator, linalg.LinearOperatorDiag)
with self.cached_session():
self.assertAllClose(
linalg.LinearOperatorDiag(diag1 + diag2).to_dense(),
operator.to_dense())
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertEqual("my_operator", operator.name)
class AddAndReturnTriLTest(test.TestCase):
def setUp(self):
self._adder = linear_operator_addition._AddAndReturnTriL()
@test_util.run_deprecated_v1
def test_diag_plus_tril(self):
diag = linalg.LinearOperatorDiag([1., 2.])
tril = linalg.LinearOperatorLowerTriangular([[10., 0.], [30., 0.]])
hints = linear_operator_addition._Hints(
is_positive_definite=True, is_non_singular=True)
self.assertTrue(self._adder.can_add(diag, diag))
self.assertTrue(self._adder.can_add(diag, tril))
operator = self._adder.add(diag, tril, "my_operator", hints)
self.assertIsInstance(operator, linalg.LinearOperatorLowerTriangular)
with self.cached_session():
self.assertAllClose([[11., 0.], [30., 2.]], operator.to_dense())
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertEqual("my_operator", operator.name)
class AddAndReturnMatrixTest(test.TestCase):
def setUp(self):
self._adder = linear_operator_addition._AddAndReturnMatrix()
@test_util.run_deprecated_v1
def test_diag_plus_diag(self):
diag1 = linalg.LinearOperatorDiag([1., 2.])
diag2 = linalg.LinearOperatorDiag([-1., 3.])
hints = linear_operator_addition._Hints(
is_positive_definite=False, is_non_singular=False)
self.assertTrue(self._adder.can_add(diag1, diag2))
operator = self._adder.add(diag1, diag2, "my_operator", hints)
self.assertIsInstance(operator, linalg.LinearOperatorFullMatrix)
with self.cached_session():
self.assertAllClose([[0., 0.], [0., 5.]], operator.to_dense())
self.assertFalse(operator.is_positive_definite)
self.assertFalse(operator.is_non_singular)
self.assertEqual("my_operator", operator.name)
if __name__ == "__main__":
test.main()
@@ -0,0 +1,287 @@
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_adjoint
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
LinearOperatorAdjoint = linear_operator_adjoint.LinearOperatorAdjoint # pylint: disable=invalid-name
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorAdjointTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
self._atol[dtypes.complex64] = 1e-5
self._rtol[dtypes.complex64] = 1e-5
def operator_and_matrix(self,
build_info,
dtype,
use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
if ensure_self_adjoint_and_pd:
matrix = linear_operator_test_util.random_positive_definite_matrix(
shape, dtype, force_well_conditioned=True)
else:
matrix = linear_operator_test_util.random_tril_matrix(
shape, dtype, force_well_conditioned=True, remove_upper=True)
lin_op_matrix = matrix
if use_placeholder:
lin_op_matrix = array_ops.placeholder_with_default(matrix, shape=None)
if ensure_self_adjoint_and_pd:
operator = LinearOperatorAdjoint(
linalg.LinearOperatorFullMatrix(
lin_op_matrix, is_positive_definite=True, is_self_adjoint=True))
else:
operator = LinearOperatorAdjoint(
linalg.LinearOperatorLowerTriangular(lin_op_matrix))
return operator, linalg.adjoint(matrix)
def test_base_operator_hint_used(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix,
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
operator_adjoint = operator.adjoint()
self.assertIsInstance(operator_adjoint, LinearOperatorAdjoint)
self.assertTrue(operator_adjoint.is_positive_definite)
self.assertTrue(operator_adjoint.is_non_singular)
self.assertFalse(operator_adjoint.is_self_adjoint)
def test_adjoint_of_adjoint_is_operator(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix)
operator_adjoint = operator.adjoint()
self.assertIsInstance(operator_adjoint, LinearOperatorAdjoint)
adjoint_of_op_adjoint = operator_adjoint.adjoint()
self.assertIsInstance(adjoint_of_op_adjoint,
linalg.LinearOperatorFullMatrix)
def test_supplied_hint_used(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix)
operator_adjoint = LinearOperatorAdjoint(
operator,
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator_adjoint.is_positive_definite)
self.assertTrue(operator_adjoint.is_non_singular)
self.assertFalse(operator_adjoint.is_self_adjoint)
def test_contradicting_hints_raise(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, is_positive_definite=False)
with self.assertRaisesRegex(ValueError, "positive-definite"):
LinearOperatorAdjoint(operator, is_positive_definite=True)
operator = linalg.LinearOperatorFullMatrix(matrix, is_self_adjoint=False)
with self.assertRaisesRegex(ValueError, "self-adjoint"):
LinearOperatorAdjoint(operator, is_self_adjoint=True)
def test_name(self):
matrix = [[11., 0.], [1., 8.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, name="my_operator", is_non_singular=True)
operator = LinearOperatorAdjoint(operator)
self.assertEqual("my_operator_adjoint", operator.name)
def test_matmul_adjoint_operator(self):
matrix1 = np.random.randn(4, 4)
matrix2 = np.random.randn(4, 4)
full_matrix1 = linalg.LinearOperatorFullMatrix(matrix1)
full_matrix2 = linalg.LinearOperatorFullMatrix(matrix2)
self.assertAllClose(
np.matmul(matrix1, matrix2.T),
self.evaluate(
full_matrix1.matmul(full_matrix2, adjoint_arg=True).to_dense()))
self.assertAllClose(
np.matmul(matrix1.T, matrix2),
self.evaluate(
full_matrix1.matmul(full_matrix2, adjoint=True).to_dense()))
self.assertAllClose(
np.matmul(matrix1.T, matrix2.T),
self.evaluate(
full_matrix1.matmul(full_matrix2, adjoint=True,
adjoint_arg=True).to_dense()))
def test_matmul_adjoint_complex_operator(self):
matrix1 = np.random.randn(4, 4) + 1j * np.random.randn(4, 4)
matrix2 = np.random.randn(4, 4) + 1j * np.random.randn(4, 4)
full_matrix1 = linalg.LinearOperatorFullMatrix(matrix1)
full_matrix2 = linalg.LinearOperatorFullMatrix(matrix2)
self.assertAllClose(
np.matmul(matrix1,
matrix2.conj().T),
self.evaluate(
full_matrix1.matmul(full_matrix2, adjoint_arg=True).to_dense()))
self.assertAllClose(
np.matmul(matrix1.conj().T, matrix2),
self.evaluate(
full_matrix1.matmul(full_matrix2, adjoint=True).to_dense()))
self.assertAllClose(
np.matmul(matrix1.conj().T,
matrix2.conj().T),
self.evaluate(
full_matrix1.matmul(full_matrix2, adjoint=True,
adjoint_arg=True).to_dense()))
def test_matvec(self):
matrix = np.array([[1., 2.], [3., 4.]])
x = np.array([1., 2.])
operator = linalg.LinearOperatorFullMatrix(matrix)
self.assertAllClose(matrix.dot(x), self.evaluate(operator.matvec(x)))
self.assertAllClose(matrix.T.dot(x), self.evaluate(operator.H.matvec(x)))
def test_solve_adjoint_operator(self):
matrix1 = self.evaluate(
linear_operator_test_util.random_tril_matrix(
[4, 4], dtype=dtypes.float64, force_well_conditioned=True))
matrix2 = np.random.randn(4, 4)
full_matrix1 = linalg.LinearOperatorLowerTriangular(
matrix1, is_non_singular=True)
full_matrix2 = linalg.LinearOperatorFullMatrix(matrix2)
self.assertAllClose(
self.evaluate(linalg.triangular_solve(matrix1, matrix2.T)),
self.evaluate(
full_matrix1.solve(full_matrix2, adjoint_arg=True).to_dense()))
self.assertAllClose(
self.evaluate(linalg.triangular_solve(matrix1.T, matrix2, lower=False)),
self.evaluate(
full_matrix1.solve(full_matrix2, adjoint=True).to_dense()))
self.assertAllClose(
self.evaluate(
linalg.triangular_solve(matrix1.T, matrix2.T, lower=False)),
self.evaluate(
full_matrix1.solve(full_matrix2, adjoint=True,
adjoint_arg=True).to_dense()))
def test_solve_adjoint_complex_operator(self):
matrix1 = self.evaluate(
linear_operator_test_util.random_tril_matrix(
[4, 4], dtype=dtypes.complex128, force_well_conditioned=True) +
1j * linear_operator_test_util.random_tril_matrix(
[4, 4], dtype=dtypes.complex128, force_well_conditioned=True))
matrix2 = np.random.randn(4, 4) + 1j * np.random.randn(4, 4)
full_matrix1 = linalg.LinearOperatorLowerTriangular(
matrix1, is_non_singular=True)
full_matrix2 = linalg.LinearOperatorFullMatrix(matrix2)
self.assertAllClose(
self.evaluate(linalg.triangular_solve(matrix1,
matrix2.conj().T)),
self.evaluate(
full_matrix1.solve(full_matrix2, adjoint_arg=True).to_dense()))
self.assertAllClose(
self.evaluate(
linalg.triangular_solve(matrix1.conj().T, matrix2, lower=False)),
self.evaluate(
full_matrix1.solve(full_matrix2, adjoint=True).to_dense()))
self.assertAllClose(
self.evaluate(
linalg.triangular_solve(
matrix1.conj().T, matrix2.conj().T, lower=False)),
self.evaluate(
full_matrix1.solve(full_matrix2, adjoint=True,
adjoint_arg=True).to_dense()))
def test_solvevec(self):
matrix = np.array([[1., 2.], [3., 4.]])
inv_matrix = np.linalg.inv(matrix)
x = np.array([1., 2.])
operator = linalg.LinearOperatorFullMatrix(matrix)
self.assertAllClose(inv_matrix.dot(x), self.evaluate(operator.solvevec(x)))
self.assertAllClose(
inv_matrix.T.dot(x), self.evaluate(operator.H.solvevec(x)))
def test_tape_safe(self):
matrix = variables_module.Variable([[1., 2.], [3., 4.]])
operator = LinearOperatorAdjoint(linalg.LinearOperatorFullMatrix(matrix))
self.check_tape_safe(operator)
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorAdjointNonSquareTest(
linear_operator_test_util.NonSquareLinearOperatorDerivedClassTest):
"""Tests done in the base class NonSquareLinearOperatorDerivedClassTest."""
def operator_and_matrix(self, build_info, dtype, use_placeholder):
shape_before_adjoint = list(build_info.shape)
# We need to swap the last two dimensions because we are taking the adjoint
# of this operator
shape_before_adjoint[-1], shape_before_adjoint[-2] = (
shape_before_adjoint[-2], shape_before_adjoint[-1])
matrix = linear_operator_test_util.random_normal(
shape_before_adjoint, dtype=dtype)
lin_op_matrix = matrix
if use_placeholder:
lin_op_matrix = array_ops.placeholder_with_default(matrix, shape=None)
operator = LinearOperatorAdjoint(
linalg.LinearOperatorFullMatrix(lin_op_matrix))
return operator, linalg.adjoint(matrix)
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorAdjointTest)
test.main()
@@ -0,0 +1,542 @@
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.eager import backprop
from tensorflow.python.eager import context
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_block_diag as block_diag
from tensorflow.python.ops.linalg import linear_operator_lower_triangular as lower_triangular
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.ops.linalg import linear_operator_util
from tensorflow.python.platform import test
linalg = linalg_lib
rng = np.random.RandomState(0)
def _block_diag_dense(expected_shape, blocks):
"""Convert a list of blocks, into a dense block diagonal matrix."""
rows = []
num_cols = 0
for block in blocks:
# Get the batch shape for the block.
batch_row_shape = array_ops.shape(block)[:-1]
zeros_to_pad_before_shape = array_ops.concat(
[batch_row_shape, [num_cols]], axis=-1)
zeros_to_pad_before = array_ops.zeros(
shape=zeros_to_pad_before_shape, dtype=block.dtype)
num_cols += array_ops.shape(block)[-1]
zeros_to_pad_after_shape = array_ops.concat(
[batch_row_shape, [expected_shape[-1] - num_cols]], axis=-1)
zeros_to_pad_after = array_ops.zeros(
zeros_to_pad_after_shape, dtype=block.dtype)
rows.append(array_ops.concat(
[zeros_to_pad_before, block, zeros_to_pad_after], axis=-1))
return array_ops.concat(rows, axis=-2)
@test_util.run_all_in_graph_and_eager_modes
class SquareLinearOperatorBlockDiagTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Increase from 1e-6 to 1e-4
self._atol[dtypes.float32] = 1e-4
self._atol[dtypes.complex64] = 1e-4
self._rtol[dtypes.float32] = 1e-4
self._rtol[dtypes.complex64] = 1e-4
@staticmethod
def optional_tests():
"""List of optional test names to run."""
return [
"operator_matmul_with_same_type",
"operator_solve_with_same_type",
]
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
return [
shape_info((0, 0)),
shape_info((1, 1)),
shape_info((1, 3, 3)),
shape_info((5, 5), blocks=[(2, 2), (3, 3)]),
shape_info((3, 7, 7), blocks=[(1, 2, 2), (3, 2, 2), (1, 3, 3)]),
shape_info((2, 1, 5, 5), blocks=[(2, 1, 2, 2), (1, 3, 3)]),
]
@staticmethod
def use_blockwise_arg():
return True
def operator_and_matrix(
self, shape_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(shape_info.shape)
expected_blocks = (
shape_info.__dict__["blocks"] if "blocks" in shape_info.__dict__
else [shape])
matrices = [
linear_operator_test_util.random_positive_definite_matrix(
block_shape, dtype, force_well_conditioned=True)
for block_shape in expected_blocks
]
lin_op_matrices = matrices
if use_placeholder:
lin_op_matrices = [
array_ops.placeholder_with_default(
matrix, shape=None) for matrix in matrices]
operator = block_diag.LinearOperatorBlockDiag(
[linalg.LinearOperatorFullMatrix(
l,
is_square=True,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
for l in lin_op_matrices])
# Should be auto-set.
self.assertTrue(operator.is_square)
# Broadcast the shapes.
expected_shape = list(shape_info.shape)
matrices = linear_operator_util.broadcast_matrix_batch_dims(matrices)
block_diag_dense = _block_diag_dense(expected_shape, matrices)
if not use_placeholder:
block_diag_dense.set_shape(
expected_shape[:-2] + [expected_shape[-1], expected_shape[-1]])
return operator, block_diag_dense
def test_is_x_flags(self):
# Matrix with two positive eigenvalues, 1, and 1.
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = block_diag.LinearOperatorBlockDiag(
[linalg.LinearOperatorFullMatrix(matrix)],
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertFalse(operator.is_self_adjoint)
def test_is_x_parameters(self):
matrix = [[1., 0.], [1., 1.]]
sub_operator = linalg.LinearOperatorFullMatrix(matrix)
operator = block_diag.LinearOperatorBlockDiag(
[sub_operator],
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertEqual(
operator.parameters,
{
"name": None,
"is_square": True,
"is_positive_definite": True,
"is_self_adjoint": False,
"is_non_singular": True,
"operators": [sub_operator],
})
self.assertEqual(
sub_operator.parameters,
{
"is_non_singular": None,
"is_positive_definite": None,
"is_self_adjoint": None,
"is_square": None,
"matrix": matrix,
"name": "LinearOperatorFullMatrix",
})
def test_block_diag_adjoint_type(self):
matrix = [[1., 0.], [0., 1.]]
operator = block_diag.LinearOperatorBlockDiag(
[
linalg.LinearOperatorFullMatrix(
matrix,
is_non_singular=True,
),
linalg.LinearOperatorFullMatrix(
matrix,
is_non_singular=True,
),
],
is_non_singular=True,
)
adjoint = operator.adjoint()
self.assertIsInstance(
adjoint,
block_diag.LinearOperatorBlockDiag)
self.assertEqual(2, len(adjoint.operators))
def test_block_diag_cholesky_type(self):
matrix = [[1., 0.], [0., 1.]]
operator = block_diag.LinearOperatorBlockDiag(
[
linalg.LinearOperatorFullMatrix(
matrix,
is_positive_definite=True,
is_self_adjoint=True,
),
linalg.LinearOperatorFullMatrix(
matrix,
is_positive_definite=True,
is_self_adjoint=True,
),
],
is_positive_definite=True,
is_self_adjoint=True,
)
cholesky_factor = operator.cholesky()
self.assertIsInstance(
cholesky_factor,
block_diag.LinearOperatorBlockDiag)
self.assertEqual(2, len(cholesky_factor.operators))
self.assertIsInstance(
cholesky_factor.operators[0],
lower_triangular.LinearOperatorLowerTriangular)
self.assertIsInstance(
cholesky_factor.operators[1],
lower_triangular.LinearOperatorLowerTriangular
)
def test_block_diag_inverse_type(self):
matrix = [[1., 0.], [0., 1.]]
operator = block_diag.LinearOperatorBlockDiag(
[
linalg.LinearOperatorFullMatrix(
matrix,
is_non_singular=True,
),
linalg.LinearOperatorFullMatrix(
matrix,
is_non_singular=True,
),
],
is_non_singular=True,
)
inverse = operator.inverse()
self.assertIsInstance(
inverse,
block_diag.LinearOperatorBlockDiag)
self.assertEqual(2, len(inverse.operators))
def test_block_diag_matmul_type(self):
matrices1 = []
matrices2 = []
for i in range(1, 5):
matrices1.append(linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
[2, i], dtype=dtypes.float32)))
matrices2.append(linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
[i, 3], dtype=dtypes.float32)))
operator1 = block_diag.LinearOperatorBlockDiag(matrices1, is_square=False)
operator2 = block_diag.LinearOperatorBlockDiag(matrices2, is_square=False)
expected_matrix = math_ops.matmul(
operator1.to_dense(), operator2.to_dense())
actual_operator = operator1.matmul(operator2)
self.assertIsInstance(
actual_operator, block_diag.LinearOperatorBlockDiag)
actual_, expected_ = self.evaluate([
actual_operator.to_dense(), expected_matrix])
self.assertAllClose(actual_, expected_)
def test_block_diag_matmul_raises(self):
matrices1 = []
for i in range(1, 5):
matrices1.append(linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
[2, i], dtype=dtypes.float32)))
operator1 = block_diag.LinearOperatorBlockDiag(matrices1, is_square=False)
operator2 = linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
[15, 3], dtype=dtypes.float32))
with self.assertRaisesRegex(ValueError, "Operators are incompatible"):
operator1.matmul(operator2)
def test_block_diag_solve_type(self):
matrices1 = []
matrices2 = []
for i in range(1, 5):
matrices1.append(linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_tril_matrix(
[i, i],
dtype=dtypes.float32,
force_well_conditioned=True)))
matrices2.append(linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
[i, 3], dtype=dtypes.float32)))
operator1 = block_diag.LinearOperatorBlockDiag(matrices1)
operator2 = block_diag.LinearOperatorBlockDiag(matrices2, is_square=False)
expected_matrix = linalg.solve(
operator1.to_dense(), operator2.to_dense())
actual_operator = operator1.solve(operator2)
self.assertIsInstance(
actual_operator, block_diag.LinearOperatorBlockDiag)
actual_, expected_ = self.evaluate([
actual_operator.to_dense(), expected_matrix])
self.assertAllClose(actual_, expected_)
def test_block_diag_solve_raises(self):
matrices1 = []
for i in range(1, 5):
matrices1.append(linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
[i, i], dtype=dtypes.float32)))
operator1 = block_diag.LinearOperatorBlockDiag(matrices1)
operator2 = linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
[15, 3], dtype=dtypes.float32))
with self.assertRaisesRegex(ValueError, "Operators are incompatible"):
operator1.solve(operator2)
def test_tape_safe(self):
matrices = []
for _ in range(4):
matrices.append(variables_module.Variable(
linear_operator_test_util.random_positive_definite_matrix(
[2, 2], dtype=dtypes.float32, force_well_conditioned=True)))
operator = block_diag.LinearOperatorBlockDiag(
[linalg.LinearOperatorFullMatrix(
matrix, is_self_adjoint=True,
is_positive_definite=True) for matrix in matrices],
is_self_adjoint=True,
is_positive_definite=True,
)
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
matrices = []
for _ in range(3):
matrices.append(variables_module.Variable(
linear_operator_test_util.random_positive_definite_matrix(
[3, 3], dtype=dtypes.float32, force_well_conditioned=True)))
operator = block_diag.LinearOperatorBlockDiag(
[linalg.LinearOperatorFullMatrix(
matrix, is_self_adjoint=True,
is_positive_definite=True) for matrix in matrices],
is_self_adjoint=True,
is_positive_definite=True,
)
with self.cached_session() as sess:
sess.run([x.initializer for x in operator.variables])
self.check_convert_variables_to_tensors(operator)
def test_composite_gradients(self):
with backprop.GradientTape() as tape:
op1 = linalg.LinearOperatorFullMatrix([[1., 0.], [0., 1.]])
op2 = linalg.LinearOperatorDiag([1., 2., 3.])
tape.watch([op1, op2])
operator = block_diag.LinearOperatorBlockDiag([op1, op2])
x = self.make_x(op1, adjoint=False)
y = op1.matmul(x)
connected_grad, disconnected_grad, composite_grad = tape.gradient(
y, [op1, op2, operator]
)
disconnected_component_grad = composite_grad.operators[1].to_dense()
self.assertAllClose(connected_grad.to_dense(),
composite_grad.operators[0].to_dense())
self.assertAllClose(disconnected_component_grad,
array_ops.zeros_like(disconnected_component_grad))
self.assertIsNone(disconnected_grad)
def test_is_non_singular_auto_set(self):
# Matrix with two positive eigenvalues, 11 and 8.
# The matrix values do not effect auto-setting of the flags.
matrix = [[11., 0.], [1., 8.]]
operator_1 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator_2 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator = block_diag.LinearOperatorBlockDiag(
[operator_1, operator_2],
is_positive_definite=False, # No reason it HAS to be False...
is_non_singular=None)
self.assertFalse(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
with self.assertRaisesRegex(ValueError, "always non-singular"):
block_diag.LinearOperatorBlockDiag(
[operator_1, operator_2], is_non_singular=False)
def test_name(self):
matrix = [[11., 0.], [1., 8.]]
operator_1 = linalg.LinearOperatorFullMatrix(matrix, name="left")
operator_2 = linalg.LinearOperatorFullMatrix(matrix, name="right")
operator = block_diag.LinearOperatorBlockDiag([operator_1, operator_2])
self.assertEqual("left_ds_right", operator.name)
def test_different_dtypes_raises(self):
operators = [
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3)),
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3).astype(np.float32))
]
with self.assertRaisesRegex(TypeError, "same dtype"):
block_diag.LinearOperatorBlockDiag(operators)
def test_empty_operators_raises(self):
with self.assertRaisesRegex(ValueError, "non-empty"):
block_diag.LinearOperatorBlockDiag([])
def test_incompatible_input_blocks_raises(self):
matrix_1 = array_ops.placeholder_with_default(rng.rand(4, 4), shape=None)
matrix_2 = array_ops.placeholder_with_default(rng.rand(3, 3), shape=None)
operators = [
linalg.LinearOperatorFullMatrix(matrix_1, is_square=True),
linalg.LinearOperatorFullMatrix(matrix_2, is_square=True)
]
operator = block_diag.LinearOperatorBlockDiag(operators)
x = np.random.rand(2, 4, 5).tolist()
msg = ("dimension does not match" if context.executing_eagerly()
else "input structure is ambiguous")
with self.assertRaisesRegex(ValueError, msg):
operator.matmul(x)
@test_util.run_all_in_graph_and_eager_modes
class NonSquareLinearOperatorBlockDiagTest(
linear_operator_test_util.NonSquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Increase from 1e-6 to 1e-4
self._atol[dtypes.float32] = 1e-4
self._atol[dtypes.complex64] = 1e-4
self._rtol[dtypes.float32] = 1e-4
self._rtol[dtypes.complex64] = 1e-4
super(NonSquareLinearOperatorBlockDiagTest, self).setUp()
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
return [
shape_info((1, 0)),
shape_info((1, 2, 3)),
shape_info((5, 3), blocks=[(2, 1), (3, 2)]),
shape_info((3, 6, 5), blocks=[(1, 2, 1), (3, 1, 2), (1, 3, 2)]),
shape_info((2, 1, 5, 2), blocks=[(2, 1, 2, 1), (1, 3, 1)]),
]
@staticmethod
def skip_these_tests():
return [
"cholesky",
"cond",
"det",
"diag_part",
"eigvalsh",
"inverse",
"log_abs_det",
"solve",
"solve_with_broadcast",
"trace"]
@staticmethod
def use_blockwise_arg():
return True
def operator_and_matrix(
self, shape_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
del ensure_self_adjoint_and_pd
shape = list(shape_info.shape)
expected_blocks = (
shape_info.__dict__["blocks"] if "blocks" in shape_info.__dict__
else [shape])
matrices = [
linear_operator_test_util.random_normal(block_shape, dtype=dtype)
for block_shape in expected_blocks
]
lin_op_matrices = matrices
if use_placeholder:
lin_op_matrices = [
array_ops.placeholder_with_default(
matrix, shape=None) for matrix in matrices]
blocks = []
for l in lin_op_matrices:
blocks.append(
linalg.LinearOperatorFullMatrix(
l,
is_square=False,
is_self_adjoint=False,
is_positive_definite=False))
operator = block_diag.LinearOperatorBlockDiag(blocks)
# Broadcast the shapes.
expected_shape = list(shape_info.shape)
matrices = linear_operator_util.broadcast_matrix_batch_dims(matrices)
block_diag_dense = _block_diag_dense(expected_shape, matrices)
if not use_placeholder:
block_diag_dense.set_shape(expected_shape)
return operator, block_diag_dense
if __name__ == "__main__":
linear_operator_test_util.add_tests(SquareLinearOperatorBlockDiagTest)
linear_operator_test_util.add_tests(NonSquareLinearOperatorBlockDiagTest)
test.main()
@@ -0,0 +1,339 @@
# Copyright 2020 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.eager import backprop
from tensorflow.python.eager import context
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_block_lower_triangular as block_lower_triangular
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.ops.linalg import linear_operator_util
from tensorflow.python.platform import test
linalg = linalg_lib
rng = np.random.RandomState(0)
def _block_lower_triangular_dense(expected_shape, blocks):
"""Convert a list of blocks into a dense blockwise lower-triangular matrix."""
rows = []
num_cols = 0
for row_blocks in blocks:
# Get the batch shape for the block.
batch_row_shape = array_ops.shape(row_blocks[0])[:-1]
num_cols += array_ops.shape(row_blocks[-1])[-1]
zeros_to_pad_after_shape = array_ops.concat(
[batch_row_shape, [expected_shape[-2] - num_cols]], axis=-1)
zeros_to_pad_after = array_ops.zeros(
zeros_to_pad_after_shape, dtype=row_blocks[-1].dtype)
row_blocks.append(zeros_to_pad_after)
rows.append(array_ops.concat(row_blocks, axis=-1))
return array_ops.concat(rows, axis=-2)
@test_util.run_all_in_graph_and_eager_modes
class SquareLinearOperatorBlockLowerTriangularTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Increase from 1e-6 to 1e-5
self._atol[dtypes.float32] = 1e-5
self._atol[dtypes.complex64] = 1e-5
self._rtol[dtypes.float32] = 1e-5
self._rtol[dtypes.complex64] = 1e-5
super(SquareLinearOperatorBlockLowerTriangularTest, self).setUp()
@staticmethod
def use_blockwise_arg():
return True
@staticmethod
def skip_these_tests():
# Skipping since `LinearOperatorBlockLowerTriangular` is in general not
# self-adjoint.
return ["cholesky", "eigvalsh"]
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
return [
shape_info((0, 0)),
shape_info((1, 1)),
shape_info((1, 3, 3)),
shape_info((5, 5), blocks=[[(2, 2)], [(3, 2), (3, 3)]]),
shape_info((3, 7, 7),
blocks=[[(1, 2, 2)], [(1, 3, 2), (3, 3, 3)],
[(1, 2, 2), (1, 2, 3), (1, 2, 2)]]),
shape_info((2, 4, 6, 6),
blocks=[[(2, 1, 2, 2)], [(1, 4, 2), (4, 4, 4)]]),
]
def operator_and_matrix(
self, shape_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
expected_blocks = (
shape_info.__dict__["blocks"] if "blocks" in shape_info.__dict__
else [[list(shape_info.shape)]])
matrices = []
for i, row_shapes in enumerate(expected_blocks):
row = []
for j, block_shape in enumerate(row_shapes):
if i == j: # operator is on the diagonal
row.append(
linear_operator_test_util.random_positive_definite_matrix(
block_shape, dtype, force_well_conditioned=True))
else:
row.append(
linear_operator_test_util.random_normal(block_shape, dtype=dtype))
matrices.append(row)
lin_op_matrices = matrices
if use_placeholder:
lin_op_matrices = [[
array_ops.placeholder_with_default(
matrix, shape=None) for matrix in row] for row in matrices]
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[linalg.LinearOperatorFullMatrix( # pylint:disable=g-complex-comprehension
l,
is_square=True,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
for l in row] for row in lin_op_matrices])
# Should be auto-set.
self.assertTrue(operator.is_square)
# Broadcast the shapes.
expected_shape = list(shape_info.shape)
broadcasted_matrices = linear_operator_util.broadcast_matrix_batch_dims(
[op for row in matrices for op in row]) # pylint: disable=g-complex-comprehension
matrices = [broadcasted_matrices[i * (i + 1) // 2:(i + 1) * (i + 2) // 2]
for i in range(len(matrices))]
block_lower_triangular_dense = _block_lower_triangular_dense(
expected_shape, matrices)
if not use_placeholder:
block_lower_triangular_dense.set_shape(expected_shape)
return operator, block_lower_triangular_dense
def test_is_x_flags(self):
# Matrix with two positive eigenvalues, 1, and 1.
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[linalg.LinearOperatorFullMatrix(matrix)]],
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertFalse(operator.is_self_adjoint)
def test_block_lower_triangular_inverse_type(self):
matrix = [[1., 0.], [0., 1.]]
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)],
[linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True),
linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)]],
is_non_singular=True,
)
inverse = operator.inverse()
self.assertIsInstance(
inverse,
block_lower_triangular.LinearOperatorBlockLowerTriangular)
self.assertEqual(2, len(inverse.operators))
self.assertEqual(1, len(inverse.operators[0]))
self.assertEqual(2, len(inverse.operators[1]))
def test_tape_safe(self):
operator_1 = linalg.LinearOperatorFullMatrix(
variables_module.Variable([[1., 0.], [0., 1.]]),
is_self_adjoint=True,
is_positive_definite=True)
operator_2 = linalg.LinearOperatorFullMatrix(
variables_module.Variable([[2., 0.], [1., 0.]]))
operator_3 = linalg.LinearOperatorFullMatrix(
variables_module.Variable([[3., 1.], [1., 3.]]),
is_self_adjoint=True,
is_positive_definite=True)
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[operator_1], [operator_2, operator_3]],
is_self_adjoint=False,
is_positive_definite=True)
diagonal_grads_only = ["diag_part", "trace", "determinant",
"log_abs_determinant"]
self.check_tape_safe(operator, skip_options=diagonal_grads_only)
for y in diagonal_grads_only:
for diag_block in [operator_1, operator_3]:
with backprop.GradientTape() as tape:
grads = tape.gradient(getattr(operator, y)(), diag_block.variables)
for item in grads:
self.assertIsNotNone(item)
def test_convert_variables_to_tensors(self):
operator_1 = linalg.LinearOperatorFullMatrix(
variables_module.Variable([[1., 0.], [0., 1.]]),
is_self_adjoint=True,
is_positive_definite=True)
operator_2 = linalg.LinearOperatorFullMatrix(
variables_module.Variable([[2., 0.], [1., 0.]]))
operator_3 = linalg.LinearOperatorFullMatrix(
variables_module.Variable([[3., 1.], [1., 3.]]),
is_self_adjoint=True,
is_positive_definite=True)
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[operator_1], [operator_2, operator_3]],
is_self_adjoint=False,
is_positive_definite=True)
with self.cached_session() as sess:
sess.run([x.initializer for x in operator.variables])
self.check_convert_variables_to_tensors(operator)
def test_is_non_singular_auto_set(self):
# Matrix with two positive eigenvalues, 11 and 8.
# The matrix values do not effect auto-setting of the flags.
matrix = [[11., 0.], [1., 8.]]
operator_1 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator_2 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator_3 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[operator_1], [operator_2, operator_3]],
is_positive_definite=False, # No reason it HAS to be False...
is_non_singular=None)
self.assertFalse(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
with self.assertRaisesRegex(ValueError, "always non-singular"):
block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[operator_1], [operator_2, operator_3]], is_non_singular=False)
operator_4 = linalg.LinearOperatorFullMatrix(
[[1., 0.], [2., 0.]], is_non_singular=False)
# A singular operator off of the main diagonal shouldn't raise
block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[operator_1], [operator_4, operator_2]], is_non_singular=True)
with self.assertRaisesRegex(ValueError, "always singular"):
block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[operator_1], [operator_2, operator_4]], is_non_singular=True)
def test_different_dtypes_raises(self):
operators = [
[linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3))],
[linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3)),
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3).astype(np.float32))]
]
with self.assertRaisesRegex(TypeError, "same dtype"):
block_lower_triangular.LinearOperatorBlockLowerTriangular(operators)
def test_non_square_operator_raises(self):
operators = [
[linalg.LinearOperatorFullMatrix(rng.rand(3, 4), is_square=False)],
[linalg.LinearOperatorFullMatrix(rng.rand(4, 4)),
linalg.LinearOperatorFullMatrix(rng.rand(4, 4))]
]
with self.assertRaisesRegex(ValueError, "must be square"):
block_lower_triangular.LinearOperatorBlockLowerTriangular(operators)
def test_empty_operators_raises(self):
with self.assertRaisesRegex(ValueError, "must be a list of >=1"):
block_lower_triangular.LinearOperatorBlockLowerTriangular([])
def test_operators_wrong_length_raises(self):
with self.assertRaisesRegex(ValueError, "must contain `2` blocks"):
block_lower_triangular.LinearOperatorBlockLowerTriangular([
[linalg.LinearOperatorFullMatrix(rng.rand(2, 2))],
[linalg.LinearOperatorFullMatrix(rng.rand(2, 2))
for _ in range(3)]])
def test_operators_mismatched_dimension_raises(self):
operators = [
[linalg.LinearOperatorFullMatrix(rng.rand(3, 3))],
[linalg.LinearOperatorFullMatrix(rng.rand(3, 4)),
linalg.LinearOperatorFullMatrix(rng.rand(3, 3))]
]
with self.assertRaisesRegex(ValueError, "must be the same as"):
block_lower_triangular.LinearOperatorBlockLowerTriangular(operators)
def test_incompatible_input_blocks_raises(self):
matrix_1 = array_ops.placeholder_with_default(rng.rand(4, 4), shape=None)
matrix_2 = array_ops.placeholder_with_default(rng.rand(3, 4), shape=None)
matrix_3 = array_ops.placeholder_with_default(rng.rand(3, 3), shape=None)
operators = [
[linalg.LinearOperatorFullMatrix(matrix_1, is_square=True)],
[linalg.LinearOperatorFullMatrix(matrix_2),
linalg.LinearOperatorFullMatrix(matrix_3, is_square=True)]
]
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
operators)
x = np.random.rand(2, 4, 5).tolist()
msg = ("dimension does not match" if context.executing_eagerly()
else "input structure is ambiguous")
with self.assertRaisesRegex(ValueError, msg):
operator.matmul(x)
def test_composite_gradients(self):
with backprop.GradientTape() as tape:
op1 = linalg.LinearOperatorFullMatrix(rng.rand(4, 4), is_square=True)
op2 = linalg.LinearOperatorFullMatrix(rng.rand(3, 4))
op3 = linalg.LinearOperatorFullMatrix(rng.rand(3, 3), is_square=True)
tape.watch([op1, op2, op3])
operator = block_lower_triangular.LinearOperatorBlockLowerTriangular(
[[op1], [op2, op3]])
x = self.make_x(op1, adjoint=False)
y = op1.matmul(x)
connected_grad, disconnected_grad, composite_grad = tape.gradient(
y, [op1, op3, operator]
)
disconnected_component_grad = composite_grad.operators[1][1].to_dense()
self.assertAllClose(connected_grad.to_dense(),
composite_grad.operators[0][0].to_dense())
self.assertAllClose(disconnected_component_grad,
array_ops.zeros_like(disconnected_component_grad))
self.assertIsNone(disconnected_grad)
if __name__ == "__main__":
linear_operator_test_util.add_tests(
SquareLinearOperatorBlockLowerTriangularTest)
test.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,292 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
rng = np.random.RandomState(0)
class SquareLinearOperatorCompositionTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Increase from 1e-6 to 1e-4 and 2e-4.
self._atol[dtypes.float32] = 2e-4
self._atol[dtypes.complex64] = 1e-4
self._rtol[dtypes.float32] = 2e-4
self._rtol[dtypes.complex64] = 1e-4
@staticmethod
def skip_these_tests():
# Cholesky not implemented.
return ["cholesky"]
def operator_and_matrix(self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
# Either 1 or 2 matrices, depending.
num_operators = rng.randint(low=1, high=3)
if ensure_self_adjoint_and_pd:
# The random PD matrices are also symmetric. Here we are computing
# A @ A ... @ A. Since A is symmetric and PD, so are any powers of it.
matrices = [
linear_operator_test_util.random_positive_definite_matrix(
shape, dtype, force_well_conditioned=True)] * num_operators
else:
matrices = [
linear_operator_test_util.random_positive_definite_matrix(
shape, dtype, force_well_conditioned=True)
for _ in range(num_operators)
]
lin_op_matrices = matrices
if use_placeholder:
lin_op_matrices = [
array_ops.placeholder_with_default(
matrix, shape=None) for matrix in matrices]
operator = linalg.LinearOperatorComposition(
[linalg.LinearOperatorFullMatrix(l) for l in lin_op_matrices],
is_positive_definite=True if ensure_self_adjoint_and_pd else None,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_square=True)
matmul_order_list = list(reversed(matrices))
mat = matmul_order_list[0]
for other_mat in matmul_order_list[1:]:
mat = math_ops.matmul(other_mat, mat)
return operator, mat
def test_is_x_flags(self):
# Matrix with two positive eigenvalues, 1, and 1.
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorComposition(
[linalg.LinearOperatorFullMatrix(matrix)],
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertFalse(operator.is_self_adjoint)
def test_is_non_singular_auto_set(self):
# Matrix with two positive eigenvalues, 11 and 8.
# The matrix values do not effect auto-setting of the flags.
matrix = [[11., 0.], [1., 8.]]
operator_1 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator_2 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator = linalg.LinearOperatorComposition(
[operator_1, operator_2],
is_positive_definite=False, # No reason it HAS to be False...
is_non_singular=None)
self.assertFalse(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
with self.assertRaisesRegex(ValueError, "always non-singular"):
linalg.LinearOperatorComposition(
[operator_1, operator_2], is_non_singular=False)
def test_is_spd_is_auto_set(self):
matrix = [[11., 0.], [1., 8.]]
x = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
y = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator = linalg.LinearOperatorComposition(
[x, y, y.H, x.H], is_non_singular=None)
self.assertTrue(operator.is_self_adjoint)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
with self.assertRaisesRegex(ValueError, "self-adjoint"):
linalg.LinearOperatorComposition([x, x.H], is_self_adjoint=False)
with self.assertRaisesRegex(ValueError, "non-singular"):
linalg.LinearOperatorComposition([x, x.H], is_non_singular=False)
def test_name(self):
matrix = [[11., 0.], [1., 8.]]
operator_1 = linalg.LinearOperatorFullMatrix(matrix, name="left")
operator_2 = linalg.LinearOperatorFullMatrix(matrix, name="right")
operator = linalg.LinearOperatorComposition([operator_1, operator_2])
self.assertEqual("left_o_right", operator.name)
def test_different_dtypes_raises(self):
operators = [
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3)),
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3).astype(np.float32))
]
with self.assertRaisesRegex(TypeError, "same dtype"):
linalg.LinearOperatorComposition(operators)
def test_empty_operators_raises(self):
with self.assertRaisesRegex(ValueError, "non-empty"):
linalg.LinearOperatorComposition([])
class NonSquareLinearOperatorCompositionTest(
linear_operator_test_util.NonSquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Increase from 1e-6 to 1e-4
self._atol[dtypes.float32] = 1e-4
self._atol[dtypes.complex64] = 1e-4
self._rtol[dtypes.float32] = 1e-4
self._rtol[dtypes.complex64] = 1e-4
@staticmethod
def skip_these_tests():
# Testing the condition number fails when using XLA with cuBLASLt
# A slight numerical difference between different matmul algorithms
# leads to large precision issues
return linear_operator_test_util.NonSquareLinearOperatorDerivedClassTest.skip_these_tests(
) + ["cond"]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
del ensure_self_adjoint_and_pd
shape = list(build_info.shape)
# Create 2 matrices/operators, A1, A2, which becomes A = A1 A2.
# Use inner dimension of 2.
k = 2
batch_shape = shape[:-2]
shape_1 = batch_shape + [shape[-2], k]
shape_2 = batch_shape + [k, shape[-1]]
# Ensure that the matrices are well-conditioned by generating
# random matrices whose singular values are close to 1.
# The reason to do this is because cond(AB) <= cond(A) * cond(B).
# By ensuring that each factor has condition number close to 1, we ensure
# that the condition number of the product isn't too far away from 1.
def generate_well_conditioned(shape, dtype):
m, n = shape[-2], shape[-1]
min_dim = min(m, n)
# Generate singular values that are close to 1.
d = linear_operator_test_util.random_normal(
shape[:-2] + [min_dim],
mean=1.,
stddev=0.1,
dtype=dtype)
zeros = array_ops.zeros(shape=shape[:-2] + [m, n], dtype=dtype)
d = linalg_lib.set_diag(zeros, d)
u, _ = linalg_lib.qr(linear_operator_test_util.random_normal(
shape[:-2] + [m, m], dtype=dtype))
v, _ = linalg_lib.qr(linear_operator_test_util.random_normal(
shape[:-2] + [n, n], dtype=dtype))
return math_ops.matmul(u, math_ops.matmul(d, v))
matrices = [
generate_well_conditioned(shape_1, dtype=dtype),
generate_well_conditioned(shape_2, dtype=dtype),
]
lin_op_matrices = matrices
if use_placeholder:
lin_op_matrices = [
array_ops.placeholder_with_default(
matrix, shape=None) for matrix in matrices]
operator = linalg.LinearOperatorComposition(
[linalg.LinearOperatorFullMatrix(l) for l in lin_op_matrices])
matmul_order_list = list(reversed(matrices))
mat = matmul_order_list[0]
for other_mat in matmul_order_list[1:]:
mat = math_ops.matmul(other_mat, mat)
return operator, mat
@test_util.run_deprecated_v1
def test_static_shapes(self):
operators = [
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 4)),
linalg.LinearOperatorFullMatrix(rng.rand(2, 4, 5))
]
operator = linalg.LinearOperatorComposition(operators)
self.assertAllEqual((2, 3, 5), operator.shape)
@test_util.run_deprecated_v1
def test_shape_tensors_when_statically_available(self):
operators = [
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 4)),
linalg.LinearOperatorFullMatrix(rng.rand(2, 4, 5))
]
operator = linalg.LinearOperatorComposition(operators)
with self.cached_session():
self.assertAllEqual((2, 3, 5), operator.shape_tensor())
@test_util.run_deprecated_v1
def test_shape_tensors_when_only_dynamically_available(self):
mat_1 = rng.rand(1, 2, 3, 4)
mat_2 = rng.rand(1, 2, 4, 5)
mat_ph_1 = array_ops.placeholder(dtypes.float64)
mat_ph_2 = array_ops.placeholder(dtypes.float64)
feed_dict = {mat_ph_1: mat_1, mat_ph_2: mat_2}
operators = [
linalg.LinearOperatorFullMatrix(mat_ph_1),
linalg.LinearOperatorFullMatrix(mat_ph_2)
]
operator = linalg.LinearOperatorComposition(operators)
with self.cached_session():
self.assertAllEqual(
(1, 2, 3, 5), operator.shape_tensor().eval(feed_dict=feed_dict))
@test_util.run_deprecated_v1
def test_is_square_set_for_aat_form(self):
mat_ph = array_ops.placeholder(dtypes.float64) # No shape set at all.
x = linalg.LinearOperatorFullMatrix(mat_ph, is_square=False)
operator = linalg.LinearOperatorComposition([x, x.H])
self.assertTrue(operator.is_square)
if __name__ == "__main__":
linear_operator_test_util.add_tests(SquareLinearOperatorCompositionTest)
linear_operator_test_util.add_tests(NonSquareLinearOperatorCompositionTest)
test.main()
@@ -0,0 +1,264 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from tensorflow.python.framework import config
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorDiagTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
@staticmethod
def optional_tests():
"""List of optional test names to run."""
return [
"operator_matmul_with_same_type",
"operator_solve_with_same_type",
]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
diag = linear_operator_test_util.random_sign_uniform(
shape[:-1], minval=1., maxval=2., dtype=dtype)
if ensure_self_adjoint_and_pd:
# Abs on complex64 will result in a float32, so we cast back up.
diag = math_ops.cast(math_ops.abs(diag), dtype=dtype)
lin_op_diag = diag
if use_placeholder:
lin_op_diag = array_ops.placeholder_with_default(diag, shape=None)
operator = linalg.LinearOperatorDiag(
lin_op_diag,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
matrix = array_ops.matrix_diag(diag)
return operator, matrix
def test_assert_positive_definite_raises_for_zero_eigenvalue(self):
# Matrix with one positive eigenvalue and one zero eigenvalue.
with self.cached_session():
diag = [1.0, 0.0]
operator = linalg.LinearOperatorDiag(diag)
# is_self_adjoint should be auto-set for real diag.
self.assertTrue(operator.is_self_adjoint)
with self.assertRaisesOpError("non-positive.*not positive definite"):
operator.assert_positive_definite().run()
def test_assert_positive_definite_raises_for_negative_real_eigvalues(self):
with self.cached_session():
diag_x = [1.0, -2.0]
diag_y = [0., 0.] # Imaginary eigenvalues should not matter.
diag = math_ops.complex(diag_x, diag_y)
operator = linalg.LinearOperatorDiag(diag)
# is_self_adjoint should not be auto-set for complex diag.
self.assertTrue(operator.is_self_adjoint is None)
with self.assertRaisesOpError("non-positive real.*not positive definite"):
operator.assert_positive_definite().run()
def test_assert_positive_definite_does_not_raise_if_pd_and_complex(self):
with self.cached_session():
x = [1., 2.]
y = [1., 0.]
diag = math_ops.complex(x, y) # Re[diag] > 0.
# Should not fail
self.evaluate(linalg.LinearOperatorDiag(diag).assert_positive_definite())
def test_assert_non_singular_raises_if_zero_eigenvalue(self):
# Singular matrix with one positive eigenvalue and one zero eigenvalue.
with self.cached_session():
diag = [1.0, 0.0]
operator = linalg.LinearOperatorDiag(diag, is_self_adjoint=True)
with self.assertRaisesOpError("Singular operator"):
operator.assert_non_singular().run()
def test_assert_non_singular_does_not_raise_for_complex_nonsingular(self):
with self.cached_session():
x = [1., 0.]
y = [0., 1.]
diag = math_ops.complex(x, y)
# Should not raise.
self.evaluate(linalg.LinearOperatorDiag(diag).assert_non_singular())
def test_assert_self_adjoint_raises_if_diag_has_complex_part(self):
with self.cached_session():
x = [1., 0.]
y = [0., 1.]
diag = math_ops.complex(x, y)
operator = linalg.LinearOperatorDiag(diag)
with self.assertRaisesOpError("imaginary.*not self-adjoint"):
operator.assert_self_adjoint().run()
def test_assert_self_adjoint_does_not_raise_for_diag_with_zero_imag(self):
with self.cached_session():
x = [1., 0.]
y = [0., 0.]
diag = math_ops.complex(x, y)
operator = linalg.LinearOperatorDiag(diag)
# Should not raise
self.evaluate(operator.assert_self_adjoint())
def test_scalar_diag_raises(self):
with self.assertRaisesRegex(ValueError, "must have at least 1 dimension"):
linalg.LinearOperatorDiag(1.)
def test_broadcast_matmul_and_solve(self):
# These cannot be done in the automated (base test class) tests since they
# test shapes that tf.matmul cannot handle.
# In particular, tf.matmul does not broadcast.
with self.cached_session() as sess:
x = random_ops.random_normal(shape=(2, 2, 3, 4))
# This LinearOperatorDiag will be broadcast to (2, 2, 3, 3) during solve
# and matmul with 'x' as the argument.
diag = random_ops.random_uniform(shape=(2, 1, 3))
operator = linalg.LinearOperatorDiag(diag, is_self_adjoint=True)
self.assertAllEqual((2, 1, 3, 3), operator.shape)
# Create a batch matrix with the broadcast shape of operator.
diag_broadcast = array_ops.concat((diag, diag), 1)
mat = array_ops.matrix_diag(diag_broadcast)
self.assertAllEqual((2, 2, 3, 3), mat.shape) # being pedantic.
operator_matmul = operator.matmul(x)
mat_matmul = math_ops.matmul(mat, x)
self.assertAllEqual(operator_matmul.shape, mat_matmul.shape)
self.assertAllClose(*self.evaluate([operator_matmul, mat_matmul]))
operator_solve = operator.solve(x)
mat_solve = linalg_ops.matrix_solve(mat, x)
self.assertAllEqual(operator_solve.shape, mat_solve.shape)
self.assertAllClose(*self.evaluate([operator_solve, mat_solve]))
def test_diag_matmul(self):
operator1 = linalg_lib.LinearOperatorDiag([2., 3.])
operator2 = linalg_lib.LinearOperatorDiag([1., 2.])
operator3 = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=3.)
operator_matmul = operator1.matmul(operator2)
self.assertTrue(isinstance(
operator_matmul,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([2., 6.], self.evaluate(operator_matmul.diag))
operator_matmul = operator2.matmul(operator1)
self.assertTrue(isinstance(
operator_matmul,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([2., 6.], self.evaluate(operator_matmul.diag))
operator_matmul = operator1.matmul(operator3)
self.assertTrue(isinstance(
operator_matmul,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([6., 9.], self.evaluate(operator_matmul.diag))
operator_matmul = operator3.matmul(operator1)
self.assertTrue(isinstance(
operator_matmul,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([6., 9.], self.evaluate(operator_matmul.diag))
def test_diag_solve(self):
operator1 = linalg_lib.LinearOperatorDiag([2., 3.], is_non_singular=True)
operator2 = linalg_lib.LinearOperatorDiag([1., 2.], is_non_singular=True)
operator3 = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=3., is_non_singular=True)
operator_solve = operator1.solve(operator2)
self.assertTrue(isinstance(
operator_solve,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([0.5, 2 / 3.], self.evaluate(operator_solve.diag))
operator_solve = operator2.solve(operator1)
self.assertTrue(isinstance(
operator_solve,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([2., 3 / 2.], self.evaluate(operator_solve.diag))
operator_solve = operator1.solve(operator3)
self.assertTrue(isinstance(
operator_solve,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([3 / 2., 1.], self.evaluate(operator_solve.diag))
operator_solve = operator3.solve(operator1)
self.assertTrue(isinstance(
operator_solve,
linalg_lib.LinearOperatorDiag))
self.assertAllClose([2 / 3., 1.], self.evaluate(operator_solve.diag))
def test_diag_adjoint_type(self):
diag = [1., 3., 5., 8.]
operator = linalg.LinearOperatorDiag(diag, is_non_singular=True)
self.assertIsInstance(operator.adjoint(), linalg.LinearOperatorDiag)
def test_diag_cholesky_type(self):
diag = [1., 3., 5., 8.]
operator = linalg.LinearOperatorDiag(
diag,
is_positive_definite=True,
is_self_adjoint=True,
)
self.assertIsInstance(operator.cholesky(), linalg.LinearOperatorDiag)
def test_diag_inverse_type(self):
diag = [1., 3., 5., 8.]
operator = linalg.LinearOperatorDiag(diag, is_non_singular=True)
self.assertIsInstance(operator.inverse(), linalg.LinearOperatorDiag)
def test_tape_safe(self):
diag = variables_module.Variable([[2.]])
operator = linalg.LinearOperatorDiag(diag)
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
diag = variables_module.Variable([[2.]])
operator = linalg.LinearOperatorDiag(diag)
with self.cached_session() as sess:
sess.run([diag.initializer])
self.check_convert_variables_to_tensors(operator)
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorDiagTest)
test.main()
@@ -0,0 +1,271 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
@test_util.run_all_in_graph_and_eager_modes
class SquareLinearOperatorFullMatrixTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
matrix = linear_operator_test_util.random_positive_definite_matrix(
shape, dtype)
lin_op_matrix = matrix
if use_placeholder:
lin_op_matrix = array_ops.placeholder_with_default(matrix, shape=None)
# Set the hints to none to test non-symmetric PD code paths.
operator = linalg.LinearOperatorFullMatrix(
lin_op_matrix,
is_square=True,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
return operator, matrix
def test_is_x_flags(self):
# Matrix with two positive eigenvalues.
matrix = [[1., 0.], [1., 11.]]
operator = linalg.LinearOperatorFullMatrix(
matrix,
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertFalse(operator.is_self_adjoint)
# Auto-detected.
self.assertTrue(operator.is_square)
def test_assert_non_singular_raises_if_cond_too_big_but_finite(self):
with self.cached_session():
tril = linear_operator_test_util.random_tril_matrix(
shape=(50, 50), dtype=np.float32)
diag = np.logspace(-2, 2, 50).astype(np.float32)
tril = array_ops.matrix_set_diag(tril, diag)
matrix = self.evaluate(math_ops.matmul(tril, tril, transpose_b=True))
operator = linalg.LinearOperatorFullMatrix(matrix)
with self.assertRaisesOpError("Singular matrix"):
# Ensure that we have finite condition number...just HUGE.
cond = np.linalg.cond(matrix)
self.assertTrue(np.isfinite(cond))
self.assertGreater(cond, 1e12)
operator.assert_non_singular().run()
def test_assert_non_singular_raises_if_cond_infinite(self):
with self.cached_session():
matrix = [[1., 1.], [1., 1.]]
# We don't pass the is_self_adjoint hint here, which means we take the
# generic code path.
operator = linalg.LinearOperatorFullMatrix(matrix)
with self.assertRaisesOpError("Singular matrix"):
operator.assert_non_singular().run()
def test_assert_self_adjoint(self):
matrix = [[0., 1.], [0., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix)
with self.cached_session():
with self.assertRaisesOpError("not equal to its adjoint"):
operator.assert_self_adjoint().run()
@test_util.disable_xla("Assert statements in kernels not supported in XLA")
def test_assert_positive_definite(self):
matrix = [[1., 1.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix, is_self_adjoint=True)
with self.cached_session():
with self.assertRaises(errors.InvalidArgumentError):
operator.assert_positive_definite().run()
def test_tape_safe(self):
matrix = variables_module.Variable([[2.]])
operator = linalg.LinearOperatorFullMatrix(matrix)
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
matrix = variables_module.Variable([[3.]])
operator = linalg.LinearOperatorFullMatrix(matrix)
with self.cached_session() as sess:
sess.run([matrix.initializer])
self.check_convert_variables_to_tensors(operator)
@test_util.run_all_in_graph_and_eager_modes
class SquareLinearOperatorFullMatrixSymmetricPositiveDefiniteTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest.
In this test, the operator is constructed with hints that invoke the use of
a Cholesky decomposition for solves/determinant.
"""
def setUp(self):
# Increase from 1e-6 to 1e-5. This reduction in tolerance happens,
# presumably, because we are taking a different code path in the operator
# and the matrix. The operator uses a Cholesky, the matrix uses standard
# solve.
self._atol[dtypes.float32] = 1e-5
self._rtol[dtypes.float32] = 1e-5
self._atol[dtypes.float64] = 1e-10
self._rtol[dtypes.float64] = 1e-10
@staticmethod
def dtypes_to_test():
return [dtypes.float32, dtypes.float64]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
# Matrix is always symmetric and positive definite in this class.
del ensure_self_adjoint_and_pd
shape = list(build_info.shape)
matrix = linear_operator_test_util.random_positive_definite_matrix(
shape, dtype, force_well_conditioned=True)
lin_op_matrix = matrix
if use_placeholder:
lin_op_matrix = array_ops.placeholder_with_default(matrix, shape=None)
operator = linalg.LinearOperatorFullMatrix(
lin_op_matrix,
is_square=True,
is_self_adjoint=True,
is_positive_definite=True)
return operator, matrix
def test_is_x_flags(self):
# Matrix with two positive eigenvalues.
matrix = [[1., 0.], [0., 7.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, is_positive_definite=True, is_self_adjoint=True)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_self_adjoint)
# Should be auto-set
self.assertTrue(operator.is_non_singular)
self.assertTrue(operator._can_use_cholesky)
self.assertTrue(operator.is_square)
@test_util.disable_xla("Assert statements in kernels not supported in XLA")
def test_assert_non_singular(self):
matrix = [[1., 1.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, is_self_adjoint=True, is_positive_definite=True)
with self.cached_session():
# Cholesky decomposition may fail, so the error is not specific to
# non-singular.
with self.assertRaisesOpError(""):
operator.assert_non_singular().run()
def test_assert_self_adjoint(self):
matrix = [[0., 1.], [0., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, is_self_adjoint=True, is_positive_definite=True)
with self.cached_session():
with self.assertRaisesOpError("not equal to its adjoint"):
operator.assert_self_adjoint().run()
@test_util.disable_xla("Assert statements in kernels not supported in XLA")
def test_assert_positive_definite(self):
matrix = [[1., 1.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, is_self_adjoint=True, is_positive_definite=True)
with self.cached_session():
# Cholesky decomposition may fail, so the error is not specific to
# non-singular.
with self.assertRaisesOpError(""):
operator.assert_positive_definite().run()
def test_tape_safe(self):
matrix = variables_module.Variable([[2.]])
operator = linalg.LinearOperatorFullMatrix(
matrix, is_self_adjoint=True, is_positive_definite=True)
self.check_tape_safe(operator)
@test_util.run_all_in_graph_and_eager_modes
class NonSquareLinearOperatorFullMatrixTest(
linear_operator_test_util.NonSquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
del ensure_self_adjoint_and_pd
shape = list(build_info.shape)
matrix = linear_operator_test_util.random_normal(shape, dtype=dtype)
lin_op_matrix = matrix
if use_placeholder:
lin_op_matrix = array_ops.placeholder_with_default(matrix, shape=None)
operator = linalg.LinearOperatorFullMatrix(lin_op_matrix, is_square=True)
return operator, matrix
def test_is_x_flags(self):
matrix = [[3., 2., 1.], [1., 1., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix,
is_self_adjoint=False)
self.assertEqual(operator.is_positive_definite, None)
self.assertEqual(operator.is_non_singular, None)
self.assertFalse(operator.is_self_adjoint)
self.assertFalse(operator.is_square)
def test_matrix_must_have_at_least_two_dims_or_raises(self):
with self.assertRaisesRegex(ValueError, "at least 2 dimensions"):
linalg.LinearOperatorFullMatrix([1.])
def test_tape_safe(self):
matrix = variables_module.Variable([[2., 1.]])
operator = linalg.LinearOperatorFullMatrix(matrix)
self.check_tape_safe(operator)
if __name__ == "__main__":
config.enable_tensor_float_32_execution(False)
linear_operator_test_util.add_tests(SquareLinearOperatorFullMatrixTest)
linear_operator_test_util.add_tests(NonSquareLinearOperatorFullMatrixTest)
linear_operator_test_util.add_tests(
SquareLinearOperatorFullMatrixSymmetricPositiveDefiniteTest)
test.main()
@@ -0,0 +1,121 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from tensorflow.python.framework import config
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_householder as householder
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
CheckTapeSafeSkipOptions = linear_operator_test_util.CheckTapeSafeSkipOptions
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorHouseholderTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
return [
shape_info((1, 1)),
shape_info((1, 3, 3)),
shape_info((3, 4, 4)),
shape_info((2, 1, 4, 4))]
@staticmethod
def skip_these_tests():
# This linear operator is never positive definite.
return ["cholesky"]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
reflection_axis = linear_operator_test_util.random_sign_uniform(
shape[:-1], minval=1., maxval=2., dtype=dtype)
# Make sure unit norm.
reflection_axis = reflection_axis / linalg_ops.norm(
reflection_axis, axis=-1, keepdims=True)
lin_op_reflection_axis = reflection_axis
if use_placeholder:
lin_op_reflection_axis = array_ops.placeholder_with_default(
reflection_axis, shape=None)
operator = householder.LinearOperatorHouseholder(lin_op_reflection_axis)
mat = reflection_axis[..., array_ops.newaxis]
matrix = -2 * math_ops.matmul(mat, mat, adjoint_b=True)
matrix = array_ops.matrix_set_diag(
matrix, 1. + array_ops.matrix_diag_part(matrix))
return operator, matrix
def test_scalar_reflection_axis_raises(self):
with self.assertRaisesRegex(ValueError, "must have at least 1 dimension"):
householder.LinearOperatorHouseholder(1.)
def test_householder_adjoint_type(self):
reflection_axis = [1., 3., 5., 8.]
operator = householder.LinearOperatorHouseholder(reflection_axis)
self.assertIsInstance(
operator.adjoint(), householder.LinearOperatorHouseholder)
def test_householder_inverse_type(self):
reflection_axis = [1., 3., 5., 8.]
operator = householder.LinearOperatorHouseholder(reflection_axis)
self.assertIsInstance(
operator.inverse(), householder.LinearOperatorHouseholder)
def test_tape_safe(self):
reflection_axis = variables_module.Variable([1., 3., 5., 8.])
operator = householder.LinearOperatorHouseholder(reflection_axis)
self.check_tape_safe(
operator,
skip_options=[
# Determinant hard-coded as 1.
CheckTapeSafeSkipOptions.DETERMINANT,
CheckTapeSafeSkipOptions.LOG_ABS_DETERMINANT,
# Trace hard-coded.
CheckTapeSafeSkipOptions.TRACE,
])
def test_convert_variables_to_tensors(self):
reflection_axis = variables_module.Variable([1., 3., 5., 8.])
operator = householder.LinearOperatorHouseholder(reflection_axis)
with self.cached_session() as sess:
sess.run([reflection_axis.initializer])
self.check_convert_variables_to_tensors(operator)
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorHouseholderTest)
test.main()
@@ -0,0 +1,598 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
rng = np.random.RandomState(2016)
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorIdentityTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
@staticmethod
def dtypes_to_test():
# TODO(langmore) Test tf.float16 once tf.linalg.solve works in
# 16bit.
return [dtypes.float32, dtypes.float64, dtypes.complex64, dtypes.complex128]
@staticmethod
def optional_tests():
"""List of optional test names to run."""
return [
"operator_matmul_with_same_type",
"operator_solve_with_same_type",
]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
# Identity matrix is already Hermitian Positive Definite.
del ensure_self_adjoint_and_pd
shape = list(build_info.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)
return operator, mat
def test_assert_positive_definite(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorIdentity(num_rows=2)
self.evaluate(operator.assert_positive_definite()) # Should not fail
def test_assert_non_singular(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorIdentity(num_rows=2)
self.evaluate(operator.assert_non_singular()) # Should not fail
def test_assert_self_adjoint(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorIdentity(num_rows=2)
self.evaluate(operator.assert_self_adjoint()) # Should not fail
def test_float16_matmul(self):
# float16 cannot be tested by base test class because tf.linalg.solve does
# not work with float16.
with self.cached_session():
operator = linalg_lib.LinearOperatorIdentity(
num_rows=2, dtype=dtypes.float16)
x = rng.randn(2, 3).astype(np.float16)
y = operator.matmul(x)
self.assertAllClose(x, self.evaluate(y))
def test_non_scalar_num_rows_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be a 0-D Tensor"):
linalg_lib.LinearOperatorIdentity(num_rows=[2])
def test_non_integer_num_rows_raises_static(self):
with self.assertRaisesRegex(TypeError, "must be integer"):
linalg_lib.LinearOperatorIdentity(num_rows=2.)
def test_negative_num_rows_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be non-negative"):
linalg_lib.LinearOperatorIdentity(num_rows=-2)
def test_non_1d_batch_shape_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be a 1-D"):
linalg_lib.LinearOperatorIdentity(num_rows=2, batch_shape=2)
def test_non_integer_batch_shape_raises_static(self):
with self.assertRaisesRegex(TypeError, "must be integer"):
linalg_lib.LinearOperatorIdentity(num_rows=2, batch_shape=[2.])
def test_negative_batch_shape_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be non-negative"):
linalg_lib.LinearOperatorIdentity(num_rows=2, batch_shape=[-2])
def test_non_scalar_num_rows_raises_dynamic(self):
with self.cached_session():
num_rows = array_ops.placeholder_with_default([2], shape=None)
with self.assertRaisesError("must be a 0-D Tensor"):
operator = linalg_lib.LinearOperatorIdentity(
num_rows, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
def test_negative_num_rows_raises_dynamic(self):
with self.cached_session():
num_rows = array_ops.placeholder_with_default(-2, shape=None)
with self.assertRaisesError("must be non-negative"):
operator = linalg_lib.LinearOperatorIdentity(
num_rows, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
def test_non_1d_batch_shape_raises_dynamic(self):
with self.cached_session():
batch_shape = array_ops.placeholder_with_default(2, shape=None)
with self.assertRaisesError("must be a 1-D"):
operator = linalg_lib.LinearOperatorIdentity(
num_rows=2, batch_shape=batch_shape, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
def test_negative_batch_shape_raises_dynamic(self):
with self.cached_session():
batch_shape = array_ops.placeholder_with_default([-2], shape=None)
with self.assertRaisesError("must be non-negative"):
operator = linalg_lib.LinearOperatorIdentity(
num_rows=2, batch_shape=batch_shape, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
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.assertRaisesRegex(ValueError, "Dimensions.*not compatible"):
operator.matmul(x)
def test_wrong_matrix_dimensions_raises_dynamic(self):
num_rows = array_ops.placeholder_with_default(2, shape=None)
x = array_ops.placeholder_with_default(
rng.rand(3, 3).astype(np.float32), shape=None)
with self.cached_session():
with self.assertRaisesError("Dimensions.*not.compatible"):
operator = linalg_lib.LinearOperatorIdentity(
num_rows, assert_proper_shapes=True)
self.evaluate(operator.matmul(x))
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.cached_session() as sess:
x = random_ops.random_normal(shape=(1, 2, 3, 4))
operator = linalg_lib.LinearOperatorIdentity(num_rows=3, dtype=x.dtype)
operator_matmul = operator.matmul(x)
expected = x
self.assertAllEqual(operator_matmul.shape, expected.shape)
self.assertAllClose(*self.evaluate([operator_matmul, 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.cached_session():
x = array_ops.placeholder_with_default(rng.randn(1, 2, 3, 4), shape=None)
operator = linalg_lib.LinearOperatorIdentity(num_rows=3, dtype=x.dtype)
operator_matmul = operator.matmul(x)
expected = x
self.assertAllClose(*self.evaluate([operator_matmul, expected]))
def test_broadcast_matmul_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.cached_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 matmul and solve.
expected = x + zeros
operator_matmul = operator.matmul(x)
self.assertAllEqual(operator_matmul.shape, expected.shape)
self.assertAllClose(*self.evaluate([operator_matmul, expected]))
def test_broadcast_matmul_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.cached_session():
# 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_with_default(rng.rand(1, 2, 3, 4), shape=None)
num_rows = array_ops.placeholder_with_default(3, shape=None)
batch_shape = array_ops.placeholder_with_default((2, 1), shape=None)
operator = linalg_lib.LinearOperatorIdentity(
num_rows, batch_shape=batch_shape, dtype=dtypes.float64)
# 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 matmul and solve.
expected = x + zeros
operator_matmul = operator.matmul(x)
self.assertAllClose(*self.evaluate([operator_matmul, expected]))
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.assertRaisesRegex(ValueError, "is always non-singular"):
operator = linalg_lib.LinearOperatorIdentity(
num_rows=2,
is_non_singular=None,
)
def test_identity_adjoint_type(self):
operator = linalg_lib.LinearOperatorIdentity(
num_rows=2, is_non_singular=True)
self.assertIsInstance(
operator.adjoint(), linalg_lib.LinearOperatorIdentity)
def test_identity_cholesky_type(self):
operator = linalg_lib.LinearOperatorIdentity(
num_rows=2,
is_positive_definite=True,
is_self_adjoint=True,
)
self.assertIsInstance(
operator.cholesky(), linalg_lib.LinearOperatorIdentity)
def test_identity_inverse_type(self):
operator = linalg_lib.LinearOperatorIdentity(
num_rows=2, is_non_singular=True)
self.assertIsInstance(
operator.inverse(), linalg_lib.LinearOperatorIdentity)
def test_ref_type_shape_args_raises(self):
with self.assertRaisesRegex(TypeError, "num_rows.*reference"):
linalg_lib.LinearOperatorIdentity(num_rows=variables_module.Variable(2))
with self.assertRaisesRegex(TypeError, "batch_shape.*reference"):
linalg_lib.LinearOperatorIdentity(
num_rows=2, batch_shape=variables_module.Variable([3]))
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorScaledIdentityTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
@staticmethod
def dtypes_to_test():
# TODO(langmore) Test tf.float16 once tf.linalg.solve works in
# 16bit.
return [dtypes.float32, dtypes.float64, dtypes.complex64, dtypes.complex128]
@staticmethod
def optional_tests():
"""List of optional test names to run."""
return [
"operator_matmul_with_same_type",
"operator_solve_with_same_type",
]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
assert shape[-1] == shape[-2]
batch_shape = shape[:-2]
num_rows = shape[-1]
# Uniform values that are at least length 1 from the origin. Allows the
# operator to be well conditioned.
# Shape batch_shape
multiplier = linear_operator_test_util.random_sign_uniform(
shape=batch_shape, minval=1., maxval=2., dtype=dtype)
if ensure_self_adjoint_and_pd:
# Abs on complex64 will result in a float32, so we cast back up.
multiplier = math_ops.cast(math_ops.abs(multiplier), dtype=dtype)
# Nothing to feed since LinearOperatorScaledIdentity takes no Tensor args.
lin_op_multiplier = multiplier
if use_placeholder:
lin_op_multiplier = array_ops.placeholder_with_default(
multiplier, shape=None)
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows,
lin_op_multiplier,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
multiplier_matrix = array_ops.expand_dims(
array_ops.expand_dims(multiplier, -1), -1)
matrix = multiplier_matrix * linalg_ops.eye(
num_rows, batch_shape=batch_shape, dtype=dtype)
return operator, matrix
def test_assert_positive_definite_does_not_raise_when_positive(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=1.)
self.evaluate(operator.assert_positive_definite()) # Should not fail
def test_assert_positive_definite_raises_when_negative(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=-1.)
with self.assertRaisesOpError("not positive definite"):
self.evaluate(operator.assert_positive_definite())
def test_assert_non_singular_does_not_raise_when_non_singular(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=[1., 2., 3.])
self.evaluate(operator.assert_non_singular()) # Should not fail
def test_assert_non_singular_raises_when_singular(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=[1., 2., 0.])
with self.assertRaisesOpError("was singular"):
self.evaluate(operator.assert_non_singular())
def test_assert_self_adjoint_does_not_raise_when_self_adjoint(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=[1. + 0J])
self.evaluate(operator.assert_self_adjoint()) # Should not fail
def test_assert_self_adjoint_raises_when_not_self_adjoint(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=[1. + 1J])
with self.assertRaisesOpError("not self-adjoint"):
self.evaluate(operator.assert_self_adjoint())
def test_float16_matmul(self):
# float16 cannot be tested by base test class because tf.linalg.solve does
# not work with float16.
with self.cached_session():
multiplier = rng.rand(3).astype(np.float16)
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=multiplier)
x = rng.randn(2, 3).astype(np.float16)
y = operator.matmul(x)
self.assertAllClose(multiplier[..., None, None] * x, self.evaluate(y))
def test_non_scalar_num_rows_raises_static(self):
# Many "test_...num_rows" tests are performed in LinearOperatorIdentity.
with self.assertRaisesRegex(ValueError, "must be a 0-D Tensor"):
linalg_lib.LinearOperatorScaledIdentity(
num_rows=[2], multiplier=123.)
def test_wrong_matrix_dimensions_raises_static(self):
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=2.2)
x = rng.randn(3, 3).astype(np.float32)
with self.assertRaisesRegex(ValueError, "Dimensions.*not compatible"):
operator.matmul(x)
def test_wrong_matrix_dimensions_raises_dynamic(self):
num_rows = array_ops.placeholder_with_default(2, shape=None)
x = array_ops.placeholder_with_default(
rng.rand(3, 3).astype(np.float32), shape=None)
with self.cached_session():
with self.assertRaisesError("Dimensions.*not.compatible"):
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows,
multiplier=[1., 2],
assert_proper_shapes=True)
self.evaluate(operator.matmul(x))
def test_broadcast_matmul_and_solve(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.cached_session() as sess:
# Given this x and LinearOperatorScaledIdentity 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 is 2.2 * identity (with a batch shape).
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=3, multiplier=2.2 * array_ops.ones((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)
# Test matmul
expected = x * 2.2 + zeros
operator_matmul = operator.matmul(x)
self.assertAllEqual(operator_matmul.shape, expected.shape)
self.assertAllClose(*self.evaluate([operator_matmul, expected]))
# Test solve
expected = x / 2.2 + zeros
operator_solve = operator.solve(x)
self.assertAllEqual(operator_solve.shape, expected.shape)
self.assertAllClose(*self.evaluate([operator_solve, expected]))
def test_broadcast_matmul_and_solve_scalar_scale_multiplier(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.cached_session() as sess:
# Given this x and LinearOperatorScaledIdentity shape of (3, 3), the
# broadcast shape of operator and 'x' is (1, 2, 3, 4), which is the same
# shape as x.
x = random_ops.random_normal(shape=(1, 2, 3, 4))
# operator is 2.2 * identity (with a batch shape).
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=3, multiplier=2.2)
# Test matmul
expected = x * 2.2
operator_matmul = operator.matmul(x)
self.assertAllEqual(operator_matmul.shape, expected.shape)
self.assertAllClose(*self.evaluate([operator_matmul, expected]))
# Test solve
expected = x / 2.2
operator_solve = operator.solve(x)
self.assertAllEqual(operator_solve.shape, expected.shape)
self.assertAllClose(*self.evaluate([operator_solve, expected]))
def test_is_x_flags(self):
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=1.,
is_positive_definite=False, is_non_singular=True)
self.assertFalse(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertTrue(operator.is_self_adjoint) # Auto-set due to real multiplier
def test_identity_adjoint_type(self):
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=1., is_non_singular=True)
self.assertIsInstance(
operator.adjoint(), linalg_lib.LinearOperatorScaledIdentity)
def test_identity_matmul(self):
operator1 = linalg_lib.LinearOperatorIdentity(num_rows=2)
operator2 = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=3.)
self.assertIsInstance(
operator1.matmul(operator1),
linalg_lib.LinearOperatorIdentity)
self.assertIsInstance(
operator1.matmul(operator1),
linalg_lib.LinearOperatorIdentity)
self.assertIsInstance(
operator2.matmul(operator2),
linalg_lib.LinearOperatorScaledIdentity)
operator_matmul = operator1.matmul(operator2)
self.assertIsInstance(
operator_matmul,
linalg_lib.LinearOperatorScaledIdentity)
self.assertAllClose(3., self.evaluate(operator_matmul.multiplier))
operator_matmul = operator2.matmul(operator1)
self.assertIsInstance(
operator_matmul,
linalg_lib.LinearOperatorScaledIdentity)
self.assertAllClose(3., self.evaluate(operator_matmul.multiplier))
def test_identity_solve(self):
operator1 = linalg_lib.LinearOperatorIdentity(num_rows=2)
operator2 = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=3.)
self.assertIsInstance(
operator1.solve(operator1),
linalg_lib.LinearOperatorIdentity)
self.assertIsInstance(
operator2.solve(operator2),
linalg_lib.LinearOperatorScaledIdentity)
operator_solve = operator1.solve(operator2)
self.assertIsInstance(
operator_solve,
linalg_lib.LinearOperatorScaledIdentity)
self.assertAllClose(3., self.evaluate(operator_solve.multiplier))
operator_solve = operator2.solve(operator1)
self.assertIsInstance(
operator_solve,
linalg_lib.LinearOperatorScaledIdentity)
self.assertAllClose(1. / 3., self.evaluate(operator_solve.multiplier))
def test_scaled_identity_cholesky_type(self):
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2,
multiplier=3.,
is_positive_definite=True,
is_self_adjoint=True,
)
self.assertIsInstance(
operator.cholesky(),
linalg_lib.LinearOperatorScaledIdentity)
def test_scaled_identity_inverse_type(self):
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2,
multiplier=3.,
is_non_singular=True,
)
self.assertIsInstance(
operator.inverse(),
linalg_lib.LinearOperatorScaledIdentity)
def test_ref_type_shape_args_raises(self):
with self.assertRaisesRegex(TypeError, "num_rows.*reference"):
linalg_lib.LinearOperatorScaledIdentity(
num_rows=variables_module.Variable(2), multiplier=1.23)
def test_tape_safe(self):
multiplier = variables_module.Variable(1.23)
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=multiplier)
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
multiplier = variables_module.Variable(1.23)
operator = linalg_lib.LinearOperatorScaledIdentity(
num_rows=2, multiplier=multiplier)
with self.cached_session() as sess:
sess.run([multiplier.initializer])
self.check_convert_variables_to_tensors(operator)
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorIdentityTest)
linear_operator_test_util.add_tests(LinearOperatorScaledIdentityTest)
test.main()
@@ -0,0 +1,167 @@
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_inversion
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
LinearOperatorInversion = linear_operator_inversion.LinearOperatorInversion # pylint: disable=invalid-name
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorInversionTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
# TODO: b/311343496 - Re-enable this test.
@staticmethod
def skip_these_tests() -> list[str]:
return [
"test_saved_model",
]
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
self._atol[dtypes.complex64] = 1e-5
self._rtol[dtypes.complex64] = 1e-5
def operator_and_matrix(self,
build_info,
dtype,
use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
if ensure_self_adjoint_and_pd:
matrix = linear_operator_test_util.random_positive_definite_matrix(
shape, dtype, force_well_conditioned=True)
else:
matrix = linear_operator_test_util.random_tril_matrix(
shape, dtype, force_well_conditioned=True, remove_upper=True)
lin_op_matrix = matrix
if use_placeholder:
lin_op_matrix = array_ops.placeholder_with_default(matrix, shape=None)
if ensure_self_adjoint_and_pd:
operator = LinearOperatorInversion(
linalg.LinearOperatorFullMatrix(
lin_op_matrix, is_positive_definite=True, is_self_adjoint=True))
else:
operator = LinearOperatorInversion(
linalg.LinearOperatorLowerTriangular(lin_op_matrix))
return operator, linalg.inv(matrix)
def test_base_operator_hint_used(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix,
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
operator_inv = LinearOperatorInversion(operator)
self.assertTrue(operator_inv.is_positive_definite)
self.assertTrue(operator_inv.is_non_singular)
self.assertFalse(operator_inv.is_self_adjoint)
def test_supplied_hint_used(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix)
operator_inv = LinearOperatorInversion(
operator,
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator_inv.is_positive_definite)
self.assertTrue(operator_inv.is_non_singular)
self.assertFalse(operator_inv.is_self_adjoint)
def test_solve_on_inverse(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix)
operator_inv = operator.inverse()
solved_result = operator_inv.solve(operator)
self.assertIsInstance(
solved_result, linalg.LinearOperatorComposition)
def test_inverse_of_inverse(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix)
operator_inv = operator.inverse()
self.assertIsInstance(operator_inv, LinearOperatorInversion)
inverse_of_op_inverse = operator_inv.inverse()
self.assertIsInstance(
inverse_of_op_inverse, linalg.LinearOperatorFullMatrix)
def test_contradicting_hints_raise(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, is_positive_definite=False)
with self.assertRaisesRegex(ValueError, "positive-definite"):
LinearOperatorInversion(operator, is_positive_definite=True)
operator = linalg.LinearOperatorFullMatrix(matrix, is_self_adjoint=False)
with self.assertRaisesRegex(ValueError, "self-adjoint"):
LinearOperatorInversion(operator, is_self_adjoint=True)
def test_singular_raises(self):
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 1.], [1., 1.]]
operator = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=False)
with self.assertRaisesRegex(ValueError, "is_non_singular"):
LinearOperatorInversion(operator)
operator = linalg.LinearOperatorFullMatrix(matrix)
with self.assertRaisesRegex(ValueError, "is_non_singular"):
LinearOperatorInversion(operator, is_non_singular=False)
def test_name(self):
matrix = [[11., 0.], [1., 8.]]
operator = linalg.LinearOperatorFullMatrix(
matrix, name="my_operator", is_non_singular=True)
operator = LinearOperatorInversion(operator)
self.assertEqual("my_operator_inv", operator.name)
def test_tape_safe(self):
matrix = variables_module.Variable([[1., 2.], [3., 4.]])
operator = LinearOperatorInversion(linalg.LinearOperatorFullMatrix(matrix))
self.check_tape_safe(operator)
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorInversionTest)
test.main()
@@ -0,0 +1,316 @@
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.eager import backprop
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_kronecker as kronecker
from tensorflow.python.ops.linalg import linear_operator_lower_triangular as lower_triangular
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.ops.linalg import linear_operator_util
from tensorflow.python.platform import test
linalg = linalg_lib
rng = np.random.RandomState(0)
def _kronecker_dense(factors):
"""Convert a list of factors, into a dense Kronecker product."""
product = factors[0]
for factor in factors[1:]:
product = product[..., array_ops.newaxis, :, array_ops.newaxis]
factor_to_mul = factor[..., array_ops.newaxis, :, array_ops.newaxis, :]
product *= factor_to_mul
product = array_ops.reshape(
product,
shape=array_ops.concat(
[array_ops.shape(product)[:-4],
[array_ops.shape(product)[-4] * array_ops.shape(product)[-3],
array_ops.shape(product)[-2] * array_ops.shape(product)[-1]]
], axis=0))
return product
class KroneckerDenseTest(test.TestCase):
"""Test of `_kronecker_dense` function."""
def test_kronecker_dense_matrix(self):
x = ops.convert_to_tensor([[2., 3.], [1., 2.]], dtype=dtypes.float32)
y = ops.convert_to_tensor([[1., 2.], [5., -1.]], dtype=dtypes.float32)
# From explicitly writing out the kronecker product of x and y.
z = ops.convert_to_tensor([
[2., 4., 3., 6.],
[10., -2., 15., -3.],
[1., 2., 2., 4.],
[5., -1., 10., -2.]], dtype=dtypes.float32)
# From explicitly writing out the kronecker product of y and x.
w = ops.convert_to_tensor([
[2., 3., 4., 6.],
[1., 2., 2., 4.],
[10., 15., -2., -3.],
[5., 10., -1., -2.]], dtype=dtypes.float32)
self.assertAllClose(
self.evaluate(_kronecker_dense([x, y])), self.evaluate(z))
self.assertAllClose(
self.evaluate(_kronecker_dense([y, x])), self.evaluate(w))
@test_util.run_all_in_graph_and_eager_modes
class SquareLinearOperatorKroneckerTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Increase from 1e-6 to 1e-4
self._atol[dtypes.float32] = 1e-4
self._atol[dtypes.complex64] = 1e-4
self._rtol[dtypes.float32] = 1e-4
self._rtol[dtypes.complex64] = 1e-4
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
return [
shape_info((1, 1), factors=[(1, 1), (1, 1)]),
shape_info((8, 8), factors=[(2, 2), (2, 2), (2, 2)]),
shape_info((12, 12), factors=[(2, 2), (3, 3), (2, 2)]),
shape_info((1, 3, 3), factors=[(1, 1), (1, 3, 3)]),
shape_info((3, 6, 6), factors=[(3, 1, 1), (1, 2, 2), (1, 3, 3)]),
]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
# Kronecker products constructed below will be from symmetric
# positive-definite matrices.
del ensure_self_adjoint_and_pd
shape = list(build_info.shape)
expected_factors = build_info.__dict__["factors"]
matrices = [
linear_operator_test_util.random_positive_definite_matrix(
block_shape, dtype, force_well_conditioned=True)
for block_shape in expected_factors
]
lin_op_matrices = matrices
if use_placeholder:
lin_op_matrices = [
array_ops.placeholder_with_default(m, shape=None) for m in matrices]
operator = kronecker.LinearOperatorKronecker(
[linalg.LinearOperatorFullMatrix(
l,
is_square=True,
is_self_adjoint=True,
is_positive_definite=True)
for l in lin_op_matrices])
matrices = linear_operator_util.broadcast_matrix_batch_dims(matrices)
kronecker_dense = _kronecker_dense(matrices)
if not use_placeholder:
kronecker_dense.set_shape(shape)
return operator, kronecker_dense
def test_is_x_flags(self):
# Matrix with two positive eigenvalues, 1, and 1.
# The matrix values do not effect auto-setting of the flags.
matrix = [[1., 0.], [1., 1.]]
operator = kronecker.LinearOperatorKronecker(
[linalg.LinearOperatorFullMatrix(matrix),
linalg.LinearOperatorFullMatrix(matrix)],
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertFalse(operator.is_self_adjoint)
def test_is_non_singular_auto_set(self):
# Matrix with two positive eigenvalues, 11 and 8.
# The matrix values do not effect auto-setting of the flags.
matrix = [[11., 0.], [1., 8.]]
operator_1 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator_2 = linalg.LinearOperatorFullMatrix(matrix, is_non_singular=True)
operator = kronecker.LinearOperatorKronecker(
[operator_1, operator_2],
is_positive_definite=False, # No reason it HAS to be False...
is_non_singular=None)
self.assertFalse(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
with self.assertRaisesRegex(ValueError, "always non-singular"):
kronecker.LinearOperatorKronecker(
[operator_1, operator_2], is_non_singular=False)
def test_name(self):
matrix = [[11., 0.], [1., 8.]]
operator_1 = linalg.LinearOperatorFullMatrix(matrix, name="left")
operator_2 = linalg.LinearOperatorFullMatrix(matrix, name="right")
operator = kronecker.LinearOperatorKronecker([operator_1, operator_2])
self.assertEqual("left_x_right", operator.name)
def test_different_dtypes_raises(self):
operators = [
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3)),
linalg.LinearOperatorFullMatrix(rng.rand(2, 3, 3).astype(np.float32))
]
with self.assertRaisesRegex(TypeError, "same dtype"):
kronecker.LinearOperatorKronecker(operators)
def test_empty_or_one_operators_raises(self):
with self.assertRaisesRegex(ValueError, ">=1 operators"):
kronecker.LinearOperatorKronecker([])
def test_kronecker_adjoint_type(self):
matrix = [[1., 0.], [0., 1.]]
operator = kronecker.LinearOperatorKronecker(
[
linalg.LinearOperatorFullMatrix(
matrix, is_non_singular=True),
linalg.LinearOperatorFullMatrix(
matrix, is_non_singular=True),
],
is_non_singular=True,
)
adjoint = operator.adjoint()
self.assertIsInstance(
adjoint,
kronecker.LinearOperatorKronecker)
self.assertEqual(2, len(adjoint.operators))
def test_kronecker_cholesky_type(self):
matrix = [[1., 0.], [0., 1.]]
operator = kronecker.LinearOperatorKronecker(
[
linalg.LinearOperatorFullMatrix(
matrix,
is_positive_definite=True,
is_self_adjoint=True,
),
linalg.LinearOperatorFullMatrix(
matrix,
is_positive_definite=True,
is_self_adjoint=True,
),
],
is_positive_definite=True,
is_self_adjoint=True,
)
cholesky_factor = operator.cholesky()
self.assertIsInstance(
cholesky_factor,
kronecker.LinearOperatorKronecker)
self.assertEqual(2, len(cholesky_factor.operators))
self.assertIsInstance(
cholesky_factor.operators[0],
lower_triangular.LinearOperatorLowerTriangular)
self.assertIsInstance(
cholesky_factor.operators[1],
lower_triangular.LinearOperatorLowerTriangular)
def test_kronecker_inverse_type(self):
matrix = [[1., 0.], [0., 1.]]
operator = kronecker.LinearOperatorKronecker(
[
linalg.LinearOperatorFullMatrix(
matrix, is_non_singular=True),
linalg.LinearOperatorFullMatrix(
matrix, is_non_singular=True),
],
is_non_singular=True,
)
inverse = operator.inverse()
self.assertIsInstance(
inverse,
kronecker.LinearOperatorKronecker)
self.assertEqual(2, len(inverse.operators))
def test_tape_safe(self):
matrix_1 = variables_module.Variable([[1., 0.], [0., 1.]])
matrix_2 = variables_module.Variable([[2., 0.], [0., 2.]])
operator = kronecker.LinearOperatorKronecker(
[
linalg.LinearOperatorFullMatrix(
matrix_1, is_non_singular=True),
linalg.LinearOperatorFullMatrix(
matrix_2, is_non_singular=True),
],
is_non_singular=True,
)
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
matrix_1 = variables_module.Variable([[1., 0.], [0., 1.]])
matrix_2 = variables_module.Variable([[2., 0.], [0., 2.]])
operator = kronecker.LinearOperatorKronecker(
[
linalg.LinearOperatorFullMatrix(
matrix_1, is_non_singular=True),
linalg.LinearOperatorFullMatrix(
matrix_2, is_non_singular=True),
],
is_non_singular=True,
)
with self.cached_session() as sess:
sess.run([x.initializer for x in operator.variables])
self.check_convert_variables_to_tensors(operator)
def test_composite_gradients(self):
with backprop.GradientTape() as tape:
op1 = linalg.LinearOperatorFullMatrix(
[[1., 0.], [0., 1.]], is_non_singular=True)
op2 = linalg.LinearOperatorFullMatrix(
[[2., 0.], [0., 2.]], is_non_singular=True)
tape.watch([op1, op2])
operator = kronecker.LinearOperatorKronecker(
[op1, op2], is_non_singular=True)
x = self.make_x(op1, adjoint=False)
y = op1.matmul(x)
connected_grad, disconnected_grad, composite_grad = tape.gradient(
y, [op1, op2, operator])
disconnected_component_grad = composite_grad.operators[1].to_dense()
self.assertAllClose(connected_grad.to_dense(),
composite_grad.operators[0].to_dense())
self.assertAllClose(disconnected_component_grad,
array_ops.zeros_like(disconnected_component_grad))
self.assertIsNone(disconnected_grad)
if __name__ == "__main__":
linear_operator_test_util.add_tests(SquareLinearOperatorKroneckerTest)
test.main()
@@ -0,0 +1,409 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
rng = np.random.RandomState(0)
class BaseLinearOperatorLowRankUpdatetest(object):
"""Base test for this type of operator."""
# Subclasses should set these attributes to either True or False.
# If True, A = L + UDV^H
# If False, A = L + UV^H or A = L + UU^H, depending on _use_v.
_use_diag_update = None
# If True, diag is > 0, which means D is symmetric positive definite.
_is_diag_update_positive = None
# If True, A = L + UDV^H
# If False, A = L + UDU^H or A = L + UU^H, depending on _use_diag_update
_use_v = None
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
# Previously we had a (2, 10, 10) shape at the end. We did this to test the
# inversion and determinant lemmas on not-tiny matrices, since these are
# known to have stability issues. This resulted in test timeouts, so this
# shape has been removed, but rest assured, the tests did pass.
return [
shape_info((0, 0)),
shape_info((1, 1)),
shape_info((1, 3, 3)),
shape_info((3, 4, 4)),
shape_info((2, 1, 4, 4))]
def _gen_positive_diag(self, dtype, diag_shape):
if dtype.is_complex:
diag = linear_operator_test_util.random_uniform(
diag_shape, minval=1e-4, maxval=1., dtype=dtypes.float32)
return math_ops.cast(diag, dtype=dtype)
return linear_operator_test_util.random_uniform(
diag_shape, minval=1e-4, maxval=1., dtype=dtype)
def operator_and_matrix(self, shape_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
# Recall A = L + UDV^H
shape = list(shape_info.shape)
diag_shape = shape[:-1]
k = shape[-2] // 2 + 1
u_perturbation_shape = shape[:-1] + [k]
diag_update_shape = shape[:-2] + [k]
# base_operator L will be a symmetric positive definite diagonal linear
# operator, with condition number as high as 1e4.
base_diag = self._gen_positive_diag(dtype, diag_shape)
lin_op_base_diag = base_diag
# U
u = linear_operator_test_util.random_normal_correlated_columns(
u_perturbation_shape, dtype=dtype)
lin_op_u = u
# V
v = linear_operator_test_util.random_normal_correlated_columns(
u_perturbation_shape, dtype=dtype)
lin_op_v = v
# D
if self._is_diag_update_positive or ensure_self_adjoint_and_pd:
diag_update = self._gen_positive_diag(dtype, diag_update_shape)
else:
diag_update = linear_operator_test_util.random_normal(
diag_update_shape, stddev=1e-4, dtype=dtype)
lin_op_diag_update = diag_update
if use_placeholder:
lin_op_base_diag = array_ops.placeholder_with_default(
base_diag, shape=None)
lin_op_u = array_ops.placeholder_with_default(u, shape=None)
lin_op_v = array_ops.placeholder_with_default(v, shape=None)
lin_op_diag_update = array_ops.placeholder_with_default(
diag_update, shape=None)
base_operator = linalg.LinearOperatorDiag(
lin_op_base_diag,
is_positive_definite=True,
is_self_adjoint=True)
operator = linalg.LinearOperatorLowRankUpdate(
base_operator,
lin_op_u,
v=lin_op_v if self._use_v else None,
diag_update=lin_op_diag_update if self._use_diag_update else None,
is_diag_update_positive=self._is_diag_update_positive)
# The matrix representing L
base_diag_mat = array_ops.matrix_diag(base_diag)
# The matrix representing D
diag_update_mat = array_ops.matrix_diag(diag_update)
# Set up mat as some variant of A = L + UDV^H
if self._use_v and self._use_diag_update:
# In this case, we have L + UDV^H and it isn't symmetric.
expect_use_cholesky = False
matrix = base_diag_mat + math_ops.matmul(
u, math_ops.matmul(diag_update_mat, v, adjoint_b=True))
elif self._use_v:
# In this case, we have L + UDV^H and it isn't symmetric.
expect_use_cholesky = False
matrix = base_diag_mat + math_ops.matmul(u, v, adjoint_b=True)
elif self._use_diag_update:
# In this case, we have L + UDU^H, which is PD if D > 0, since L > 0.
expect_use_cholesky = self._is_diag_update_positive
matrix = base_diag_mat + math_ops.matmul(
u, math_ops.matmul(diag_update_mat, u, adjoint_b=True))
else:
# In this case, we have L + UU^H, which is PD since L > 0.
expect_use_cholesky = True
matrix = base_diag_mat + math_ops.matmul(u, u, adjoint_b=True)
if expect_use_cholesky:
self.assertTrue(operator._use_cholesky)
else:
self.assertFalse(operator._use_cholesky)
return operator, matrix
def test_tape_safe(self):
base_operator = linalg.LinearOperatorDiag(
variables_module.Variable([1.], name="diag"),
is_positive_definite=True,
is_self_adjoint=True)
operator = linalg.LinearOperatorLowRankUpdate(
base_operator,
u=variables_module.Variable([[2.]], name="u"),
v=variables_module.Variable([[1.25]], name="v")
if self._use_v else None,
diag_update=variables_module.Variable([1.25], name="diag_update")
if self._use_diag_update else None,
is_diag_update_positive=self._is_diag_update_positive)
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
base_operator = linalg.LinearOperatorDiag(
variables_module.Variable([1.], name="diag"),
is_positive_definite=True,
is_self_adjoint=True)
operator = linalg.LinearOperatorLowRankUpdate(
base_operator,
u=variables_module.Variable([[2.]], name="u"),
v=variables_module.Variable([[1.25]], name="v")
if self._use_v else None,
diag_update=variables_module.Variable([1.25], name="diag_update")
if self._use_diag_update else None,
is_diag_update_positive=self._is_diag_update_positive)
with self.cached_session() as sess:
sess.run([x.initializer for x in operator.variables])
self.check_convert_variables_to_tensors(operator)
class LinearOperatorLowRankUpdatetestWithDiagUseCholesky(
BaseLinearOperatorLowRankUpdatetest,
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""A = L + UDU^H, D > 0, L > 0 ==> A > 0 and we can use a Cholesky."""
_use_diag_update = True
_is_diag_update_positive = True
_use_v = False
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Decrease tolerance since we are testing with condition numbers as high as
# 1e4.
self._atol[dtypes.float32] = 1e-5
self._rtol[dtypes.float32] = 1e-5
self._atol[dtypes.float64] = 1e-10
self._rtol[dtypes.float64] = 1e-10
self._rtol[dtypes.complex64] = 1e-4
class LinearOperatorLowRankUpdatetestWithDiagCannotUseCholesky(
BaseLinearOperatorLowRankUpdatetest,
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""A = L + UDU^H, D !> 0, L > 0 ==> A !> 0 and we cannot use a Cholesky."""
@staticmethod
def skip_these_tests():
return ["cholesky", "eigvalsh"]
_use_diag_update = True
_is_diag_update_positive = False
_use_v = False
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Decrease tolerance since we are testing with condition numbers as high as
# 1e4. This class does not use Cholesky, and thus needs even looser
# tolerance.
self._atol[dtypes.float32] = 1e-4
self._rtol[dtypes.float32] = 1e-4
self._atol[dtypes.float64] = 1e-9
self._rtol[dtypes.float64] = 1e-9
self._rtol[dtypes.complex64] = 2e-4
class LinearOperatorLowRankUpdatetestNoDiagUseCholesky(
BaseLinearOperatorLowRankUpdatetest,
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""A = L + UU^H, L > 0 ==> A > 0 and we can use a Cholesky."""
_use_diag_update = False
_is_diag_update_positive = None
_use_v = False
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Decrease tolerance since we are testing with condition numbers as high as
# 1e4.
self._atol[dtypes.float32] = 1e-5
self._rtol[dtypes.float32] = 1e-5
self._atol[dtypes.float64] = 1e-10
self._rtol[dtypes.float64] = 1e-10
self._rtol[dtypes.complex64] = 1e-4
class LinearOperatorLowRankUpdatetestNoDiagCannotUseCholesky(
BaseLinearOperatorLowRankUpdatetest,
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""A = L + UV^H, L > 0 ==> A is not symmetric and we cannot use a Cholesky."""
@staticmethod
def skip_these_tests():
return ["cholesky", "eigvalsh"]
_use_diag_update = False
_is_diag_update_positive = None
_use_v = True
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
# Decrease tolerance since we are testing with condition numbers as high as
# 1e4. This class does not use Cholesky, and thus needs even looser
# tolerance.
self._atol[dtypes.float32] = 1e-4
self._rtol[dtypes.float32] = 1e-4
self._atol[dtypes.float64] = 1e-9
self._rtol[dtypes.float64] = 1e-9
self._atol[dtypes.complex64] = 1e-5
self._rtol[dtypes.complex64] = 2e-4
class LinearOperatorLowRankUpdatetestWithDiagNotSquare(
BaseLinearOperatorLowRankUpdatetest,
linear_operator_test_util.NonSquareLinearOperatorDerivedClassTest):
"""A = L + UDU^H, D > 0, L > 0 ==> A > 0 and we can use a Cholesky."""
_use_diag_update = True
_is_diag_update_positive = True
_use_v = True
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
@test_util.run_all_without_tensor_float_32(
"Linear op calls matmul which uses TensorFloat-32.")
class LinearOperatorLowRankUpdateBroadcastsShape(test.TestCase):
"""Test that the operator's shape is the broadcast of arguments."""
def test_static_shape_broadcasts_up_from_operator_to_other_args(self):
base_operator = linalg.LinearOperatorIdentity(num_rows=3)
u = array_ops.ones(shape=[2, 3, 2])
diag = array_ops.ones(shape=[2, 2])
operator = linalg.LinearOperatorLowRankUpdate(base_operator, u, diag)
# domain_dimension is 3
self.assertAllEqual([2, 3, 3], operator.shape)
self.assertAllEqual([2, 3, 3], self.evaluate(operator.to_dense()).shape)
@test_util.run_deprecated_v1
def test_dynamic_shape_broadcasts_up_from_operator_to_other_args(self):
num_rows_ph = array_ops.placeholder(dtypes.int32)
base_operator = linalg.LinearOperatorIdentity(num_rows=num_rows_ph)
u_shape_ph = array_ops.placeholder(dtypes.int32)
u = array_ops.ones(shape=u_shape_ph)
v_shape_ph = array_ops.placeholder(dtypes.int32)
v = array_ops.ones(shape=v_shape_ph)
diag_shape_ph = array_ops.placeholder(dtypes.int32)
diag_update = array_ops.ones(shape=diag_shape_ph)
operator = linalg.LinearOperatorLowRankUpdate(base_operator,
u=u,
diag_update=diag_update,
v=v)
feed_dict = {
num_rows_ph: 3,
u_shape_ph: [1, 1, 2, 3, 2], # batch_shape = [1, 1, 2]
v_shape_ph: [1, 2, 1, 3, 2], # batch_shape = [1, 2, 1]
diag_shape_ph: [2, 1, 1, 2] # batch_shape = [2, 1, 1]
}
with self.cached_session():
shape_tensor = operator.shape_tensor().eval(feed_dict=feed_dict)
self.assertAllEqual([2, 2, 2, 3, 3], shape_tensor)
dense = operator.to_dense().eval(feed_dict=feed_dict)
self.assertAllEqual([2, 2, 2, 3, 3], dense.shape)
def test_u_and_v_incompatible_batch_shape_raises(self):
base_operator = linalg.LinearOperatorIdentity(num_rows=3, dtype=np.float64)
u = rng.rand(5, 3, 2)
v = rng.rand(4, 3, 2)
with self.assertRaisesRegex(ValueError, "Incompatible shapes"):
linalg.LinearOperatorLowRankUpdate(base_operator, u=u, v=v)
def test_u_and_base_operator_incompatible_batch_shape_raises(self):
base_operator = linalg.LinearOperatorIdentity(
num_rows=3, batch_shape=[4], dtype=np.float64)
u = rng.rand(5, 3, 2)
with self.assertRaisesRegex(ValueError, "Incompatible shapes"):
linalg.LinearOperatorLowRankUpdate(base_operator, u=u)
def test_u_and_base_operator_incompatible_domain_dimension(self):
base_operator = linalg.LinearOperatorIdentity(num_rows=3, dtype=np.float64)
u = rng.rand(5, 4, 2)
with self.assertRaisesRegex(ValueError, "not compatible"):
linalg.LinearOperatorLowRankUpdate(base_operator, u=u)
def test_u_and_diag_incompatible_low_rank_raises(self):
base_operator = linalg.LinearOperatorIdentity(num_rows=3, dtype=np.float64)
u = rng.rand(5, 3, 2)
diag = rng.rand(5, 4) # Last dimension should be 2
with self.assertRaisesRegex(ValueError, "not compatible"):
linalg.LinearOperatorLowRankUpdate(base_operator, u=u, diag_update=diag)
def test_diag_incompatible_batch_shape_raises(self):
base_operator = linalg.LinearOperatorIdentity(num_rows=3, dtype=np.float64)
u = rng.rand(5, 3, 2)
diag = rng.rand(4, 2) # First dimension should be 5
with self.assertRaisesRegex(ValueError, "Incompatible shapes"):
linalg.LinearOperatorLowRankUpdate(base_operator, u=u, diag_update=diag)
if __name__ == "__main__":
linear_operator_test_util.add_tests(
LinearOperatorLowRankUpdatetestWithDiagUseCholesky)
linear_operator_test_util.add_tests(
LinearOperatorLowRankUpdatetestWithDiagCannotUseCholesky)
linear_operator_test_util.add_tests(
LinearOperatorLowRankUpdatetestNoDiagUseCholesky)
linear_operator_test_util.add_tests(
LinearOperatorLowRankUpdatetestNoDiagCannotUseCholesky)
linear_operator_test_util.add_tests(
LinearOperatorLowRankUpdatetestWithDiagNotSquare)
test.main()
@@ -0,0 +1,196 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from absl.testing import parameterized
from tensorflow.python.framework import config
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorLowerTriangularTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
@staticmethod
def skip_these_tests():
# Cholesky does not make sense for triangular matrices.
return ["cholesky"]
def operator_and_matrix(self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
# Upper triangle will be nonzero, but ignored.
# Use a diagonal that ensures this matrix is well conditioned.
tril = linear_operator_test_util.random_tril_matrix(
shape, dtype=dtype, force_well_conditioned=True, remove_upper=False)
if ensure_self_adjoint_and_pd:
# Get the diagonal and make the matrix out of it.
tril = array_ops.matrix_diag_part(tril)
tril = math_ops.abs(tril) + 1e-1
tril = array_ops.matrix_diag(tril)
lin_op_tril = tril
if use_placeholder:
lin_op_tril = array_ops.placeholder_with_default(lin_op_tril, shape=None)
operator = linalg.LinearOperatorLowerTriangular(
lin_op_tril,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
matrix = array_ops.matrix_band_part(tril, -1, 0)
return operator, matrix
def test_assert_non_singular(self):
# Singular matrix with one positive eigenvalue and one zero eigenvalue.
with self.cached_session():
tril = [[1., 0.], [1., 0.]]
operator = linalg.LinearOperatorLowerTriangular(tril)
with self.assertRaisesOpError("Singular operator"):
operator.assert_non_singular().run()
def test_is_x_flags(self):
# Matrix with two positive eigenvalues.
tril = [[1., 0.], [1., 1.]]
operator = linalg.LinearOperatorLowerTriangular(
tril,
is_positive_definite=True,
is_non_singular=True,
is_self_adjoint=False)
self.assertTrue(operator.is_positive_definite)
self.assertTrue(operator.is_non_singular)
self.assertFalse(operator.is_self_adjoint)
def test_tril_must_have_at_least_two_dims_or_raises(self):
with self.assertRaisesRegex(ValueError, "at least 2 dimensions"):
linalg.LinearOperatorLowerTriangular([1.])
def test_tape_safe(self):
tril = variables_module.Variable([[1., 0.], [0., 1.]])
operator = linalg_lib.LinearOperatorLowerTriangular(
tril, is_non_singular=True)
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
tril = variables_module.Variable([[1., 0.], [0., 1.]])
operator = linalg_lib.LinearOperatorLowerTriangular(
tril, is_non_singular=True)
with self.cached_session() as sess:
sess.run([tril.initializer])
self.check_convert_variables_to_tensors(operator)
def test_llt_composition_with_pd_l(self):
l = linalg_lib.LinearOperatorLowerTriangular(
[[1., 0.], [0.5, 0.2]], is_non_singular=True, is_positive_definite=True)
self.assertIs(l, (l @ l.H).cholesky())
def test_llt_composition_with_non_pd_l(self):
# The tril matrix here is selected so that multiplying the rows by the sign
# (the correct thing to do) is different than multiplying the columns.
l = linalg_lib.LinearOperatorLowerTriangular(
[[-1., 0., 0.], [0.5, 0.2, 0.], [0.1, 0.1, 1.]], is_non_singular=True)
llt = l @ l.H
chol = llt.cholesky()
self.assertIsInstance(chol, linalg_lib.LinearOperatorLowerTriangular)
self.assertGreater(self.evaluate(chol.diag_part()).min(), 0)
self.assertAllClose(
self.evaluate(llt.to_dense()), self.evaluate(
(chol @ chol.H).to_dense()))
def test_llt_composition_with_non_pd_complex_l(self):
# The tril matrix here is selected so that multiplying the rows by the sign
# (the correct thing to do) is different than multiplying the columns.
i = math_ops.complex(0., 1.)
l = linalg_lib.LinearOperatorLowerTriangular(
[[-1. + i, 0., 0.], [0.5, 0.2 - 2 * i, 0.], [0.1, 0.1, 1.]],
is_non_singular=True)
llt = l @ l.H
chol = llt.cholesky()
self.assertIsInstance(chol, linalg_lib.LinearOperatorLowerTriangular)
self.assertGreater(self.evaluate(math_ops.real(chol.diag_part())).min(), 0)
self.assertAllClose(
self.evaluate(math_ops.imag(chol.diag_part())).min(), 0)
self.assertAllClose(
self.evaluate(llt.to_dense()), self.evaluate(
(chol @ chol.H).to_dense()))
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorLowerTriangularMatmulTest(
test.TestCase, parameterized.TestCase):
@parameterized.named_parameters(
("unbatched",
[[1., 0., 0.], [2., 1., 0.], [2., 3., 3.]],
[2., 2., 3.]),
("batched",
[[[1., 0.], [2., 1.]], [[3., 0.], [4., 5.]]],
[[2., 3.], [4., 5.]]),
)
def test_matmul_triangular_and_diag_returns_lower_triangular(
self, tril_data, diag_data):
operator1 = linalg.LinearOperatorLowerTriangular(tril_data)
operator2 = linalg.LinearOperatorDiag(diag_data)
operator_matmul = operator1.matmul(operator2)
self.assertIsInstance(
operator_matmul,
linalg.LinearOperatorLowerTriangular)
self.assertAllClose(
self.evaluate(math_ops.matmul(
operator1.to_dense(),
operator2.to_dense())),
self.evaluate(operator_matmul.to_dense()))
@parameterized.named_parameters(
("unbatched",
[[1., 0., 0.], [2., 1., 0.], [2., 3., 3.]],
[2., 2., 3.]),
("batched",
[[[1., 0.], [2., 1.]], [[3., 0.], [4., 5.]]],
[[2., 3.], [4., 5.]]),
)
def test_matmul_diag_and_triangular_returns_lower_triangular(
self, tril_data, diag_data):
operator1 = linalg.LinearOperatorLowerTriangular(tril_data)
operator2 = linalg.LinearOperatorDiag(diag_data)
operator_matmul = operator2.matmul(operator1)
self.assertIsInstance(
operator_matmul,
linalg.LinearOperatorLowerTriangular)
self.assertAllClose(
self.evaluate(math_ops.matmul(
operator2.to_dense(),
operator1.to_dense())),
self.evaluate(operator_matmul.to_dense()))
if __name__ == "__main__":
config.enable_tensor_float_32_execution(False)
linear_operator_test_util.add_tests(LinearOperatorLowerTriangularTest)
test.main()
@@ -0,0 +1,109 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import config
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_permutation as permutation
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
linalg = linalg_lib
CheckTapeSafeSkipOptions = linear_operator_test_util.CheckTapeSafeSkipOptions
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorPermutationTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
return [
shape_info((1, 1)),
shape_info((1, 3, 3)),
shape_info((3, 4, 4)),
shape_info((2, 1, 4, 4))]
@staticmethod
def skip_these_tests():
# This linear operator is almost never positive definite.
return ["cholesky", "eigvalsh"]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
perm = math_ops.range(0, shape[-1])
perm = array_ops.broadcast_to(perm, shape[:-1])
perm = random_ops.random_shuffle(perm)
if use_placeholder:
perm = array_ops.placeholder_with_default(
perm, shape=None)
operator = permutation.LinearOperatorPermutation(
perm, dtype=dtype)
matrix = math_ops.cast(
math_ops.equal(
math_ops.range(0, shape[-1]),
perm[..., array_ops.newaxis]),
dtype)
return operator, matrix
def test_permutation_raises(self):
perm = constant_op.constant(0, dtype=dtypes.int32)
with self.assertRaisesRegex(ValueError, "must have at least 1 dimension"):
permutation.LinearOperatorPermutation(perm)
perm = [0., 1., 2.]
with self.assertRaisesRegex(TypeError, "must be integer dtype"):
permutation.LinearOperatorPermutation(perm)
perm = [-1, 2, 3]
with self.assertRaisesRegex(ValueError,
"must be a vector of unique integers"):
permutation.LinearOperatorPermutation(perm)
def test_to_dense_4x4(self):
perm = [0, 1, 2, 3]
self.assertAllClose(
permutation.LinearOperatorPermutation(perm).to_dense(),
linalg_ops.eye(4))
perm = [1, 0, 3, 2]
self.assertAllClose(
permutation.LinearOperatorPermutation(perm).to_dense(),
[[0., 1, 0, 0], [1., 0, 0, 0], [0., 0, 0, 1], [0., 0, 1, 0]])
perm = [3, 2, 0, 1]
self.assertAllClose(
permutation.LinearOperatorPermutation(perm).to_dense(),
[[0., 0, 0, 1], [0., 0, 1, 0], [1., 0, 0, 0], [0., 1, 0, 0]])
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorPermutationTest)
test.main()
@@ -0,0 +1,434 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.eager import context
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_shape
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.parallel_for import control_flow_ops
from tensorflow.python.platform import test
linalg = linalg_lib
rng = np.random.RandomState(123)
class LinearOperatorShape(linalg.LinearOperator):
"""LinearOperator that implements the methods ._shape and _shape_tensor."""
def __init__(self,
shape,
is_non_singular=None,
is_self_adjoint=None,
is_positive_definite=None,
is_square=None):
parameters = dict(
shape=shape,
is_non_singular=is_non_singular,
is_self_adjoint=is_self_adjoint,
is_positive_definite=is_positive_definite,
is_square=is_square
)
self._stored_shape = shape
super(LinearOperatorShape, self).__init__(
dtype=dtypes.float32,
is_non_singular=is_non_singular,
is_self_adjoint=is_self_adjoint,
is_positive_definite=is_positive_definite,
is_square=is_square,
parameters=parameters)
def _shape(self):
return tensor_shape.TensorShape(self._stored_shape)
def _shape_tensor(self):
return constant_op.constant(self._stored_shape, dtype=dtypes.int32)
def _matmul(self):
raise NotImplementedError("Not needed for this test.")
class LinearOperatorMatmulSolve(linalg.LinearOperator):
"""LinearOperator that wraps a [batch] matrix and implements matmul/solve."""
def __init__(self,
matrix,
is_non_singular=None,
is_self_adjoint=None,
is_positive_definite=None,
is_square=None):
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
)
self._matrix = ops.convert_to_tensor(matrix, name="matrix")
super(LinearOperatorMatmulSolve, 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)
def _shape(self):
return self._matrix.shape
def _shape_tensor(self):
return array_ops.shape(self._matrix)
def _matmul(self, x, adjoint=False, adjoint_arg=False):
x = ops.convert_to_tensor(x, name="x")
return math_ops.matmul(
self._matrix, x, adjoint_a=adjoint, adjoint_b=adjoint_arg)
def _solve(self, rhs, adjoint=False, adjoint_arg=False):
rhs = ops.convert_to_tensor(rhs, name="rhs")
assert not adjoint_arg, "Not implemented for this test class."
return linalg_ops.matrix_solve(self._matrix, rhs, adjoint=adjoint)
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorTest(test.TestCase):
def test_all_shape_properties_defined_by_the_one_property_shape(self):
shape = (1, 2, 3, 4)
operator = LinearOperatorShape(shape)
self.assertAllEqual(shape, operator.shape)
self.assertAllEqual(4, operator.tensor_rank)
self.assertAllEqual((1, 2), operator.batch_shape)
self.assertAllEqual(4, operator.domain_dimension)
self.assertAllEqual(3, operator.range_dimension)
expected_parameters = {
"is_non_singular": None,
"is_positive_definite": None,
"is_self_adjoint": None,
"is_square": None,
"shape": (1, 2, 3, 4),
}
self.assertEqual(expected_parameters, operator.parameters)
def test_all_shape_methods_defined_by_the_one_method_shape(self):
with self.cached_session():
shape = (1, 2, 3, 4)
operator = LinearOperatorShape(shape)
self.assertAllEqual(shape, self.evaluate(operator.shape_tensor()))
self.assertAllEqual(4, self.evaluate(operator.tensor_rank_tensor()))
self.assertAllEqual((1, 2), self.evaluate(operator.batch_shape_tensor()))
self.assertAllEqual(4, self.evaluate(operator.domain_dimension_tensor()))
self.assertAllEqual(3, self.evaluate(operator.range_dimension_tensor()))
def test_is_x_properties(self):
operator = LinearOperatorShape(
shape=(2, 2),
is_non_singular=False,
is_self_adjoint=True,
is_positive_definite=False)
self.assertFalse(operator.is_non_singular)
self.assertTrue(operator.is_self_adjoint)
self.assertFalse(operator.is_positive_definite)
def test_nontrivial_parameters(self):
matrix = rng.randn(2, 3, 4)
matrix_ph = array_ops.placeholder_with_default(input=matrix, shape=None)
operator = LinearOperatorMatmulSolve(matrix_ph)
expected_parameters = {
"is_non_singular": None,
"is_positive_definite": None,
"is_self_adjoint": None,
"is_square": None,
"matrix": matrix_ph,
}
self.assertEqual(expected_parameters, operator.parameters)
def test_generic_to_dense_method_non_square_matrix_static(self):
matrix = rng.randn(2, 3, 4)
operator = LinearOperatorMatmulSolve(matrix)
with self.cached_session():
operator_dense = operator.to_dense()
self.assertAllEqual((2, 3, 4), operator_dense.shape)
self.assertAllClose(matrix, self.evaluate(operator_dense))
def test_generic_to_dense_method_non_square_matrix_tensor(self):
matrix = rng.randn(2, 3, 4)
matrix_ph = array_ops.placeholder_with_default(input=matrix, shape=None)
operator = LinearOperatorMatmulSolve(matrix_ph)
operator_dense = operator.to_dense()
self.assertAllClose(matrix, self.evaluate(operator_dense))
def test_matvec(self):
matrix = [[1., 0], [0., 2.]]
operator = LinearOperatorMatmulSolve(matrix)
x = [1., 1.]
with self.cached_session():
y = operator.matvec(x)
self.assertAllEqual((2,), y.shape)
self.assertAllClose([1., 2.], self.evaluate(y))
def test_solvevec(self):
matrix = [[1., 0], [0., 2.]]
operator = LinearOperatorMatmulSolve(matrix)
y = [1., 1.]
with self.cached_session():
x = operator.solvevec(y)
self.assertAllEqual((2,), x.shape)
self.assertAllClose([1., 1 / 2.], self.evaluate(x))
def test_is_square_set_to_true_for_square_static_shapes(self):
operator = LinearOperatorShape(shape=(2, 4, 4))
self.assertTrue(operator.is_square)
def test_is_square_set_to_false_for_square_static_shapes(self):
operator = LinearOperatorShape(shape=(2, 3, 4))
self.assertFalse(operator.is_square)
def test_is_square_set_incorrectly_to_false_raises(self):
with self.assertRaisesRegex(ValueError, "but.*was square"):
_ = LinearOperatorShape(shape=(2, 4, 4), is_square=False).is_square
def test_is_square_set_inconsistent_with_other_hints_raises(self):
with self.assertRaisesRegex(ValueError, "is always square"):
matrix = array_ops.placeholder_with_default(input=(), shape=None)
LinearOperatorMatmulSolve(matrix, is_non_singular=True, is_square=False)
with self.assertRaisesRegex(ValueError, "is always square"):
matrix = array_ops.placeholder_with_default(input=(), shape=None)
LinearOperatorMatmulSolve(
matrix, is_positive_definite=True, is_square=False)
def test_non_square_operators_raise_on_determinant_and_solve(self):
operator = LinearOperatorShape((2, 3))
with self.assertRaisesRegex(NotImplementedError, "not be square"):
operator.determinant()
with self.assertRaisesRegex(NotImplementedError, "not be square"):
operator.log_abs_determinant()
with self.assertRaisesRegex(NotImplementedError, "not be square"):
operator.solve(rng.rand(2, 2))
with self.assertRaisesRegex(ValueError, "is always square"):
matrix = array_ops.placeholder_with_default(input=(), shape=None)
LinearOperatorMatmulSolve(
matrix, is_positive_definite=True, is_square=False)
def test_is_square_manual_set_works(self):
matrix = array_ops.placeholder_with_default(
input=np.ones((2, 2)), shape=None)
operator = LinearOperatorMatmulSolve(matrix)
if not context.executing_eagerly():
# Eager mode will read in the default value, and discover the answer is
# True. Graph mode must rely on the hint, since the placeholder has
# shape=None...the hint is, by default, None.
self.assertEqual(None, operator.is_square)
# Set to True
operator = LinearOperatorMatmulSolve(matrix, is_square=True)
self.assertTrue(operator.is_square)
def test_linear_operator_matmul_hints_closed(self):
matrix = array_ops.placeholder_with_default(input=np.ones((2, 2)),
shape=None)
operator1 = LinearOperatorMatmulSolve(matrix)
operator_matmul = operator1.matmul(operator1)
if not context.executing_eagerly():
# Eager mode will read in the input and discover matrix is square.
self.assertEqual(None, operator_matmul.is_square)
self.assertEqual(None, operator_matmul.is_non_singular)
self.assertEqual(None, operator_matmul.is_self_adjoint)
self.assertEqual(None, operator_matmul.is_positive_definite)
operator2 = LinearOperatorMatmulSolve(
matrix,
is_non_singular=True,
is_self_adjoint=True,
is_positive_definite=True,
is_square=True,
)
operator_matmul = operator2.matmul(operator2)
self.assertTrue(operator_matmul.is_square)
self.assertTrue(operator_matmul.is_non_singular)
# A @ A is SA since A is.
self.assertEqual(True, operator_matmul.is_self_adjoint)
# A @ A is non-singular (since A is) and A @ A = A @ A.H is semi-def so...
self.assertEqual(True, operator_matmul.is_positive_definite)
def test_linear_operator_matmul_hints_false(self):
matrix1 = array_ops.placeholder_with_default(
input=rng.rand(2, 2), shape=None)
operator1 = LinearOperatorMatmulSolve(
matrix1,
is_non_singular=False,
is_self_adjoint=False,
is_positive_definite=False,
is_square=True,
)
operator_matmul = operator1.matmul(operator1)
self.assertTrue(operator_matmul.is_square)
self.assertFalse(operator_matmul.is_non_singular)
self.assertEqual(None, operator_matmul.is_self_adjoint)
self.assertEqual(None, operator_matmul.is_positive_definite)
matrix2 = array_ops.placeholder_with_default(
input=rng.rand(2, 3), shape=None)
operator2 = LinearOperatorMatmulSolve(
matrix2,
is_non_singular=False,
is_self_adjoint=False,
is_positive_definite=False,
is_square=False,
)
operator_matmul = operator2.matmul(operator2, adjoint_arg=True)
# Composition recognizes this as the form A @ A.H, which is square, SA.
self.assertTrue(operator_matmul.is_square)
self.assertTrue(operator_matmul.is_self_adjoint)
if context.executing_eagerly():
# False since we specified is_non_singular=False.
self.assertFalse(operator_matmul.is_non_singular)
else:
# May be non-singular, since it's the composition of two non-square.
# TODO(b/136162840) This is a bit inconsistent, and should probably be
# False since we specified operator2.is_non_singular == False.
self.assertIsNone(operator_matmul.is_non_singular)
# No way to deduce these, even in Eager mode.
self.assertIsNone(operator_matmul.is_positive_definite)
def test_linear_operator_matmul_hint_infer_square(self):
matrix1 = array_ops.placeholder_with_default(
input=rng.rand(2, 3), shape=(2, 3))
matrix2 = array_ops.placeholder_with_default(
input=rng.rand(3, 2), shape=(3, 2))
matrix3 = array_ops.placeholder_with_default(
input=rng.rand(3, 4), shape=(3, 4))
operator1 = LinearOperatorMatmulSolve(matrix1, is_square=False)
operator2 = LinearOperatorMatmulSolve(matrix2, is_square=False)
operator3 = LinearOperatorMatmulSolve(matrix3, is_square=False)
self.assertTrue(operator1.matmul(operator2).is_square)
self.assertTrue(operator2.matmul(operator1).is_square)
self.assertFalse(operator1.matmul(operator3).is_square)
def testDispatchedMethods(self):
operator = linalg.LinearOperatorFullMatrix(
[[1., 0.5], [0.5, 1.]],
is_square=True,
is_self_adjoint=True,
is_non_singular=True,
is_positive_definite=True)
methods = {
"trace": linalg.trace,
"diag_part": linalg.diag_part,
"log_abs_determinant": linalg.logdet,
"determinant": linalg.det
}
for method in methods:
op_val = getattr(operator, method)()
linalg_val = methods[method](operator)
self.assertAllClose(
self.evaluate(op_val),
self.evaluate(linalg_val))
# Solve and Matmul go here.
adjoint = linalg.adjoint(operator)
self.assertIsInstance(adjoint, linalg.LinearOperator)
cholesky = linalg.cholesky(operator)
self.assertIsInstance(cholesky, linalg.LinearOperator)
inverse = linalg.inv(operator)
self.assertIsInstance(inverse, linalg.LinearOperator)
def testDispatchMatmulSolve(self):
operator = linalg.LinearOperatorFullMatrix(
np.float64([[1., 0.5], [0.5, 1.]]),
is_square=True,
is_self_adjoint=True,
is_non_singular=True,
is_positive_definite=True)
rhs = np.random.uniform(-1., 1., size=[3, 2, 2])
for adjoint in [False, True]:
for adjoint_arg in [False, True]:
op_val = operator.matmul(
rhs, adjoint=adjoint, adjoint_arg=adjoint_arg)
matmul_val = math_ops.matmul(
operator, rhs, adjoint_a=adjoint, adjoint_b=adjoint_arg)
self.assertAllClose(
self.evaluate(op_val), self.evaluate(matmul_val))
op_val = operator.solve(rhs, adjoint=adjoint)
solve_val = linalg.solve(operator, rhs, adjoint=adjoint)
self.assertAllClose(
self.evaluate(op_val), self.evaluate(solve_val))
def testDispatchMatmulLeftOperatorIsTensor(self):
mat = np.float64([[1., 0.5], [0.5, 1.]])
right_operator = linalg.LinearOperatorFullMatrix(
mat,
is_square=True,
is_self_adjoint=True,
is_non_singular=True,
is_positive_definite=True)
lhs = np.random.uniform(-1., 1., size=[3, 2, 2])
for adjoint in [False, True]:
for adjoint_arg in [False, True]:
op_val = math_ops.matmul(
lhs, mat, adjoint_a=adjoint, adjoint_b=adjoint_arg)
matmul_val = math_ops.matmul(
lhs, right_operator, adjoint_a=adjoint, adjoint_b=adjoint_arg)
self.assertAllClose(
self.evaluate(op_val), self.evaluate(matmul_val))
def testVectorizedMap(self):
def fn(x):
y = constant_op.constant([3., 4.])
# Make a [2, N, N] shaped operator.
x = x * y[..., array_ops.newaxis, array_ops.newaxis]
operator = linalg.LinearOperatorFullMatrix(
x, is_square=True)
return operator
x = np.random.uniform(-1., 1., size=[3, 5, 5]).astype(np.float32)
batched_operator = control_flow_ops.vectorized_map(
fn, ops.convert_to_tensor(x))
self.assertIsInstance(batched_operator, linalg.LinearOperator)
self.assertAllEqual(batched_operator.batch_shape, [3, 2])
if __name__ == "__main__":
test.main()
@@ -0,0 +1,169 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import contextlib
import numpy as np
import scipy.linalg
from tensorflow.python.eager import backprop
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.ops.linalg import linear_operator_toeplitz
from tensorflow.python.platform import test
linalg = linalg_lib
_to_complex = linear_operator_toeplitz._to_complex
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorToeplitzTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
@contextlib.contextmanager
def _constrain_devices_and_set_default(self, sess, use_gpu, force_gpu):
"""We overwrite the FFT operation mapping for testing."""
with test.TestCase._constrain_devices_and_set_default(
self, sess, use_gpu, force_gpu) as sess:
yield sess
def setUp(self):
# TODO(srvasude): Lower these tolerances once specialized solve and
# determinants are implemented.
self._atol[dtypes.float32] = 1e-4
self._rtol[dtypes.float32] = 1e-4
self._atol[dtypes.float64] = 1e-9
self._rtol[dtypes.float64] = 1e-9
self._atol[dtypes.complex64] = 1e-4
self._rtol[dtypes.complex64] = 1e-4
self._atol[dtypes.complex128] = 1e-9
self._rtol[dtypes.complex128] = 1e-9
@staticmethod
def skip_these_tests():
# Skip solve tests, as these could have better stability
# (currently exercises the base class).
# TODO(srvasude): Enable these when solve is implemented.
return ["cholesky", "cond", "inverse", "solve", "solve_with_broadcast"]
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
# non-batch operators (n, n) and batch operators.
return [
shape_info((1, 1)),
shape_info((1, 6, 6)),
shape_info((3, 4, 4)),
shape_info((2, 1, 3, 3))
]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
shape = list(build_info.shape)
row = np.random.uniform(low=1., high=5., size=shape[:-1])
col = np.random.uniform(low=1., high=5., size=shape[:-1])
# Make sure first entry is the same
row[..., 0] = col[..., 0]
if ensure_self_adjoint_and_pd:
# Note that a Toeplitz matrix generated from a linearly decreasing
# non-negative sequence is positive definite. See
# https://www.math.cinvestav.mx/~grudsky/Papers/118_29062012_Albrecht.pdf
# for details.
row = np.linspace(start=10., stop=1., num=shape[-1])
# The entries for the first row and column should be the same to guarantee
# symmetric.
row = col
lin_op_row = math_ops.cast(row, dtype=dtype)
lin_op_col = math_ops.cast(col, dtype=dtype)
if use_placeholder:
lin_op_row = array_ops.placeholder_with_default(
lin_op_row, shape=None)
lin_op_col = array_ops.placeholder_with_default(
lin_op_col, shape=None)
operator = linear_operator_toeplitz.LinearOperatorToeplitz(
row=lin_op_row,
col=lin_op_col,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
flattened_row = np.reshape(row, (-1, shape[-1]))
flattened_col = np.reshape(col, (-1, shape[-1]))
flattened_toeplitz = np.zeros(
[flattened_row.shape[0], shape[-1], shape[-1]])
for i in range(flattened_row.shape[0]):
flattened_toeplitz[i] = scipy.linalg.toeplitz(
flattened_col[i],
flattened_row[i])
matrix = np.reshape(flattened_toeplitz, shape)
matrix = math_ops.cast(matrix, dtype=dtype)
return operator, matrix
def test_scalar_row_col_raises(self):
with self.assertRaisesRegex(ValueError, "must have at least 1 dimension"):
linear_operator_toeplitz.LinearOperatorToeplitz(1., 1.)
with self.assertRaisesRegex(ValueError, "must have at least 1 dimension"):
linear_operator_toeplitz.LinearOperatorToeplitz([1.], 1.)
with self.assertRaisesRegex(ValueError, "must have at least 1 dimension"):
linear_operator_toeplitz.LinearOperatorToeplitz(1., [1.])
def test_tape_safe(self):
col = variables_module.Variable([1.])
row = variables_module.Variable([1.])
operator = linear_operator_toeplitz.LinearOperatorToeplitz(
col, row, is_self_adjoint=True, is_positive_definite=True)
self.check_tape_safe(
operator,
skip_options=[
# .diag_part, .trace depend only on `col`, so test explicitly below.
linear_operator_test_util.CheckTapeSafeSkipOptions.DIAG_PART,
linear_operator_test_util.CheckTapeSafeSkipOptions.TRACE,
])
with backprop.GradientTape() as tape:
self.assertIsNotNone(tape.gradient(operator.diag_part(), col))
with backprop.GradientTape() as tape:
self.assertIsNotNone(tape.gradient(operator.trace(), col))
def test_convert_variables_to_tensors(self):
col = variables_module.Variable([1.])
row = variables_module.Variable([1.])
operator = linear_operator_toeplitz.LinearOperatorToeplitz(
col, row, is_self_adjoint=True, is_positive_definite=True)
with self.cached_session() as sess:
sess.run([x.initializer for x in operator.variables])
self.check_convert_variables_to_tensors(operator)
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorToeplitzTest)
test.main()
@@ -0,0 +1,218 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from tensorflow.python.framework import config
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import array_ops_stack
from tensorflow.python.ops import gen_array_ops
from tensorflow.python.ops import manip_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
class _LinearOperatorTriDiagBase(object):
def build_operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False,
diagonals_format='sequence'):
shape = list(build_info.shape)
# Ensure that diagonal has large enough values. If we generate a
# self adjoint PD matrix, then the diagonal will be dominant guaranteeing
# positive definitess.
diag = linear_operator_test_util.random_sign_uniform(
shape[:-1], minval=4., maxval=6., dtype=dtype)
# We'll truncate these depending on the format
subdiag = linear_operator_test_util.random_sign_uniform(
shape[:-1], minval=1., maxval=2., dtype=dtype)
if ensure_self_adjoint_and_pd:
# Abs on complex64 will result in a float32, so we cast back up.
diag = math_ops.cast(math_ops.abs(diag), dtype=dtype)
# The first element of subdiag is ignored. We'll add a dummy element
# to superdiag to pad it.
superdiag = math_ops.conj(subdiag)
superdiag = manip_ops.roll(superdiag, shift=-1, axis=-1)
else:
superdiag = linear_operator_test_util.random_sign_uniform(
shape[:-1], minval=1., maxval=2., dtype=dtype)
matrix_diagonals = array_ops_stack.stack(
[superdiag, diag, subdiag], axis=-2)
matrix = gen_array_ops.matrix_diag_v3(
matrix_diagonals,
k=(-1, 1),
num_rows=-1,
num_cols=-1,
align='LEFT_RIGHT',
padding_value=0.)
if diagonals_format == 'sequence':
diagonals = [superdiag, diag, subdiag]
elif diagonals_format == 'compact':
diagonals = array_ops_stack.stack([superdiag, diag, subdiag], axis=-2)
elif diagonals_format == 'matrix':
diagonals = matrix
lin_op_diagonals = diagonals
if use_placeholder:
if diagonals_format == 'sequence':
lin_op_diagonals = [array_ops.placeholder_with_default(
d, shape=None) for d in lin_op_diagonals]
else:
lin_op_diagonals = array_ops.placeholder_with_default(
lin_op_diagonals, shape=None)
operator = linalg_lib.LinearOperatorTridiag(
diagonals=lin_op_diagonals,
diagonals_format=diagonals_format,
is_self_adjoint=True if ensure_self_adjoint_and_pd else None,
is_positive_definite=True if ensure_self_adjoint_and_pd else None)
return operator, matrix
@staticmethod
def operator_shapes_infos():
shape_info = linear_operator_test_util.OperatorShapesInfo
# non-batch operators (n, n) and batch operators.
return [
shape_info((3, 3)),
shape_info((1, 6, 6)),
shape_info((3, 4, 4)),
shape_info((2, 1, 3, 3))
]
@test_util.with_eager_op_as_function
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorTriDiagCompactTest(
_LinearOperatorTriDiagBase,
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
return self.build_operator_and_matrix(
build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=ensure_self_adjoint_and_pd,
diagonals_format='compact')
@test_util.disable_xla('Current implementation does not yet support pivoting')
def test_tape_safe(self):
diag = variables_module.Variable([[3., 6., 2.], [2., 4., 2.], [5., 1., 2.]])
operator = linalg_lib.LinearOperatorTridiag(
diag, diagonals_format='compact')
self.check_tape_safe(operator)
def test_convert_variables_to_tensors(self):
diag = variables_module.Variable([[3., 6., 2.], [2., 4., 2.], [5., 1., 2.]])
operator = linalg_lib.LinearOperatorTridiag(
diag, diagonals_format='compact')
with self.cached_session() as sess:
sess.run([diag.initializer])
self.check_convert_variables_to_tensors(operator)
@test_util.with_eager_op_as_function
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorTriDiagSequenceTest(
_LinearOperatorTriDiagBase,
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
return self.build_operator_and_matrix(
build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=ensure_self_adjoint_and_pd,
diagonals_format='sequence')
@test_util.disable_xla('Current implementation does not yet support pivoting')
def test_tape_safe(self):
diagonals = [
variables_module.Variable([3., 6., 2.]),
variables_module.Variable([2., 4., 2.]),
variables_module.Variable([5., 1., 2.])]
operator = linalg_lib.LinearOperatorTridiag(
diagonals, diagonals_format='sequence')
# Skip the diagonal part and trace since this only dependent on the
# middle variable. We test this below.
self.check_tape_safe(operator, skip_options=['diag_part', 'trace'])
diagonals = [
[3., 6., 2.],
variables_module.Variable([2., 4., 2.]),
[5., 1., 2.]
]
operator = linalg_lib.LinearOperatorTridiag(
diagonals, diagonals_format='sequence')
@test_util.with_eager_op_as_function
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorTriDiagMatrixTest(
_LinearOperatorTriDiagBase,
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
def tearDown(self):
config.enable_tensor_float_32_execution(self.tf32_keep_)
def setUp(self):
self.tf32_keep_ = config.tensor_float_32_execution_enabled()
config.enable_tensor_float_32_execution(False)
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
return self.build_operator_and_matrix(
build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=ensure_self_adjoint_and_pd,
diagonals_format='matrix')
@test_util.disable_xla('Current implementation does not yet support pivoting')
def test_tape_safe(self):
matrix = variables_module.Variable([[3., 2., 0.], [1., 6., 4.], [0., 2, 2]])
operator = linalg_lib.LinearOperatorTridiag(
matrix, diagonals_format='matrix')
self.check_tape_safe(operator)
if __name__ == '__main__':
if not test_util.is_xla_enabled():
linear_operator_test_util.add_tests(LinearOperatorTriDiagCompactTest)
linear_operator_test_util.add_tests(LinearOperatorTriDiagSequenceTest)
linear_operator_test_util.add_tests(LinearOperatorTriDiagMatrixTest)
test.main()
@@ -0,0 +1,488 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from absl.testing import parameterized
import numpy as np
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor as tensor_lib
from tensorflow.python.framework import tensor_shape
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_util
from tensorflow.python.platform import test
rng = np.random.RandomState(0)
class AssertZeroImagPartTest(test.TestCase):
def test_real_tensor_doesnt_raise(self):
x = ops.convert_to_tensor([0., 2, 3])
# Should not raise.
self.evaluate(
linear_operator_util.assert_zero_imag_part(x, message="ABC123"))
def test_complex_tensor_with_imag_zero_doesnt_raise(self):
x = ops.convert_to_tensor([1., 0, 3])
y = ops.convert_to_tensor([0., 0, 0])
z = math_ops.complex(x, y)
# Should not raise.
self.evaluate(
linear_operator_util.assert_zero_imag_part(z, message="ABC123"))
def test_complex_tensor_with_nonzero_imag_raises(self):
x = ops.convert_to_tensor([1., 2, 0])
y = ops.convert_to_tensor([1., 2, 0])
z = math_ops.complex(x, y)
with self.assertRaisesOpError("ABC123"):
self.evaluate(
linear_operator_util.assert_zero_imag_part(z, message="ABC123"))
class AssertNoEntriesWithModulusZeroTest(test.TestCase):
def test_nonzero_real_tensor_doesnt_raise(self):
x = ops.convert_to_tensor([1., 2, 3])
# Should not raise.
self.evaluate(
linear_operator_util.assert_no_entries_with_modulus_zero(
x, message="ABC123"))
def test_nonzero_complex_tensor_doesnt_raise(self):
x = ops.convert_to_tensor([1., 0, 3])
y = ops.convert_to_tensor([1., 2, 0])
z = math_ops.complex(x, y)
# Should not raise.
self.evaluate(
linear_operator_util.assert_no_entries_with_modulus_zero(
z, message="ABC123"))
def test_zero_real_tensor_raises(self):
x = ops.convert_to_tensor([1., 0, 3])
with self.assertRaisesOpError("ABC123"):
self.evaluate(
linear_operator_util.assert_no_entries_with_modulus_zero(
x, message="ABC123"))
def test_zero_complex_tensor_raises(self):
x = ops.convert_to_tensor([1., 2, 0])
y = ops.convert_to_tensor([1., 2, 0])
z = math_ops.complex(x, y)
with self.assertRaisesOpError("ABC123"):
self.evaluate(
linear_operator_util.assert_no_entries_with_modulus_zero(
z, message="ABC123"))
class BroadcastMatrixBatchDimsTest(test.TestCase):
def test_zero_batch_matrices_returned_as_empty_list(self):
self.assertAllEqual([],
linear_operator_util.broadcast_matrix_batch_dims([]))
def test_one_batch_matrix_returned_after_tensor_conversion(self):
arr = rng.rand(2, 3, 4)
tensor, = linear_operator_util.broadcast_matrix_batch_dims([arr])
self.assertTrue(isinstance(tensor, tensor_lib.Tensor))
self.assertAllClose(arr, self.evaluate(tensor))
def test_static_dims_broadcast(self):
# x.batch_shape = [3, 1, 2]
# y.batch_shape = [4, 1]
# broadcast batch shape = [3, 4, 2]
x = rng.rand(3, 1, 2, 1, 5)
y = rng.rand(4, 1, 3, 7)
batch_of_zeros = np.zeros((3, 4, 2, 1, 1))
x_bc_expected = x + batch_of_zeros
y_bc_expected = y + batch_of_zeros
x_bc, y_bc = linear_operator_util.broadcast_matrix_batch_dims([x, y])
self.assertAllEqual(x_bc_expected.shape, x_bc.shape)
self.assertAllEqual(y_bc_expected.shape, y_bc.shape)
x_bc_, y_bc_ = self.evaluate([x_bc, y_bc])
self.assertAllClose(x_bc_expected, x_bc_)
self.assertAllClose(y_bc_expected, y_bc_)
def test_static_dims_broadcast_second_arg_higher_rank(self):
# x.batch_shape = [1, 2]
# y.batch_shape = [1, 3, 1]
# broadcast batch shape = [1, 3, 2]
x = rng.rand(1, 2, 1, 5)
y = rng.rand(1, 3, 2, 3, 7)
batch_of_zeros = np.zeros((1, 3, 2, 1, 1))
x_bc_expected = x + batch_of_zeros
y_bc_expected = y + batch_of_zeros
x_bc, y_bc = linear_operator_util.broadcast_matrix_batch_dims([x, y])
self.assertAllEqual(x_bc_expected.shape, x_bc.shape)
self.assertAllEqual(y_bc_expected.shape, y_bc.shape)
x_bc_, y_bc_ = self.evaluate([x_bc, y_bc])
self.assertAllClose(x_bc_expected, x_bc_)
self.assertAllClose(y_bc_expected, y_bc_)
def test_dynamic_dims_broadcast_32bit(self):
# x.batch_shape = [3, 1, 2]
# y.batch_shape = [4, 1]
# broadcast batch shape = [3, 4, 2]
x = rng.rand(3, 1, 2, 1, 5).astype(np.float32)
y = rng.rand(4, 1, 3, 7).astype(np.float32)
batch_of_zeros = np.zeros((3, 4, 2, 1, 1)).astype(np.float32)
x_bc_expected = x + batch_of_zeros
y_bc_expected = y + batch_of_zeros
x_ph = array_ops.placeholder_with_default(x, shape=None)
y_ph = array_ops.placeholder_with_default(y, shape=None)
x_bc, y_bc = linear_operator_util.broadcast_matrix_batch_dims([x_ph, y_ph])
x_bc_, y_bc_ = self.evaluate([x_bc, y_bc])
self.assertAllClose(x_bc_expected, x_bc_)
self.assertAllClose(y_bc_expected, y_bc_)
def test_dynamic_dims_broadcast_32bit_second_arg_higher_rank(self):
# x.batch_shape = [1, 2]
# y.batch_shape = [3, 4, 1]
# broadcast batch shape = [3, 4, 2]
x = rng.rand(1, 2, 1, 5).astype(np.float32)
y = rng.rand(3, 4, 1, 3, 7).astype(np.float32)
batch_of_zeros = np.zeros((3, 4, 2, 1, 1)).astype(np.float32)
x_bc_expected = x + batch_of_zeros
y_bc_expected = y + batch_of_zeros
x_ph = array_ops.placeholder_with_default(x, shape=None)
y_ph = array_ops.placeholder_with_default(y, shape=None)
x_bc, y_bc = linear_operator_util.broadcast_matrix_batch_dims([x_ph, y_ph])
x_bc_, y_bc_ = self.evaluate([x_bc, y_bc])
self.assertAllClose(x_bc_expected, x_bc_)
self.assertAllClose(y_bc_expected, y_bc_)
def test_less_than_two_dims_raises_static(self):
x = rng.rand(3)
y = rng.rand(1, 1)
with self.assertRaisesRegex(ValueError, "at least two dimensions"):
linear_operator_util.broadcast_matrix_batch_dims([x, y])
with self.assertRaisesRegex(ValueError, "at least two dimensions"):
linear_operator_util.broadcast_matrix_batch_dims([y, x])
class MatrixSolveWithBroadcastTest(test.TestCase):
def test_static_dims_broadcast_matrix_has_extra_dims(self):
# batch_shape = [2]
matrix = rng.rand(2, 3, 3)
rhs = rng.rand(3, 7)
rhs_broadcast = rhs + np.zeros((2, 1, 1))
result = linear_operator_util.matrix_solve_with_broadcast(matrix, rhs)
self.assertAllEqual((2, 3, 7), result.shape)
expected = linalg_ops.matrix_solve(matrix, rhs_broadcast)
self.assertAllClose(*self.evaluate([expected, result]))
def test_static_dims_broadcast_rhs_has_extra_dims(self):
# Since the second arg has extra dims, and the domain dim of the first arg
# is larger than the number of linear equations, code will "flip" the extra
# dims of the first arg to the far right, making extra linear equations
# (then call the matrix function, then flip back).
# We have verified that this optimization indeed happens. How? We stepped
# through with a debugger.
# batch_shape = [2]
matrix = rng.rand(3, 3)
rhs = rng.rand(2, 3, 2)
matrix_broadcast = matrix + np.zeros((2, 1, 1))
result = linear_operator_util.matrix_solve_with_broadcast(matrix, rhs)
self.assertAllEqual((2, 3, 2), result.shape)
expected = linalg_ops.matrix_solve(matrix_broadcast, rhs)
self.assertAllClose(*self.evaluate([expected, result]))
def test_static_dims_broadcast_rhs_has_extra_dims_dynamic(self):
# Since the second arg has extra dims, and the domain dim of the first arg
# is larger than the number of linear equations, code will "flip" the extra
# dims of the first arg to the far right, making extra linear equations
# (then call the matrix function, then flip back).
# We have verified that this optimization indeed happens. How? We stepped
# through with a debugger.
# batch_shape = [2]
matrix = rng.rand(3, 3)
rhs = rng.rand(2, 3, 2)
matrix_broadcast = matrix + np.zeros((2, 1, 1))
matrix_ph = array_ops.placeholder_with_default(matrix, shape=[None, None])
rhs_ph = array_ops.placeholder_with_default(rhs, shape=[None, None, None])
result = linear_operator_util.matrix_solve_with_broadcast(matrix_ph, rhs_ph)
self.assertAllEqual(3, result.shape.ndims)
expected = linalg_ops.matrix_solve(matrix_broadcast, rhs)
self.assertAllClose(*self.evaluate([expected, result]))
def test_static_dims_broadcast_rhs_has_extra_dims_and_adjoint(self):
# Since the second arg has extra dims, and the domain dim of the first arg
# is larger than the number of linear equations, code will "flip" the extra
# dims of the first arg to the far right, making extra linear equations
# (then call the matrix function, then flip back).
# We have verified that this optimization indeed happens. How? We stepped
# through with a debugger.
# batch_shape = [2]
matrix = rng.rand(3, 3)
rhs = rng.rand(2, 3, 2)
matrix_broadcast = matrix + np.zeros((2, 1, 1))
result = linear_operator_util.matrix_solve_with_broadcast(
matrix, rhs, adjoint=True)
self.assertAllEqual((2, 3, 2), result.shape)
expected = linalg_ops.matrix_solve(matrix_broadcast, rhs, adjoint=True)
self.assertAllClose(*self.evaluate([expected, result]))
def test_dynamic_dims_broadcast_64bit(self):
# batch_shape = [2, 2]
matrix = rng.rand(2, 3, 3)
rhs = rng.rand(2, 1, 3, 7)
matrix_broadcast = matrix + np.zeros((2, 2, 1, 1))
rhs_broadcast = rhs + np.zeros((2, 2, 1, 1))
matrix_ph = array_ops.placeholder_with_default(matrix, shape=None)
rhs_ph = array_ops.placeholder_with_default(rhs, shape=None)
result, expected = self.evaluate([
linear_operator_util.matrix_solve_with_broadcast(matrix_ph, rhs_ph),
linalg_ops.matrix_solve(matrix_broadcast, rhs_broadcast)
])
self.assertAllClose(expected, result)
class DomainDimensionStubOperator(object):
def __init__(self, domain_dimension):
self._domain_dimension = ops.convert_to_tensor(domain_dimension)
def domain_dimension_tensor(self):
return self._domain_dimension
class AssertCompatibleMatrixDimensionsTest(test.TestCase):
def test_compatible_dimensions_do_not_raise(self):
x = ops.convert_to_tensor(rng.rand(2, 3, 4))
operator = DomainDimensionStubOperator(3)
# Should not raise
self.evaluate(
linear_operator_util.assert_compatible_matrix_dimensions(operator, x))
def test_incompatible_dimensions_raise(self):
x = ops.convert_to_tensor(rng.rand(2, 4, 4))
operator = DomainDimensionStubOperator(3)
# pylint: disable=g-error-prone-assert-raises
with self.assertRaisesOpError("Dimensions are not compatible"):
self.evaluate(
linear_operator_util.assert_compatible_matrix_dimensions(operator, x))
# pylint: enable=g-error-prone-assert-raises
class IsAdjointPairTest(test.TestCase):
def test_one_is_explicitly_adjoint_of_other_returns_true(self):
x = linalg_lib.LinearOperatorFullMatrix(
[[1., 2.], [3., 4.]], is_self_adjoint=False)
self.assertTrue(linear_operator_util.is_adjoint_pair(x, x.H))
self.assertTrue(linear_operator_util.is_adjoint_pair(x.H, x))
def test_repeated_non_self_adjoint_operator_returns_false(self):
x = linalg_lib.LinearOperatorFullMatrix(
[[1., 2.], [3., 4.]], is_self_adjoint=False)
self.assertFalse(linear_operator_util.is_adjoint_pair(x, x))
def test_repeated_self_adjoint_operator_returns_true(self):
x = linalg_lib.LinearOperatorFullMatrix(
[[1., 2.], [2., 1.]], is_self_adjoint=True)
self.assertTrue(linear_operator_util.is_adjoint_pair(x, x))
def test_pair_of_non_self_adjoint_operator_returns_false(self):
x = linalg_lib.LinearOperatorFullMatrix(
[[1., 2.], [3., 4.]], is_self_adjoint=False)
y = linalg_lib.LinearOperatorFullMatrix(
[[10., 20.], [3., 4.]], is_self_adjoint=False)
self.assertFalse(linear_operator_util.is_adjoint_pair(x, y))
class IsAATFormTest(test.TestCase):
# Careful when writing tests to avoid LinearOperatorDiag, since D.H is D for
# real D and this will be confusing.
def test_empty_operators_raises(self):
with self.assertRaisesRegex(ValueError, "empty operators"):
linear_operator_util.is_aat_form(operators=[])
def test_odd_length_returns_false(self):
x = linalg_lib.LinearOperatorFullMatrix([[1., 2.], [2., 1]],
is_self_adjoint=True)
self.assertFalse(linear_operator_util.is_aat_form([x]))
self.assertFalse(linear_operator_util.is_aat_form([x, x, x.H]))
def test_length_2_aat_form_with_sa_x(self):
x = linalg_lib.LinearOperatorFullMatrix([[1., 2.], [2., 1]],
is_self_adjoint=True)
self.assertTrue(linear_operator_util.is_aat_form([x, x.H]))
def test_length_2_aat_form_with_non_sa_x(self):
x = linalg_lib.LinearOperatorFullMatrix([[1., 5.], [2., 1]],
is_self_adjoint=False)
self.assertTrue(linear_operator_util.is_aat_form([x, x.H]))
def test_length_4_aat_form(self):
x = linalg_lib.LinearOperatorFullMatrix([[1., 2.], [5., 1]],
is_self_adjoint=False)
y = linalg_lib.LinearOperatorFullMatrix([[10., 2.], [3., 10]],
is_self_adjoint=False)
self.assertTrue(linear_operator_util.is_aat_form([x, y, y.H, x.H]))
class DummyOperatorWithHint(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
class UseOperatorOrProvidedHintUnlessContradictingTest(test.TestCase,
parameterized.TestCase):
@parameterized.named_parameters(
("none_none", None, None, None),
("none_true", None, True, True),
("true_none", True, None, True),
("true_true", True, True, True),
("none_false", None, False, False),
("false_none", False, None, False),
("false_false", False, False, False),
)
def test_computes_an_or_if_non_contradicting(self, operator_hint_value,
provided_hint_value,
expected_result):
self.assertEqual(
expected_result,
linear_operator_util.use_operator_or_provided_hint_unless_contradicting(
operator=DummyOperatorWithHint(my_hint=operator_hint_value),
hint_attr_name="my_hint",
provided_hint_value=provided_hint_value,
message="should not be needed here"))
@parameterized.named_parameters(
("true_false", True, False),
("false_true", False, True),
)
def test_raises_if_contradicting(self, operator_hint_value,
provided_hint_value):
with self.assertRaisesRegex(ValueError, "my error message"):
linear_operator_util.use_operator_or_provided_hint_unless_contradicting(
operator=DummyOperatorWithHint(my_hint=operator_hint_value),
hint_attr_name="my_hint",
provided_hint_value=provided_hint_value,
message="my error message")
class BlockwiseTest(test.TestCase, parameterized.TestCase):
@parameterized.named_parameters(
("split_dim_1", [3, 3, 4], -1),
("split_dim_2", [2, 5], -2),
)
def test_blockwise_input(self, op_dimension_values, split_dim):
op_dimensions = [
tensor_shape.Dimension(v) for v in op_dimension_values]
unknown_op_dimensions = [
tensor_shape.Dimension(None) for _ in op_dimension_values]
batch_shape = [2, 1]
arg_dim = 5
if split_dim == -1:
blockwise_arrays = [np.zeros(batch_shape + [arg_dim, d])
for d in op_dimension_values]
else:
blockwise_arrays = [np.zeros(batch_shape + [d, arg_dim])
for d in op_dimension_values]
blockwise_list = [block.tolist() for block in blockwise_arrays]
blockwise_tensors = [ops.convert_to_tensor(block)
for block in blockwise_arrays]
blockwise_placeholders = [
array_ops.placeholder_with_default(block, shape=None)
for block in blockwise_arrays]
# Iterables of non-nested structures are always interpreted as blockwise.
# The list of lists is interpreted as blockwise as well, regardless of
# whether the operator dimensions are known, since the sizes of its elements
# along `split_dim` are non-identical.
for op_dims in [op_dimensions, unknown_op_dimensions]:
for blockwise_inputs in [
blockwise_arrays, blockwise_list,
blockwise_tensors, blockwise_placeholders]:
self.assertTrue(linear_operator_util.arg_is_blockwise(
op_dims, blockwise_inputs, split_dim))
def test_non_blockwise_input(self):
x = np.zeros((2, 3, 4, 6))
x_tensor = ops.convert_to_tensor(x)
x_placeholder = array_ops.placeholder_with_default(x, shape=None)
x_list = x.tolist()
# For known and matching operator dimensions, interpret all as non-blockwise
op_dimension_values = [2, 1, 3]
op_dimensions = [tensor_shape.Dimension(d) for d in op_dimension_values]
for inputs in [x, x_tensor, x_placeholder, x_list]:
self.assertFalse(linear_operator_util.arg_is_blockwise(
op_dimensions, inputs, -1))
# The input is still interpreted as non-blockwise for unknown operator
# dimensions (`x_list` has an outermost dimension that does not matcn the
# number of blocks, and the other inputs are not iterables).
unknown_op_dimensions = [
tensor_shape.Dimension(None) for _ in op_dimension_values]
for inputs in [x, x_tensor, x_placeholder, x_list]:
self.assertFalse(linear_operator_util.arg_is_blockwise(
unknown_op_dimensions, inputs, -1))
def test_ambiguous_input_raises(self):
x = np.zeros((3, 4, 2)).tolist()
op_dimensions = [tensor_shape.Dimension(None) for _ in range(3)]
# Since the leftmost dimension of `x` is equal to the number of blocks, and
# the operators have unknown dimension, the input is ambiguous.
with self.assertRaisesRegex(ValueError, "structure is ambiguous"):
linear_operator_util.arg_is_blockwise(op_dimensions, x, -2)
def test_mismatched_input_raises(self):
x = np.zeros((2, 3, 4, 6)).tolist()
op_dimension_values = [4, 3]
op_dimensions = [tensor_shape.Dimension(v) for v in op_dimension_values]
# The dimensions of the two operator-blocks sum to 7. `x` is a
# two-element list; if interpreted blockwise, its corresponding dimensions
# sum to 12 (=6*2). If not interpreted blockwise, its corresponding
# dimension is 6. This is a mismatch.
with self.assertRaisesRegex(ValueError, "dimension does not match"):
linear_operator_util.arg_is_blockwise(op_dimensions, x, -1)
if __name__ == "__main__":
test.main()
@@ -0,0 +1,229 @@
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import variables as variables_module
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.platform import test
rng = np.random.RandomState(2016)
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorZerosTest(
linear_operator_test_util.SquareLinearOperatorDerivedClassTest):
"""Most tests done in the base class LinearOperatorDerivedClassTest."""
@staticmethod
def skip_these_tests():
return [
"cholesky",
"cond",
"inverse",
"log_abs_det",
"solve",
"solve_with_broadcast"
]
@staticmethod
def operator_shapes_infos():
shapes_info = linear_operator_test_util.OperatorShapesInfo
return [
shapes_info((1, 1)),
shapes_info((1, 3, 3)),
shapes_info((3, 4, 4)),
shapes_info((2, 1, 4, 4))]
@staticmethod
def optional_tests():
"""List of optional test names to run."""
return [
"operator_matmul_with_same_type",
]
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
del ensure_self_adjoint_and_pd
del use_placeholder
shape = list(build_info.shape)
assert shape[-1] == shape[-2]
batch_shape = shape[:-2]
num_rows = shape[-1]
operator = linalg_lib.LinearOperatorZeros(
num_rows, batch_shape=batch_shape, dtype=dtype)
matrix = array_ops.zeros(shape=shape, dtype=dtype)
return operator, matrix
def test_assert_positive_definite(self):
operator = linalg_lib.LinearOperatorZeros(num_rows=2)
with self.assertRaisesOpError("non-positive definite"):
operator.assert_positive_definite()
def test_assert_non_singular(self):
with self.assertRaisesOpError("non-invertible"):
operator = linalg_lib.LinearOperatorZeros(num_rows=2)
operator.assert_non_singular()
def test_assert_self_adjoint(self):
with self.cached_session():
operator = linalg_lib.LinearOperatorZeros(num_rows=2)
self.evaluate(operator.assert_self_adjoint()) # Should not fail
def test_non_scalar_num_rows_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be a 0-D Tensor"):
linalg_lib.LinearOperatorZeros(num_rows=[2])
with self.assertRaisesRegex(ValueError, "must be a 0-D Tensor"):
linalg_lib.LinearOperatorZeros(num_rows=2, num_columns=[2])
def test_non_integer_num_rows_raises_static(self):
with self.assertRaisesRegex(TypeError, "must be integer"):
linalg_lib.LinearOperatorZeros(num_rows=2.)
with self.assertRaisesRegex(TypeError, "must be integer"):
linalg_lib.LinearOperatorZeros(num_rows=2, num_columns=2.)
def test_negative_num_rows_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be non-negative"):
linalg_lib.LinearOperatorZeros(num_rows=-2)
with self.assertRaisesRegex(ValueError, "must be non-negative"):
linalg_lib.LinearOperatorZeros(num_rows=2, num_columns=-2)
def test_non_1d_batch_shape_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be a 1-D"):
linalg_lib.LinearOperatorZeros(num_rows=2, batch_shape=2)
def test_non_integer_batch_shape_raises_static(self):
with self.assertRaisesRegex(TypeError, "must be integer"):
linalg_lib.LinearOperatorZeros(num_rows=2, batch_shape=[2.])
def test_negative_batch_shape_raises_static(self):
with self.assertRaisesRegex(ValueError, "must be non-negative"):
linalg_lib.LinearOperatorZeros(num_rows=2, batch_shape=[-2])
def test_non_scalar_num_rows_raises_dynamic(self):
with self.cached_session():
num_rows = array_ops.placeholder_with_default([2], shape=None)
with self.assertRaisesError("must be a 0-D Tensor"):
operator = linalg_lib.LinearOperatorZeros(
num_rows, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
def test_negative_num_rows_raises_dynamic(self):
with self.cached_session():
n = array_ops.placeholder_with_default(-2, shape=None)
with self.assertRaisesError("must be non-negative"):
operator = linalg_lib.LinearOperatorZeros(
num_rows=n, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
def test_non_1d_batch_shape_raises_dynamic(self):
with self.cached_session():
batch_shape = array_ops.placeholder_with_default(2, shape=None)
with self.assertRaisesError("must be a 1-D"):
operator = linalg_lib.LinearOperatorZeros(
num_rows=2, batch_shape=batch_shape, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
def test_negative_batch_shape_raises_dynamic(self):
with self.cached_session():
batch_shape = array_ops.placeholder_with_default([-2], shape=None)
with self.assertRaisesError("must be non-negative"):
operator = linalg_lib.LinearOperatorZeros(
num_rows=2, batch_shape=batch_shape, assert_proper_shapes=True)
self.evaluate(operator.to_dense())
def test_wrong_matrix_dimensions_raises_static(self):
operator = linalg_lib.LinearOperatorZeros(num_rows=2)
x = rng.randn(3, 3).astype(np.float32)
with self.assertRaisesRegex(ValueError, "Dimensions.*not compatible"):
operator.matmul(x)
def test_wrong_matrix_dimensions_raises_dynamic(self):
num_rows = array_ops.placeholder_with_default(2, shape=None)
x = array_ops.placeholder_with_default(rng.rand(3, 3), shape=None)
with self.cached_session():
with self.assertRaisesError("Dimensions.*not.compatible"):
operator = linalg_lib.LinearOperatorZeros(
num_rows, assert_proper_shapes=True, dtype=dtypes.float64)
self.evaluate(operator.matmul(x))
def test_is_x_flags(self):
# The is_x flags are by default all True.
operator = linalg_lib.LinearOperatorZeros(num_rows=2)
self.assertFalse(operator.is_positive_definite)
self.assertFalse(operator.is_non_singular)
self.assertTrue(operator.is_self_adjoint)
def test_zeros_matmul(self):
operator1 = linalg_lib.LinearOperatorIdentity(num_rows=2)
operator2 = linalg_lib.LinearOperatorZeros(num_rows=2)
self.assertTrue(isinstance(
operator1.matmul(operator2),
linalg_lib.LinearOperatorZeros))
self.assertTrue(isinstance(
operator2.matmul(operator1),
linalg_lib.LinearOperatorZeros))
def test_ref_type_shape_args_raises(self):
with self.assertRaisesRegex(TypeError, "num_rows.cannot.be.reference"):
linalg_lib.LinearOperatorZeros(num_rows=variables_module.Variable(2))
with self.assertRaisesRegex(TypeError, "num_columns.cannot.be.reference"):
linalg_lib.LinearOperatorZeros(
num_rows=2, num_columns=variables_module.Variable(3))
with self.assertRaisesRegex(TypeError, "batch_shape.cannot.be.reference"):
linalg_lib.LinearOperatorZeros(
num_rows=2, batch_shape=variables_module.Variable([2]))
@test_util.run_all_in_graph_and_eager_modes
class LinearOperatorZerosNotSquareTest(
linear_operator_test_util.NonSquareLinearOperatorDerivedClassTest):
def operator_and_matrix(
self, build_info, dtype, use_placeholder,
ensure_self_adjoint_and_pd=False):
del use_placeholder
del ensure_self_adjoint_and_pd
shape = list(build_info.shape)
batch_shape = shape[:-2]
num_rows = shape[-2]
num_columns = shape[-1]
operator = linalg_lib.LinearOperatorZeros(
num_rows, num_columns, is_square=False, is_self_adjoint=False,
batch_shape=batch_shape, dtype=dtype)
matrix = array_ops.zeros(shape=shape, dtype=dtype)
return operator, matrix
if __name__ == "__main__":
linear_operator_test_util.add_tests(LinearOperatorZerosTest)
linear_operator_test_util.add_tests(LinearOperatorZerosNotSquareTest)
test.main()
@@ -0,0 +1,294 @@
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.tf.Lu."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import array_ops_stack
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import map_fn
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import stateless_random_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
@test_util.with_eager_op_as_function
class LuOpTest(test.TestCase):
@property
def float_types(self):
return set((np.float64, np.float32, np.complex64, np.complex128))
def _verifyLuBase(self, x, lower, upper, perm, verification,
output_idx_type):
lower_np, upper_np, perm_np, verification_np = self.evaluate(
[lower, upper, perm, verification])
self.assertAllClose(x, verification_np)
self.assertShapeEqual(x, lower)
self.assertShapeEqual(x, upper)
self.assertAllEqual(x.shape[:-1], perm.shape.as_list())
# Check dtypes are as expected.
self.assertEqual(x.dtype, lower_np.dtype)
self.assertEqual(x.dtype, upper_np.dtype)
self.assertEqual(output_idx_type.as_numpy_dtype, perm_np.dtype)
# Check that the permutation is valid.
if perm_np.shape[-1] > 0:
perm_reshaped = np.reshape(perm_np, (-1, perm_np.shape[-1]))
for perm_vector in perm_reshaped:
self.assertAllClose(np.arange(len(perm_vector)), np.sort(perm_vector))
def _verifyLu(self, x, output_idx_type=dtypes.int64):
# Verify that Px = LU.
lu, perm = linalg_ops.lu(x, output_idx_type=output_idx_type)
# Prepare the lower factor of shape num_rows x num_rows
lu_shape = np.array(lu.shape.as_list())
batch_shape = lu_shape[:-2]
num_rows = lu_shape[-2]
num_cols = lu_shape[-1]
lower = array_ops.matrix_band_part(lu, -1, 0)
if num_rows > num_cols:
eye = linalg_ops.eye(
num_rows, batch_shape=batch_shape, dtype=lower.dtype)
lower = array_ops.concat([lower, eye[..., num_cols:]], axis=-1)
elif num_rows < num_cols:
lower = lower[..., :num_rows]
# Fill the diagonal with ones.
ones_diag = array_ops.ones(
np.append(batch_shape, num_rows), dtype=lower.dtype)
lower = array_ops.matrix_set_diag(lower, ones_diag)
# Prepare the upper factor.
upper = array_ops.matrix_band_part(lu, 0, -1)
verification = test_util.matmul_without_tf32(lower, upper)
# Permute the rows of product of the Cholesky factors.
if num_rows > 0:
# Reshape the product of the triangular factors and permutation indices
# to a single batch dimension. This makes it easy to apply
# invert_permutation and gather_nd ops.
perm_reshaped = array_ops.reshape(perm, [-1, num_rows])
verification_reshaped = array_ops.reshape(verification,
[-1, num_rows, num_cols])
# Invert the permutation in each batch.
inv_perm_reshaped = map_fn.map_fn(array_ops.invert_permutation,
perm_reshaped)
batch_size = perm_reshaped.shape.as_list()[0]
# Prepare the batch indices with the same shape as the permutation.
# The corresponding batch index is paired with each of the `num_rows`
# permutation indices.
batch_indices = math_ops.cast(
array_ops.broadcast_to(
math_ops.range(batch_size)[:, None], perm_reshaped.shape),
dtype=output_idx_type)
if inv_perm_reshaped.shape == [0]:
inv_perm_reshaped = array_ops.zeros_like(batch_indices)
permuted_verification_reshaped = array_ops.gather_nd(
verification_reshaped,
array_ops_stack.stack([batch_indices, inv_perm_reshaped], axis=-1))
# Reshape the verification matrix back to the original shape.
verification = array_ops.reshape(permuted_verification_reshaped,
lu_shape)
self._verifyLuBase(x, lower, upper, perm, verification,
output_idx_type)
def testBasic(self):
data = np.array([[4., -1., 2.], [-1., 6., 0], [10., 0., 5.]])
for dtype in (np.float32, np.float64):
for output_idx_type in (dtypes.int32, dtypes.int64):
with self.subTest(dtype=dtype, output_idx_type=output_idx_type):
self._verifyLu(data.astype(dtype), output_idx_type=output_idx_type)
for dtype in (np.complex64, np.complex128):
for output_idx_type in (dtypes.int32, dtypes.int64):
with self.subTest(dtype=dtype, output_idx_type=output_idx_type):
complex_data = np.tril(1j * data, -1).astype(dtype)
complex_data += np.triu(-1j * data, 1).astype(dtype)
complex_data += data
self._verifyLu(complex_data, output_idx_type=output_idx_type)
def testPivoting(self):
# This matrix triggers partial pivoting because the first diagonal entry
# is small.
data = np.array([[1e-9, 1., 0.], [1., 0., 0], [0., 1., 5]])
self._verifyLu(data.astype(np.float32))
for dtype in (np.float32, np.float64):
with self.subTest(dtype=dtype):
self._verifyLu(data.astype(dtype))
_, p = linalg_ops.lu(data)
p_val = self.evaluate([p])
# Make sure p_val is not the identity permutation.
self.assertNotAllClose(np.arange(3), p_val)
for dtype in (np.complex64, np.complex128):
with self.subTest(dtype=dtype):
complex_data = np.tril(1j * data, -1).astype(dtype)
complex_data += np.triu(-1j * data, 1).astype(dtype)
complex_data += data
self._verifyLu(complex_data)
_, p = linalg_ops.lu(data)
p_val = self.evaluate([p])
# Make sure p_val is not the identity permutation.
self.assertNotAllClose(np.arange(3), p_val)
def testInvalidMatrix(self):
# LU factorization gives an error when the input is singular.
# Note: A singular matrix may return without error but it won't be a valid
# factorization.
for dtype in self.float_types:
with self.subTest(dtype=dtype):
with self.assertRaises(errors.InvalidArgumentError):
self.evaluate(
linalg_ops.lu(
np.array([[1., 2., 3.], [2., 4., 6.], [2., 3., 4.]],
dtype=dtype)))
with self.assertRaises(errors.InvalidArgumentError):
self.evaluate(
linalg_ops.lu(
np.array([[[1., 2., 3.], [2., 4., 6.], [1., 2., 3.]],
[[1., 2., 3.], [3., 4., 5.], [5., 6., 7.]]],
dtype=dtype)))
def testBatch(self):
simple_array = np.array([[[1., -1.], [2., 5.]]]) # shape (1, 2, 2)
self._verifyLu(simple_array)
self._verifyLu(np.vstack((simple_array, simple_array)))
odd_sized_array = np.array([[[4., -1., 2.], [-1., 6., 0], [2., 0., 5.]]])
self._verifyLu(np.vstack((odd_sized_array, odd_sized_array)))
batch_size = 200
# Generate random matrices.
np.random.seed(42)
matrices = np.random.rand(batch_size, 5, 5)
self._verifyLu(matrices)
# Generate random complex valued matrices.
np.random.seed(52)
matrices = np.random.rand(batch_size, 5,
5) + 1j * np.random.rand(batch_size, 5, 5)
self._verifyLu(matrices)
def testLargeMatrix(self):
# Generate random matrices.
n = 500
np.random.seed(64)
data = np.random.rand(n, n)
self._verifyLu(data)
# Generate random complex valued matrices.
np.random.seed(129)
data = np.random.rand(n, n) + 1j * np.random.rand(n, n)
self._verifyLu(data)
@test_util.disable_xla("b/206106619")
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testEmpty(self):
self._verifyLu(np.empty([0, 2, 2]))
self._verifyLu(np.empty([2, 0, 0]))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testConcurrentExecutesWithoutError(self):
matrix_shape = [5, 5]
seed = [42, 24]
matrix1 = stateless_random_ops.stateless_random_normal(
shape=matrix_shape, seed=seed)
matrix2 = stateless_random_ops.stateless_random_normal(
shape=matrix_shape, seed=seed)
self.assertAllEqual(matrix1, matrix2)
lu1, p1 = linalg_ops.lu(matrix1)
lu2, p2 = linalg_ops.lu(matrix2)
lu1_val, p1_val, lu2_val, p2_val = self.evaluate([lu1, p1, lu2, p2])
self.assertAllEqual(lu1_val, lu2_val)
self.assertAllEqual(p1_val, p2_val)
class LuBenchmark(test.Benchmark):
shapes = [
(4, 4),
(10, 10),
(16, 16),
(101, 101),
(256, 256),
(1000, 1000),
(1024, 1024),
(2048, 2048),
(4096, 4096),
(513, 2, 2),
(513, 8, 8),
(513, 256, 256),
(4, 513, 2, 2),
]
def _GenerateMatrix(self, shape):
batch_shape = shape[:-2]
shape = shape[-2:]
assert shape[0] == shape[1]
n = shape[0]
matrix = np.ones(shape).astype(np.float32) / (2.0 * n) + np.diag(
np.ones(n).astype(np.float32))
return np.tile(matrix, batch_shape + (1, 1))
def benchmarkLuOp(self):
for shape in self.shapes:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix = variables.Variable(self._GenerateMatrix(shape))
lu, p = linalg_ops.lu(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(lu, p),
min_iters=25,
name="lu_cpu_{shape}".format(shape=shape))
if test.is_gpu_available(True):
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/device:GPU:0"):
matrix = variables.Variable(self._GenerateMatrix(shape))
lu, p = linalg_ops.lu(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(lu, p),
min_iters=25,
name="lu_gpu_{shape}".format(shape=shape))
if __name__ == "__main__":
test.main()
@@ -0,0 +1,262 @@
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.linalg.linalg_impl.matrix_exponential."""
import itertools
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import variables
from tensorflow.python.ops.linalg import linalg_impl
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
def np_expm(x): # pylint: disable=invalid-name
"""Slow but accurate Taylor series matrix exponential."""
y = np.zeros(x.shape, dtype=x.dtype)
xn = np.eye(x.shape[0], dtype=x.dtype)
for n in range(40):
if n > 0:
xn /= float(n)
y += xn
xn = np.dot(xn, x)
return y
@test_util.run_all_without_tensor_float_32("Avoid TF32-based matmuls.")
class ExponentialOpTest(test.TestCase):
def _verifyExponential(self, x, np_type):
inp = x.astype(np_type)
with test_util.use_gpu():
tf_ans = linalg_impl.matrix_exponential(inp)
if x.size == 0:
np_ans = np.empty(x.shape, dtype=np_type)
else:
if x.ndim > 2:
np_ans = np.zeros(inp.shape, dtype=np_type)
for i in itertools.product(*[range(x) for x in inp.shape[:-2]]):
np_ans[i] = np_expm(inp[i])
else:
np_ans = np_expm(inp)
out = self.evaluate(tf_ans)
self.assertAllClose(np_ans, out, rtol=1e-3, atol=1e-3)
def _verifyExponentialReal(self, x):
for np_type in [np.float32, np.float64]:
self._verifyExponential(x, np_type)
def _verifyExponentialComplex(self, x):
for np_type in [np.complex64, np.complex128]:
self._verifyExponential(x, np_type)
def _makeBatch(self, matrix1, matrix2):
matrix_batch = np.concatenate(
[np.expand_dims(matrix1, 0),
np.expand_dims(matrix2, 0)])
matrix_batch = np.tile(matrix_batch, [2, 3, 1, 1])
return matrix_batch
def testNonsymmetricReal(self):
# 2x2 matrices
matrix1 = np.array([[1., 2.], [3., 4.]])
matrix2 = np.array([[1., 3.], [3., 5.]])
self._verifyExponentialReal(matrix1)
self._verifyExponentialReal(matrix2)
# A multidimensional batch of 2x2 matrices
self._verifyExponentialReal(self._makeBatch(matrix1, matrix2))
@test_util.run_deprecated_v1
def testNonsymmetricComplex(self):
matrix1 = np.array([[1., 2.], [3., 4.]])
matrix2 = np.array([[1., 3.], [3., 5.]])
matrix1 = matrix1.astype(np.complex64)
matrix1 += 1j * matrix1
matrix2 = matrix2.astype(np.complex64)
matrix2 += 1j * matrix2
self._verifyExponentialComplex(matrix1)
self._verifyExponentialComplex(matrix2)
# Complex batch
self._verifyExponentialComplex(self._makeBatch(matrix1, matrix2))
def testSymmetricPositiveDefiniteReal(self):
# 2x2 matrices
matrix1 = np.array([[2., 1.], [1., 2.]])
matrix2 = np.array([[3., -1.], [-1., 3.]])
self._verifyExponentialReal(matrix1)
self._verifyExponentialReal(matrix2)
# A multidimensional batch of 2x2 matrices
self._verifyExponentialReal(self._makeBatch(matrix1, matrix2))
def testSymmetricPositiveDefiniteComplex(self):
matrix1 = np.array([[2., 1.], [1., 2.]])
matrix2 = np.array([[3., -1.], [-1., 3.]])
matrix1 = matrix1.astype(np.complex64)
matrix1 += 1j * matrix1
matrix2 = matrix2.astype(np.complex64)
matrix2 += 1j * matrix2
self._verifyExponentialComplex(matrix1)
self._verifyExponentialComplex(matrix2)
# Complex batch
self._verifyExponentialComplex(self._makeBatch(matrix1, matrix2))
@test_util.run_deprecated_v1
def testNonSquareMatrix(self):
# When the exponential of a non-square matrix is attempted we should return
# an error
with self.assertRaises(ValueError):
linalg_impl.matrix_exponential(np.array([[1., 2., 3.], [3., 4., 5.]]))
@test_util.run_deprecated_v1
def testWrongDimensions(self):
# The input to the exponential should be at least a 2-dimensional tensor.
tensor3 = constant_op.constant([1., 2.])
with self.assertRaises(ValueError):
linalg_impl.matrix_exponential(tensor3)
def testInfinite(self):
# Check that the op does not loop forever on infinite inputs. (b/158433036)
in_tensor = [[np.inf, 1.], [1., 1.]]
result = self.evaluate(linalg_impl.matrix_exponential(in_tensor))
self.assertTrue(np.all(np.isnan(result)))
def testEmpty(self):
self._verifyExponentialReal(np.empty([0, 2, 2]))
self._verifyExponentialReal(np.empty([2, 0, 0]))
@test_util.run_deprecated_v1
def testDynamic(self):
with self.session() as sess:
inp = array_ops.placeholder(ops.dtypes.float32)
expm = linalg_impl.matrix_exponential(inp)
matrix = np.array([[1., 2.], [3., 4.]])
sess.run(expm, feed_dict={inp: matrix})
@test_util.run_deprecated_v1
def testConcurrentExecutesWithoutError(self):
with self.session():
matrix1 = random_ops.random_normal([5, 5], seed=42)
matrix2 = random_ops.random_normal([5, 5], seed=42)
expm1 = linalg_impl.matrix_exponential(matrix1)
expm2 = linalg_impl.matrix_exponential(matrix2)
expm = self.evaluate([expm1, expm2])
self.assertAllEqual(expm[0], expm[1])
class MatrixExponentialBenchmark(test.Benchmark):
shapes = [
(4, 4),
(10, 10),
(16, 16),
(101, 101),
(256, 256),
(1000, 1000),
(1024, 1024),
(2048, 2048),
(513, 4, 4),
(513, 16, 16),
(513, 256, 256),
]
def _GenerateMatrix(self, shape):
batch_shape = shape[:-2]
shape = shape[-2:]
assert shape[0] == shape[1]
n = shape[0]
matrix = np.ones(shape).astype(np.float32) / (2.0 * n) + np.diag(
np.ones(n).astype(np.float32))
return variables.Variable(np.tile(matrix, batch_shape + (1, 1)))
def benchmarkMatrixExponentialOp(self):
for shape in self.shapes:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix = self._GenerateMatrix(shape)
expm = linalg_impl.matrix_exponential(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(expm),
min_iters=25,
name="matrix_exponential_cpu_{shape}".format(shape=shape))
if test.is_gpu_available(True):
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/gpu:0"):
matrix = self._GenerateMatrix(shape)
expm = linalg_impl.matrix_exponential(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(expm),
min_iters=25,
name="matrix_exponential_gpu_{shape}".format(shape=shape))
def _TestRandomSmall(dtype, batch_dims, size):
def Test(self):
np.random.seed(42)
shape = batch_dims + (size, size)
matrix = np.random.uniform(low=-1.0, high=1.0, size=shape).astype(dtype)
self._verifyExponentialReal(matrix)
return Test
def _TestL1Norms(dtype, shape, scale):
def Test(self):
np.random.seed(42)
matrix = np.random.uniform(
low=-1.0, high=1.0, size=np.prod(shape)).reshape(shape).astype(dtype)
l1_norm = np.max(np.sum(np.abs(matrix), axis=matrix.ndim - 2))
matrix /= l1_norm
self._verifyExponentialReal(scale * matrix)
return Test
if __name__ == "__main__":
for dtype_ in [np.float32, np.float64, np.complex64, np.complex128]:
for batch_ in [(), (2,), (2, 2)]:
for size_ in [4, 7]:
name = "%s_%d_%d" % (dtype_.__name__, len(batch_), size_)
setattr(ExponentialOpTest, "testL1Norms_" + name,
_TestRandomSmall(dtype_, batch_, size_))
for shape_ in [(3, 3), (2, 3, 3)]:
for dtype_ in [np.float32, np.complex64]:
for scale_ in [0.1, 1.5, 5.0, 20.0]:
name = "%s_%d_%d" % (dtype_.__name__, len(shape_), int(scale_ * 10))
setattr(ExponentialOpTest, "testL1Norms_" + name,
_TestL1Norms(dtype_, shape_, scale_))
for dtype_ in [np.float64, np.complex128]:
for scale_ in [0.01, 0.2, 0.5, 1.5, 6.0, 25.0]:
name = "%s_%d_%d" % (dtype_.__name__, len(shape_), int(scale_ * 100))
setattr(ExponentialOpTest, "testL1Norms_" + name,
_TestL1Norms(dtype_, shape_, scale_))
test.main()
@@ -0,0 +1,220 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.math_ops.matrix_inverse."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
class InverseOpTest(test.TestCase):
def _high_precision_matmul(self, a, b, adjoint_b):
"""Do a higher-precision matmul, casting either to float32 or float64."""
if a.dtype == dtypes.float16:
a = math_ops.cast(a, dtypes.float32)
b = math_ops.cast(b, dtypes.float32)
ret = test_util.matmul_without_tf32(a, b, adjoint_b=adjoint_b)
return math_ops.cast(ret, a.dtype)
def _verifyInverse(self, x, np_type):
for adjoint in False, True:
y = x.astype(np_type)
with self.cached_session(use_gpu=test_util.is_gpu_available()):
# Verify that x^{-1} * x == Identity matrix.
inv = linalg_ops.matrix_inverse(y, adjoint=adjoint)
tf_ans = self._high_precision_matmul(inv, y, adjoint_b=adjoint)
np_ans = np.identity(y.shape[-1]).astype(np_type)
if x.ndim > 2:
tiling = list(y.shape)
tiling[-2:] = [1, 1]
np_ans = np.tile(np_ans, tiling)
out = self.evaluate(tf_ans)
self.assertAllCloseAccordingToType(
np_ans, out, atol=0.001, half_atol=0.1)
self.assertShapeEqual(y, tf_ans)
def _verifyInverseReal(self, x):
for np_type in [np.float16, np.float32, np.float64]:
self._verifyInverse(x, np_type)
def _verifyInverseComplex(self, x):
for np_type in [np.complex64, np.complex128]:
self._verifyInverse(x, np_type)
def _makeBatch(self, matrix1, matrix2):
matrix_batch = np.concatenate(
[np.expand_dims(matrix1, 0),
np.expand_dims(matrix2, 0)])
matrix_batch = np.tile(matrix_batch, [2, 3, 1, 1])
return matrix_batch
def testNonsymmetric(self):
# 2x2 matrices
matrix1 = np.array([[1., 2.], [3., 4.]])
matrix2 = np.array([[1., 3.], [3., 5.]])
self._verifyInverseReal(matrix1)
self._verifyInverseReal(matrix2)
# A multidimensional batch of 2x2 matrices
self._verifyInverseReal(self._makeBatch(matrix1, matrix2))
matrix1 = matrix1.astype(np.complex64)
matrix1 += 1j * matrix1
matrix2 = matrix2.astype(np.complex64)
matrix2 += 1j * matrix2
self._verifyInverseComplex(matrix1)
self._verifyInverseComplex(matrix2)
# Complex batch
self._verifyInverseComplex(self._makeBatch(matrix1, matrix2))
def testSymmetricPositiveDefinite(self):
# 2x2 matrices
matrix1 = np.array([[2., 1.], [1., 2.]])
matrix2 = np.array([[3., -1.], [-1., 3.]])
self._verifyInverseReal(matrix1)
self._verifyInverseReal(matrix2)
# A multidimensional batch of 2x2 matrices
self._verifyInverseReal(self._makeBatch(matrix1, matrix2))
matrix1 = matrix1.astype(np.complex64)
matrix1 += 1j * matrix1
matrix2 = matrix2.astype(np.complex64)
matrix2 += 1j * matrix2
self._verifyInverseComplex(matrix1)
self._verifyInverseComplex(matrix2)
# Complex batch
self._verifyInverseComplex(self._makeBatch(matrix1, matrix2))
@test_util.deprecated_graph_mode_only
def testNonSquareMatrix(self):
# When the inverse of a non-square matrix is attempted we should return
# an error
with self.assertRaises(ValueError):
linalg_ops.matrix_inverse(np.array([[1., 2., 3.], [3., 4., 5.]]))
@test_util.deprecated_graph_mode_only
def testWrongDimensions(self):
# The input to the inverse should be at least a 2-dimensional tensor.
tensor3 = constant_op.constant([1., 2.])
with self.assertRaises(ValueError):
linalg_ops.matrix_inverse(tensor3)
def testNotInvertible(self):
# The input should be invertible.
with self.cached_session():
with self.assertRaisesOpError("Input is not invertible."):
# All rows of the matrix below add to zero.
tensor3 = constant_op.constant([[1., 0., -1.], [-1., 1., 0.],
[0., -1., 1.]])
linalg_ops.matrix_inverse(tensor3).eval()
def testEmpty(self):
self._verifyInverseReal(np.empty([0, 2, 2]))
self._verifyInverseReal(np.empty([2, 0, 0]))
def testRandomSmallAndLarge(self):
np.random.seed(42)
for dtype in np.float32, np.float64, np.complex64, np.complex128:
for batch_dims in [(), (1,), (3,), (2, 2)]:
for size in 8, 31, 32:
shape = batch_dims + (size, size)
matrix = np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(shape)).reshape(shape).astype(dtype)
self._verifyInverseReal(matrix)
@test_util.deprecated_graph_mode_only
def testConcurrentExecutesWithoutError(self):
with self.session() as sess:
all_ops = []
for adjoint_ in True, False:
matrix1 = random_ops.random_normal([5, 5], seed=42)
matrix2 = random_ops.random_normal([5, 5], seed=42)
inv1 = linalg_ops.matrix_inverse(matrix1, adjoint=adjoint_)
inv2 = linalg_ops.matrix_inverse(matrix2, adjoint=adjoint_)
all_ops += [inv1, inv2]
inv = self.evaluate(all_ops)
self.assertAllEqual(inv[0], inv[1])
self.assertAllEqual(inv[2], inv[3])
class MatrixInverseBenchmark(test.Benchmark):
shapes = [
(4, 4),
(10, 10),
(16, 16),
(101, 101),
(256, 256),
(1000, 1000),
(1024, 1024),
(2048, 2048),
(513, 4, 4),
(513, 16, 16),
(513, 256, 256),
]
def _GenerateMatrix(self, shape):
batch_shape = shape[:-2]
shape = shape[-2:]
assert shape[0] == shape[1]
n = shape[0]
matrix = np.ones(shape).astype(np.float32) / (
2.0 * n) + np.diag(np.ones(n).astype(np.float32))
return variables.Variable(np.tile(matrix, batch_shape + (1, 1)))
def benchmarkMatrixInverseOp(self):
for adjoint in False, True:
for shape in self.shapes:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix = self._GenerateMatrix(shape)
inv = linalg_ops.matrix_inverse(matrix, adjoint=adjoint)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(inv),
min_iters=25,
name="matrix_inverse_cpu_{shape}_adjoint_{adjoint}".format(
shape=shape, adjoint=adjoint))
if test.is_gpu_available(True):
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/gpu:0"):
matrix = self._GenerateMatrix(shape)
inv = linalg_ops.matrix_inverse(matrix, adjoint=adjoint)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(inv),
min_iters=25,
name="matrix_inverse_gpu_{shape}_adjoint_{adjoint}".format(
shape=shape, adjoint=adjoint))
if __name__ == "__main__":
test.main()
@@ -0,0 +1,185 @@
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.gen_linalg_ops.matrix_logarithm."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import gen_linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import stateless_random_ops
from tensorflow.python.ops import variables
from tensorflow.python.ops.linalg import linalg_impl
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
class LogarithmOpTest(test.TestCase):
def _verifyLogarithm(self, x, np_type):
inp = x.astype(np_type)
with test_util.use_gpu():
# Verify that expm(logm(A)) == A.
tf_ans = linalg_impl.matrix_exponential(
gen_linalg_ops.matrix_logarithm(inp))
out = self.evaluate(tf_ans)
self.assertAllClose(inp, out, rtol=1e-4, atol=1e-3)
def _verifyLogarithmComplex(self, x):
for np_type in [np.complex64, np.complex128]:
self._verifyLogarithm(x, np_type)
def _makeBatch(self, matrix1, matrix2):
matrix_batch = np.concatenate(
[np.expand_dims(matrix1, 0),
np.expand_dims(matrix2, 0)])
matrix_batch = np.tile(matrix_batch, [2, 3, 1, 1])
return matrix_batch
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testNonsymmetric(self):
# 2x2 matrices
matrix1 = np.array([[1., 2.], [3., 4.]])
matrix2 = np.array([[1., 3.], [3., 5.]])
matrix1 = matrix1.astype(np.complex64)
matrix1 += 1j * matrix1
matrix2 = matrix2.astype(np.complex64)
matrix2 += 1j * matrix2
self._verifyLogarithmComplex(matrix1)
self._verifyLogarithmComplex(matrix2)
# Complex batch
self._verifyLogarithmComplex(self._makeBatch(matrix1, matrix2))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testSymmetricPositiveDefinite(self):
# 2x2 matrices
matrix1 = np.array([[2., 1.], [1., 2.]])
matrix2 = np.array([[3., -1.], [-1., 3.]])
matrix1 = matrix1.astype(np.complex64)
matrix1 += 1j * matrix1
matrix2 = matrix2.astype(np.complex64)
matrix2 += 1j * matrix2
self._verifyLogarithmComplex(matrix1)
self._verifyLogarithmComplex(matrix2)
# Complex batch
self._verifyLogarithmComplex(self._makeBatch(matrix1, matrix2))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testNonSquareMatrix(self):
# When the logarithm of a non-square matrix is attempted we should return
# an error
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
gen_linalg_ops.matrix_logarithm(
np.array([[1., 2., 3.], [3., 4., 5.]], dtype=np.complex64))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testWrongDimensions(self):
# The input to the logarithm should be at least a 2-dimensional tensor.
tensor3 = constant_op.constant([1., 2.], dtype=dtypes.complex64)
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
gen_linalg_ops.matrix_logarithm(tensor3)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testEmpty(self):
self._verifyLogarithmComplex(np.empty([0, 2, 2], dtype=np.complex64))
self._verifyLogarithmComplex(np.empty([2, 0, 0], dtype=np.complex64))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testRandomSmallAndLargeComplex64(self):
np.random.seed(42)
for batch_dims in [(), (1,), (3,), (2, 2)]:
for size in 8, 31, 32:
shape = batch_dims + (size, size)
matrix = np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(shape)).reshape(shape).astype(np.complex64)
self._verifyLogarithmComplex(matrix)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testRandomSmallAndLargeComplex128(self):
np.random.seed(42)
for batch_dims in [(), (1,), (3,), (2, 2)]:
for size in 8, 31, 32:
shape = batch_dims + (size, size)
matrix = np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(shape)).reshape(shape).astype(np.complex128)
self._verifyLogarithmComplex(matrix)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testConcurrentExecutesWithoutError(self):
matrix_shape = [5, 5]
seed = [42, 24]
matrix1 = math_ops.cast(
stateless_random_ops.stateless_random_normal(matrix_shape, seed=seed),
dtypes.complex64)
matrix2 = math_ops.cast(
stateless_random_ops.stateless_random_normal(matrix_shape, seed=seed),
dtypes.complex64)
self.assertAllEqual(matrix1, matrix2)
logm1 = gen_linalg_ops.matrix_logarithm(matrix1)
logm2 = gen_linalg_ops.matrix_logarithm(matrix2)
logm = self.evaluate([logm1, logm2])
self.assertAllEqual(logm[0], logm[1])
class MatrixLogarithmBenchmark(test.Benchmark):
shapes = [
(4, 4),
(10, 10),
(16, 16),
(101, 101),
(256, 256),
(1000, 1000),
(1024, 1024),
(2048, 2048),
(513, 4, 4),
(513, 16, 16),
(513, 256, 256),
]
def _GenerateMatrix(self, shape):
batch_shape = shape[:-2]
shape = shape[-2:]
assert shape[0] == shape[1]
n = shape[0]
matrix = np.ones(shape).astype(np.complex64) / (2.0 * n) + np.diag(
np.ones(n).astype(np.complex64))
return variables.Variable(np.tile(matrix, batch_shape + (1, 1)))
def benchmarkMatrixLogarithmOp(self):
for shape in self.shapes:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix = self._GenerateMatrix(shape)
logm = gen_linalg_ops.matrix_logarithm(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(logm),
min_iters=25,
name="matrix_logarithm_cpu_{shape}".format(shape=shape))
if __name__ == "__main__":
test.main()
@@ -0,0 +1,382 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.math_ops.matrix_solve."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.eager import context
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test as test_lib
def _AddTest(test, op_name, testcase_name, fn):
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test, test_name, fn)
def _GenerateTestData(matrix_shape, num_rhs):
batch_shape = matrix_shape[:-2]
matrix_shape = matrix_shape[-2:]
m = matrix_shape[-2]
np.random.seed(1)
matrix = np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(matrix_shape)).reshape(matrix_shape).astype(np.float32)
rhs = np.ones([m, num_rhs]).astype(np.float32)
matrix = variables.Variable(
np.tile(matrix, batch_shape + (1, 1)), trainable=False)
rhs = variables.Variable(np.tile(rhs, batch_shape + (1, 1)), trainable=False)
return matrix, rhs
def _SolveWithNumpy(matrix, rhs, l2_regularizer=0):
if l2_regularizer == 0:
np_ans, _, _, _ = np.linalg.lstsq(matrix, rhs)
return np_ans
else:
rows = matrix.shape[-2]
cols = matrix.shape[-1]
if rows >= cols:
preconditioner = l2_regularizer * np.identity(cols)
gramian = np.dot(np.conj(matrix.T), matrix) + preconditioner
rhs = np.dot(np.conj(matrix.T), rhs)
return np.linalg.solve(gramian, rhs)
else:
preconditioner = l2_regularizer * np.identity(rows)
gramian = np.dot(matrix, np.conj(matrix.T)) + preconditioner
z = np.linalg.solve(gramian, rhs)
return np.dot(np.conj(matrix.T), z)
@test_util.with_eager_op_as_function
class MatrixSolveLsOpTest(test_lib.TestCase):
def _verifySolve(self,
x,
y,
dtype,
use_placeholder,
fast,
l2_regularizer,
batch_shape=()):
if not fast and l2_regularizer != 0:
# The slow path does not support regularization.
return
if use_placeholder and context.executing_eagerly():
return
maxdim = np.max(x.shape)
if dtype == np.float32 or dtype == np.complex64:
tol = maxdim * 5e-4
else:
tol = maxdim * 5e-7
a = x.astype(dtype)
b = y.astype(dtype)
if dtype in [np.complex64, np.complex128]:
a.imag = a.real
b.imag = b.real
# numpy.linalg.lstqr does not batching, so we just solve a single system
# and replicate the solution. and residual norm.
np_ans = _SolveWithNumpy(x, y, l2_regularizer=l2_regularizer)
np_r = np.dot(np.conj(a.T), b - np.dot(a, np_ans))
np_r_norm = np.sqrt(np.sum(np.conj(np_r) * np_r))
if batch_shape != ():
a = np.tile(a, batch_shape + (1, 1))
b = np.tile(b, batch_shape + (1, 1))
np_ans = np.tile(np_ans, batch_shape + (1, 1))
np_r_norm = np.tile(np_r_norm, batch_shape)
if use_placeholder:
a_ph = array_ops.placeholder(dtypes.as_dtype(dtype))
b_ph = array_ops.placeholder(dtypes.as_dtype(dtype))
feed_dict = {a_ph: a, b_ph: b}
tf_ans = linalg_ops.matrix_solve_ls(
a_ph, b_ph, fast=fast, l2_regularizer=l2_regularizer)
else:
tf_ans = linalg_ops.matrix_solve_ls(
a, b, fast=fast, l2_regularizer=l2_regularizer)
feed_dict = None
self.assertEqual(np_ans.shape, tf_ans.get_shape())
if feed_dict:
with self.session() as sess:
tf_ans_val = sess.run(tf_ans, feed_dict=feed_dict)
else:
tf_ans_val = self.evaluate(tf_ans)
self.assertEqual(np_ans.shape, tf_ans_val.shape)
self.assertAllClose(np_ans, tf_ans_val, atol=2 * tol, rtol=2 * tol)
if l2_regularizer == 0:
# The least squares solution should satisfy A^H * (b - A*x) = 0.
tf_r = b - math_ops.matmul(a, tf_ans)
tf_r = math_ops.matmul(a, tf_r, adjoint_a=True)
tf_r_norm = linalg_ops.norm(tf_r, ord="fro", axis=[-2, -1])
if feed_dict:
with self.session() as sess:
tf_ans_val, tf_r_norm_val = sess.run([tf_ans, tf_r_norm],
feed_dict=feed_dict)
else:
tf_ans_val, tf_r_norm_val = self.evaluate([tf_ans, tf_r_norm])
self.assertAllClose(np_r_norm, tf_r_norm_val, atol=tol, rtol=tol)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testWrongDimensions(self):
# The matrix and right-hand sides should have the same number of rows.
with self.session():
matrix = constant_op.constant([[1., 0.], [0., 1.]])
rhs = constant_op.constant([[1., 0.]])
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
linalg_ops.matrix_solve_ls(matrix, rhs)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testEmpty(self):
full = np.array([[1., 2.], [3., 4.], [5., 6.]])
empty0 = np.empty([3, 0])
empty1 = np.empty([0, 2])
for fast in [True, False]:
tf_ans = self.evaluate(
linalg_ops.matrix_solve_ls(empty0, empty0, fast=fast))
self.assertEqual(tf_ans.shape, (0, 0))
tf_ans = self.evaluate(
linalg_ops.matrix_solve_ls(empty0, full, fast=fast))
self.assertEqual(tf_ans.shape, (0, 2))
tf_ans = self.evaluate(
linalg_ops.matrix_solve_ls(full, empty0, fast=fast))
self.assertEqual(tf_ans.shape, (2, 0))
tf_ans = self.evaluate(
linalg_ops.matrix_solve_ls(empty1, empty1, fast=fast))
self.assertEqual(tf_ans.shape, (2, 2))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testBatchResultSize(self):
# 3x3x3 matrices, 3x3x1 right-hand sides.
matrix = np.array([1., 0., 0., 0., 1., 0., 0., 0., 1.] * 3).reshape(3, 3, 3) # pylint: disable=too-many-function-args
rhs = np.array([1., 2., 3.] * 3).reshape(3, 3, 1) # pylint: disable=too-many-function-args
answer = linalg_ops.matrix_solve(matrix, rhs)
ls_answer = linalg_ops.matrix_solve_ls(matrix, rhs)
self.assertEqual(ls_answer.get_shape(), [3, 3, 1])
self.assertEqual(answer.get_shape(), [3, 3, 1])
def _GetSmallMatrixSolveLsOpTests(dtype, use_placeholder, fast, l2_regularizer):
def Square(self):
# 2x2 matrices, 2x3 right-hand sides.
matrix = np.array([[1., 2.], [3., 4.]])
rhs = np.array([[1., 0., 1.], [0., 1., 1.]])
for batch_shape in (), (2, 3):
self._verifySolve(
matrix,
rhs,
dtype,
use_placeholder,
fast,
l2_regularizer,
batch_shape=batch_shape)
def Overdetermined(self):
# 2x2 matrices, 2x3 right-hand sides.
matrix = np.array([[1., 2.], [3., 4.], [5., 6.]])
rhs = np.array([[1., 0., 1.], [0., 1., 1.], [1., 1., 0.]])
for batch_shape in (), (2, 3):
self._verifySolve(
matrix,
rhs,
dtype,
use_placeholder,
fast,
l2_regularizer,
batch_shape=batch_shape)
def Underdetermined(self):
# 2x2 matrices, 2x3 right-hand sides.
matrix = np.array([[1., 2., 3], [4., 5., 6.]])
rhs = np.array([[1., 0., 1.], [0., 1., 1.]])
for batch_shape in (), (2, 3):
self._verifySolve(
matrix,
rhs,
dtype,
use_placeholder,
fast,
l2_regularizer,
batch_shape=batch_shape)
return (Square, Overdetermined, Underdetermined)
def _GetLargeMatrixSolveLsOpTests(dtype, use_placeholder, fast, l2_regularizer):
def LargeBatchSquare(self):
np.random.seed(1)
num_rhs = 1
matrix_shape = (127, 127)
matrix = np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(matrix_shape)).reshape(matrix_shape).astype(np.float32)
rhs = np.ones([matrix_shape[0], num_rhs]).astype(np.float32)
self._verifySolve(
matrix,
rhs,
dtype,
use_placeholder,
fast,
l2_regularizer,
batch_shape=(16, 8))
def LargeBatchOverdetermined(self):
np.random.seed(1)
num_rhs = 1
matrix_shape = (127, 64)
matrix = np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(matrix_shape)).reshape(matrix_shape).astype(np.float32)
rhs = np.ones([matrix_shape[0], num_rhs]).astype(np.float32)
self._verifySolve(
matrix,
rhs,
dtype,
use_placeholder,
fast,
l2_regularizer,
batch_shape=(16, 8))
def LargeBatchUnderdetermined(self):
np.random.seed(1)
num_rhs = 1
matrix_shape = (64, 127)
matrix = np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(matrix_shape)).reshape(matrix_shape).astype(np.float32)
rhs = np.ones([matrix_shape[0], num_rhs]).astype(np.float32)
self._verifySolve(
matrix,
rhs,
dtype,
use_placeholder,
fast,
l2_regularizer,
batch_shape=(16, 8))
return (LargeBatchSquare, LargeBatchOverdetermined, LargeBatchUnderdetermined)
class MatrixSolveLsBenchmark(test_lib.Benchmark):
matrix_shapes = [
(4, 4),
(8, 4),
(4, 8),
(10, 10),
(10, 8),
(8, 10),
(16, 16),
(16, 10),
(10, 16),
(101, 101),
(101, 31),
(31, 101),
(256, 256),
(256, 200),
(200, 256),
(1001, 1001),
(1001, 501),
(501, 1001),
(1024, 1024),
(1024, 128),
(128, 1024),
(2048, 2048),
(2048, 64),
(64, 2048),
(513, 4, 4),
(513, 4, 2),
(513, 2, 4),
(513, 16, 16),
(513, 16, 10),
(513, 10, 16),
(513, 256, 256),
(513, 256, 128),
(513, 128, 256),
]
def benchmarkMatrixSolveLsOp(self):
run_gpu_test = test_lib.is_gpu_available(True)
regularizer = 1.0
for matrix_shape in self.matrix_shapes:
for num_rhs in 1, 2, matrix_shape[-1]:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix, rhs = _GenerateTestData(matrix_shape, num_rhs)
x = linalg_ops.matrix_solve_ls(matrix, rhs, regularizer)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(x),
min_iters=25,
store_memory_usage=False,
name=("matrix_solve_ls_cpu_shape_{matrix_shape}_num_rhs_{num_rhs}"
).format(matrix_shape=matrix_shape, num_rhs=num_rhs))
if run_gpu_test and (len(matrix_shape) < 3 or matrix_shape[0] < 513):
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/gpu:0"):
matrix, rhs = _GenerateTestData(matrix_shape, num_rhs)
x = linalg_ops.matrix_solve_ls(matrix, rhs, regularizer)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(x),
min_iters=25,
store_memory_usage=False,
name=("matrix_solve_ls_gpu_shape_{matrix_shape}_num_rhs_"
"{num_rhs}").format(
matrix_shape=matrix_shape, num_rhs=num_rhs))
if __name__ == "__main__":
dtypes_to_test = [np.float32, np.float64, np.complex64, np.complex128]
for dtype_ in dtypes_to_test:
for use_placeholder_ in set([False, True]):
for fast_ in [True, False]:
l2_regularizers = [0] if dtype_ == np.complex128 else [0, 0.1]
for l2_regularizer_ in l2_regularizers:
for test_case in _GetSmallMatrixSolveLsOpTests(
dtype_, use_placeholder_, fast_, l2_regularizer_):
name = "%s_%s_placeholder_%s_fast_%s_regu_%s" % (test_case.__name__,
dtype_.__name__,
use_placeholder_,
fast_,
l2_regularizer_)
_AddTest(MatrixSolveLsOpTest, "MatrixSolveLsOpTest", name,
test_case)
for dtype_ in dtypes_to_test:
for test_case in _GetLargeMatrixSolveLsOpTests(dtype_, False, True, 0.0):
name = "%s_%s" % (test_case.__name__, dtype_.__name__)
_AddTest(MatrixSolveLsOpTest, "MatrixSolveLsOpTest", name, test_case)
test_lib.main()
@@ -0,0 +1,219 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.math_ops.matrix_solve."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.eager import context
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import stateless_random_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
class MatrixSolveOpTest(test.TestCase):
def _verifySolve(self, x, y, batch_dims=None):
for np_type in [np.float32, np.float64, np.complex64, np.complex128]:
if np_type == np.float32 or np_type == np.complex64:
tol = 1e-5
else:
tol = 1e-12
for adjoint in False, True:
if np_type in (np.float32, np.float64):
a = x.real.astype(np_type)
b = y.real.astype(np_type)
a_np = np.transpose(a) if adjoint else a
else:
a = x.astype(np_type)
b = y.astype(np_type)
a_np = np.conj(np.transpose(a)) if adjoint else a
if batch_dims is not None:
a = np.tile(a, batch_dims + [1, 1])
a_np = np.tile(a_np, batch_dims + [1, 1])
b = np.tile(b, batch_dims + [1, 1])
np_ans = np.linalg.solve(a_np, b)
for use_placeholder in set((False, not context.executing_eagerly())):
if use_placeholder:
a_ph = array_ops.placeholder(dtypes.as_dtype(np_type))
b_ph = array_ops.placeholder(dtypes.as_dtype(np_type))
tf_ans = linalg_ops.matrix_solve(a_ph, b_ph, adjoint=adjoint)
with self.cached_session() as sess:
out = sess.run(tf_ans, {a_ph: a, b_ph: b})
else:
tf_ans = linalg_ops.matrix_solve(a, b, adjoint=adjoint)
out = self.evaluate(tf_ans)
self.assertEqual(tf_ans.get_shape(), out.shape)
self.assertEqual(np_ans.shape, out.shape)
self.assertAllClose(np_ans, out, atol=tol, rtol=tol)
def _generateMatrix(self, m, n):
matrix = (np.random.normal(-5, 5,
m * n).astype(np.complex128).reshape([m, n]))
matrix.imag = (np.random.normal(-5, 5, m * n).astype(np.complex128).reshape(
[m, n]))
return matrix
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testSolve(self):
for n in 1, 2, 4, 9:
matrix = self._generateMatrix(n, n)
for nrhs in 1, 2, n:
rhs = self._generateMatrix(n, nrhs)
self._verifySolve(matrix, rhs)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testSolveBatch(self):
for n in 2, 5:
matrix = self._generateMatrix(n, n)
for nrhs in 1, n:
rhs = self._generateMatrix(n, nrhs)
for batch_dims in [[2], [2, 2], [7, 4]]:
self._verifySolve(matrix, rhs, batch_dims=batch_dims)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testNonSquareMatrix(self):
# When the solve of a non-square matrix is attempted we should return
# an error
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
matrix = constant_op.constant([[1., 2., 3.], [3., 4., 5.]])
self.evaluate(linalg_ops.matrix_solve(matrix, matrix))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testWrongDimensions(self):
# The matrix and right-hand sides should have the same number of rows.
matrix = constant_op.constant([[1., 0.], [0., 1.]])
rhs = constant_op.constant([[1., 0.]])
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
self.evaluate(linalg_ops.matrix_solve(matrix, rhs))
# The matrix and right-hand side should have the same batch dimensions
matrix = np.random.normal(size=(2, 6, 2, 2))
rhs = np.random.normal(size=(2, 3, 2, 2))
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
self.evaluate(linalg_ops.matrix_solve(matrix, rhs))
def testNotInvertible(self):
# The input should be invertible.
with self.assertRaisesOpError("Input matrix is not invertible."):
# All rows of the matrix below add to zero
matrix = constant_op.constant([[1., 0., -1.], [-1., 1., 0.],
[0., -1., 1.]])
self.evaluate(linalg_ops.matrix_solve(matrix, matrix))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testConcurrent(self):
seed = [42, 24]
matrix_shape = [3, 3]
all_ops = []
for adjoint_ in False, True:
lhs1 = stateless_random_ops.stateless_random_normal(
matrix_shape, seed=seed)
lhs2 = stateless_random_ops.stateless_random_normal(
matrix_shape, seed=seed)
rhs1 = stateless_random_ops.stateless_random_normal(
matrix_shape, seed=seed)
rhs2 = stateless_random_ops.stateless_random_normal(
matrix_shape, seed=seed)
s1 = linalg_ops.matrix_solve(lhs1, rhs1, adjoint=adjoint_)
s2 = linalg_ops.matrix_solve(lhs2, rhs2, adjoint=adjoint_)
all_ops += [s1, s2]
val = self.evaluate(all_ops)
for i in range(0, len(all_ops), 2):
self.assertAllEqual(val[i], val[i + 1])
class MatrixSolveBenchmark(test.Benchmark):
matrix_shapes = [
(4, 4),
(10, 10),
(16, 16),
(101, 101),
(256, 256),
(1001, 1001),
(1024, 1024),
(2048, 2048),
(513, 4, 4),
(513, 16, 16),
(513, 256, 256),
]
def _GenerateTestData(self, matrix_shape, num_rhs):
batch_shape = matrix_shape[:-2]
matrix_shape = matrix_shape[-2:]
assert matrix_shape[0] == matrix_shape[1]
n = matrix_shape[0]
matrix = (np.ones(matrix_shape).astype(np.float32) /
(2.0 * n) + np.diag(np.ones(n).astype(np.float32)))
rhs = np.ones([n, num_rhs]).astype(np.float32)
matrix = variables.Variable(
np.tile(matrix, batch_shape + (1, 1)), trainable=False)
rhs = variables.Variable(
np.tile(rhs, batch_shape + (1, 1)), trainable=False)
return matrix, rhs
def benchmarkMatrixSolveOp(self):
run_gpu_test = test.is_gpu_available(True)
for adjoint in False, True:
for matrix_shape in self.matrix_shapes:
for num_rhs in 1, 2, matrix_shape[-1]:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix, rhs = self._GenerateTestData(matrix_shape, num_rhs)
x = linalg_ops.matrix_solve(matrix, rhs, adjoint=adjoint)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(x),
min_iters=25,
store_memory_usage=False,
name=("matrix_solve_cpu_shape_{matrix_shape}_num_rhs_{num_rhs}_"
"adjoint_{adjoint}").format(
matrix_shape=matrix_shape,
num_rhs=num_rhs,
adjoint=adjoint))
if run_gpu_test:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/gpu:0"):
matrix, rhs = self._GenerateTestData(matrix_shape, num_rhs)
x = linalg_ops.matrix_solve(matrix, rhs, adjoint=adjoint)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(x),
min_iters=25,
store_memory_usage=False,
name=("matrix_solve_gpu_shape_{matrix_shape}_num_rhs_"
"{num_rhs}_adjoint_{adjoint}").format(
matrix_shape=matrix_shape, num_rhs=num_rhs,
adjoint=adjoint))
if __name__ == "__main__":
test.main()
@@ -0,0 +1,128 @@
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.math_ops.matrix_square_root."""
import numpy as np
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import test_util
from tensorflow.python.ops import gen_linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import stateless_random_ops
from tensorflow.python.platform import test
class SquareRootOpTest(test.TestCase):
def _verifySquareRoot(self, matrix, np_type):
matrix = matrix.astype(np_type)
# Verify that matmul(sqrtm(A), sqrtm(A)) = A
sqrt = gen_linalg_ops.matrix_square_root(matrix)
square = test_util.matmul_without_tf32(sqrt, sqrt)
self.assertShapeEqual(matrix, square)
self.assertAllClose(matrix, square, rtol=1e-4, atol=1e-3)
def _verifySquareRootReal(self, x):
for np_type in [np.float32, np.float64]:
self._verifySquareRoot(x, np_type)
def _verifySquareRootComplex(self, x):
for np_type in [np.complex64, np.complex128]:
self._verifySquareRoot(x, np_type)
def _makeBatch(self, matrix1, matrix2):
matrix_batch = np.concatenate(
[np.expand_dims(matrix1, 0),
np.expand_dims(matrix2, 0)])
matrix_batch = np.tile(matrix_batch, [2, 3, 1, 1])
return matrix_batch
def _testMatrices(self, matrix1, matrix2):
# Real
self._verifySquareRootReal(matrix1)
self._verifySquareRootReal(matrix2)
self._verifySquareRootReal(self._makeBatch(matrix1, matrix2))
matrix1 = matrix1.astype(np.complex64)
matrix2 = matrix2.astype(np.complex64)
matrix1 += 1j * matrix1
matrix2 += 1j * matrix2
self._verifySquareRootComplex(matrix1)
self._verifySquareRootComplex(matrix2)
self._verifySquareRootComplex(self._makeBatch(matrix1, matrix2))
@test_util.run_without_tensor_float_32
def testSymmetricPositiveDefinite(self):
matrix1 = np.array([[2., 1.], [1., 2.]])
matrix2 = np.array([[3., -1.], [-1., 3.]])
self._testMatrices(matrix1, matrix2)
@test_util.run_without_tensor_float_32
def testAsymmetric(self):
matrix1 = np.array([[0., 4.], [-1., 5.]])
matrix2 = np.array([[33., 24.], [48., 57.]])
self._testMatrices(matrix1, matrix2)
@test_util.run_without_tensor_float_32
def testIdentityMatrix(self):
# 2x2
identity = np.array([[1., 0], [0, 1.]])
self._verifySquareRootReal(identity)
# 3x3
identity = np.array([[1., 0, 0], [0, 1., 0], [0, 0, 1.]])
self._verifySquareRootReal(identity)
@test_util.run_without_tensor_float_32
def testEmpty(self):
self._verifySquareRootReal(np.empty([0, 2, 2]))
self._verifySquareRootReal(np.empty([2, 0, 0]))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
@test_util.run_without_tensor_float_32
def testWrongDimensions(self):
# The input to the square root should be at least a 2-dimensional tensor.
tensor = constant_op.constant([1., 2.])
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
gen_linalg_ops.matrix_square_root(tensor)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
@test_util.run_without_tensor_float_32
def testNotSquare(self):
with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)):
tensor = constant_op.constant([[1., 0., -1.], [-1., 1., 0.]])
self.evaluate(gen_linalg_ops.matrix_square_root(tensor))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
@test_util.run_without_tensor_float_32
def testConcurrentExecutesWithoutError(self):
matrix_shape = [5, 5]
seed = [42, 24]
matrix1 = stateless_random_ops.stateless_random_normal(
shape=matrix_shape, seed=seed)
matrix2 = stateless_random_ops.stateless_random_normal(
shape=matrix_shape, seed=seed)
self.assertAllEqual(matrix1, matrix2)
square1 = math_ops.matmul(matrix1, matrix1)
square2 = math_ops.matmul(matrix2, matrix2)
sqrt1 = gen_linalg_ops.matrix_square_root(square1)
sqrt2 = gen_linalg_ops.matrix_square_root(square2)
all_ops = [sqrt1, sqrt2]
sqrt = self.evaluate(all_ops)
self.assertAllClose(sqrt[0], sqrt[1])
if __name__ == "__main__":
test.main()
@@ -0,0 +1,244 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.math_ops.matrix_triangular_solve."""
import numpy as np
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.platform import test
class MatrixTriangularSolveOpTest(test.TestCase):
def _verifySolveAllWays(self, x, y, dtypes, batch_dims=None):
for lower in True, False:
for adjoint in True, False:
for use_placeholder in True, False:
self._verifySolve(
x,
y,
lower=lower,
adjoint=adjoint,
batch_dims=batch_dims,
use_placeholder=use_placeholder,
dtypes=dtypes)
def _verifySolveAllWaysReal(self, x, y, batch_dims=None):
self._verifySolveAllWays(x, y, (np.float32, np.float64), batch_dims)
def _verifySolveAllWaysComplex(self, x, y, batch_dims=None):
self._verifySolveAllWays(x, y, (np.complex64, np.complex128), batch_dims)
def _verifySolve(self,
x,
y,
lower=True,
adjoint=False,
batch_dims=None,
use_placeholder=False,
dtypes=(np.float32, np.float64)):
for np_type in dtypes:
a = x.astype(np_type)
b = y.astype(np_type)
# For numpy.solve we have to explicitly zero out the strictly
# upper or lower triangle.
if lower and a.size > 0:
a_np = np.tril(a)
elif a.size > 0:
a_np = np.triu(a)
else:
a_np = a
if adjoint:
axes = list(range(len(a_np.shape)))
axes[-2] = -1
axes[-1] = -2
a_np = np.conj(np.transpose(a_np, axes=axes))
if batch_dims is not None:
a = np.tile(a, batch_dims + [1, 1])
a_np = np.tile(a_np, batch_dims + [1, 1])
b = np.tile(b, batch_dims + [1, 1])
def broadcast(a, b):
b1 = b + np.zeros(a.shape[:-2] + (1, 1), dtype=b.dtype)
return a, b1
a_tf = a
b_tf = b
if use_placeholder:
a_tf = array_ops.placeholder_with_default(a_tf, shape=None)
b_tf = array_ops.placeholder_with_default(b_tf, shape=None)
tf_ans = linalg_ops.matrix_triangular_solve(
a_tf, b_tf, lower=lower, adjoint=adjoint)
tf_val = self.evaluate(tf_ans)
a_np, b = broadcast(a_np, b)
np_ans = np.linalg.solve(a_np, b)
self.assertEqual(np_ans.shape, tf_val.shape)
self.assertAllClose(np_ans, tf_val)
@test_util.run_deprecated_v1
def testSolve(self):
# 1x1 matrix, single rhs.
matrix = np.array([[0.1]])
rhs0 = np.array([[1.]])
self._verifySolveAllWaysReal(matrix, rhs0)
# 2x2 matrices, single right-hand side.
matrix = np.array([[1., 2.], [3., 4.]])
rhs0 = np.array([[1.], [1.]])
self._verifySolveAllWaysReal(matrix, rhs0)
# 2x2 matrices, 3 right-hand sides.
rhs1 = np.array([[1., 0., 1.], [0., 1., 1.]])
self._verifySolveAllWaysReal(matrix, rhs1)
@test_util.run_deprecated_v1
def testSolveComplex(self):
# 1x1 matrix, single rhs.
matrix = np.array([[0.1 + 1j * 0.1]])
rhs0 = np.array([[1. + 1j]])
self._verifySolveAllWaysComplex(matrix, rhs0)
# 2x2 matrices, single right-hand side.
matrix = np.array([[1., 2.], [3., 4.]]).astype(np.complex64)
matrix += 1j * matrix
rhs0 = np.array([[1.], [1.]]).astype(np.complex64)
rhs0 += 1j * rhs0
self._verifySolveAllWaysComplex(matrix, rhs0)
# 2x2 matrices, 3 right-hand sides.
rhs1 = np.array([[1., 0., 1.], [0., 1., 1.]]).astype(np.complex64)
rhs1 += 1j * rhs1
self._verifySolveAllWaysComplex(matrix, rhs1)
@test_util.run_deprecated_v1
def testSolveBatch(self):
matrix = np.array([[1., 2.], [3., 4.]])
rhs = np.array([[1., 0., 1.], [0., 1., 1.]])
# Batch of 2x3x2x2 matrices, 2x3x2x3 right-hand sides.
self._verifySolveAllWaysReal(matrix, rhs, batch_dims=[2, 3])
# Batch of 3x2x2x2 matrices, 3x2x2x3 right-hand sides.
self._verifySolveAllWaysReal(matrix, rhs, batch_dims=[3, 2])
@test_util.run_deprecated_v1
def testSolveBatchBroadcast(self):
# 2 x 2 x 2
matrix = np.array([[[1., 0.], [3., 4.]], [[1., 0.], [2., 1.]]])
# 2 x 3
rhs = np.array([[1., 0., 1.], [0., 1., 1.]])
# 2 x 2 x 3
self._verifySolveAllWaysReal(matrix, rhs)
# 2 x 2 x 2
matrix2 = np.array([[[1., 0.], [3., 4.]], [[2., 0.], [1., 6.3]]])
# 1 x 2 x 3
rhs = np.array([[[1., 0., 1.], [0., 1., 1.]]])
# 2 x 2 x 3
self._verifySolveAllWaysReal(matrix2, rhs)
@test_util.run_deprecated_v1
def testSolveBatchBroadcastLargerBatches(self):
# 1 x 10 x 10
matrix = np.random.uniform(low=1, high=2., size=[1, 10, 10])
# 10 x 1
rhs = np.random.uniform(size=[10, 1])
# 1 x 10 x 1
self._verifySolveAllWaysReal(matrix, rhs)
# 2 x 10 x 10
matrix = np.random.uniform(low=1, high=2., size=[2, 10, 10])
# 10 x 1
rhs = np.random.uniform(size=[10, 1])
# 2 x 10 x 1
self._verifySolveAllWaysReal(matrix, rhs)
# 2 x 257 x 257
matrix = np.random.uniform(low=1, high=2., size=[2, 257, 257])
# Also ensure the matrix is well conditioned by making it diagonally
# dominant.
np.fill_diagonal(matrix[0, ...], 257 * 2)
np.fill_diagonal(matrix[1, ...], 257 * 2)
# 257 x 1
rhs = np.random.uniform(size=[257, 1])
# 2 x 257 x 1
self._verifySolveAllWaysReal(matrix, rhs)
@test_util.run_deprecated_v1
def testSolveBatchComplex(self):
matrix = np.array([[1., 2.], [3., 4.]]).astype(np.complex64)
matrix += 1j * matrix
rhs = np.array([[1., 0., 1.], [0., 1., 1.]]).astype(np.complex64)
rhs += 1j * rhs
# Batch of 2x3x2x2 matrices, 2x3x2x3 right-hand sides.
self._verifySolveAllWaysComplex(matrix, rhs, batch_dims=[2, 3])
# Batch of 3x2x2x2 matrices, 3x2x2x3 right-hand sides.
self._verifySolveAllWaysComplex(matrix, rhs, batch_dims=[3, 2])
@test_util.run_deprecated_v1
def testNonSquareMatrix(self):
# A non-square matrix should cause an error.
matrix = np.array([[1., 2., 3.], [3., 4., 5.]])
with self.cached_session():
with self.assertRaises(ValueError):
self._verifySolve(matrix, matrix)
with self.assertRaises(ValueError):
self._verifySolve(matrix, matrix, batch_dims=[2, 3])
@test_util.run_deprecated_v1
def testWrongDimensions(self):
# The matrix should have the same number of rows as the
# right-hand sides.
matrix = np.array([[1., 0.], [0., 1.]])
rhs = np.array([[1., 0.]])
with self.cached_session():
with self.assertRaises(ValueError):
self._verifySolve(matrix, rhs)
with self.assertRaises(ValueError):
self._verifySolve(matrix, rhs, batch_dims=[2, 3])
@test_util.run_deprecated_v1
@test_util.disable_xla("XLA cannot throw assertion errors during a kernel.")
def testNotInvertible(self):
# The input should be invertible.
# The matrix is singular because it has a zero on the diagonal.
singular_matrix = np.array(
[[[1., 0., 0.],
[-1., 0., 0.],
[0., -1., 1.]],
[[1., 0., 0.],
[-1., 1., 0.],
[0., -1., 0.]],
[[1., 0., 0.],
[-1., 1., 0.],
[0., -1., 1.]]])
rhs = np.array([[3.], [5.], [1.]])
expected = np.array([
[[3.], [np.inf], [np.inf]],
[[3.], [8.], [np.inf]],
[[3.], [8.], [9.]]])
with self.cached_session(use_gpu=False):
ans = linalg_ops.matrix_triangular_solve(singular_matrix, rhs)
self.assertAllClose(self.evaluate(ans), expected)
def testEmpty(self):
self._verifySolve(np.empty([0, 2, 2]), np.empty([0, 2, 2]), lower=True)
self._verifySolve(np.empty([2, 0, 0]), np.empty([2, 0, 0]), lower=True)
self._verifySolve(np.empty([2, 0, 0]), np.empty([2, 0, 0]), lower=False)
self._verifySolve(
np.empty([2, 0, 0]), np.empty([2, 0, 0]), lower=True, batch_dims=[3, 2])
self._verifySolve(np.empty([0, 0]), np.empty([0, 0]), lower=True)
if __name__ == "__main__":
test.main()
@@ -0,0 +1,117 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.tf.norm."""
import numpy as np
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import linalg_ops
from tensorflow.python.platform import test as test_lib
def _AddTest(test, test_name, fn):
test_name = "_".join(["test", test_name])
if hasattr(test, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test, test_name, fn)
class NormOpTest(test_lib.TestCase):
@test_util.run_v1_only("b/120545219")
def testBadOrder(self):
matrix = [[0., 1.], [2., 3.]]
for ord_ in "fro", -7, -1.1, 0:
with self.assertRaisesRegex(ValueError,
"'ord' must be a supported vector norm"):
linalg_ops.norm(matrix, ord=ord_)
for ord_ in "fro", -7, -1.1, 0:
with self.assertRaisesRegex(ValueError,
"'ord' must be a supported vector norm"):
linalg_ops.norm(matrix, ord=ord_, axis=-1)
for ord_ in "foo", -7, -1.1, 1.1:
with self.assertRaisesRegex(ValueError,
"'ord' must be a supported matrix norm"):
linalg_ops.norm(matrix, ord=ord_, axis=[-2, -1])
@test_util.run_v1_only("b/120545219")
def testInvalidAxis(self):
matrix = [[0., 1.], [2., 3.]]
for axis_ in [], [1, 2, 3], [[1]], [[1], [2]], [3.1415], [1, 1]:
error_prefix = ("'axis' must be None, an integer, or a tuple of 2 unique "
"integers")
with self.assertRaisesRegex(ValueError, error_prefix):
linalg_ops.norm(matrix, axis=axis_)
def _GetNormOpTest(dtype_, shape_, ord_, axis_, keep_dims_, use_static_shape_):
def _CompareNorm(self, matrix):
np_norm = np.linalg.norm(matrix, ord=ord_, axis=axis_, keepdims=keep_dims_)
with self.cached_session() as sess:
if use_static_shape_:
tf_matrix = constant_op.constant(matrix)
tf_norm = linalg_ops.norm(
tf_matrix, ord=ord_, axis=axis_, keepdims=keep_dims_)
tf_norm_val = self.evaluate(tf_norm)
else:
tf_matrix = array_ops.placeholder(dtype_)
tf_norm = linalg_ops.norm(
tf_matrix, ord=ord_, axis=axis_, keepdims=keep_dims_)
tf_norm_val = sess.run(tf_norm, feed_dict={tf_matrix: matrix})
self.assertAllClose(np_norm, tf_norm_val, rtol=1e-5, atol=1e-5)
@test_util.run_v1_only("b/120545219")
def Test(self):
is_matrix_norm = (isinstance(axis_, tuple) or
isinstance(axis_, list)) and len(axis_) == 2
is_fancy_p_norm = np.isreal(ord_) and np.floor(ord_) != ord_
if ((not is_matrix_norm and ord_ == "fro") or
(is_matrix_norm and is_fancy_p_norm)):
self.skipTest("Not supported by neither numpy.linalg.norm nor tf.norm")
if ord_ == "euclidean" or (axis_ is None and len(shape) > 2):
self.skipTest("Not supported by numpy.linalg.norm")
matrix = np.random.randn(*shape_).astype(dtype_)
if dtype_ in (np.complex64, np.complex128):
matrix += 1j * np.random.randn(*shape_).astype(dtype_)
_CompareNorm(self, matrix)
return Test
# pylint: disable=redefined-builtin
if __name__ == "__main__":
for use_static_shape in False, True:
for dtype in np.float32, np.float64, np.complex64, np.complex128:
for rows in 2, 5:
for cols in 2, 5:
for batch in [], [2], [2, 3]:
shape = batch + [rows, cols]
for ord in "euclidean", "fro", 0.5, 1, 2, np.inf:
for axis in [
None, (-2, -1), (-1, -2), -len(shape), 0, len(shape) - 1
]:
for keep_dims in False, True:
name = "%s_%s_ord_%s_axis_%s_%s_%s" % (
dtype.__name__, "_".join(map(str, shape)), ord, axis,
keep_dims, use_static_shape)
_AddTest(NormOpTest, "Norm_" + name,
_GetNormOpTest(dtype, shape, ord, axis, keep_dims,
use_static_shape))
test_lib.main()
@@ -0,0 +1,96 @@
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.tf.norm."""
import numpy as np
from tensorflow.python.framework import test_util
from tensorflow.python.ops import nn_impl
from tensorflow.python.platform import test as test_lib
def _AddTest(test, test_name, fn):
test_name = "_".join(["test", test_name])
if hasattr(test, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test, test_name, fn)
# pylint: disable=redefined-builtin
def _Normalize(x, ord, axis):
if isinstance(axis, (list, tuple)):
norm = np.linalg.norm(x, ord, tuple(axis))
if axis[0] < axis[1]:
# This prevents axis to be inserted in-between
# e.g. when (-2, -1)
for d in reversed(axis):
norm = np.expand_dims(norm, d)
else:
for d in axis:
norm = np.expand_dims(norm, d)
return x / norm
elif axis is None:
# Tensorflow handles None differently
norm = np.linalg.norm(x.flatten(), ord, axis)
return x / norm
else:
norm = np.apply_along_axis(np.linalg.norm, axis, x, ord)
return x / np.expand_dims(norm, axis)
class NormalizeOpTest(test_lib.TestCase):
pass
def _GetNormalizeOpTest(dtype_, shape_, ord_, axis_):
@test_util.run_in_graph_and_eager_modes
def Test(self):
is_matrix_norm = (isinstance(axis_, tuple) or
isinstance(axis_, list)) and len(axis_) == 2
is_fancy_p_norm = np.isreal(ord_) and np.floor(ord_) != ord_
if ((not is_matrix_norm and ord_ == "fro") or
(is_matrix_norm and is_fancy_p_norm)):
self.skipTest("Not supported by neither numpy.linalg.norm nor tf.norm")
if ord_ == "euclidean" or (axis_ is None and len(shape) > 2):
self.skipTest("Not supported by numpy.linalg.norm")
matrix = np.random.randn(*shape_).astype(dtype_)
if dtype_ in (np.complex64, np.complex128):
matrix += 1j * np.random.randn(*shape_).astype(dtype_)
tf_np_n, _ = self.evaluate(nn_impl.normalize(matrix, ord_, axis_))
np_n = _Normalize(matrix, ord_, axis_)
self.assertAllClose(tf_np_n, np_n, rtol=1e-5, atol=1e-5)
return Test
# pylint: disable=redefined-builtin
if __name__ == "__main__":
for dtype in np.float32, np.float64, np.complex64, np.complex128:
for rows in 2, 5:
for cols in 2, 5:
for batch in [], [2], [2, 3]:
shape = batch + [rows, cols]
for ord in "euclidean", "fro", 0.5, 1, 2, np.inf:
for axis in [
None, (-2, -1), (-1, -2), -len(shape), 0,
len(shape) - 1
]:
name = "%s_%s_ord_%s_axis_%s" % (dtype.__name__, "_".join(
map(str, shape)), ord, axis)
_AddTest(NormalizeOpTest, "Normalize_" + name,
_GetNormalizeOpTest(dtype, shape, ord, axis))
test_lib.main()
@@ -0,0 +1,308 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.math_ops.matrix_inverse."""
import numpy as np
from tensorflow.python.eager import backprop
from tensorflow.python.client import session
from tensorflow.python.eager import context
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import stateless_random_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
def _AddTest(test_class, op_name, testcase_name, fn):
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test_class, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test_class, test_name, fn)
class QrOpTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testWrongDimensions(self):
# The input to qr should be a tensor of at least rank 2.
scalar = constant_op.constant(1.)
with self.assertRaisesRegex((ValueError, errors_impl.InvalidArgumentError),
"rank.* 2.*0"):
linalg_ops.qr(scalar)
vector = constant_op.constant([1., 2.])
with self.assertRaisesRegex((ValueError, errors_impl.InvalidArgumentError),
"rank.* 2.*1"):
linalg_ops.qr(vector)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testConcurrentExecutesWithoutError(self):
seed = [42, 24]
all_ops = []
for full_matrices_ in True, False:
for rows_ in 4, 5:
for cols_ in 4, 5:
matrix_shape = [rows_, cols_]
matrix1 = stateless_random_ops.stateless_random_normal(
matrix_shape, seed)
matrix2 = stateless_random_ops.stateless_random_normal(
matrix_shape, seed)
self.assertAllEqual(matrix1, matrix2)
q1, r1 = linalg_ops.qr(matrix1, full_matrices=full_matrices_)
q2, r2 = linalg_ops.qr(matrix2, full_matrices=full_matrices_)
all_ops += [q1, q2, r1, r2]
val = self.evaluate(all_ops)
for i in range(0, len(val), 2):
self.assertAllClose(val[i], val[i + 1])
def _GetQrOpTest(dtype_, shape_, full_matrices_, use_static_shape_):
is_complex = dtype_ in (np.complex64, np.complex128)
is_single = dtype_ in (np.float32, np.complex64)
def CompareOrthogonal(self, x, y, rank):
if is_single:
atol = 5e-4
else:
atol = 5e-14
# We only compare the first 'rank' orthogonal vectors since the
# remainder form an arbitrary orthonormal basis for the
# (row- or column-) null space, whose exact value depends on
# implementation details. Notice that since we check that the
# matrices of singular vectors are unitary elsewhere, we do
# implicitly test that the trailing vectors of x and y span the
# same space.
x = x[..., 0:rank]
y = y[..., 0:rank]
# Q is only unique up to sign (complex phase factor for complex matrices),
# so we normalize the sign first.
sum_of_ratios = np.sum(np.divide(y, x), -2, keepdims=True)
phases = np.divide(sum_of_ratios, np.abs(sum_of_ratios))
x *= phases
self.assertAllClose(x, y, atol=atol)
def CheckApproximation(self, a, q, r):
if is_single:
tol = 1e-5
else:
tol = 1e-14
# Tests that a ~= q*r.
a_recon = test_util.matmul_without_tf32(q, r)
self.assertAllClose(a_recon, a, rtol=tol, atol=tol)
def CheckUnitary(self, x):
# Tests that x[...,:,:]^H * x[...,:,:] is close to the identity.
xx = test_util.matmul_without_tf32(x, x, adjoint_a=True)
identity = array_ops.matrix_band_part(array_ops.ones_like(xx), 0, 0)
if is_single:
tol = 1e-5
else:
tol = 1e-14
self.assertAllClose(identity, xx, atol=tol)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def Test(self):
if not use_static_shape_ and context.executing_eagerly():
return
np.random.seed(1)
x_np = np.random.uniform(
low=-1.0, high=1.0, size=np.prod(shape_)).reshape(shape_).astype(dtype_)
if is_complex:
x_np += 1j * np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(shape_)).reshape(shape_).astype(dtype_)
if use_static_shape_:
x_tf = constant_op.constant(x_np)
else:
x_tf = array_ops.placeholder(dtype_)
q_tf, r_tf = linalg_ops.qr(x_tf, full_matrices=full_matrices_)
if use_static_shape_:
q_tf_val, r_tf_val = self.evaluate([q_tf, r_tf])
else:
with self.session() as sess:
q_tf_val, r_tf_val = sess.run([q_tf, r_tf], feed_dict={x_tf: x_np})
q_dims = q_tf_val.shape
np_q = np.ndarray(q_dims, dtype_)
np_q_reshape = np.reshape(np_q, (-1, q_dims[-2], q_dims[-1]))
new_first_dim = np_q_reshape.shape[0]
x_reshape = np.reshape(x_np, (-1, x_np.shape[-2], x_np.shape[-1]))
for i in range(new_first_dim):
if full_matrices_:
np_q_reshape[i, :, :], _ = np.linalg.qr(
x_reshape[i, :, :], mode="complete")
else:
np_q_reshape[i, :, :], _ = np.linalg.qr(
x_reshape[i, :, :], mode="reduced")
np_q = np.reshape(np_q_reshape, q_dims)
CompareOrthogonal(self, np_q, q_tf_val, min(shape_[-2:]))
CheckApproximation(self, x_np, q_tf_val, r_tf_val)
CheckUnitary(self, q_tf_val)
return Test
class QrGradOpTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testNotImplementedCheck(self):
# Test that the correct message is issued
np.random.seed(42)
matrix = constant_op.constant(
np.random.uniform(low=-1.0, high=1.0, size=(5, 2)).astype(np.float32))
def _NoGrad(x):
with backprop.GradientTape() as tape:
tape.watch(x)
ret = linalg_ops.qr(x, full_matrices=True)
return tape.gradient(ret, x)
m = r"QrGrad not implemented when nrows > ncols and full_matrices is true."
with self.assertRaisesRegex(NotImplementedError, m):
_NoGrad(matrix)
def _GetQrGradOpTest(dtype_, shape_, full_matrices_):
def RandomInput():
a = np.random.uniform(low=-1.0, high=1.0, size=shape_).astype(dtype_)
if dtype_ in [np.complex64, np.complex128]:
a += 1j * np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(dtype_)
return a
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
@test_util.run_without_tensor_float_32("Tests Qr gradient, which calls matmul"
)
def Test(self):
np.random.seed(42)
# Optimal stepsize for central difference is O(epsilon^{1/3}).
epsilon = np.finfo(dtype_).eps
delta = 0.1 * epsilon**(1.0 / 3.0)
if dtype_ in [np.float32, np.complex64]:
tol = 3e-2
else:
tol = 1e-6
# TODO(b/157171666): Sadly we have to double the computation because
# gradient_checker_v2.compute_gradient expects a list of functions.
funcs = [
lambda a: linalg_ops.qr(a, full_matrices=full_matrices_)[0],
lambda a: linalg_ops.qr(a, full_matrices=full_matrices_)[1]
]
for f in funcs:
theoretical, numerical = gradient_checker_v2.compute_gradient(
f, [RandomInput()], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
return Test
class QRBenchmark(test.Benchmark):
shapes = [
(4, 4),
(8, 8),
(16, 16),
(101, 101),
(256, 256),
(1024, 1024),
(2048, 2048),
(1024, 2),
(1024, 32),
(1024, 128),
(1024, 512),
(1, 8, 8),
(10, 8, 8),
(100, 8, 8),
(1, 256, 256),
(10, 256, 256),
(100, 256, 256),
]
def benchmarkQROp(self):
for shape_ in self.shapes:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix_value = np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(np.float32)
matrix = variables.Variable(matrix_value)
q, r = linalg_ops.qr(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(q, r),
min_iters=25,
name="QR_cpu_{shape}".format(shape=shape_))
if test.is_gpu_available(True):
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/device:GPU:0"):
matrix_value = np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(np.float32)
matrix = variables.Variable(matrix_value)
q, r = linalg_ops.qr(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(q, r),
min_iters=25,
name="QR_gpu_{shape}".format(shape=shape_))
if __name__ == "__main__":
for dtype in np.float32, np.float64, np.complex64, np.complex128:
for rows in 1, 2, 5, 10, 32, 100:
for cols in 1, 2, 5, 10, 32, 100:
for full_matrices in False, True:
for batch_dims in [(), (3,)] + [(3, 2)] * (max(rows, cols) < 10):
# TF2 does not support placeholders under eager so we skip it
for use_static_shape in [True, False]:
shape = batch_dims + (rows, cols)
name = "%s_%s_full_%s_static_%s" % (dtype.__name__,
"_".join(map(str, shape)),
full_matrices,
use_static_shape)
_AddTest(QrOpTest, "Qr", name,
_GetQrOpTest(dtype, shape, full_matrices,
use_static_shape))
# TODO(pfau): Get working with full_matrices when rows > cols
# TODO(pfau): Get working with shapeholders (dynamic shapes)
for full_matrices in False, True:
for dtype in np.float32, np.float64, np.complex64, np.complex128:
for rows in 1, 2, 5, 10:
for cols in 1, 2, 5, 10:
if rows <= cols or (not full_matrices and rows > cols):
for batch_dims in [(), (3,)] + [(3, 2)] * (max(rows, cols) < 10):
shape = batch_dims + (rows, cols)
name = "%s_%s_full_%s" % (dtype.__name__,
"_".join(map(str, shape)),
full_matrices)
_AddTest(QrGradOpTest, "QrGrad", name,
_GetQrGradOpTest(dtype, shape, full_matrices))
test.main()
@@ -0,0 +1,255 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.linalg_ops.self_adjoint_eig."""
import numpy as np
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes as dtypes_lib
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.platform import test
def _AddTest(test_class, op_name, testcase_name, fn):
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test_class, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test_class, test_name, fn)
@test_util.run_all_without_tensor_float_32
class SelfAdjointEigTest(test.TestCase):
@test_util.run_deprecated_v1
def testWrongDimensions(self):
# The input to self_adjoint_eig should be a tensor of
# at least rank 2.
scalar = constant_op.constant(1.)
with self.assertRaises(ValueError):
linalg_ops.self_adjoint_eig(scalar)
vector = constant_op.constant([1., 2.])
with self.assertRaises(ValueError):
linalg_ops.self_adjoint_eig(vector)
@test_util.run_deprecated_v1
def testConcurrentExecutesWithoutError(self):
all_ops = []
with self.session():
for compute_v_ in True, False:
matrix1 = random_ops.random_normal([5, 5], seed=42)
matrix2 = random_ops.random_normal([5, 5], seed=42)
if compute_v_:
e1, v1 = linalg_ops.self_adjoint_eig(matrix1)
e2, v2 = linalg_ops.self_adjoint_eig(matrix2)
all_ops += [e1, v1, e2, v2]
else:
e1 = linalg_ops.self_adjoint_eigvals(matrix1)
e2 = linalg_ops.self_adjoint_eigvals(matrix2)
all_ops += [e1, e2]
val = self.evaluate(all_ops)
self.assertAllEqual(val[0], val[2])
# The algorithm is slightly different for compute_v being True and False,
# so require approximate equality only here.
self.assertAllClose(val[2], val[4])
self.assertAllEqual(val[4], val[5])
self.assertAllEqual(val[1], val[3])
def testMatrixThatFailsWhenFlushingDenormsToZero(self):
# Test a 32x32 matrix which is known to fail if denorm floats are flushed to
# zero.
matrix = np.genfromtxt(
test.test_src_dir_path(
"python/kernel_tests/linalg/testdata/"
"self_adjoint_eig_fail_if_denorms_flushed.txt")).astype(np.float32)
self.assertEqual(matrix.shape, (32, 32))
matrix_tensor = constant_op.constant(matrix)
with self.session():
(e, v) = self.evaluate(linalg_ops.self_adjoint_eig(matrix_tensor))
self.assertEqual(e.size, 32)
self.assertAllClose(
np.matmul(v, v.transpose()), np.eye(32, dtype=np.float32), atol=2e-3)
self.assertAllClose(matrix,
np.matmul(np.matmul(v, np.diag(e)), v.transpose()))
def SortEigenDecomposition(e, v):
if v.ndim < 2:
return e, v
else:
perm = np.argsort(e, -1)
return np.take(e, perm, -1), np.take(v, perm, -1)
def EquilibrateEigenVectorPhases(x, y):
"""Equilibrate the phase of the Eigenvectors in the columns of `x` and `y`.
Eigenvectors are only unique up to an arbitrary phase. This function rotates x
such that it matches y. Precondition: The columns of x and y differ by a
multiplicative complex phase factor only.
Args:
x: `np.ndarray` with Eigenvectors
y: `np.ndarray` with Eigenvectors
Returns:
`np.ndarray` containing an equilibrated version of x.
"""
phases = np.sum(np.conj(x) * y, -2, keepdims=True)
phases /= np.abs(phases)
return phases * x
def _GetSelfAdjointEigTest(dtype_, shape_, compute_v_):
def CompareEigenVectors(self, x, y, tol):
x = EquilibrateEigenVectorPhases(x, y)
self.assertAllClose(x, y, atol=tol)
def CompareEigenDecompositions(self, x_e, x_v, y_e, y_v, tol):
num_batches = int(np.prod(x_e.shape[:-1]))
n = x_e.shape[-1]
x_e = np.reshape(x_e, [num_batches] + [n])
x_v = np.reshape(x_v, [num_batches] + [n, n])
y_e = np.reshape(y_e, [num_batches] + [n])
y_v = np.reshape(y_v, [num_batches] + [n, n])
for i in range(num_batches):
x_ei, x_vi = SortEigenDecomposition(x_e[i, :], x_v[i, :, :])
y_ei, y_vi = SortEigenDecomposition(y_e[i, :], y_v[i, :, :])
self.assertAllClose(x_ei, y_ei, atol=tol, rtol=tol)
CompareEigenVectors(self, x_vi, y_vi, tol)
def Test(self):
np.random.seed(1)
n = shape_[-1]
batch_shape = shape_[:-2]
np_dtype = dtype_.as_numpy_dtype
a = np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
if dtype_.is_complex:
a += 1j * np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
a += np.conj(a.T)
a = np.tile(a, batch_shape + (1, 1))
if dtype_ in (dtypes_lib.float32, dtypes_lib.complex64):
atol = 1e-4
else:
atol = 1e-12
np_e, np_v = np.linalg.eigh(a)
with self.session():
if compute_v_:
tf_e, tf_v = linalg_ops.self_adjoint_eig(constant_op.constant(a))
# Check that V*diag(E)*V^T is close to A.
a_ev = test_util.matmul_without_tf32(
test_util.matmul_without_tf32(tf_v, array_ops.matrix_diag(tf_e)),
tf_v,
adjoint_b=True)
self.assertAllClose(self.evaluate(a_ev), a, atol=atol)
# Compare to numpy.linalg.eigh.
CompareEigenDecompositions(self, np_e, np_v, self.evaluate(tf_e),
self.evaluate(tf_v), atol)
else:
tf_e = linalg_ops.self_adjoint_eigvals(constant_op.constant(a))
self.assertAllClose(
np.sort(np_e, -1), np.sort(self.evaluate(tf_e), -1), atol=atol)
return Test
class SelfAdjointEigGradTest(test.TestCase):
pass # Filled in below
def _GetSelfAdjointEigGradTest(dtype_, shape_, compute_v_):
def Test(self):
np.random.seed(1)
n = shape_[-1]
batch_shape = shape_[:-2]
np_dtype = dtype_.as_numpy_dtype
def RandomInput():
a = np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
if dtype_.is_complex:
a += 1j * np.random.uniform(
low=-1.0, high=1.0, size=n * n).reshape([n, n]).astype(np_dtype)
a += np.conj(a.T)
a = np.tile(a, batch_shape + (1, 1))
return a
# Optimal stepsize for central difference is O(epsilon^{1/3}).
epsilon = np.finfo(np_dtype).eps
delta = 0.1 * epsilon**(1.0 / 3.0)
# Tolerance obtained by perturbing inputs and delta.
# Changing the step size delta above yields max errors of approx 0.016 for
# float32.
_ = RandomInput()
if dtype_ in (dtypes_lib.float32, dtypes_lib.complex64):
tol = 2e-2
else:
tol = 1e-7
with self.session():
def Compute(x):
e, v = linalg_ops.self_adjoint_eig(x)
# (complex) Eigenvectors are only unique up to an arbitrary phase
# We normalize the vectors such that the first component has phase 0.
top_rows = v[..., 0:1, :]
if dtype_.is_complex:
angle = -math_ops.angle(top_rows)
phase = math_ops.complex(math_ops.cos(angle), math_ops.sin(angle))
else:
phase = math_ops.sign(top_rows)
v *= phase
return e, v
if compute_v_:
funcs = [lambda x: Compute(x)[0], lambda x: Compute(x)[1]]
else:
funcs = [linalg_ops.self_adjoint_eigvals]
for f in funcs:
theoretical, numerical = gradient_checker_v2.compute_gradient(
f,
[RandomInput()],
delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
return Test
if __name__ == "__main__":
dtypes_to_test = [
dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.complex64,
dtypes_lib.complex128
]
for compute_v in True, False:
for dtype in dtypes_to_test:
for size in 1, 2, 5, 10:
for batch_dims in [(), (3,)] + [(3, 2)] * (max(size, size) < 10):
shape = batch_dims + (size, size)
name = "%s_%s_%s" % (dtype.name, "_".join(map(str, shape)), compute_v)
_AddTest(SelfAdjointEigTest, "SelfAdjointEig", name,
_GetSelfAdjointEigTest(dtype, shape, compute_v))
_AddTest(SelfAdjointEigGradTest, "SelfAdjointEigGrad", name,
_GetSelfAdjointEigGradTest(dtype, shape, compute_v))
test.main()
@@ -0,0 +1,215 @@
# Copyright 2022 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops.linalg import linalg as linalg_lib
from tensorflow.python.ops.linalg import linear_operator_test_util
from tensorflow.python.ops.linalg import slicing
from tensorflow.python.platform import test
linalg = linalg_lib
class _MakeSlices(object):
def __getitem__(self, slices):
return slices if isinstance(slices, tuple) else (slices,)
make_slices = _MakeSlices()
@test_util.run_all_in_graph_and_eager_modes
class SlicingTest(test.TestCase):
"""Tests for slicing LinearOperators."""
def test_single_param_slice_withstep_broadcastdim(self):
event_dim = 3
sliced = slicing._slice_single_param(
array_ops.zeros([1, 1, event_dim]),
param_ndims_to_matrix_ndims=1,
slices=make_slices[44:-52:-3, -94::],
batch_shape=constant_op.constant([2, 7], dtype=dtypes.int32))
self.assertAllEqual((1, 1, event_dim), self.evaluate(sliced).shape)
def test_single_param_slice_stop_leadingdim(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:2],
batch_shape=constant_op.constant([7, 6, 5], dtype=dtypes.int32))
self.assertAllEqual((2, 6, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_stop_trailingdim(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[..., :2],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 6, 2, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_stop_broadcastdim(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 1, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, :2],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 1, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_newaxis_leading(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, array_ops.newaxis],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 1, 6, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_newaxis_trailing(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[..., array_ops.newaxis, :],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 6, 1, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_start(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, 2:],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 4, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_start_broadcastdim(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 1, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, 2:],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 1, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_int(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, 2],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_int_broadcastdim(self):
sliced = slicing._slice_single_param(
array_ops.zeros([7, 1, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, 2],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_tensor(self):
param = array_ops.placeholder_with_default(
array_ops.zeros([7, 6, 5, 4, 3]), shape=None)
idx = array_ops.placeholder_with_default(
constant_op.constant(2, dtype=dtypes.int32), shape=[])
sliced = slicing._slice_single_param(
param,
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, idx],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_tensor_broadcastdim(self):
param = array_ops.placeholder_with_default(
array_ops.zeros([7, 1, 5, 4, 3]), shape=None)
idx = array_ops.placeholder_with_default(
constant_op.constant(2, dtype=dtypes.int32), shape=[])
sliced = slicing._slice_single_param(
param,
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, idx],
batch_shape=constant_op.constant([7, 6, 5]))
self.assertAllEqual((7, 5, 4, 3), self.evaluate(sliced).shape)
def test_single_param_slice_broadcast_batch(self):
sliced = slicing._slice_single_param(
array_ops.zeros([4, 3, 1]), # batch = [4, 3], event = [1]
param_ndims_to_matrix_ndims=1,
slices=make_slices[..., array_ops.newaxis, 2:, array_ops.newaxis],
batch_shape=constant_op.constant([7, 4, 3]))
self.assertAllEqual(
list(array_ops.zeros([1, 4, 3])[
..., array_ops.newaxis, 2:, array_ops.newaxis].shape) + [1],
self.evaluate(sliced).shape)
def test_single_param_slice_broadcast_batch_leading_newaxis(self):
sliced = slicing._slice_single_param(
array_ops.zeros([4, 3, 1]), # batch = [4, 3], event = [1]
param_ndims_to_matrix_ndims=1,
slices=make_slices[
array_ops.newaxis, ..., array_ops.newaxis, 2:, array_ops.newaxis],
batch_shape=constant_op.constant([7, 4, 3]))
expected = array_ops.zeros(
[1, 4, 3])[
array_ops.newaxis, ..., array_ops.newaxis,
2:, array_ops.newaxis].shape + [1]
self.assertAllEqual(expected, self.evaluate(sliced).shape)
def test_single_param_multi_ellipsis(self):
with self.assertRaisesRegex(ValueError, 'Found multiple `...`'):
slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, ..., 2, ...],
batch_shape=constant_op.constant([7, 6, 5]))
def test_single_param_too_many_slices(self):
with self.assertRaises(
(IndexError, ValueError, errors.InvalidArgumentError)):
slicing._slice_single_param(
array_ops.zeros([7, 6, 5, 4, 3]),
param_ndims_to_matrix_ndims=2,
slices=make_slices[:, :3, ..., -2:, :],
batch_shape=constant_op.constant([7, 6, 5]))
def test_slice_single_param_operator(self):
matrix = linear_operator_test_util.random_normal(
shape=[1, 4, 3, 2, 2], dtype=dtypes.float32)
operator = linalg.LinearOperatorFullMatrix(matrix, is_square=True)
sliced = operator[..., array_ops.newaxis, 2:, array_ops.newaxis]
self.assertAllEqual(
list(array_ops.zeros([1, 4, 3])[
..., array_ops.newaxis, 2:, array_ops.newaxis].shape),
sliced.batch_shape_tensor())
def test_slice_nested_operator(self):
linop = linalg.LinearOperatorKronecker([
linalg.LinearOperatorBlockDiag([
linalg.LinearOperatorDiag(array_ops.ones([1, 2, 2])),
linalg.LinearOperatorDiag(array_ops.ones([3, 5, 2, 2]))]),
linalg.LinearOperatorFullMatrix(
linear_operator_test_util.random_normal(
shape=[4, 1, 1, 1, 3, 3], dtype=dtypes.float32))])
self.assertAllEqual(linop[0, ...].batch_shape_tensor(), [3, 5, 2])
self.assertAllEqual(linop[
0, ..., array_ops.newaxis].batch_shape_tensor(), [3, 5, 2, 1])
self.assertAllEqual(linop[
..., array_ops.newaxis].batch_shape_tensor(), [4, 3, 5, 2, 1])
if __name__ == '__main__':
test.main()
@@ -0,0 +1,165 @@
# Tests of TensorFlow sparse linear algebra kernels using the Python API.
load("//tensorflow:tensorflow.default.bzl", "cuda_py_strict_test", "tf_py_strict_test")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:license"],
default_visibility = ["//tensorflow:internal"],
licenses = ["notice"],
)
cuda_py_strict_test(
name = "conjugate_gradient_test",
size = "medium",
srcs = ["conjugate_gradient_test.py"],
deps = [
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops/linalg",
"//tensorflow/python/ops/linalg/sparse:conjugate_gradient",
"//tensorflow/python/platform:client_testlib",
"//third_party/py/numpy",
"@absl_py//absl/testing:parameterized",
],
)
cuda_py_strict_test(
name = "csr_sparse_matrix_test",
size = "medium",
srcs = ["csr_sparse_matrix_test.py"],
main = "csr_sparse_matrix_test.py",
tags = [
"no_cuda_asan", # TODO(b/190824595)
],
deps = [
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:sparse_tensor",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_ops",
"//tensorflow/python/platform:client_testlib",
],
)
cuda_py_strict_test(
name = "csr_sparse_matrix_ops_test",
size = "medium",
timeout = "long",
srcs = ["csr_sparse_matrix_ops_test.py"],
main = "csr_sparse_matrix_ops_test.py",
shard_count = 10,
tags = [
"no_gpu", # b/203655060 (cuda 11.5 specific)
],
deps = [
"//tensorflow/core:protos_all_py",
"//tensorflow/python/client:session",
"//tensorflow/python/framework:constant_op",
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:errors",
"//tensorflow/python/framework:ops",
"//tensorflow/python/framework:random_seed",
"//tensorflow/python/framework:sparse_tensor",
"//tensorflow/python/framework:tensor_shape",
"//tensorflow/python/framework:tensor_util",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:array_ops_stack",
"//tensorflow/python/ops:linalg_ops",
"//tensorflow/python/ops:map_fn",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops:nn_ops",
"//tensorflow/python/ops:random_ops",
"//tensorflow/python/ops:sparse_ops",
"//tensorflow/python/ops:state_ops",
"//tensorflow/python/ops:variable_scope",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_ops",
"//tensorflow/python/platform:client_testlib",
"//tensorflow/python/platform:tf_logging",
"@absl_py//absl/testing:parameterized",
],
)
cuda_py_strict_test(
name = "csr_sparse_matrix_grad_test",
size = "medium",
srcs = ["csr_sparse_matrix_grad_test.py"],
main = "csr_sparse_matrix_grad_test.py",
shard_count = 3,
deps = [
"//tensorflow/python/framework:dtypes",
"//tensorflow/python/framework:ops",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradients_impl",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_grad",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_ops",
"//tensorflow/python/platform:client_testlib",
"//tensorflow/python/platform:tf_logging",
],
)
cuda_py_strict_test(
name = "csr_sparse_matrix_dense_mat_mul_grad_test",
size = "medium",
srcs = ["csr_sparse_matrix_dense_mat_mul_grad_test.py"],
main = "csr_sparse_matrix_dense_mat_mul_grad_test.py",
shard_count = 50,
tags = [
"no_cuda_asan", # TODO(b/190824595)
],
deps = [
"//tensorflow/python/framework:ops",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradient_checker",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_grad",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_ops",
"//tensorflow/python/platform:client_testlib",
"//tensorflow/python/platform:tf_logging",
],
)
tf_py_strict_test(
name = "csr_sparse_matrix_dense_mat_mul_onednn_grad_test",
size = "medium",
srcs = ["csr_sparse_matrix_dense_mat_mul_grad_test.py"],
env = {
"TF_ENABLE_ONEDNN_SPMM": "1",
},
main = "csr_sparse_matrix_dense_mat_mul_grad_test.py",
shard_count = 50,
tags = [
"no_cuda_asan", # TODO(b/190824595)
],
deps = [
"//tensorflow/python/framework:ops",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradient_checker",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_grad",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_ops",
"//tensorflow/python/platform:client_testlib",
"//tensorflow/python/platform:tf_logging",
],
)
cuda_py_strict_test(
name = "csr_sparse_matrix_sparse_mat_mul_grad_test",
size = "medium",
srcs = ["csr_sparse_matrix_sparse_mat_mul_grad_test.py"],
main = "csr_sparse_matrix_sparse_mat_mul_grad_test.py",
shard_count = 10,
deps = [
"//tensorflow/python/framework:ops",
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/ops:array_ops",
"//tensorflow/python/ops:gradient_checker",
"//tensorflow/python/ops:math_ops",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_grad",
"//tensorflow/python/ops/linalg/sparse:sparse_csr_matrix_ops",
"//tensorflow/python/platform:client_testlib",
"//tensorflow/python/platform:tf_logging",
],
)
@@ -0,0 +1,110 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for preconditioned conjugate gradient."""
import itertools
from absl.testing import parameterized
import numpy as np
from tensorflow.python.framework import constant_op
from tensorflow.python.ops import array_ops
from tensorflow.python.ops.linalg import linalg
from tensorflow.python.ops.linalg.sparse import conjugate_gradient
from tensorflow.python.platform import test
class ConjugateGradientTest(test.TestCase, parameterized.TestCase):
@parameterized.parameters(
itertools.product([np.float32, np.float64], [1, 4, 10], [True, False])
)
def test_conjugate_gradient(self, dtype, size, use_static_shape):
shape = [size, size]
np.random.seed(1)
a_np = (
np.random.uniform(low=-1.0, high=1.0, size=np.prod(shape))
.reshape(shape)
.astype(dtype)
)
# Make a self-adjoint, positive definite.
a_np = np.dot(a_np.T, a_np)
# jacobi preconditioner
jacobi_np = np.zeros_like(a_np)
jacobi_np[range(a_np.shape[0]), range(a_np.shape[1])] = (
1.0 / a_np.diagonal()
)
rhs_np = np.random.uniform(low=-1.0, high=1.0, size=shape[0]).astype(dtype)
x_np = np.zeros_like(rhs_np)
tol = 1e-6 if dtype == np.float64 else 1e-3
max_iter = 20
if use_static_shape:
a = constant_op.constant(a_np)
rhs = constant_op.constant(rhs_np)
x = constant_op.constant(x_np)
jacobi = constant_op.constant(jacobi_np)
else:
a = array_ops.placeholder_with_default(a_np, shape=None)
rhs = array_ops.placeholder_with_default(rhs_np, shape=None)
x = array_ops.placeholder_with_default(x_np, shape=None)
jacobi = array_ops.placeholder_with_default(jacobi_np, shape=None)
operator = linalg.LinearOperatorFullMatrix(
a, is_positive_definite=True, is_self_adjoint=True
)
preconditioners = [
None,
# Preconditioner that does nothing beyond change shape.
linalg.LinearOperatorIdentity(
a_np.shape[-1],
dtype=a_np.dtype,
is_positive_definite=True,
is_self_adjoint=True,
),
# Jacobi preconditioner.
linalg.LinearOperatorFullMatrix(
jacobi, is_positive_definite=True, is_self_adjoint=True
),
]
cg_results = []
for preconditioner in preconditioners:
cg_graph = conjugate_gradient.conjugate_gradient(
operator,
rhs,
preconditioner=preconditioner,
x=x,
tol=tol,
max_iter=max_iter,
)
cg_val = self.evaluate(cg_graph)
norm_r0 = np.linalg.norm(rhs_np)
norm_r = np.linalg.norm(cg_val.r)
self.assertLessEqual(norm_r, tol * norm_r0)
# Validate that we get an equally small residual norm with numpy
# using the computed solution.
r_np = rhs_np - np.dot(a_np, cg_val.x)
norm_r_np = np.linalg.norm(r_np)
self.assertLessEqual(norm_r_np, tol * norm_r0)
cg_results.append(cg_val)
# Validate that we get same results using identity_preconditioner
# and None
self.assertEqual(cg_results[0].i, cg_results[1].i)
self.assertAlmostEqual(cg_results[0].gamma, cg_results[1].gamma)
self.assertAllClose(cg_results[0].r, cg_results[1].r, rtol=tol)
self.assertAllClose(cg_results[0].x, cg_results[1].x, rtol=tol)
self.assertAllClose(cg_results[0].p, cg_results[1].p, rtol=tol)
if __name__ == "__main__":
test.main()
@@ -0,0 +1,145 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""CSR sparse matrix tests."""
import itertools
import numpy as np
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gradient_checker
from tensorflow.python.ops.linalg.sparse import sparse_csr_matrix_grad # pylint: disable=unused-import
from tensorflow.python.ops.linalg.sparse import sparse_csr_matrix_ops
from tensorflow.python.platform import test
from tensorflow.python.platform import tf_logging
def _add_test(test, op_name, testcase_name, fn): # pylint: disable=redefined-outer-name
if fn is None:
return
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test, test_name, fn)
class CSRSparseMatrixDenseMatMulGradTest(test.TestCase):
@classmethod
def setUpClass(cls):
super(CSRSparseMatrixDenseMatMulGradTest, cls).setUpClass()
cls._gpu_available = test_util.is_gpu_available()
# TODO(penporn): Make these tests runnable on eager mode.
# (tf.gradients and gradient_checker only run in graph mode.)
@test_util.run_deprecated_v1
def _testLargeBatchSparseMatrixMatMulGrad(
self,
datatype,
transpose_a,
transpose_b,
adjoint_a,
adjoint_b,
transpose_output,
conjugate_output,
batched_inputs,
):
if batched_inputs:
a_shape = (3, 5, 11)
b_shape = (3, 11, 13)
transpose = lambda x: np.transpose(x, (0, 2, 1))
else:
a_shape = (5, 11)
b_shape = (11, 13)
transpose = np.transpose
sparsify = lambda m: m * (m > 0)
a_mats_val = sparsify(
np.random.randn(*a_shape) +
1.j * np.random.randn(*a_shape)).astype(datatype)
if transpose_a or adjoint_a:
a_mats_val = transpose(a_mats_val)
if adjoint_a:
a_mats_val = np.conj(a_mats_val)
b_mats_val = (np.random.randn(*b_shape) +
1.j * np.random.randn(*b_shape)).astype(datatype)
if transpose_b or adjoint_b:
b_mats_val = transpose(b_mats_val)
if adjoint_b:
b_mats_val = np.conj(b_mats_val)
with self.test_session():
a_mats = ops.convert_to_tensor(a_mats_val, dtype=datatype)
b_mats = ops.convert_to_tensor(b_mats_val, dtype=datatype)
locs = array_ops.where(abs(a_mats_val) > 0)
a_sm = sparse_csr_matrix_ops.dense_to_csr_sparse_matrix(a_mats, locs)
c_mats = sparse_csr_matrix_ops.sparse_matrix_mat_mul(
a_sm,
b_mats,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b,
transpose_output=transpose_output,
conjugate_output=conjugate_output)
for [ten, val, nn] in [[a_mats, a_mats_val, "a"],
[b_mats, b_mats_val, "b"]]:
tf_logging.info("Testing gradients for %s" % nn)
theoretical, numerical = gradient_checker.compute_gradient(
ten,
ten.get_shape().as_list(),
c_mats,
c_mats.get_shape().as_list(),
x_init_value=val,
delta=1e-3)
self.assertAllClose(theoretical, numerical, atol=1e-3, rtol=1e-3)
# These tests are refactored from sparse_csr_matrix_grad_test to keep its size
# "medium".
dtypes_to_test = [np.float32, np.complex64]
for dtype in dtypes_to_test:
for (t_a, t_b, adj_a, adj_b, t_out,
conj_out, batched) in itertools.product(*(([False, True],) * 7)):
def create_mat_mul_test_fn(dtype_, t_a_, t_b_, adj_a_, adj_b_, t_out_,
conj_out_, batched_):
# Skip invalid cases.
if (t_a_ and adj_a_) or (t_b_ and adj_b_):
return
# Skip cases where we conjugate real matrices.
if dtype_ == np.float32 and (adj_a_ or adj_b_ or conj_out_):
return
def test_fn(self):
self._testLargeBatchSparseMatrixMatMulGrad(dtype_, t_a_, t_b_, adj_a_,
adj_b_, t_out_, conj_out_,
batched_)
return test_fn
name = (
"_testLargeBatchSparseMatrixMatMulGrad_dtype_%s_t_a_%s_t_b_%s_adj_a_%s_"
"adj_b_%s_t_out_%s_conj_out_%s_batched_%s" %
(dtype.__name__, t_a, t_b, adj_a, adj_b, t_out, conj_out, batched))
_add_test(
CSRSparseMatrixDenseMatMulGradTest, "CSRSparseMatrixGradTest", name,
create_mat_mul_test_fn(dtype, t_a, t_b, adj_a, adj_b, t_out, conj_out,
batched))
if __name__ == "__main__":
test.main()
@@ -0,0 +1,145 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""CSR sparse matrix tests."""
import numpy as np
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gradients_impl
from tensorflow.python.ops import math_ops
from tensorflow.python.ops.linalg.sparse import sparse_csr_matrix_grad # pylint: disable=unused-import
from tensorflow.python.ops.linalg.sparse import sparse_csr_matrix_ops
from tensorflow.python.platform import test
from tensorflow.python.platform import tf_logging
def dense_to_csr_sparse_matrix(dense):
dense_t = ops.convert_to_tensor(dense)
locs = array_ops.where(math_ops.abs(dense_t) > 0)
return sparse_csr_matrix_ops.dense_to_csr_sparse_matrix(dense_t, locs)
def _add_test(test, op_name, testcase_name, fn): # pylint: disable=redefined-outer-name
if fn is None:
return
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test, test_name, fn)
class CSRSparseMatrixGradTest(test.TestCase):
@classmethod
def setUpClass(cls):
super(CSRSparseMatrixGradTest, cls).setUpClass()
cls._gpu_available = test_util.is_gpu_available()
# TODO(penporn): Make these tests runnable on eager mode.
# (tf.gradients and gradient_checker only run in graph mode.)
@test_util.run_deprecated_v1
def testLargeBatchConversionGrad(self):
if not self._gpu_available:
return
sparsify = lambda m: m * (m > 0)
for dense_shape in ([53, 65, 127], [127, 65]):
mats_val = sparsify(np.random.randn(*dense_shape))
with self.test_session() as sess:
mats = math_ops.cast(mats_val, dtype=dtypes.float32)
sparse_mats = dense_to_csr_sparse_matrix(mats)
dense_mats = sparse_csr_matrix_ops.csr_sparse_matrix_to_dense(
sparse_mats, dtypes.float32)
grad_vals = np.random.randn(*dense_shape).astype(np.float32)
grad_out = gradients_impl.gradients([dense_mats], [mats],
[grad_vals])[0]
self.assertEqual(grad_out.dtype, dtypes.float32)
self.assertEqual(grad_out.shape, dense_shape)
grad_out_value = sess.run(grad_out)
tf_logging.info("testLargeBatchConversionGrad: Testing shape %s" %
dense_shape)
nonzero_indices = abs(mats_val) > 0.0
self.assertAllEqual(grad_out_value[nonzero_indices],
grad_vals[nonzero_indices])
self.assertTrue(
np.all(grad_out_value[np.logical_not(nonzero_indices)] == 0.0))
@test_util.run_deprecated_v1
def testLargeBatchSparseConversionGrad(self):
sparsify = lambda m: m * (m > 0)
for dense_shape in ([53, 65, 127], [127, 65]):
mats_val = sparsify(np.random.randn(*dense_shape))
with self.session(use_gpu=True) as sess:
indices = array_ops.where_v2(
math_ops.not_equal(mats_val, array_ops.zeros_like(mats_val)))
values = math_ops.cast(
array_ops.gather_nd(mats_val, indices), dtype=dtypes.float32)
grad_vals = np.random.randn(*sess.run(values).shape).astype(np.float32)
csr_matrix = sparse_csr_matrix_ops.sparse_tensor_to_csr_sparse_matrix(
indices, values, dense_shape)
new_coo_tensor = (
sparse_csr_matrix_ops.csr_sparse_matrix_to_sparse_tensor(
csr_matrix, type=dtypes.float32))
grad_out = gradients_impl.gradients([new_coo_tensor.values], [values],
[grad_vals])[0]
self.assertEqual(grad_out.dtype, dtypes.float32)
grad_out_vals = sess.run(grad_out)
self.assertAllClose(grad_vals, grad_out_vals)
@test_util.run_deprecated_v1
def testLargeBatchSparseMatrixAddGrad(self):
if not self._gpu_available:
return
sparsify = lambda m: m * (m > 0)
for dense_shape in ([53, 65, 127], [127, 65]):
a_mats_val = sparsify(np.random.randn(*dense_shape))
b_mats_val = sparsify(np.random.randn(*dense_shape))
alpha = np.float32(0.5)
beta = np.float32(-1.5)
grad_vals = np.random.randn(*dense_shape).astype(np.float32)
expected_a_grad = alpha * grad_vals
expected_b_grad = beta * grad_vals
expected_a_grad[abs(a_mats_val) == 0.0] = 0.0
expected_b_grad[abs(b_mats_val) == 0.0] = 0.0
with self.test_session() as sess:
a_mats = math_ops.cast(a_mats_val, dtype=dtypes.float32)
b_mats = math_ops.cast(b_mats_val, dtype=dtypes.float32)
a_sm = dense_to_csr_sparse_matrix(a_mats)
b_sm = dense_to_csr_sparse_matrix(b_mats)
c_sm = sparse_csr_matrix_ops.sparse_matrix_add(
a_sm, b_sm, alpha=alpha, beta=beta)
c_dense = sparse_csr_matrix_ops.csr_sparse_matrix_to_dense(
c_sm, dtypes.float32)
a_grad, b_grad = gradients_impl.gradients([c_dense], [a_mats, b_mats],
[grad_vals])
self.assertEqual(a_grad.dtype, dtypes.float32)
self.assertEqual(b_grad.dtype, dtypes.float32)
self.assertEqual(a_grad.shape, dense_shape)
self.assertEqual(b_grad.shape, dense_shape)
a_grad_value, b_grad_value = sess.run((a_grad, b_grad))
tf_logging.info("testLargeBatchConversionGrad: Testing shape %s" %
dense_shape)
self.assertAllEqual(expected_a_grad, a_grad_value)
self.assertAllEqual(expected_b_grad, b_grad_value)
if __name__ == "__main__":
test.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,132 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""CSR sparse matrix tests."""
import itertools
import numpy as np
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gradient_checker
from tensorflow.python.ops import math_ops
from tensorflow.python.ops.linalg.sparse import sparse_csr_matrix_grad # pylint: disable=unused-import
from tensorflow.python.ops.linalg.sparse import sparse_csr_matrix_ops
from tensorflow.python.platform import test
from tensorflow.python.platform import tf_logging
def dense_and_sparse_from_vals(vals, datatype):
locs = array_ops.where(math_ops.abs(vals) > 0)
dense_t = ops.convert_to_tensor(vals, dtype=datatype)
return (dense_t,
sparse_csr_matrix_ops.dense_to_csr_sparse_matrix(dense_t, locs))
def _add_test(test, op_name, testcase_name, fn): # pylint: disable=redefined-outer-name
if fn is None:
return
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test, test_name, fn)
class CSRSparseMatrixGradTest(test.TestCase):
@classmethod
def setUpClass(cls):
super(CSRSparseMatrixGradTest, cls).setUpClass()
cls._gpu_available = test_util.is_gpu_available()
# TODO(penporn): Make these tests runnable on eager mode.
# (tf.gradients and gradient_checker only run in graph mode.)
@test_util.run_deprecated_v1
def _testLargeBatchSparseMatrixSparseMatMulGrad(self, datatype, transpose_a,
transpose_b, adjoint_a,
adjoint_b):
if not self._gpu_available:
return
sparsify = lambda m: m * (m > 0)
a_mats_val = sparsify(
np.random.randn(3, 5, 11) +
1.j * np.random.randn(3, 5, 11)).astype(datatype)
if transpose_a or adjoint_a:
a_mats_val = np.transpose(a_mats_val, (0, 2, 1))
if adjoint_a:
a_mats_val = np.conj(a_mats_val)
b_mats_val = sparsify(
np.random.randn(3, 11, 13) +
1.j * np.random.randn(3, 11, 13)).astype(datatype)
if transpose_b or adjoint_b:
b_mats_val = np.transpose(b_mats_val, (0, 2, 1))
if adjoint_b:
b_mats_val = np.conj(b_mats_val)
with self.test_session():
a_mats, a_sm = dense_and_sparse_from_vals(a_mats_val, datatype)
b_mats, b_sm = dense_and_sparse_from_vals(b_mats_val, datatype)
c_sm = sparse_csr_matrix_ops.sparse_matrix_sparse_mat_mul(
a_sm,
b_sm,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b,
type=datatype)
c_dense = sparse_csr_matrix_ops.csr_sparse_matrix_to_dense(
c_sm, type=datatype)
for ten, val, nn in [[a_mats, a_mats_val, "a"], [b_mats, b_mats_val,
"b"]]:
tf_logging.info("Testing gradients for %s" % nn)
theoretical, numerical = gradient_checker.compute_gradient(
ten,
ten.get_shape().as_list(),
c_dense,
c_dense.get_shape().as_list(),
x_init_value=val,
delta=1e-3)
self.assertAllClose(theoretical, numerical, atol=1e-3, rtol=1e-3)
# These tests are refactored from sparse_csr_matrix_grad_test to keep its size
# "medium".
for dtype in (np.float32, np.complex64):
for (t_a, t_b, adj_a, adj_b) in itertools.product(*(([False, True],) * 4)):
def create_sparse_mat_mul_test_fn(dtype_, t_a_, t_b_, adj_a_, adj_b_):
# Skip invalid cases.
if (t_a_ and adj_a_) or (t_b_ and adj_b_):
return
# Skip cases where we conjugate real matrices.
if dtype_ == np.float32 and (adj_a_ or adj_b_):
return
def test_fn(self):
self._testLargeBatchSparseMatrixSparseMatMulGrad(
dtype_, t_a_, t_b_, adj_a_, adj_b_)
return test_fn
name = (
"_testLargeBatchSparseMatrixSparseMatMulGrad_dtype_%s_t_a_%s_t_b_%s_"
"adj_a_%s_adj_b_%s" % (dtype.__name__, t_a, t_b, adj_a, adj_b))
_add_test(CSRSparseMatrixGradTest, "CSRSparseMatrixSparseGradTest", name,
create_sparse_mat_mul_test_fn(dtype, t_a, t_b, adj_a, adj_b))
if __name__ == "__main__":
test.main()
@@ -0,0 +1,265 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""CSR sparse matrix tests."""
import itertools
import numpy as np
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import sparse_tensor
from tensorflow.python.framework import test_util
from tensorflow.python.ops import math_ops
from tensorflow.python.ops.linalg.sparse import sparse_csr_matrix_ops
from tensorflow.python.platform import test
class CSRSparseMatrixTest(test.TestCase):
@classmethod
def setUpClass(cls): # pylint: disable=g-missing-super-call
cls._gpu_available = test_util.is_gpu_available()
@test_util.run_in_graph_and_eager_modes
def testConstructorFromSparseTensor(self):
if not self._gpu_available:
return
a_indices = np.array([[0, 0], [2, 3], [2, 4], [3, 0]])
a_values = [1.0, 5.0, -1.0, -2.0]
a_dense_shape = [5, 6]
a_st = sparse_tensor.SparseTensor(a_indices, a_values, a_dense_shape)
a_st = math_ops.cast(a_st, dtypes.float32)
a_sm = sparse_csr_matrix_ops.CSRSparseMatrix(a_st)
self.assertEqual(a_sm.shape, a_dense_shape)
a_st_rt = a_sm.to_sparse_tensor()
a_st_rt = self.evaluate(a_st_rt)
self.assertAllEqual(a_indices, a_st_rt.indices)
self.assertAllClose(a_values, a_st_rt.values)
self.assertAllEqual(a_dense_shape, a_st_rt.dense_shape)
@test_util.run_in_graph_and_eager_modes
def testConstructorFromDenseTensorNoIndices(self):
if not self._gpu_available:
return
sparsify = lambda m: m * (m > 0)
dense_shape = [5, 7, 13]
a_mats = sparsify(np.random.randn(*dense_shape)).astype(np.float32)
a_sm = sparse_csr_matrix_ops.CSRSparseMatrix(a_mats)
self.assertEqual(a_sm.shape, a_mats.shape)
a_sm_rt = a_sm.to_dense()
a_sm_nnz = a_sm.nnz()
a_sm_nnz, a_sm_rt = self.evaluate([a_sm_nnz, a_sm_rt])
# Count number of nonzero entries for each batch using bincount.
nz = np.bincount(a_mats.nonzero()[0], minlength=a_mats.shape[0])
self.assertAllEqual(nz, a_sm_nnz)
self.assertAllClose(a_mats, a_sm_rt)
@test_util.run_in_graph_and_eager_modes
def testConstructorFromDenseTensorWithIndices(self):
if not self._gpu_available:
return
dense_shape = [5, 7, 13]
a_mats = np.random.randn(*dense_shape).astype(np.float32)
indices = np.array([[0, 0, 0],
[1, 0, 0]], dtype=np.int64)
a_sm = sparse_csr_matrix_ops.CSRSparseMatrix(a_mats, indices=indices)
self.assertEqual(a_sm.shape, a_mats.shape)
a_sm_st = a_sm.to_sparse_tensor()
a_sm_st = self.evaluate(a_sm_st)
# Count number of nonzero entries for each batch using bincount.
self.assertAllEqual(indices, a_sm_st.indices)
self.assertAllEqual(dense_shape, a_sm.shape)
self.assertAllEqual(dense_shape, a_sm_st.dense_shape)
self.assertAllClose([a_mats[tuple(x)] for x in indices], a_sm_st.values)
@test_util.run_in_graph_and_eager_modes
def testConj(self):
if not self._gpu_available:
return
sparsify = lambda m: m * (m.real > 0)
dense_shape = [5, 7, 13]
a_mats = sparsify(
(np.random.randn(*dense_shape) + 1.j * np.random.randn(*dense_shape))
.astype(np.complex64))
a_sm = sparse_csr_matrix_ops.CSRSparseMatrix(a_mats)
a_sm_conj = a_sm.conj()
self.assertIsInstance(a_sm_conj, sparse_csr_matrix_ops.CSRSparseMatrix)
a_sm_conj_dense = a_sm_conj.to_dense()
a_sm_conj_dense = self.evaluate(a_sm_conj_dense)
self.assertAllClose(a_mats.conj(), a_sm_conj_dense)
@test_util.run_in_graph_and_eager_modes
def testTranspose(self):
if not self._gpu_available:
return
for conjugate in False, True:
sparsify = lambda m: m * (m > 0)
dense_shape = [5, 7, 13]
a_mats = sparsify((np.random.randn(*dense_shape) +
1.j * np.random.randn(*dense_shape))).astype(
np.complex64)
expected = np.transpose(a_mats, (0, 2, 1))
if conjugate:
expected = np.conj(expected)
a_sm = sparse_csr_matrix_ops.CSRSparseMatrix(a_mats)
if conjugate:
a_sm_t = a_sm.hermitian_transpose()
else:
a_sm_t = a_sm.transpose()
self.assertIsInstance(a_sm_t, sparse_csr_matrix_ops.CSRSparseMatrix)
a_sm_t_dense = a_sm_t.to_dense()
a_sm_t_dense = self.evaluate(a_sm_t_dense)
self.assertAllClose(expected, a_sm_t_dense)
class SparseMatrixMatmulTest(test.TestCase):
@classmethod
def setUpClass(cls): # pylint: disable=g-missing-super-call
cls._gpu_available = test_util.is_gpu_available()
def _testSparseSparse(self, transpose_a, transpose_b, adjoint_a, adjoint_b):
if not self._gpu_available:
return
sparsify = lambda m: m * (m > 0)
dense_shape_a = [5, 13, 7] if transpose_a or adjoint_a else [5, 7, 13]
dense_shape_b = [5, 15, 13] if transpose_b or adjoint_b else [5, 13, 15]
dtypes_to_test = [np.float32, np.complex64]
for dtype in dtypes_to_test:
a_mats = sparsify((np.random.randn(*dense_shape_a) +
1.j * np.random.randn(*dense_shape_a))).astype(dtype)
b_mats = sparsify((np.random.randn(*dense_shape_b) +
1.j * np.random.randn(*dense_shape_b))).astype(dtype)
a_sm = sparse_csr_matrix_ops.CSRSparseMatrix(a_mats)
b_sm = sparse_csr_matrix_ops.CSRSparseMatrix(b_mats)
c_dense = test_util.matmul_without_tf32(
a_mats,
b_mats,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b)
c_sm = sparse_csr_matrix_ops.matmul(
a_sm,
b_sm,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b)
self.assertIsInstance(c_sm, sparse_csr_matrix_ops.CSRSparseMatrix)
c_sm_dense = c_sm.to_dense()
c_dense, c_sm_dense = self.evaluate([c_dense, c_sm_dense])
self.assertAllClose(c_dense, c_sm_dense)
@test_util.run_in_graph_and_eager_modes
def testSparseSparse(self):
for (t_a, t_b, adj_a, adj_b) in itertools.product(*(([False, True],) * 4)):
if (t_a and adj_a) or (t_b and adj_b):
continue
self._testSparseSparse(t_a, t_b, adj_a, adj_b)
def _testSparseDense(self, transpose_a, transpose_b, adjoint_a, adjoint_b):
if not self._gpu_available:
return
sparsify = lambda m: m * (m > 0)
dense_shape_a = [5, 13, 7] if transpose_a or adjoint_a else [5, 7, 13]
dense_shape_b = [5, 15, 13] if transpose_b or adjoint_b else [5, 13, 15]
dtypes_to_test = [np.float32, np.complex64]
for dtype in dtypes_to_test:
a_mats = sparsify((np.random.randn(*dense_shape_a) +
1.j * np.random.randn(*dense_shape_a))).astype(dtype)
b_mats = (np.random.randn(*dense_shape_b) +
1.j * np.random.randn(*dense_shape_b)).astype(dtype)
a_sm = sparse_csr_matrix_ops.CSRSparseMatrix(a_mats)
c_dense = test_util.matmul_without_tf32(
a_mats,
b_mats,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b)
c_sm_dense = sparse_csr_matrix_ops.matmul(
a_sm,
b_mats,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b)
c_dense, c_sm_dense = self.evaluate([c_dense, c_sm_dense])
self.assertAllClose(c_dense, c_sm_dense)
@test_util.run_in_graph_and_eager_modes
def testSparseDense(self):
for (t_a, t_b, adj_a, adj_b) in itertools.product(*(([False, True],) * 4)):
if (t_a and adj_a) or (t_b and adj_b):
continue
self._testSparseDense(t_a, t_b, adj_a, adj_b)
def _testDenseSparse(self, transpose_a, transpose_b, adjoint_a, adjoint_b):
if not self._gpu_available:
return
sparsify = lambda m: m * (m > 0)
dense_shape_a = [5, 13, 7] if transpose_a or adjoint_a else [5, 7, 13]
dense_shape_b = [5, 15, 13] if transpose_b or adjoint_b else [5, 13, 15]
dtypes_to_test = [np.float32, np.complex64]
for dtype in dtypes_to_test:
a_mats = (np.random.randn(*dense_shape_a) +
1.j * np.random.randn(*dense_shape_a)).astype(dtype)
b_mats = sparsify((np.random.randn(*dense_shape_b) +
1.j * np.random.randn(*dense_shape_b))).astype(dtype)
b_sm = sparse_csr_matrix_ops.CSRSparseMatrix(b_mats)
c_dense = test_util.matmul_without_tf32(
a_mats,
b_mats,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b)
c_sm_dense = sparse_csr_matrix_ops.matmul(
a_mats,
b_sm,
transpose_a=transpose_a,
transpose_b=transpose_b,
adjoint_a=adjoint_a,
adjoint_b=adjoint_b)
c_dense, c_sm_dense = self.evaluate([c_dense, c_sm_dense])
self.assertAllClose(c_dense, c_sm_dense)
@test_util.run_in_graph_and_eager_modes
def testDenseSparse(self):
for (t_a, t_b, adj_a, adj_b) in itertools.product(*(([False, True],) * 4)):
if (t_a and adj_a) or (t_b and adj_b):
continue
self._testDenseSparse(t_a, t_b, adj_a, adj_b)
if __name__ == "__main__":
test.main()
@@ -0,0 +1,453 @@
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.math_ops.matrix_inverse."""
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.eager import context
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import gradient_checker
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import gradients_impl
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import stateless_random_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
def _AddTest(test_class, op_name, testcase_name, fn):
test_name = "_".join(["test", op_name, testcase_name])
if hasattr(test_class, test_name):
raise RuntimeError("Test %s defined more than once" % test_name)
setattr(test_class, test_name, fn)
class SvdOpTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testWrongDimensions(self):
# The input to svd should be a tensor of at least rank 2.
scalar = constant_op.constant(1.)
with self.assertRaisesRegex((ValueError, errors_impl.InvalidArgumentError),
"rank.* 2.*0"):
linalg_ops.svd(scalar)
vector = constant_op.constant([1., 2.])
with self.assertRaisesRegex((ValueError, errors_impl.InvalidArgumentError),
"rank.* 2.*1"):
linalg_ops.svd(vector)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testThrowDeterminismError(self):
shape = [6, 1]
seed = [42, 24]
matrix = stateless_random_ops.stateless_random_normal(shape, seed)
with test_util.deterministic_ops():
if test_util.is_gpu_available(cuda_only=True):
with self.assertRaisesRegex(
errors_impl.UnimplementedError,
"Determinism is not yet supported for SVD of matrices with 1 column."
):
self.evaluate(linalg_ops.svd(matrix))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testDeterminism(self):
shape = [6, 5]
seed = [42, 24]
matrix = stateless_random_ops.stateless_random_normal(shape, seed)
with test_util.deterministic_ops():
if test_util.is_gpu_available(cuda_only=True):
s1, u1, v1 = self.evaluate(linalg_ops.svd(matrix))
for _ in range(5):
s2, u2, v2 = self.evaluate(linalg_ops.svd(matrix))
self.assertAllEqual(s1, s2)
self.assertAllEqual(u1, u2)
self.assertAllEqual(v1, v2)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def DISABLED_testBadInputs(self):
# TODO(b/185822300): re-enable after the bug is fixed in CUDA-11.x
# The input to svd should be a tensor of at least rank 2.
for bad_val in [np.nan, np.inf]:
matrix = np.array([[1, bad_val], [0, 1]])
s, u, v = linalg_ops.svd(matrix, compute_uv=True)
s, u, v = self.evaluate([s, u, v])
for i in range(2):
self.assertTrue(np.isnan(s[i]))
for j in range(2):
self.assertTrue(np.isnan(u[i, j]))
self.assertTrue(np.isnan(v[i, j]))
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testExecuteMultipleWithoutError(self):
all_ops = []
shape = [6, 5]
seed = [42, 24]
for compute_uv_ in True, False:
for full_matrices_ in True, False:
matrix1 = stateless_random_ops.stateless_random_normal(shape, seed)
matrix2 = stateless_random_ops.stateless_random_normal(shape, seed)
self.assertAllEqual(matrix1, matrix2)
if compute_uv_:
s1, u1, v1 = linalg_ops.svd(
matrix1, compute_uv=compute_uv_, full_matrices=full_matrices_)
s2, u2, v2 = linalg_ops.svd(
matrix2, compute_uv=compute_uv_, full_matrices=full_matrices_)
all_ops += [s1, s2, u1, u2, v1, v2]
else:
s1 = linalg_ops.svd(
matrix1, compute_uv=compute_uv_, full_matrices=full_matrices_)
s2 = linalg_ops.svd(
matrix2, compute_uv=compute_uv_, full_matrices=full_matrices_)
all_ops += [s1, s2]
val = self.evaluate(all_ops)
for i in range(0, len(val), 2):
self.assertAllEqual(val[i], val[i + 1])
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def testEmptyBatches(self):
matrices = constant_op.constant(1.0, shape=[0, 2, 2])
s, u, v = self.evaluate(linalg_ops.svd(matrices))
self.assertAllEqual(s, np.zeros([0, 2]))
self.assertAllEqual(u, np.zeros([0, 2, 2]))
self.assertAllEqual(v, np.zeros([0, 2, 2]))
def _GetSvdOpTest(dtype_, shape_, use_static_shape_, compute_uv_,
full_matrices_):
def CompareSingularValues(self, x, y, tol):
atol = (x[0] + y[0]) * tol if len(x) else tol
self.assertAllClose(x, y, atol=atol)
def CompareSingularVectors(self, x, y, rank, tol):
# We only compare the first 'rank' singular vectors since the
# remainder form an arbitrary orthonormal basis for the
# (row- or column-) null space, whose exact value depends on
# implementation details. Notice that since we check that the
# matrices of singular vectors are unitary elsewhere, we do
# implicitly test that the trailing vectors of x and y span the
# same space.
x = x[..., 0:rank]
y = y[..., 0:rank]
# Singular vectors are only unique up to sign (complex phase factor for
# complex matrices), so we normalize the sign first.
sum_of_ratios = np.sum(np.divide(y, x), -2, keepdims=True)
phases = np.divide(sum_of_ratios, np.abs(sum_of_ratios))
x *= phases
self.assertAllClose(x, y, atol=2 * tol)
def CheckApproximation(self, a, u, s, v, full_matrices_, tol):
# Tests that a ~= u*diag(s)*transpose(v).
batch_shape = a.shape[:-2]
m = a.shape[-2]
n = a.shape[-1]
diag_s = math_ops.cast(array_ops.matrix_diag(s), dtype=dtype_)
if full_matrices_:
if m > n:
zeros = array_ops.zeros(batch_shape + (m - n, n), dtype=dtype_)
diag_s = array_ops.concat([diag_s, zeros], a.ndim - 2)
elif n > m:
zeros = array_ops.zeros(batch_shape + (m, n - m), dtype=dtype_)
diag_s = array_ops.concat([diag_s, zeros], a.ndim - 1)
a_recon = math_ops.matmul(u, diag_s)
a_recon = math_ops.matmul(a_recon, v, adjoint_b=True)
self.assertAllClose(a_recon, a, rtol=tol, atol=tol)
def CheckUnitary(self, x, tol):
# Tests that x[...,:,:]^H * x[...,:,:] is close to the identity.
xx = math_ops.matmul(x, x, adjoint_a=True)
identity = array_ops.matrix_band_part(array_ops.ones_like(xx), 0, 0)
self.assertAllClose(identity, xx, atol=tol)
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def Test(self):
if not use_static_shape_ and context.executing_eagerly():
return
is_complex = dtype_ in (np.complex64, np.complex128)
is_single = dtype_ in (np.float32, np.complex64)
tol = 3e-4 if is_single else 1e-12
if test.is_gpu_available():
# The gpu version returns results that are much less accurate.
tol *= 200
np.random.seed(42)
x_np = np.random.uniform(
low=-1.0, high=1.0, size=np.prod(shape_)).reshape(shape_).astype(dtype_)
if is_complex:
x_np += 1j * np.random.uniform(
low=-1.0, high=1.0,
size=np.prod(shape_)).reshape(shape_).astype(dtype_)
if use_static_shape_:
x_tf = constant_op.constant(x_np)
else:
x_tf = array_ops.placeholder(dtype_)
if compute_uv_:
s_tf, u_tf, v_tf = linalg_ops.svd(
x_tf, compute_uv=compute_uv_, full_matrices=full_matrices_)
if use_static_shape_:
s_tf_val, u_tf_val, v_tf_val = self.evaluate([s_tf, u_tf, v_tf])
else:
with self.session() as sess:
s_tf_val, u_tf_val, v_tf_val = sess.run([s_tf, u_tf, v_tf],
feed_dict={x_tf: x_np})
else:
s_tf = linalg_ops.svd(
x_tf, compute_uv=compute_uv_, full_matrices=full_matrices_)
if use_static_shape_:
s_tf_val = self.evaluate(s_tf)
else:
with self.session() as sess:
s_tf_val = sess.run(s_tf, feed_dict={x_tf: x_np})
if compute_uv_:
u_np, s_np, v_np = np.linalg.svd(
x_np, compute_uv=compute_uv_, full_matrices=full_matrices_)
else:
s_np = np.linalg.svd(
x_np, compute_uv=compute_uv_, full_matrices=full_matrices_)
# We explicitly avoid the situation where numpy eliminates a first
# dimension that is equal to one.
s_np = np.reshape(s_np, s_tf_val.shape)
CompareSingularValues(self, s_np, s_tf_val, tol)
if compute_uv_:
CompareSingularVectors(self, u_np, u_tf_val, min(shape_[-2:]), tol)
CompareSingularVectors(self, np.conj(np.swapaxes(v_np, -2, -1)), v_tf_val,
min(shape_[-2:]), tol)
CheckApproximation(self, x_np, u_tf_val, s_tf_val, v_tf_val,
full_matrices_, tol)
CheckUnitary(self, u_tf_val, tol)
CheckUnitary(self, v_tf_val, tol)
return Test
class SvdGradOpTest(test.TestCase):
pass # Filled in below
def _NormalizingSvd(tf_a, full_matrices_):
tf_s, tf_u, tf_v = linalg_ops.svd(
tf_a, compute_uv=True, full_matrices=full_matrices_)
# Singular vectors are only unique up to an arbitrary phase. We normalize
# the vectors such that the first component of u (if m >=n) or v (if n > m)
# have phase 0.
m = tf_a.shape[-2]
n = tf_a.shape[-1]
if m >= n:
top_rows = tf_u[..., 0:1, :]
else:
top_rows = tf_v[..., 0:1, :]
if tf_u.dtype.is_complex:
angle = -math_ops.angle(top_rows)
phase = math_ops.complex(math_ops.cos(angle), math_ops.sin(angle))
else:
phase = math_ops.sign(top_rows)
tf_u *= phase[..., :m]
tf_v *= phase[..., :n]
return tf_s, tf_u, tf_v
def _GetSvdGradOpTest(dtype_, shape_, compute_uv_, full_matrices_):
@test_util.run_in_graph_and_eager_modes(use_gpu=True)
def Test(self):
def RandomInput():
np.random.seed(42)
a = np.random.uniform(low=-1.0, high=1.0, size=shape_).astype(dtype_)
if dtype_ in [np.complex64, np.complex128]:
a += 1j * np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(dtype_)
return a
# Optimal stepsize for central difference is O(epsilon^{1/3}).
# See Equation (21) in:
# http://www.karenkopecky.net/Teaching/eco613614/Notes_NumericalDifferentiation.pdf
# TODO(rmlarsen): Move step size control to gradient checker.
epsilon = np.finfo(dtype_).eps
delta = 0.25 * epsilon**(1.0 / 3.0)
if dtype_ in [np.float32, np.complex64]:
tol = 3e-2
else:
tol = 1e-6
if compute_uv_:
funcs = [
lambda a: _NormalizingSvd(a, full_matrices_)[0],
lambda a: _NormalizingSvd(a, full_matrices_)[1],
lambda a: _NormalizingSvd(a, full_matrices_)[2]
]
else:
funcs = [lambda a: linalg_ops.svd(a, compute_uv=False)]
for f in funcs:
theoretical, numerical = gradient_checker_v2.compute_gradient(
f, [RandomInput()], delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
return Test
class SvdGradGradOpTest(test.TestCase):
pass # Filled in below
def _GetSvdGradGradOpTest(dtype_, shape_, compute_uv_, full_matrices_):
@test_util.run_v1_only("b/120545219")
def Test(self):
np.random.seed(42)
a = np.random.uniform(low=-1.0, high=1.0, size=shape_).astype(dtype_)
if dtype_ in [np.complex64, np.complex128]:
a += 1j * np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(dtype_)
# Optimal stepsize for central difference is O(epsilon^{1/3}).
# See Equation (21) in:
# http://www.karenkopecky.net/Teaching/eco613614/Notes_NumericalDifferentiation.pdf
# TODO(rmlarsen): Move step size control to gradient checker.
epsilon = np.finfo(dtype_).eps
delta = 0.1 * epsilon**(1.0 / 3.0)
tol = 1e-5
with self.session():
tf_a = constant_op.constant(a)
if compute_uv_:
tf_s, tf_u, tf_v = _NormalizingSvd(tf_a, full_matrices_)
outputs = [tf_s, tf_u, tf_v]
else:
tf_s = linalg_ops.svd(tf_a, compute_uv=False)
outputs = [tf_s]
outputs_sums = [math_ops.reduce_sum(o) for o in outputs]
tf_func_outputs = math_ops.add_n(outputs_sums)
grad = gradients_impl.gradients(tf_func_outputs, tf_a)[0]
x_init = np.random.uniform(low=-1.0, high=1.0, size=shape_).astype(dtype_)
if dtype_ in [np.complex64, np.complex128]:
x_init += 1j * np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(dtype_)
theoretical, numerical = gradient_checker.compute_gradient(
tf_a,
tf_a.get_shape().as_list(),
grad,
grad.get_shape().as_list(),
x_init_value=x_init,
delta=delta)
self.assertAllClose(theoretical, numerical, atol=tol, rtol=tol)
return Test
class SVDBenchmark(test.Benchmark):
shapes = [
(4, 4),
(8, 8),
(16, 16),
(101, 101),
(256, 256),
(1024, 1024),
(2048, 2048),
(1, 8, 8),
(10, 8, 8),
(100, 8, 8),
(1000, 8, 8),
(1, 32, 32),
(10, 32, 32),
(100, 32, 32),
(1000, 32, 32),
(1, 256, 256),
(10, 256, 256),
(100, 256, 256),
]
def benchmarkSVDOp(self):
for shape_ in self.shapes:
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/cpu:0"):
matrix_value = np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(np.float32)
matrix = variables.Variable(matrix_value)
u, s, v = linalg_ops.svd(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(u, s, v),
min_iters=25,
name="SVD_cpu_{shape}".format(shape=shape_))
if test.is_gpu_available(True):
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device("/device:GPU:0"):
matrix_value = np.random.uniform(
low=-1.0, high=1.0, size=shape_).astype(np.float32)
matrix = variables.Variable(matrix_value)
u, s, v = linalg_ops.svd(matrix)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(u, s, v),
min_iters=25,
name="SVD_gpu_{shape}".format(shape=shape_))
if __name__ == "__main__":
dtypes_to_test = [np.float32, np.float64, np.complex64, np.complex128]
for compute_uv in False, True:
for full_matrices in False, True:
for dtype in dtypes_to_test:
for rows in 0, 1, 2, 5, 10, 32, 100:
for cols in 0, 1, 2, 5, 10, 32, 100:
for batch_dims in [(), (3,)] + [(3, 2)] * (max(rows, cols) < 10):
full_shape = batch_dims + (rows, cols)
for use_static_shape in set([True, False]):
name = "%s_%s_static_shape_%s__compute_uv_%s_full_%s" % (
dtype.__name__, "_".join(map(str, full_shape)),
use_static_shape, compute_uv, full_matrices)
_AddTest(
SvdOpTest, "Svd", name,
_GetSvdOpTest(dtype, full_shape, use_static_shape,
compute_uv, full_matrices))
for compute_uv in False, True:
for full_matrices in False, True:
dtypes = ([np.float32, np.float64] + [np.complex64, np.complex128] *
(not compute_uv))
for dtype in dtypes:
mat_shapes = [(10, 11), (11, 10), (11, 11), (2, 2, 2, 3)]
if not full_matrices or not compute_uv:
mat_shapes += [(5, 11), (11, 5)]
for mat_shape in mat_shapes:
for batch_dims in [(), (3,)]:
full_shape = batch_dims + mat_shape
name = "%s_%s_compute_uv_%s_full_%s" % (dtype.__name__, "_".join(
map(str, full_shape)), compute_uv, full_matrices)
_AddTest(
SvdGradOpTest, "SvdGrad", name,
_GetSvdGradOpTest(dtype, full_shape, compute_uv, full_matrices))
# The results are too inaccurate for float32.
if dtype in (np.float64, np.complex128):
_AddTest(
SvdGradGradOpTest, "SvdGradGrad", name,
_GetSvdGradGradOpTest(dtype, full_shape, compute_uv,
full_matrices))
test.main()
+12
View File
@@ -0,0 +1,12 @@
# Data files for kernel tests.
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:license"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
filegroup(
name = "self_adjoint_eig_op_test_files",
srcs = ["self_adjoint_eig_fail_if_denorms_flushed.txt"],
)
@@ -0,0 +1,32 @@
2.60986303e-17 -9.66826148e-21 -1.68610775e-24 -9.16104778e-17 -1.1039539e-18 -1.66460338e-25 -2.12362492e-23 1.90946688e-21 -3.34190535e-22 1.2000634e-18 -7.31782583e-20 2.57851762e-20 -2.55509e-20 -9.54284927e-20 -1.04248315e-17 -5.32450516e-22 -1.81712853e-17 6.0044594e-18 3.96602716e-11 2.89077487e-25 -2.47461475e-25 1.77941757e-24 -7.30388687e-21 -3.84350041e-16 -3.88532388e-21 -4.29928618e-21 4.13551131e-16 -2.63408791e-25 -2.84830375e-21 -1.6450072e-16 -2.8585296e-21 -3.65413296e-21
-9.66826148e-21 5.03939189e-22 9.17361108e-26 5.17304053e-20 1.99338895e-20 1.25259775e-28 -8.70441942e-26 9.91474109e-25 -5.80960164e-24 -1.19022314e-21 3.90467165e-22 -1.38179098e-22 1.79253406e-22 2.23977705e-22 1.1864143e-19 7.16291934e-24 4.10159639e-20 -2.16798529e-20 -4.95460504e-14 -2.6881406e-27 5.32861213e-27 -4.54567085e-28 1.99794328e-23 1.26854541e-17 -1.92916739e-23 8.60632417e-24 -1.04721097e-18 -7.00607669e-28 6.86771954e-23 8.65173173e-19 1.24469175e-22 6.03883081e-24
-1.68610775e-24 9.17361108e-26 1.34889529e-26 2.65059e-22 2.39713735e-23 -2.00915344e-30 -1.135692e-27 -6.46049964e-26 -1.03607712e-26 -1.57623654e-23 -1.63805162e-24 -5.95741642e-25 3.24984759e-25 6.49561204e-24 2.28504969e-21 2.8319611e-25 3.96494845e-22 -2.1988623e-22 6.26027228e-16 1.2418479e-30 2.1016041e-30 6.22813846e-30 -1.0708067e-25 6.90778045e-21 1.86361622e-25 7.08789674e-26 -9.23628499e-21 1.65335067e-30 -1.12173032e-26 8.2257321e-22 -4.72686764e-27 -2.58501275e-26
-9.16104778e-17 5.17304053e-20 2.65059e-22 2.69965968e-14 7.06005733e-17 1.69851446e-22 -2.75994304e-21 -6.61589523e-20 3.8682048e-20 -1.69253147e-17 -2.68580354e-18 -7.74994098e-19 -9.75466696e-19 2.13537585e-18 2.13185342e-16 6.89417478e-21 1.35805044e-16 -3.48309239e-16 1.0448622e-09 -2.17287918e-23 7.41749185e-24 -7.36683057e-23 -1.31083094e-20 1.574e-14 5.72646592e-19 -9.85673749e-21 -1.0654985e-14 2.70679318e-23 4.0943479e-20 -3.42938568e-15 8.57373804e-20 -2.18094505e-20
-1.1039539e-18 1.99338895e-20 2.39713735e-23 7.06005733e-17 1.83801666e-17 1.09735975e-24 -5.73058223e-24 7.2227645e-22 -8.94843118e-22 -2.30558605e-19 -7.84892038e-20 -1.88692532e-20 -1.02217713e-20 2.95458834e-20 2.42873413e-17 8.89161401e-22 1.21669872e-17 -6.85317731e-18 -7.345906e-12 -3.1158751e-25 1.36359449e-24 -1.57981417e-24 3.89633371e-21 9.94580899e-16 1.45732115e-20 6.92065325e-22 -1.86114433e-16 6.00601346e-26 3.26844e-21 4.38573742e-17 1.06803444e-20 4.60203933e-22
-1.66460338e-25 1.25259775e-28 -2.00915344e-30 1.69851446e-22 1.09735975e-24 5.75549306e-30 4.74050864e-29 -5.99239043e-28 -1.5784658e-27 -1.74631273e-25 -1.22702975e-25 -1.03371979e-26 -1.96967552e-26 -1.56446725e-26 -3.06462576e-25 -6.33857393e-28 -6.08829397e-24 -7.07478859e-24 -4.82614847e-18 -2.7324345e-31 1.23830207e-31 -7.96172e-31 -1.9034503e-27 -3.82709848e-22 -2.69257733e-26 -3.84934809e-27 -1.48572725e-22 4.14585761e-31 2.5611404e-28 -2.77402858e-24 3.10373361e-28 -5.09669241e-28
-2.12362492e-23 -8.70441942e-26 -1.135692e-27 -2.75994304e-21 -5.73058223e-24 4.74050864e-29 6.28162e-26 -3.30076462e-25 -3.30065418e-25 -1.1370873e-23 -8.97722764e-24 -1.03190629e-24 -9.52908672e-25 -3.27285413e-24 1.36216664e-22 -8.0549564e-26 -1.94826821e-22 -3.64999226e-22 -2.92500975e-15 -3.00986528e-29 2.39712646e-29 -1.02470704e-28 -4.99034099e-25 -1.32277916e-19 -5.05595e-24 -3.04012473e-25 -1.44724215e-20 5.04614184e-30 -4.12370105e-26 4.20735765e-21 -1.02818953e-25 3.41267575e-26
1.90946688e-21 9.91474109e-25 -6.46049964e-26 -6.61589523e-20 7.2227645e-22 -5.99239043e-28 -3.30076462e-25 1.8948059e-22 1.83367373e-23 1.06616038e-21 -2.81616502e-22 1.18347412e-22 8.3458038e-23 9.67703245e-24 -1.37445558e-20 2.11412652e-24 2.64820742e-21 8.02510339e-20 4.39926334e-13 9.58727772e-27 2.9838033e-28 1.29183353e-26 1.78626483e-22 3.03531056e-19 9.62612316e-23 1.33722715e-23 2.92905627e-18 -9.42286262e-28 3.23170971e-24 4.10885529e-19 -8.38673724e-25 -8.63732285e-25
-3.34190535e-22 -5.80960164e-24 -1.03607712e-26 3.8682048e-20 -8.94843118e-22 -1.5784658e-27 -3.30065418e-25 1.83367373e-23 9.30693173e-23 1.48929558e-21 1.83278606e-21 1.08468362e-22 2.61703785e-22 4.42441537e-23 1.23906316e-20 2.55235433e-24 8.36323349e-20 1.2152038e-19 9.83332204e-14 5.14523933e-27 -3.28220159e-28 8.22099066e-27 3.34939233e-23 4.3309476e-19 5.82711129e-22 1.14299394e-22 3.25240717e-18 5.84184241e-28 -1.76991199e-24 5.5568966e-20 -2.80294941e-24 4.59071175e-24
1.2000634e-18 -1.19022314e-21 -1.57623654e-23 -1.69253147e-17 -2.30558605e-19 -1.74631273e-25 -1.1370873e-23 1.06616038e-21 1.48929558e-21 2.05547703e-18 2.01471341e-20 2.65473229e-20 1.36331708e-20 -2.19777252e-20 -3.09825792e-18 -1.93365673e-22 -2.25608735e-18 7.98997246e-18 1.45582661e-11 6.29004356e-25 -1.14866332e-25 -5.51419319e-26 2.97082139e-21 -2.39052259e-16 1.48920411e-20 1.28589326e-21 4.27717466e-16 -4.44694851e-26 -1.80270052e-22 3.29932795e-18 -5.11645591e-22 5.53091711e-23
-7.31782583e-20 3.90467165e-22 -1.63805162e-24 -2.68580354e-18 -7.84892038e-20 -1.22702975e-25 -8.97722764e-24 -2.81616502e-22 1.83278606e-21 2.01471341e-20 4.38037939e-19 -4.46678177e-21 3.48516266e-20 7.32592348e-21 1.11928135e-18 8.58541052e-23 8.80645183e-18 4.80109643e-21 -1.7163557e-11 1.92262335e-26 -2.78003951e-26 5.48322572e-25 8.95330117e-23 -1.11570766e-17 3.13666242e-20 4.47195205e-21 -1.09014604e-17 7.69340111e-26 1.64649306e-22 1.71054085e-17 1.33471053e-23 6.40747815e-22
2.57851762e-20 -1.38179098e-22 -5.95741642e-25 -7.74994098e-19 -1.88692532e-20 -1.03371979e-26 -1.03190629e-24 1.18347412e-22 1.08468362e-22 2.65473229e-20 -4.46678177e-21 5.22731861e-21 1.06412616e-21 -8.0508039e-22 -1.68829721e-19 -2.7699538e-23 -2.15173717e-19 7.46895651e-19 1.71858101e-12 5.41956e-26 -6.15013064e-27 1.54884457e-26 2.54028029e-22 -1.50009535e-18 1.11920465e-21 1.05890428e-22 3.6487132e-17 -2.06798384e-27 -5.5143889e-23 -1.71529414e-18 -7.38099094e-23 -6.5250472e-24
-2.55509e-20 1.79253406e-22 3.24984759e-25 -9.75466696e-19 -1.02217713e-20 -1.96967552e-26 -9.52908672e-25 8.3458038e-23 2.61703785e-22 1.36331708e-20 3.48516266e-20 1.06412616e-21 4.08927657e-20 -2.76503659e-21 -6.81059804e-20 5.13487959e-23 1.80612902e-18 5.32462054e-19 -3.89327199e-12 3.60012729e-26 -2.5575456e-26 3.14316426e-25 4.56614351e-22 -1.24545392e-17 9.14707146e-21 7.97421952e-22 2.84371096e-17 2.98359736e-26 1.33439467e-23 1.00242743e-17 -4.94476664e-23 3.28816461e-22
-9.54284927e-20 2.23977705e-22 6.49561204e-24 2.13537585e-18 2.95458834e-20 -1.56446725e-26 -3.27285413e-24 9.67703245e-24 4.42441537e-23 -2.19777252e-20 7.32592348e-21 -8.0508039e-22 -2.76503659e-21 5.02409342e-20 1.57549297e-18 2.63027228e-22 6.11241908e-19 -2.71906856e-19 1.41003203e-12 2.66730019e-26 2.25679315e-26 1.00596535e-25 3.02875382e-22 3.85539387e-17 6.79708607e-22 1.60452617e-22 -2.08440846e-17 -5.40071056e-28 4.56236979e-23 -1.00868521e-17 1.22265047e-22 -1.81997389e-23
-1.04248315e-17 1.1864143e-19 2.28504969e-21 2.13185342e-16 2.42873413e-17 -3.06462576e-25 1.36216664e-22 -1.37445558e-20 1.23906316e-20 -3.09825792e-18 1.11928135e-18 -1.68829721e-19 -6.81059804e-20 1.57549297e-18 2.5311263e-15 9.97996576e-20 2.26115975e-16 -3.86907114e-17 3.68487445e-12 8.23669787e-24 1.00324064e-23 3.38722042e-24 8.64234911e-21 2.46521189e-15 1.72823337e-19 9.24995431e-20 -3.16903295e-15 5.94130048e-25 1.73965082e-20 1.17371651e-15 2.26718703e-20 4.16709318e-21
-5.32450516e-22 7.16291934e-24 2.8319611e-25 6.89417478e-21 8.89161401e-22 -6.33857393e-28 -8.0549564e-26 2.11412652e-24 2.55235433e-24 -1.93365673e-22 8.58541052e-23 -2.7699538e-23 5.13487959e-23 2.63027228e-22 9.97996576e-20 2.88326168e-23 1.35358898e-20 5.43364968e-21 4.24011412e-14 1.88486064e-27 8.93106076e-29 4.5748278e-27 2.48573168e-24 5.81165621e-19 1.96505062e-23 5.84813631e-24 -2.46866108e-20 1.912471e-29 2.0243857e-24 -2.88983463e-20 1.35761502e-24 1.40424791e-27
-1.81712853e-17 4.10159639e-20 3.96494845e-22 1.35805044e-16 1.21669872e-17 -6.08829397e-24 -1.94826821e-22 2.64820742e-21 8.36323349e-20 -2.25608735e-18 8.80645183e-18 -2.15173717e-19 1.80612902e-18 6.11241908e-19 2.26115975e-16 1.35358898e-20 3.66013906e-15 1.35652384e-17 -1.97764849e-09 4.16586597e-24 1.28936031e-24 6.96597122e-23 2.43147439e-21 -1.25627342e-15 1.52711738e-18 2.61025243e-19 -2.00782109e-15 9.75835691e-24 4.0203e-21 1.40790259e-15 -7.8869e-21 8.51983e-20
6.0044594e-18 -2.16798529e-20 -2.1988623e-22 -3.48309239e-16 -6.85317731e-18 -7.07478859e-24 -3.64999226e-22 8.02510339e-20 1.2152038e-19 7.98997246e-18 4.80109643e-21 7.46895651e-19 5.32462054e-19 -2.71906856e-19 -3.86907114e-17 5.43364968e-21 1.35652384e-17 1.19795414e-15 1.18472676e-09 2.74214961e-23 -7.6305178e-26 1.25969175e-23 1.68466447e-19 1.33873166e-15 1.0739288e-18 1.02533716e-19 2.73480291e-14 -1.87024011e-24 -9.73944425e-21 2.74769918e-16 -1.48632788e-20 1.69142815e-21
3.96602716e-11 -4.95460504e-14 6.26027228e-16 1.0448622e-09 -7.345906e-12 -4.82614847e-18 -2.92500975e-15 4.39926334e-13 9.83332204e-14 1.45582661e-11 -1.7163557e-11 1.71858101e-12 -3.89327199e-12 1.41003203e-12 3.68487445e-12 4.24011412e-14 -1.97764849e-09 1.18472676e-09 0.0257282555 5.64106473e-17 5.83845666e-18 -1.72409096e-16 1.02886027e-12 1.42563525e-08 -1.57067415e-12 -4.61972799e-13 3.30651737e-08 -5.20615037e-17 -1.71347193e-14 2.87764201e-10 5.03749196e-14 -1.97989316e-13
2.89077487e-25 -2.6881406e-27 1.2418479e-30 -2.17287918e-23 -3.1158751e-25 -2.7324345e-31 -3.00986528e-29 9.58727772e-27 5.14523933e-27 6.29004356e-25 1.92262335e-26 5.41956e-26 3.60012729e-26 2.66730019e-26 8.23669787e-24 1.88486064e-27 4.16586597e-24 2.74214961e-23 5.64106473e-17 1.2555855e-29 -1.30304595e-31 8.42884087e-31 1.75222077e-26 -2.89058862e-23 3.0225144e-26 6.67962117e-27 8.54181718e-22 -1.2385176e-32 -5.78078369e-28 3.34704626e-23 -2.00599605e-27 2.05674681e-28
-2.47461475e-25 5.32861213e-27 2.1016041e-30 7.41749185e-24 1.36359449e-24 1.23830207e-31 2.39712646e-29 2.9838033e-28 -3.28220159e-28 -1.14866332e-25 -2.78003951e-26 -6.15013064e-27 -2.5575456e-26 2.25679315e-26 1.00324064e-23 8.93106076e-29 1.28936031e-24 -7.6305178e-26 5.83845666e-18 -1.30304595e-31 2.26490979e-30 -4.25637053e-31 1.40697e-27 5.91197152e-22 -2.08475892e-26 -5.64982671e-28 -3.97199197e-23 -5.06794406e-32 1.11993943e-27 -2.94280711e-23 2.65858181e-27 -2.23093754e-28
1.77941757e-24 -4.54567085e-28 6.22813846e-30 -7.36683057e-23 -1.57981417e-24 -7.96172e-31 -1.02470704e-28 1.29183353e-26 8.22099066e-27 -5.51419319e-26 5.48322572e-25 1.54884457e-26 3.14316426e-25 1.00596535e-25 3.38722042e-24 4.5748278e-27 6.96597122e-23 1.25969175e-23 -1.72409096e-16 8.42884087e-31 -4.25637053e-31 1.40764294e-28 1.38735442e-26 -1.93810515e-22 1.93660175e-25 1.97417449e-26 1.62145272e-22 2.52533191e-31 -3.42833345e-28 6.34130774e-22 -2.01859e-27 6.1781768e-27
-7.30388687e-21 1.99794328e-23 -1.0708067e-25 -1.31083094e-20 3.89633371e-21 -1.9034503e-27 -4.99034099e-25 1.78626483e-22 3.34939233e-23 2.97082139e-21 8.95330117e-23 2.54028029e-22 4.56614351e-22 3.02875382e-22 8.64234911e-21 2.48573168e-24 2.43147439e-21 1.68466447e-19 1.02886027e-12 1.75222077e-26 1.40697e-27 1.38735442e-26 1.18400807e-21 1.40670976e-18 2.40320429e-22 3.69528133e-23 4.81603371e-18 -1.49322683e-27 -2.70670724e-25 1.59463723e-19 6.40406749e-24 1.17170599e-23
-3.84350041e-16 1.26854541e-17 6.90778045e-21 1.574e-14 9.94580899e-16 -3.82709848e-22 -1.32277916e-19 3.03531056e-19 4.3309476e-19 -2.39052259e-16 -1.11570766e-17 -1.50009535e-18 -1.24545392e-17 3.85539387e-17 2.46521189e-15 5.81165621e-19 -1.25627342e-15 1.33873166e-15 1.42563525e-08 -2.89058862e-23 5.91197152e-22 -1.93810515e-22 1.40670976e-18 4.40677789e-12 7.86017934e-19 7.73466606e-19 1.96690791e-15 -1.65941347e-22 2.63659933e-18 -3.0624544e-14 5.87194631e-18 -3.46291098e-19
-3.88532388e-21 -1.92916739e-23 1.86361622e-25 5.72646592e-19 1.45732115e-20 -2.69257733e-26 -5.05595e-24 9.62612316e-23 5.82711129e-22 1.48920411e-20 3.13666242e-20 1.11920465e-21 9.14707146e-21 6.79708607e-22 1.72823337e-19 1.96505062e-23 1.52711738e-18 1.0739288e-18 -1.57067415e-12 3.0225144e-26 -2.08475892e-26 1.93660175e-25 2.40320429e-22 7.86017934e-19 1.80741048e-20 9.85491491e-22 5.08456938e-17 1.08072265e-26 -1.75036654e-23 4.36436952e-18 -1.77728563e-23 1.01268548e-22
-4.29928618e-21 8.60632417e-24 7.08789674e-26 -9.85673749e-21 6.92065325e-22 -3.84934809e-27 -3.04012473e-25 1.33722715e-23 1.14299394e-22 1.28589326e-21 4.47195205e-21 1.05890428e-22 7.97421952e-22 1.60452617e-22 9.24995431e-20 5.84813631e-24 2.61025243e-19 1.02533716e-19 -4.61972799e-13 6.67962117e-27 -5.64982671e-28 1.97417449e-26 3.69528133e-23 7.73466606e-19 9.85491491e-22 3.68332283e-22 1.76753773e-18 2.6167718e-27 3.55918682e-25 1.95786374e-19 -2.60077304e-24 1.84790635e-23
4.13551131e-16 -1.04721097e-18 -9.23628499e-21 -1.0654985e-14 -1.86114433e-16 -1.48572725e-22 -1.44724215e-20 2.92905627e-18 3.25240717e-18 4.27717466e-16 -1.09014604e-17 3.6487132e-17 2.84371096e-17 -2.08440846e-17 -3.16903295e-15 -2.46866108e-20 -2.00782109e-15 2.73480291e-14 3.30651737e-08 8.54181718e-22 -3.97199197e-23 1.62145272e-22 4.81603371e-18 1.96690791e-15 5.08456938e-17 1.76753773e-18 1.57092991e-12 -4.31425852e-23 -3.78241e-19 -1.15899865e-14 -7.61890782e-19 -1.15344546e-19
-2.63408791e-25 -7.00607669e-28 1.65335067e-30 2.70679318e-23 6.00601346e-26 4.14585761e-31 5.04614184e-30 -9.42286262e-28 5.84184241e-28 -4.44694851e-26 7.69340111e-26 -2.06798384e-27 2.98359736e-26 -5.40071056e-28 5.94130048e-25 1.912471e-29 9.75835691e-24 -1.87024011e-24 -5.20615037e-17 -1.2385176e-32 -5.06794406e-32 2.52533191e-31 -1.49322683e-27 -1.65941347e-22 1.08072265e-26 2.6167718e-27 -4.31425852e-23 1.5576233e-30 -6.14697676e-29 -5.39097603e-24 -8.01112167e-29 1.81063126e-27
-2.84830375e-21 6.86771954e-23 -1.12173032e-26 4.0943479e-20 3.26844e-21 2.5611404e-28 -4.12370105e-26 3.23170971e-24 -1.76991199e-24 -1.80270052e-22 1.64649306e-22 -5.5143889e-23 1.33439467e-23 4.56236979e-23 1.73965082e-20 2.0243857e-24 4.0203e-21 -9.73944425e-21 -1.71347193e-14 -5.78078369e-28 1.11993943e-27 -3.42833345e-28 -2.70670724e-25 2.63659933e-18 -1.75036654e-23 3.55918682e-25 -3.78241e-19 -6.14697676e-29 2.71732416e-23 2.4136621e-19 2.38938648e-23 1.21468477e-24
-1.6450072e-16 8.65173173e-19 8.2257321e-22 -3.42938568e-15 4.38573742e-17 -2.77402858e-24 4.20735765e-21 4.10885529e-19 5.5568966e-20 3.29932795e-18 1.71054085e-17 -1.71529414e-18 1.00242743e-17 -1.00868521e-17 1.17371651e-15 -2.88983463e-20 1.40790259e-15 2.74769918e-16 2.87764201e-10 3.34704626e-23 -2.94280711e-23 6.34130774e-22 1.59463723e-19 -3.0624544e-14 4.36436952e-18 1.95786374e-19 -1.15899865e-14 -5.39097603e-24 2.4136621e-19 2.10373291e-13 4.84257897e-20 2.71571227e-19
-2.8585296e-21 1.24469175e-22 -4.72686764e-27 8.57373804e-20 1.06803444e-20 3.10373361e-28 -1.02818953e-25 -8.38673724e-25 -2.80294941e-24 -5.11645591e-22 1.33471053e-23 -7.38099094e-23 -4.94476664e-23 1.22265047e-22 2.26718703e-20 1.35761502e-24 -7.8869e-21 -1.48632788e-20 5.03749196e-14 -2.00599605e-27 2.65858181e-27 -2.01859e-27 6.40406749e-24 5.87194631e-18 -1.77728563e-23 -2.60077304e-24 -7.61890782e-19 -8.01112167e-29 2.38938648e-23 4.84257897e-20 7.77486414e-23 -7.38542574e-25
-3.65413296e-21 6.03883081e-24 -2.58501275e-26 -2.18094505e-20 4.60203933e-22 -5.09669241e-28 3.41267575e-26 -8.63732285e-25 4.59071175e-24 5.53091711e-23 6.40747815e-22 -6.5250472e-24 3.28816461e-22 -1.81997389e-23 4.16709318e-21 1.40424791e-27 8.51983e-20 1.69142815e-21 -1.97989316e-13 2.05674681e-28 -2.23093754e-28 6.1781768e-27 1.17170599e-23 -3.46291098e-19 1.01268548e-22 1.84790635e-23 -1.15344546e-19 1.81063126e-27 1.21468477e-24 2.71571227e-19 -7.38542574e-25 3.49516247e-23
@@ -0,0 +1,281 @@
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.linalg.linalg_impl.tridiagonal_matmul."""
import itertools
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.eager import context
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import array_ops_stack
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import gradient_checker_v2
from tensorflow.python.ops import linalg_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables
from tensorflow.python.ops.linalg import linalg_impl
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
class TridiagonalMulOpTest(test.TestCase):
def _testAllFormats(self,
superdiag,
maindiag,
subdiag,
rhs,
expected,
dtype=dtypes.float64):
superdiag_extended = np.pad(superdiag, [0, 1], 'constant')
subdiag_extended = np.pad(subdiag, [1, 0], 'constant')
diags_compact = np.stack([superdiag_extended, maindiag, subdiag_extended])
diags_matrix = np.diag(superdiag, 1) + np.diag(maindiag, 0) + np.diag(
subdiag, -1)
diags_sequence = (constant_op.constant(superdiag_extended, dtype),
constant_op.constant(maindiag, dtype),
constant_op.constant(subdiag_extended, dtype))
diags_compact = constant_op.constant(diags_compact, dtype)
diags_matrix = constant_op.constant(diags_matrix, dtype)
rhs = constant_op.constant(rhs, dtype)
rhs_batch = array_ops_stack.stack(
[rhs, 2 * rhs])
diags_compact_batch = array_ops_stack.stack(
[diags_compact, 2 * diags_compact])
diags_matrix_batch = array_ops_stack.stack(
[diags_matrix, 2 * diags_matrix])
diags_sequence_batch = [array_ops_stack.stack(
[x, 2 * x]) for x in diags_sequence]
results = [
linalg_impl.tridiagonal_matmul(
diags_sequence, rhs, diagonals_format='sequence'),
linalg_impl.tridiagonal_matmul(
diags_compact, rhs, diagonals_format='compact'),
linalg_impl.tridiagonal_matmul(
diags_matrix, rhs, diagonals_format='matrix')
]
results_batch = [
linalg_impl.tridiagonal_matmul(
diags_sequence_batch, rhs_batch, diagonals_format='sequence'),
linalg_impl.tridiagonal_matmul(
diags_compact_batch, rhs_batch, diagonals_format='compact'),
linalg_impl.tridiagonal_matmul(
diags_matrix_batch, rhs_batch, diagonals_format='matrix')
]
with self.cached_session():
results = self.evaluate(results)
results_batch = self.evaluate(results_batch)
expected = np.array(expected)
expected_batch = np.stack([expected, 4 * expected])
for result in results:
self.assertAllClose(result, expected)
for result in results_batch:
self.assertAllClose(result, expected_batch)
def _makeTridiagonalMatrix(self, superdiag, maindiag, subdiag):
super_pad = [[0, 0], [0, 1], [1, 0]]
sub_pad = [[0, 0], [1, 0], [0, 1]]
super_part = array_ops.pad(array_ops.matrix_diag(superdiag), super_pad)
main_part = array_ops.matrix_diag(maindiag)
sub_part = array_ops.pad(array_ops.matrix_diag(subdiag), sub_pad)
return super_part + main_part + sub_part
def _randomComplexArray(self, shape):
np.random.seed(43)
return (np.random.uniform(-10, 10, shape) +
np.random.uniform(-10, 10, shape) * 1j)
def _gradientTest(self, diags, rhs, dtype=dtypes.float64):
def reference_matmul(diags, rhs):
matrix = self._makeTridiagonalMatrix(diags[..., 0, :-1], diags[..., 1, :],
diags[..., 2, 1:])
return math_ops.matmul(matrix, rhs)
diags = constant_op.constant(diags, dtype=dtype)
rhs = constant_op.constant(rhs, dtype=dtype)
with self.cached_session():
grad_reference, _ = gradient_checker_v2.compute_gradient(
reference_matmul, [diags, rhs])
grad_theoretical, grad_numerical = gradient_checker_v2.compute_gradient(
linalg_impl.tridiagonal_matmul, [diags, rhs])
self.assertAllClose(grad_theoretical, grad_numerical)
self.assertAllClose(grad_theoretical, grad_reference)
def test2x2(self):
self._testAllFormats([1], [2, 3], [4], [[2, 1], [4, 3]], [[8, 5], [20, 13]])
def test3x3(self):
for dtype in [dtypes.float32, dtypes.float64]:
self._testAllFormats([1, 2], [1, 2, 1], [2, 1], [[1, 1], [2, 2], [3, 3]],
[[3, 3], [12, 12], [5, 5]],
dtype=dtype)
def testComplex(self):
for dtype in [dtypes.complex64, dtypes.complex128]:
self._testAllFormats([1j, 1j], [1, -1, 0], [1j, 1j],
np.array([[1, 1j], [1, 1j], [1, 1j]]),
[[1 + 1j, -1 + 1j], [-1 + 2j, -2 - 1j], [1j, -1]],
dtype=dtype)
def testBatch(self):
b = 20
m = 10
n = 15
superdiag = self._randomComplexArray((b, m - 1))
maindiag = self._randomComplexArray((b, m))
subdiag = self._randomComplexArray((b, m - 1))
rhs = self._randomComplexArray((b, m, n))
matrix = np.stack([np.diag(superdiag[i], 1) + \
np.diag(maindiag[i], 0) + \
np.diag(subdiag[i], -1) for i in range(b)])
expected_result = np.matmul(matrix, rhs)
result = linalg_impl.tridiagonal_matmul(
constant_op.constant(matrix, dtype=dtypes.complex128),
constant_op.constant(rhs, dtype=dtypes.complex128),
diagonals_format='matrix')
with self.cached_session():
result = self.evaluate(result)
self.assertAllClose(result, expected_result)
def testGradientSmall(self):
self._gradientTest([[[1, 2, 0], [1, 2, 3], [0, 1, 2]]],
[[[1, 2], [3, 4], [5, 6]]],
dtype=dtypes.float64)
def testGradientComplexSmall(self):
self._gradientTest(
np.array([[[1 + 1j, 2j, 0], [1 + 2j, 2j, 3 + 0j], [0, 1j, 2 + 0j]]]),
np.array([[[1j, 2 + 0j], [3 + 1j, 4j], [5j, 6 + 3j]]]),
dtype=dtypes.complex128)
def testGradientComplexWithBatches(self):
b = 5
m = 10
n = 15
diags = self._randomComplexArray((b, 3, m))
rhs = self._randomComplexArray((b, m, n))
self._gradientTest(diags, rhs, dtype=dtypes.complex128)
def _testErrorWithShapesEager(self, exception_regex, superdiag_shape,
maindiag_shape, subdiag_shape, rhs_shape):
with context.eager_mode():
superdiag = array_ops.ones(superdiag_shape)
maindiag = array_ops.ones(maindiag_shape)
subdiag = array_ops.ones(subdiag_shape)
rhs = array_ops.ones(rhs_shape)
with self.assertRaisesRegex(errors_impl.InvalidArgumentError,
exception_regex):
linalg_ops.tridiagonal_mat_mul(superdiag, maindiag, subdiag, rhs)
def testInvalidShapesEagerGpu(self):
if test.is_built_with_rocm():
self.skipTest('Incorrect Regex on rocm')
if not test.is_gpu_available():
self.skipTest('Test requires GPU')
self._testErrorWithShapesEager('Input must have rank >= 2, but got ',
[2], [2], [2], [2])
self._testErrorWithShapesEager(
'superdiag must have same rank as rhs, but got 3 and 2',
[2, 1, 2], [2, 1], [2, 1], [2, 2])
self._testErrorWithShapesEager(
'maindiag must have same outer dimensions as rhs, but for index 0, got '
'3 and 2',
[2, 1, 2], [3, 1, 2], [2, 1, 2], [2, 2, 2])
self._testErrorWithShapesEager(
"subdiag's second-to-last dimension must be 1, but got 3",
[2, 1, 2], [2, 1, 2], [2, 3, 2], [2, 2, 2])
self._testErrorWithShapesEager(
"subdiag's last dimension size must be rhs's second-to-last dimension "
"size, but got 3 and 2",
[2, 1, 2], [2, 1, 2], [2, 1, 3], [2, 2, 2])
# Benchmark
class TridiagonalMatMulBenchmark(test.Benchmark):
sizes = [(100000, 1, 1), (1000000, 1, 1), (10000000, 1, 1), (100000, 10, 1),
(100000, 100, 1), (10000, 1, 100), (10000, 1, 1000),
(10000, 1, 10000)]
def baseline(self, upper, diag, lower, vec):
diag_part = array_ops.expand_dims(diag, -1) * vec
lower_part = array_ops.pad(
array_ops.expand_dims(lower[:, 1:], -1) * vec[:, :-1, :],
[[0, 0], [1, 0], [0, 0]])
upper_part = array_ops.pad(
array_ops.expand_dims(upper[:, :-1], -1) * vec[:, 1:, :],
[[0, 0], [0, 1], [0, 0]])
return lower_part + diag_part + upper_part
def _generateData(self, batch_size, m, n, seed=42):
np.random.seed(seed)
data = np.random.normal(size=(batch_size, m, 3 + n))
return (variables.Variable(data[:, :, 0], dtype=dtypes.float64),
variables.Variable(data[:, :, 1], dtype=dtypes.float64),
variables.Variable(data[:, :, 2], dtype=dtypes.float64),
variables.Variable(data[:, :, 3:], dtype=dtypes.float64))
def benchmarkTridiagonalMulOp(self):
devices = [('/cpu:0', 'cpu')]
if test.is_gpu_available(cuda_only=True):
devices += [('/gpu:0', 'gpu')]
for device_option, size_option in itertools.product(devices, self.sizes):
device_id, device_name = device_option
m, batch_size, n = size_option
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device(device_id):
upper, diag, lower, vec = self._generateData(batch_size, m, n)
x1 = self.baseline(upper, diag, lower, vec)
x2 = linalg_impl.tridiagonal_matmul((upper, diag, lower),
vec,
diagonals_format='sequence')
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(x1),
min_iters=10,
store_memory_usage=False,
name=('tridiagonal_matmul_baseline_%s'
'_batch_size_%d_m_%d_n_%d' %
(device_name, batch_size, m, n)))
self.run_op_benchmark(
sess,
control_flow_ops.group(x2),
min_iters=10,
store_memory_usage=False,
name=('tridiagonal_matmul_%s_batch_size_%d_m_%d_n_%d' %
(device_name, batch_size, m, n)))
if __name__ == '__main__':
test.main()
@@ -0,0 +1,753 @@
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for tensorflow.ops.linalg.linalg_impl.tridiagonal_solve."""
import itertools
import numpy as np
from tensorflow.python.client import session
from tensorflow.python.eager import backprop
from tensorflow.python.eager import context
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import variables
from tensorflow.python.ops.linalg import linalg_impl
from tensorflow.python.platform import benchmark
from tensorflow.python.platform import test
_sample_diags = np.array([[2, 1, 4, 0], [1, 3, 2, 2], [0, 1, -1, 1]])
_sample_rhs = np.array([1, 2, 3, 4])
_sample_result = np.array([-9, 5, -4, 4])
# Flag, indicating that test should be run only with partial_pivoting=True
FLAG_REQUIRES_PIVOTING = "FLAG_REQUIRES_PIVOT"
# Flag, indicating that test shouldn't be parameterized by different values of
# partial_pivoting, etc.
FLAG_NO_PARAMETERIZATION = "FLAG_NO_PARAMETERIZATION"
def flags(*args):
def decorator(f):
for flag in args:
setattr(f, flag, True)
return f
return decorator
def _tfconst(array):
if array is not None:
return constant_op.constant(array, dtypes.float64)
def _tf_ones(shape):
return array_ops.ones(shape, dtype=dtypes.float64)
class TridiagonalSolveOpTest(test.TestCase):
def _test(self,
diags,
rhs,
expected,
diags_format="compact",
transpose_rhs=False,
conjugate_rhs=False):
with self.cached_session():
pivoting = True
if hasattr(self, "pivoting"):
pivoting = self.pivoting
if test_util.is_xla_enabled() and pivoting:
# Pivoting is not supported by xla backends.
return
result = linalg_impl.tridiagonal_solve(
diags,
rhs,
diags_format,
transpose_rhs,
conjugate_rhs,
partial_pivoting=pivoting)
result = self.evaluate(result)
if expected is None:
self.assertAllEqual(
np.zeros_like(result, dtype=np.bool_), np.isfinite(result))
else:
self.assertAllClose(result, expected)
def _testWithLists(self,
diags,
rhs,
expected=None,
diags_format="compact",
transpose_rhs=False,
conjugate_rhs=False):
self._test(
_tfconst(diags), _tfconst(rhs), _tfconst(expected), diags_format,
transpose_rhs, conjugate_rhs)
def _assertRaises(self, diags, rhs, diags_format="compact"):
pivoting = True
if hasattr(self, "pivoting"):
pivoting = self.pivoting
if test_util.is_xla_enabled() and pivoting:
# Pivoting is not supported by xla backends.
return
with self.assertRaises(ValueError):
linalg_impl.tridiagonal_solve(
diags, rhs, diags_format, partial_pivoting=pivoting)
# Tests with various dtypes
def testReal(self):
for dtype in dtypes.float32, dtypes.float64:
self._test(
diags=constant_op.constant(_sample_diags, dtype),
rhs=constant_op.constant(_sample_rhs, dtype),
expected=constant_op.constant(_sample_result, dtype))
def testComplex(self):
for dtype in dtypes.complex64, dtypes.complex128:
self._test(
diags=constant_op.constant(_sample_diags, dtype) * (1 + 1j),
rhs=constant_op.constant(_sample_rhs, dtype) * (1 - 1j),
expected=constant_op.constant(_sample_result, dtype) * (1 - 1j) /
(1 + 1j))
# Tests with small matrix sizes
def test3x3(self):
self._testWithLists(
diags=[[2, -1, 0], [1, 3, 1], [0, -1, -2]],
rhs=[1, 2, 3],
expected=[-3, 2, 7])
def test2x2(self):
self._testWithLists(
diags=[[2, 0], [1, 3], [0, 1]], rhs=[1, 4], expected=[-5, 3])
def test2x2Complex(self):
for dtype in dtypes.complex64, dtypes.complex128:
self._test(
diags=constant_op.constant([[2j, 0j], [1j, 3j], [0j, 1j]], dtype),
rhs=constant_op.constant([1 - 1j, 4 - 4j], dtype),
expected=constant_op.constant([5 + 5j, -3 - 3j], dtype))
def test1x1(self):
self._testWithLists(diags=[[0], [3], [0]], rhs=[6], expected=[2])
def test0x0(self):
if test_util.is_xla_enabled():
# The following test crashes with XLA due to slicing 0 length tensors.
return
self._test(
diags=constant_op.constant(0, shape=(3, 0), dtype=dtypes.float32),
rhs=constant_op.constant(0, shape=(0, 1), dtype=dtypes.float32),
expected=constant_op.constant(0, shape=(0, 1), dtype=dtypes.float32))
def test2x2WithMultipleRhs(self):
self._testWithLists(
diags=[[2, 0], [1, 3], [0, 1]],
rhs=[[1, 2, 3], [4, 8, 12]],
expected=[[-5, -10, -15], [3, 6, 9]])
def test1x1WithMultipleRhs(self):
self._testWithLists(
diags=[[0], [3], [0]], rhs=[[6, 9, 12]], expected=[[2, 3, 4]])
def test1x1NotInvertible(self):
if test_util.is_xla_enabled():
# XLA implementation does not check invertibility.
return
self._testWithLists(diags=[[0], [0], [0]], rhs=[[6, 9, 12]])
def test2x2NotInvertible(self):
if test_util.is_xla_enabled():
# XLA implementation does not check invertibility.
return
self._testWithLists(diags=[[3, 0], [1, 3], [0, 1]], rhs=[1, 4])
# Other edge cases
@flags(FLAG_REQUIRES_PIVOTING)
def testCaseRequiringPivoting(self):
# Without partial pivoting (e.g. Thomas algorithm) this would fail.
self._testWithLists(
diags=[[2, -1, 1, 0], [1, 4, 1, -1], [0, 2, -2, 3]],
rhs=[1, 2, 3, 4],
expected=[8, -3.5, 0, -4])
@flags(FLAG_REQUIRES_PIVOTING)
def testCaseRequiringPivotingLastRows(self):
self._testWithLists(
diags=[[2, 1, -1, 0], [1, -1, 2, 1], [0, 1, -6, 1]],
rhs=[1, 2, -1, -2],
expected=[5, -2, -5, 3])
def testNotInvertible(self):
if test_util.is_xla_enabled():
return
self._testWithLists(
diags=[[2, -1, 1, 0], [1, 4, 1, -1], [0, 2, 0, 3]], rhs=[1, 2, 3, 4])
def testDiagonal(self):
self._testWithLists(
diags=[[0, 0, 0, 0], [1, 2, -1, -2], [0, 0, 0, 0]],
rhs=[1, 2, 3, 4],
expected=[1, 1, -3, -2])
def testUpperTriangular(self):
self._testWithLists(
diags=[[2, 4, -1, 0], [1, 3, 1, 2], [0, 0, 0, 0]],
rhs=[1, 6, 4, 4],
expected=[13, -6, 6, 2])
def testLowerTriangular(self):
self._testWithLists(
diags=[[0, 0, 0, 0], [2, -1, 3, 1], [0, 1, 4, 2]],
rhs=[4, 5, 6, 1],
expected=[2, -3, 6, -11])
# Multiple right-hand sides and batching
def testWithTwoRightHandSides(self):
self._testWithLists(
diags=_sample_diags,
rhs=np.transpose([_sample_rhs, 2 * _sample_rhs]),
expected=np.transpose([_sample_result, 2 * _sample_result]))
def testBatching(self):
self._testWithLists(
diags=np.array([_sample_diags, -_sample_diags]),
rhs=np.array([_sample_rhs, 2 * _sample_rhs]),
expected=np.array([_sample_result, -2 * _sample_result]))
def testWithTwoBatchingDimensions(self):
self._testWithLists(
diags=np.array([[_sample_diags, -_sample_diags, _sample_diags],
[-_sample_diags, _sample_diags, -_sample_diags]]),
rhs=np.array([[_sample_rhs, 2 * _sample_rhs, 3 * _sample_rhs],
[4 * _sample_rhs, 5 * _sample_rhs, 6 * _sample_rhs]]),
expected=np.array(
[[_sample_result, -2 * _sample_result, 3 * _sample_result],
[-4 * _sample_result, 5 * _sample_result, -6 * _sample_result]]))
def testBatchingAndTwoRightHandSides(self):
rhs = np.transpose([_sample_rhs, 2 * _sample_rhs])
expected_result = np.transpose([_sample_result, 2 * _sample_result])
self._testWithLists(
diags=np.array([_sample_diags, -_sample_diags]),
rhs=np.array([rhs, 2 * rhs]),
expected=np.array([expected_result, -2 * expected_result]))
# Various input formats
def testSequenceFormat(self):
self._test(
diags=(_tfconst([2, 1, 4]), _tfconst([1, 3, 2, 2]), _tfconst([1, -1,
1])),
rhs=_tfconst([1, 2, 3, 4]),
expected=_tfconst([-9, 5, -4, 4]),
diags_format="sequence")
def testSequenceFormatWithDummyElements(self):
dummy = 20
self._test(
diags=(_tfconst([2, 1, 4,
dummy]), _tfconst([1, 3, 2,
2]), _tfconst([dummy, 1, -1, 1])),
rhs=_tfconst([1, 2, 3, 4]),
expected=_tfconst([-9, 5, -4, 4]),
diags_format="sequence")
def testSequenceFormatWithBatching(self):
self._test(
diags=(_tfconst([[2, 1, 4], [-2, -1, -4]]),
_tfconst([[1, 3, 2, 2],
[-1, -3, -2, -2]]), _tfconst([[1, -1, 1], [-1, 1,
-1]])),
rhs=_tfconst([[1, 2, 3, 4], [1, 2, 3, 4]]),
expected=_tfconst([[-9, 5, -4, 4], [9, -5, 4, -4]]),
diags_format="sequence")
def testMatrixFormat(self):
self._testWithLists(
diags=[[1, 2, 0, 0], [1, 3, 1, 0], [0, -1, 2, 4], [0, 0, 1, 2]],
rhs=[1, 2, 3, 4],
expected=[-9, 5, -4, 4],
diags_format="matrix")
def testMatrixFormatWithMultipleRightHandSides(self):
self._testWithLists(
diags=[[1, 2, 0, 0], [1, 3, 1, 0], [0, -1, 2, 4], [0, 0, 1, 2]],
rhs=[[1, -1], [2, -2], [3, -3], [4, -4]],
expected=[[-9, 9], [5, -5], [-4, 4], [4, -4]],
diags_format="matrix")
def testMatrixFormatWithBatching(self):
self._testWithLists(
diags=[[[1, 2, 0, 0], [1, 3, 1, 0], [0, -1, 2, 4], [0, 0, 1, 2]],
[[-1, -2, 0, 0], [-1, -3, -1, 0], [0, 1, -2, -4], [0, 0, -1,
-2]]],
rhs=[[1, 2, 3, 4], [1, 2, 3, 4]],
expected=[[-9, 5, -4, 4], [9, -5, 4, -4]],
diags_format="matrix")
def testRightHandSideAsColumn(self):
self._testWithLists(
diags=_sample_diags,
rhs=np.transpose([_sample_rhs]),
expected=np.transpose([_sample_result]),
diags_format="compact")
# Tests with transpose and adjoint
def testTransposeRhs(self):
self._testWithLists(
diags=_sample_diags,
rhs=np.array([_sample_rhs, 2 * _sample_rhs]),
expected=np.array([_sample_result, 2 * _sample_result]).T,
transpose_rhs=True)
def testConjugateRhs(self):
self._testWithLists(
diags=_sample_diags,
rhs=np.transpose([_sample_rhs * (1 + 1j), _sample_rhs * (1 - 2j)]),
expected=np.transpose(
[_sample_result * (1 - 1j), _sample_result * (1 + 2j)]),
conjugate_rhs=True)
def testAdjointRhs(self):
self._testWithLists(
diags=_sample_diags,
rhs=np.array([_sample_rhs * (1 + 1j), _sample_rhs * (1 - 2j)]),
expected=np.array(
[_sample_result * (1 - 1j), _sample_result * (1 + 2j)]).T,
transpose_rhs=True,
conjugate_rhs=True)
def testTransposeRhsWithBatching(self):
self._testWithLists(
diags=np.array([_sample_diags, -_sample_diags]),
rhs=np.array([[_sample_rhs, 2 * _sample_rhs],
[3 * _sample_rhs, 4 * _sample_rhs]]),
expected=np.array([[_sample_result, 2 * _sample_result],
[-3 * _sample_result,
-4 * _sample_result]]).transpose(0, 2, 1),
transpose_rhs=True)
def testTransposeRhsWithRhsAsVector(self):
self._testWithLists(
diags=_sample_diags,
rhs=_sample_rhs,
expected=_sample_result,
transpose_rhs=True)
def testConjugateRhsWithRhsAsVector(self):
self._testWithLists(
diags=_sample_diags,
rhs=_sample_rhs * (1 + 1j),
expected=_sample_result * (1 - 1j),
conjugate_rhs=True)
def testTransposeRhsWithRhsAsVectorAndBatching(self):
self._testWithLists(
diags=np.array([_sample_diags, -_sample_diags]),
rhs=np.array([_sample_rhs, 2 * _sample_rhs]),
expected=np.array([_sample_result, -2 * _sample_result]),
transpose_rhs=True)
# Gradient tests
def _gradientTest(
self,
diags,
rhs,
y, # output = reduce_sum(y * tridiag_solve(diags, rhs))
expected_grad_diags, # expected gradient of output w.r.t. diags
expected_grad_rhs, # expected gradient of output w.r.t. rhs
diags_format="compact",
transpose_rhs=False,
conjugate_rhs=False,
feed_dict=None):
expected_grad_diags = _tfconst(expected_grad_diags)
expected_grad_rhs = _tfconst(expected_grad_rhs)
with backprop.GradientTape() as tape_diags:
with backprop.GradientTape() as tape_rhs:
tape_diags.watch(diags)
tape_rhs.watch(rhs)
if test_util.is_xla_enabled():
# Pivoting is not supported by xla backends.
return
x = linalg_impl.tridiagonal_solve(
diags,
rhs,
diagonals_format=diags_format,
transpose_rhs=transpose_rhs,
conjugate_rhs=conjugate_rhs)
res = math_ops.reduce_sum(x * y)
with self.cached_session() as sess:
actual_grad_diags = sess.run(
tape_diags.gradient(res, diags), feed_dict=feed_dict)
actual_rhs_diags = sess.run(
tape_rhs.gradient(res, rhs), feed_dict=feed_dict)
self.assertAllClose(expected_grad_diags, actual_grad_diags)
self.assertAllClose(expected_grad_rhs, actual_rhs_diags)
def _gradientTestWithLists(self,
diags,
rhs,
y,
expected_grad_diags,
expected_grad_rhs,
diags_format="compact",
transpose_rhs=False,
conjugate_rhs=False):
self._gradientTest(
_tfconst(diags), _tfconst(rhs), _tfconst(y), expected_grad_diags,
expected_grad_rhs, diags_format, transpose_rhs, conjugate_rhs)
def testGradientSimple(self):
self._gradientTestWithLists(
diags=_sample_diags,
rhs=_sample_rhs,
y=[1, 3, 2, 4],
expected_grad_diags=[[-5, 0, 4, 0], [9, 0, -4, -16], [0, 0, 5, 16]],
expected_grad_rhs=[1, 0, -1, 4])
def testGradientWithMultipleRhs(self):
self._gradientTestWithLists(
diags=_sample_diags,
rhs=[[1, 2], [2, 4], [3, 6], [4, 8]],
y=[[1, 5], [2, 6], [3, 7], [4, 8]],
expected_grad_diags=([[-20, 28, -60, 0], [36, -35, 60, 80],
[0, 63, -75, -80]]),
expected_grad_rhs=[[0, 2], [1, 3], [1, 7], [0, -10]])
def _makeDataForGradientWithBatching(self):
y = np.array([1, 3, 2, 4])
grad_diags = np.array([[-5, 0, 4, 0], [9, 0, -4, -16], [0, 0, 5, 16]])
grad_rhs = np.array([1, 0, -1, 4])
diags_batched = np.array(
[[_sample_diags, 2 * _sample_diags, 3 * _sample_diags],
[4 * _sample_diags, 5 * _sample_diags, 6 * _sample_diags]])
rhs_batched = np.array([[_sample_rhs, -_sample_rhs, _sample_rhs],
[-_sample_rhs, _sample_rhs, -_sample_rhs]])
y_batched = np.array([[y, y, y], [y, y, y]])
expected_grad_diags_batched = np.array(
[[grad_diags, -grad_diags / 4, grad_diags / 9],
[-grad_diags / 16, grad_diags / 25, -grad_diags / 36]])
expected_grad_rhs_batched = np.array(
[[grad_rhs, grad_rhs / 2, grad_rhs / 3],
[grad_rhs / 4, grad_rhs / 5, grad_rhs / 6]])
return (y_batched, diags_batched, rhs_batched, expected_grad_diags_batched,
expected_grad_rhs_batched)
def testGradientWithBatchDims(self):
y, diags, rhs, expected_grad_diags, expected_grad_rhs = \
self._makeDataForGradientWithBatching()
self._gradientTestWithLists(
diags=diags,
rhs=rhs,
y=y,
expected_grad_diags=expected_grad_diags,
expected_grad_rhs=expected_grad_rhs)
@test_util.run_deprecated_v1
def testGradientWithUnknownShapes(self):
def placeholder(rank):
return array_ops.placeholder(
dtypes.float64, shape=(None for _ in range(rank)))
y, diags, rhs, expected_grad_diags, expected_grad_rhs = \
self._makeDataForGradientWithBatching()
diags_placeholder = placeholder(rank=4)
rhs_placeholder = placeholder(rank=3)
y_placeholder = placeholder(rank=3)
self._gradientTest(
diags=diags_placeholder,
rhs=rhs_placeholder,
y=y_placeholder,
expected_grad_diags=expected_grad_diags,
expected_grad_rhs=expected_grad_rhs,
feed_dict={
diags_placeholder: diags,
rhs_placeholder: rhs,
y_placeholder: y
})
# Invalid input shapes
@flags(FLAG_NO_PARAMETERIZATION)
def testInvalidShapesCompactFormat(self):
def test_raises(diags_shape, rhs_shape):
self._assertRaises(_tf_ones(diags_shape), _tf_ones(rhs_shape), "compact")
test_raises((5, 4, 4), (5, 4))
test_raises((5, 3, 4), (4, 5))
test_raises((5, 3, 4), (5))
test_raises((5), (5, 4))
@flags(FLAG_NO_PARAMETERIZATION)
def testInvalidShapesSequenceFormat(self):
def test_raises(diags_tuple_shapes, rhs_shape):
diagonals = tuple(_tf_ones(shape) for shape in diags_tuple_shapes)
self._assertRaises(diagonals, _tf_ones(rhs_shape), "sequence")
test_raises(((5, 4), (5, 4)), (5, 4))
test_raises(((5, 4), (5, 4), (5, 6)), (5, 4))
test_raises(((5, 3), (5, 4), (5, 6)), (5, 4))
test_raises(((5, 6), (5, 4), (5, 3)), (5, 4))
test_raises(((5, 4), (7, 4), (5, 4)), (5, 4))
test_raises(((5, 4), (7, 4), (5, 4)), (3, 4))
@flags(FLAG_NO_PARAMETERIZATION)
def testInvalidShapesMatrixFormat(self):
def test_raises(diags_shape, rhs_shape):
self._assertRaises(_tf_ones(diags_shape), _tf_ones(rhs_shape), "matrix")
test_raises((5, 4, 7), (5, 4))
test_raises((5, 4, 4), (3, 4))
test_raises((5, 4, 4), (5, 3))
# Tests with placeholders
def _testWithPlaceholders(self,
diags_shape,
rhs_shape,
diags_feed,
rhs_feed,
expected,
diags_format="compact"):
if context.executing_eagerly():
return
diags = array_ops.placeholder(dtypes.float64, shape=diags_shape)
rhs = array_ops.placeholder(dtypes.float64, shape=rhs_shape)
if test_util.is_xla_enabled() and self.pivoting:
# Pivoting is not supported by xla backends.
return
x = linalg_impl.tridiagonal_solve(
diags, rhs, diags_format, partial_pivoting=self.pivoting)
with self.cached_session() as sess:
result = sess.run(x, feed_dict={diags: diags_feed, rhs: rhs_feed})
self.assertAllClose(result, expected)
@test_util.run_deprecated_v1
def testCompactFormatAllDimsUnknown(self):
self._testWithPlaceholders(
diags_shape=[None, None],
rhs_shape=[None],
diags_feed=_sample_diags,
rhs_feed=_sample_rhs,
expected=_sample_result)
@test_util.run_deprecated_v1
def testCompactFormatUnknownMatrixSize(self):
self._testWithPlaceholders(
diags_shape=[3, None],
rhs_shape=[4],
diags_feed=_sample_diags,
rhs_feed=_sample_rhs,
expected=_sample_result)
@test_util.run_deprecated_v1
def testCompactFormatUnknownRhsCount(self):
self._testWithPlaceholders(
diags_shape=[3, 4],
rhs_shape=[4, None],
diags_feed=_sample_diags,
rhs_feed=np.transpose([_sample_rhs, 2 * _sample_rhs]),
expected=np.transpose([_sample_result, 2 * _sample_result]))
@test_util.run_deprecated_v1
def testCompactFormatUnknownBatchSize(self):
self._testWithPlaceholders(
diags_shape=[None, 3, 4],
rhs_shape=[None, 4],
diags_feed=np.array([_sample_diags, -_sample_diags]),
rhs_feed=np.array([_sample_rhs, 2 * _sample_rhs]),
expected=np.array([_sample_result, -2 * _sample_result]))
@test_util.run_deprecated_v1
def testMatrixFormatWithUnknownDims(self):
if context.executing_eagerly():
return
def test_with_matrix_shapes(matrix_shape, rhs_shape=None):
matrix = np.array([[1, 2, 0, 0], [1, 3, 1, 0], [0, -1, 2, 4],
[0, 0, 1, 2]])
rhs = np.array([1, 2, 3, 4])
x = np.array([-9, 5, -4, 4])
self._testWithPlaceholders(
diags_shape=matrix_shape,
rhs_shape=rhs_shape,
diags_feed=matrix,
rhs_feed=np.transpose([rhs, 2 * rhs]),
expected=np.transpose([x, 2 * x]),
diags_format="matrix")
test_with_matrix_shapes(matrix_shape=[4, 4], rhs_shape=[None, None])
test_with_matrix_shapes(matrix_shape=[None, 4], rhs_shape=[None, None])
test_with_matrix_shapes(matrix_shape=[4, None], rhs_shape=[None, None])
test_with_matrix_shapes(matrix_shape=[None, None], rhs_shape=[None, None])
test_with_matrix_shapes(matrix_shape=[4, 4])
test_with_matrix_shapes(matrix_shape=[None, 4])
test_with_matrix_shapes(matrix_shape=[4, None])
test_with_matrix_shapes(matrix_shape=[None, None])
test_with_matrix_shapes(matrix_shape=None, rhs_shape=[None, None])
test_with_matrix_shapes(matrix_shape=None)
@test_util.run_deprecated_v1
def testSequenceFormatWithUnknownDims(self):
if context.executing_eagerly():
return
if test_util.is_xla_enabled() and self.pivoting:
# Pivoting is not supported by xla backends.
return
superdiag = array_ops.placeholder(dtypes.float64, shape=[None])
diag = array_ops.placeholder(dtypes.float64, shape=[None])
subdiag = array_ops.placeholder(dtypes.float64, shape=[None])
rhs = array_ops.placeholder(dtypes.float64, shape=[None])
x = linalg_impl.tridiagonal_solve((superdiag, diag, subdiag),
rhs,
diagonals_format="sequence",
partial_pivoting=self.pivoting)
with self.cached_session() as sess:
result = sess.run(
x,
feed_dict={
subdiag: [20, 1, -1, 1],
diag: [1, 3, 2, 2],
superdiag: [2, 1, 4, 20],
rhs: [1, 2, 3, 4]
})
self.assertAllClose(result, [-9, 5, -4, 4])
# Benchmark
class TridiagonalSolveBenchmark(test.Benchmark):
sizes = [(100000, 1, 1), (1000000, 1, 1), (10000000, 1, 1), (100000, 10, 1),
(100000, 100, 1), (10000, 1, 10), (10000, 1, 100)]
pivoting_options = [(True, "pivoting"), (False, "no_pivoting")]
def _generateData(self, matrix_size, batch_size, num_rhs, seed=42):
np.random.seed(seed)
data = np.random.normal(size=(batch_size, matrix_size, 3 + num_rhs))
diags = np.stack([data[:, :, 0], data[:, :, 1], data[:, :, 2]], axis=-2)
rhs = data[:, :, 3:]
return (variables.Variable(diags, dtype=dtypes.float64),
variables.Variable(rhs, dtype=dtypes.float64))
def _generateMatrixData(self, matrix_size, batch_size, num_rhs, seed=42):
np.random.seed(seed)
import scipy.sparse as sparse # pylint:disable=g-import-not-at-top
# By being strictly diagonally dominant, we guarantee invertibility.d
diag = 2 * np.abs(np.random.randn(matrix_size)) + 4.1
subdiag = 2 * np.abs(np.random.randn(matrix_size - 1))
superdiag = 2 * np.abs(np.random.randn(matrix_size - 1))
matrix = sparse.diags([superdiag, diag, subdiag], [1, 0, -1]).toarray()
vector = np.random.randn(batch_size, matrix_size, num_rhs)
return (variables.Variable(np.tile(matrix, (batch_size, 1, 1))),
variables.Variable(vector))
def _benchmark(self, generate_data_fn, test_name_format_string):
devices = [("/cpu:0", "cpu")]
if test.is_gpu_available(cuda_only=True):
devices += [("/gpu:0", "gpu")]
for device_option, pivoting_option, size_option in \
itertools.product(devices, self.pivoting_options, self.sizes):
device_id, device_name = device_option
pivoting, pivoting_name = pivoting_option
matrix_size, batch_size, num_rhs = size_option
with ops.Graph().as_default(), \
session.Session(config=benchmark.benchmark_config()) as sess, \
ops.device(device_id):
diags, rhs = generate_data_fn(matrix_size, batch_size, num_rhs)
# Pivoting is not supported by XLA backends.
if test.is_xla_enabled() and pivoting:
return
x = linalg_impl.tridiagonal_solve(
diags, rhs, partial_pivoting=pivoting)
self.evaluate(variables.global_variables_initializer())
self.run_op_benchmark(
sess,
control_flow_ops.group(x),
min_iters=10,
store_memory_usage=False,
name=test_name_format_string.format(device_name, matrix_size,
batch_size, num_rhs,
pivoting_name))
def benchmarkTridiagonalSolveOp_WithMatrixInput(self):
self._benchmark(
self._generateMatrixData,
test_name_format_string=(
"tridiagonal_solve_matrix_format_{}_matrix_size_{}_"
"batch_size_{}_num_rhs_{}_{}"))
def benchmarkTridiagonalSolveOp(self):
self._benchmark(
self._generateMatrixData,
test_name_format_string=("tridiagonal_solve_{}_matrix_size_{}_"
"batch_size_{}_num_rhs_{}_{}"))
if __name__ == "__main__":
for name, fun in dict(TridiagonalSolveOpTest.__dict__).items():
if not name.startswith("test"):
continue
if hasattr(fun, FLAG_NO_PARAMETERIZATION):
continue
# Replace testFoo with testFoo_pivoting and testFoo_noPivoting, setting
# self.pivoting to corresponding value.
delattr(TridiagonalSolveOpTest, name)
def decor(test_fun, pivoting):
def wrapped(instance):
instance.pivoting = pivoting
test_fun(instance)
return wrapped
setattr(TridiagonalSolveOpTest, name + "_pivoting",
decor(fun, pivoting=True))
if not hasattr(fun, FLAG_REQUIRES_PIVOTING):
setattr(TridiagonalSolveOpTest, name + "_noPivoting",
decor(fun, pivoting=False))
test.main()