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)
def testConcurrentExecutesWithoutError(self): all_ops = [] with self.session(use_gpu=True) as sess: 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 _EigGrad(op, grad_e, grad_v): """Gradient for Eig. Based on eq. 4.77 from paper by Christoph Boeddeker et al. https://arxiv.org/abs/1701.00392 See also "Computation of eigenvalue and eigenvector derivatives for a general complex-valued eigensystem" by Nico van der Aa. As for now only distinct eigenvalue case is considered. """ e = op.outputs[0] compute_v = op.get_attr("compute_v") # a = op.inputs[0], which satisfies # a[...,:,:] * v[...,:,i] = e[...,i] * v[...,i] with ops.control_dependencies([grad_e, grad_v]): if compute_v: v = op.outputs[1] vt = _linalg.adjoint(v) # Construct the matrix f(i,j) = (i != j ? 1 / (e_i - e_j) : 0). # Notice that because of the term involving f, the gradient becomes # infinite (or NaN in practice) when eigenvalues are not unique. # Mathematically this should not be surprising, since for (k-fold) # degenerate eigenvalues, the corresponding eigenvectors are only defined # up to arbitrary rotation in a (k-dimensional) subspace. f = array_ops.matrix_set_diag( _SafeReciprocal( array_ops.expand_dims(e, -2) - array_ops.expand_dims(e, -1)), array_ops.zeros_like(e)) f = math_ops.conj(f) vgv = math_ops.matmul(vt, grad_v) mid = array_ops.matrix_diag(grad_e) diag_grad_part = array_ops.matrix_diag( array_ops.matrix_diag_part( math_ops.cast(math_ops.real(vgv), vgv.dtype))) mid += f * ( vgv - math_ops.matmul(math_ops.matmul(vt, v), diag_grad_part)) # vt is formally invertible as long as the original matrix is # diagonalizable. However, in practice, vt may # be ill-conditioned when matrix original matrix is close to # non-diagonalizable one grad_a = linalg_ops.matrix_solve(vt, math_ops.matmul(mid, vt)) else: _, v = linalg_ops.eig(op.inputs[0]) vt = _linalg.adjoint(v) # vt is formally invertible as long as the original matrix is # diagonalizable. However, in practice, vt may # be ill-conditioned when matrix original matrix is close to # non-diagonalizable one grad_a = linalg_ops.matrix_solve( vt, math_ops.matmul(array_ops.matrix_diag(grad_e), vt)) return math_ops.cast(grad_a, op.inputs[0].dtype)
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
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)