Example #1
0
def diagonalize_real_symmetric_and_sorted_diagonal_matrices(
        symmetric_matrix: np.ndarray,
        diagonal_matrix: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT) -> np.ndarray:
    """Returns an orthogonal matrix that diagonalizes both given matrices.

    The given matrices must commute.
    Guarantees that the sorted diagonal matrix is not permuted by the
    diagonalization (except for nearly-equal values).

    Args:
        symmetric_matrix: A real symmetric matrix.
        diagonal_matrix: A real diagonal matrix with entries along the diagonal
            sorted into descending order.
        tolerance: Numeric error thresholds.

    Returns:
        An orthogonal matrix P such that P.T @ symmetric_matrix @ P is diagonal
        and P.T @ diagonal_matrix @ P = diagonal_matrix (up to tolerance).

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not symmetric).
        ArithmeticError: Failed to meet specified tolerance.
    """

    # Verify preconditions.
    if (np.any(np.imag(symmetric_matrix) != 0)
            or not predicates.is_hermitian(symmetric_matrix)):
        raise ValueError('symmetric_matrix must be real symmetric.')
    if (not predicates.is_diagonal(diagonal_matrix)
            or np.any(np.imag(diagonal_matrix) != 0)
            or np.any(diagonal_matrix[:-1, :-1] < diagonal_matrix[1:, 1:])):
        raise ValueError('diagonal_matrix must be real diagonal descending.')
    if not predicates.commutes(diagonal_matrix, symmetric_matrix):
        raise ValueError('Given matrices must commute.')

    def similar_singular(i, j):
        return tolerance.all_close(diagonal_matrix[i, i], diagonal_matrix[j,
                                                                          j])

    # Because the symmetric matrix commutes with the diagonal singulars matrix,
    # the symmetric matrix should be block-diagonal with a block boundary
    # wherever the singular values happen change. So we can use the singular
    # values to extract blocks that can be independently diagonalized.
    ranges = _contiguous_groups(diagonal_matrix.shape[0], similar_singular)

    # Build the overall diagonalization by diagonalizing each block.
    p = np.zeros(symmetric_matrix.shape, dtype=np.float64)
    for start, end in ranges:
        block = symmetric_matrix[start:end, start:end]
        p[start:end, start:end] = diagonalize_real_symmetric_matrix(block)

    # Check acceptability vs tolerances.
    if (not predicates.is_diagonal(
            p.T.dot(symmetric_matrix).dot(p), tolerance)
            or not tolerance.all_close(diagonal_matrix,
                                       p.T.dot(diagonal_matrix).dot(p))):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return p
Example #2
0
def test_is_diagonal_tolerance():
    tol = Tolerance(atol=0.5)

    # Pays attention to specified tolerance.
    assert predicates.is_diagonal(np.array([[1, 0], [-0.5, 1]]), tol)
    assert not predicates.is_diagonal(np.array([[1, 0], [-0.6, 1]]), tol)

    # Error isn't accumulated across entries.
    assert predicates.is_diagonal(np.array([[1, 0.5], [-0.5, 1]]), tol)
    assert not predicates.is_diagonal(np.array([[1, 0.5], [-0.6, 1]]), tol)
Example #3
0
def assert_bidiagonalized_by(m,
                             p,
                             q,
                             rtol: float = DEFAULT_RTOL,
                             atol: float = DEFAULT_ATOL):
    d = p.dot(m).dot(q)

    try:
        assert predicates.is_orthogonal(p)
        assert predicates.is_orthogonal(q)
        assert predicates.is_diagonal(d, rtol=rtol, atol=atol)
    except AssertionError:
        # coverage: ignore

        print("m.round(3)")
        print(np.round(m, 3))

        print("p.round(3)")
        print(np.round(p, 3))

        print("q.round(3)")
        print(np.round(q, 3))

        print("np.log10(np.abs(p @ m @ q)).round(2)")
        print(np.log10(np.abs(d)).round(2))

        raise
Example #4
0
def unitary_eig(matrix: np.ndarray,
                check_preconditions: bool = True,
                atol: float = 1e-8) -> Tuple[np.array, np.ndarray]:
    """Gives the guaranteed unitary eigendecomposition of a normal matrix.

    All hermitian and unitary matrices are normal matrices. This method was
    introduced as for certain classes of unitary matrices (where the eigenvalues
    are close to each other) the eigenvectors returned by `numpy.linalg.eig` are
    not guaranteed to be orthogonal.
    For more information, see https://github.com/numpy/numpy/issues/15461.

    Args:
        matrix: a normal matrix. If not normal, this method is not
            guaranteed to return correct eigenvalues.
        check_preconditions: when true and matrix is not unitary,
            a `ValueError` is raised
        atol: the absolute tolerance when checking whether the original matrix
            was unitary

    Returns:
         eigvals: the eigenvalues of `matrix`
         V: the unitary matrix with the eigenvectors as columns
    """
    R, V = scipy.linalg.schur(matrix, output="complex")
    if check_preconditions and not predicates.is_diagonal(R, atol=atol):
        raise ValueError('Input must correspond to a unitary matrix '
                         f'.Received input:\n{matrix}')
    return R.diagonal(), V
Example #5
0
def diagonalize_real_symmetric_matrix(
        matrix: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT) -> np.ndarray:
    """Returns an orthogonal matrix that diagonalizes the given matrix.

    Args:
        matrix: A real symmetric matrix to diagonalize.
        tolerance: Numeric error thresholds.

    Returns:
        An orthogonal matrix P such that P.T @ matrix @ P is diagonal.

    Raises:
        ValueError: Matrix isn't real symmetric.
        ArithmeticError: Failed to meet specified tolerance.
    """

    if np.any(np.imag(matrix) != 0) or not predicates.is_hermitian(matrix):
        raise ValueError('Input must be real and symmetric.')

    _, result = np.linalg.eigh(matrix)

    # Check acceptability vs tolerances.
    if (not predicates.is_orthogonal(result, tolerance)
            or not predicates.is_diagonal(
                result.T.dot(matrix).dot(result), tolerance)):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return result
Example #6
0
def diagonalize_real_symmetric_matrix(
        matrix: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT
) -> np.ndarray:
    """Returns an orthogonal matrix that diagonalizes the given matrix.

    Args:
        matrix: A real symmetric matrix to diagonalize.
        tolerance: Numeric error thresholds.

    Returns:
        An orthogonal matrix P such that P.T @ matrix @ P is diagonal.

    Raises:
        ValueError: Matrix isn't real symmetric.
        ArithmeticError: Failed to meet specified tolerance.
    """

    if np.any(np.imag(matrix) != 0) or not predicates.is_hermitian(matrix):
        raise ValueError('Input must be real and symmetric.')

    _, result = np.linalg.eigh(matrix)

    # Check acceptability vs tolerances.
    if (not predicates.is_orthogonal(result, tolerance) or
            not predicates.is_diagonal(result.T.dot(matrix).dot(result),
                                        tolerance)):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return result
Example #7
0
def two_qubit_matrix_to_diagonal_and_operations(
    q0: 'cirq.Qid',
    q1: 'cirq.Qid',
    mat: np.ndarray,
    allow_partial_czs: bool = False,
    atol: float = 1e-8,
    clean_operations: bool = True,
) -> Tuple[np.ndarray, List['cirq.Operation']]:
    """Decomposes a 2-qubit unitary to a diagonal and the remaining operations.

    For a 2-qubit unitary V, return ops, a list of operations and
    D diagonal unitary, so that:
        V = cirq.Circuit(ops) @ D

    Args:
        q0: The first qubit being operated on.
        q1: The other qubit being operated on.
        mat: the input unitary
        allow_partial_czs: Enables the use of Partial-CZ gates.
        atol: A limit on the amount of absolute error introduced by the
            construction.
        clean_operations: Enables optimizing resulting operation list by
            merging operations and ejecting phased Paulis and Z operations.
    Returns:
        tuple(ops,D): operations `ops`, and the diagonal `D`
    """
    if predicates.is_diagonal(mat, atol=atol):
        return mat, []

    if num_cnots_required(mat) == 3:
        right_diag = extract_right_diag(mat)
        two_cnot_unitary = mat @ right_diag
        # note that this implies that two_cnot_unitary @ d = mat
        return right_diag.conj().T, two_qubit_matrix_to_operations(
            q0,
            q1,
            two_cnot_unitary,
            allow_partial_czs=allow_partial_czs,
            atol=atol,
            clean_operations=clean_operations,
        )

    return np.eye(4), two_qubit_matrix_to_operations(
        q0,
        q1,
        mat,
        allow_partial_czs=allow_partial_czs,
        atol=atol,
        clean_operations=clean_operations,
    )
Example #8
0
def assertdiagonalized_by(m, p, tol=Tolerance.DEFAULT):
    d = p.T.dot(m).dot(p)

    try:
        assert predicates.is_orthogonal(p)
        assert predicates.is_diagonal(d, tol)
    except AssertionError:
        # coverage: ignore

        print("m.round(3)")
        print(np.round(m, 3))

        print("p.round(3)")
        print(np.round(p, 3))

        print("np.log10(np.abs(p.T @ m @ p)).round(2)")
        print(np.log10(np.abs(d)).round(2))

        raise
Example #9
0
def bidiagonalize_unitary_with_special_orthogonals(
        mat: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT
) -> Tuple[np.ndarray, np.array, np.ndarray]:
    """Finds orthogonal matrices L, R such that L @ matrix @ R is diagonal.

    Args:
        mat: A unitary matrix.
        tolerance: Numeric error thresholds.

    Returns:
        A triplet (L, d, R) such that L @ mat @ R = diag(d). Both L and R will
        be orthogonal matrices with determinant equal to 1.

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not real).
        ArithmeticError: Failed to meet specified tolerance.
    """

    if not predicates.is_unitary(mat, tolerance):
        raise ValueError('matrix must be unitary.')

    # Note: Because mat is unitary, setting A = real(mat) and B = imag(mat)
    # guarantees that both A @ B.T and A.T @ B are Hermitian.
    left, right = bidiagonalize_real_matrix_pair_with_symmetric_products(
        np.real(mat),
        np.imag(mat),
        tolerance)

    # Convert to special orthogonal w/o breaking diagonalization.
    if np.linalg.det(left) < 0:
        left[0, :] *= -1
    if np.linalg.det(right) < 0:
        right[:, 0] *= -1

    diag = combinators.dot(left, mat, right)

    # Check acceptability vs tolerances.
    if not predicates.is_diagonal(diag, tolerance):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return left, np.diag(diag), right
Example #10
0
def bidiagonalize_unitary_with_special_orthogonals(
    mat: np.ndarray,
    tolerance: Tolerance = Tolerance.DEFAULT
) -> Tuple[np.ndarray, np.array, np.ndarray]:
    """Finds orthogonal matrices L, R such that L @ matrix @ R is diagonal.

    Args:
        mat: A unitary matrix.
        tolerance: Numeric error thresholds.

    Returns:
        A triplet (L, d, R) such that L @ mat @ R = diag(d). Both L and R will
        be orthogonal matrices with determinant equal to 1.

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not real).
        ArithmeticError: Failed to meet specified tolerance.
    """

    if not predicates.is_unitary(mat, tolerance):
        raise ValueError('matrix must be unitary.')

    # Note: Because mat is unitary, setting A = real(mat) and B = imag(mat)
    # guarantees that both A @ B.T and A.T @ B are Hermitian.
    left, right = bidiagonalize_real_matrix_pair_with_symmetric_products(
        np.real(mat), np.imag(mat), tolerance)

    # Convert to special orthogonal w/o breaking diagonalization.
    if np.linalg.det(left) < 0:
        left[0, :] *= -1
    if np.linalg.det(right) < 0:
        right[:, 0] *= -1

    diag = combinators.dot(left, mat, right)

    # Check acceptability vs tolerances.
    if not predicates.is_diagonal(diag, tolerance):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return left, np.diag(diag), right
def diagonalize_real_symmetric_and_sorted_diagonal_matrices(
        symmetric_matrix: np.ndarray,
        diagonal_matrix: np.ndarray,
        *,
        rtol: float = 1e-5,
        atol: float = 1e-8,
        check_preconditions: bool = True) -> np.ndarray:
    """Returns an orthogonal matrix that diagonalizes both given matrices.

    The given matrices must commute.
    Guarantees that the sorted diagonal matrix is not permuted by the
    diagonalization (except for nearly-equal values).

    Args:
        symmetric_matrix: A real symmetric matrix.
        diagonal_matrix: A real diagonal matrix with entries along the diagonal
            sorted into descending order.
        rtol: Relative numeric error threshold.
        atol: Absolute numeric error threshold.
        check_preconditions: If set, verifies that the input matrices commute
            and are respectively symmetric and diagonal descending.

    Returns:
        An orthogonal matrix P such that P.T @ symmetric_matrix @ P is diagonal
        and P.T @ diagonal_matrix @ P = diagonal_matrix (up to tolerance).

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not symmetric).
    """

    # Verify preconditions.
    if check_preconditions:
        if (np.any(np.imag(symmetric_matrix)) or not predicates.is_hermitian(
                symmetric_matrix, rtol=rtol, atol=atol)):
            raise ValueError('symmetric_matrix must be real symmetric.')
        if (not predicates.is_diagonal(diagonal_matrix, atol=atol)
                or np.any(np.imag(diagonal_matrix)) or
                np.any(diagonal_matrix[:-1, :-1] < diagonal_matrix[1:, 1:])):
            raise ValueError(
                'diagonal_matrix must be real diagonal descending.')
        if not predicates.commutes(
                diagonal_matrix, symmetric_matrix, rtol=rtol, atol=atol):
            raise ValueError('Given matrices must commute.')

    def similar_singular(i, j):
        return np.allclose(diagonal_matrix[i, i],
                           diagonal_matrix[j, j],
                           rtol=rtol)

    # Because the symmetric matrix commutes with the diagonal singulars matrix,
    # the symmetric matrix should be block-diagonal with a block boundary
    # wherever the singular values happen change. So we can use the singular
    # values to extract blocks that can be independently diagonalized.
    ranges = _contiguous_groups(diagonal_matrix.shape[0], similar_singular)

    # Build the overall diagonalization by diagonalizing each block.
    p = np.zeros(symmetric_matrix.shape, dtype=np.float64)
    for start, end in ranges:
        block = symmetric_matrix[start:end, start:end]
        p[start:end, start:end] = diagonalize_real_symmetric_matrix(
            block, rtol=rtol, atol=atol, check_preconditions=False)

    return p
Example #12
0
def bidiagonalize_real_matrix_pair_with_symmetric_products(
        mat1: np.ndarray,
        mat2: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT
) -> Tuple[np.ndarray, np.ndarray]:
    """Finds orthogonal matrices that diagonalize both mat1 and mat2.

    Requires mat1 and mat2 to be real.
    Requires mat1.T @ mat2 to be symmetric.
    Requires mat1 @ mat2.T to be symmetric.

    Args:
        mat1: One of the real matrices.
        mat2: The other real matrix.
        tolerance: Numeric error thresholds.

    Returns:
        A tuple (L, R) of two orthogonal matrices, such that both L @ mat1 @ R
        and L @ mat2 @ R are diagonal matrices.

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not real).
        ArithmeticError: Failed to meet specified tolerance.
    """

    if np.any(np.imag(mat1) != 0):
        raise ValueError('mat1 must be real.')
    if np.any(np.imag(mat2) != 0):
        raise ValueError('mat2 must be real.')
    if not predicates.is_hermitian(mat1.dot(mat2.T), tolerance):
        raise ValueError('mat1 @ mat2.T must be symmetric.')
    if not predicates.is_hermitian(mat1.T.dot(mat2), tolerance):
        raise ValueError('mat1.T @ mat2 must be symmetric.')

    # Use SVD to bi-diagonalize the first matrix.
    base_left, base_diag, base_right = _svd_handling_empty(np.real(mat1))
    base_diag = np.diag(base_diag)

    # Determine where we switch between diagonalization-fixup strategies.
    dim = base_diag.shape[0]
    rank = dim
    while rank > 0 and tolerance.all_near_zero(base_diag[rank - 1, rank - 1]):
        rank -= 1
    base_diag = base_diag[:rank, :rank]

    # Try diagonalizing the second matrix with the same factors as the first.
    semi_corrected = base_left.T.dot(np.real(mat2)).dot(base_right.T)

    # Fix up the part of the second matrix's diagonalization that's matched
    # against non-zero diagonal entries in the first matrix's diagonalization
    # by performing simultaneous diagonalization.
    overlap = semi_corrected[:rank, :rank]
    overlap_adjust = diagonalize_real_symmetric_and_sorted_diagonal_matrices(
        overlap, base_diag, tolerance)

    # Fix up the part of the second matrix's diagonalization that's matched
    # against zeros in the first matrix's diagonalization by performing an SVD.
    extra = semi_corrected[rank:, rank:]
    extra_left_adjust, _, extra_right_adjust = _svd_handling_empty(extra)

    # Merge the fixup factors into the initial diagonalization.
    left_adjust = combinators.block_diag(overlap_adjust, extra_left_adjust)
    right_adjust = combinators.block_diag(overlap_adjust.T, extra_right_adjust)
    left = left_adjust.T.dot(base_left.T)
    right = base_right.T.dot(right_adjust.T)

    # Check acceptability vs tolerances.
    if any(not predicates.is_diagonal(left.dot(mat).dot(right), tolerance)
           for mat in [mat1, mat2]):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return left, right
Example #13
0
def test_is_diagonal():
    assert predicates.is_diagonal(np.empty((0, 0)))
    assert predicates.is_diagonal(np.empty((1, 0)))
    assert predicates.is_diagonal(np.empty((0, 1)))

    assert predicates.is_diagonal(np.array([[1]]))
    assert predicates.is_diagonal(np.array([[-1]]))
    assert predicates.is_diagonal(np.array([[5]]))
    assert predicates.is_diagonal(np.array([[3j]]))

    assert predicates.is_diagonal(np.array([[1, 0]]))
    assert predicates.is_diagonal(np.array([[1], [0]]))
    assert not predicates.is_diagonal(np.array([[1, 1]]))
    assert not predicates.is_diagonal(np.array([[1], [1]]))

    assert predicates.is_diagonal(np.array([[5j, 0], [0, 2]]))
    assert predicates.is_diagonal(np.array([[1, 0], [0, 1]]))
    assert not predicates.is_diagonal(np.array([[1, 0], [1, 1]]))
    assert not predicates.is_diagonal(np.array([[1, 1], [0, 1]]))
    assert not predicates.is_diagonal(np.array([[1, 1], [1, 1]]))
    assert not predicates.is_diagonal(np.array([[1, 0.1], [0.1, 1]]))

    assert predicates.is_diagonal(np.array([[1, 1e-11], [1e-10, 1]]))
Example #14
0
def diagonalize_real_symmetric_and_sorted_diagonal_matrices(
        symmetric_matrix: np.ndarray,
        diagonal_matrix: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT
) -> np.ndarray:
    """Returns an orthogonal matrix that diagonalizes both given matrices.

    The given matrices must commute.
    Guarantees that the sorted diagonal matrix is not permuted by the
    diagonalization (except for nearly-equal values).

    Args:
        symmetric_matrix: A real symmetric matrix.
        diagonal_matrix: A real diagonal matrix with entries along the diagonal
            sorted into descending order.
        tolerance: Numeric error thresholds.

    Returns:
        An orthogonal matrix P such that P.T @ symmetric_matrix @ P is diagonal
        and P.T @ diagonal_matrix @ P = diagonal_matrix (up to tolerance).

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not symmetric).
        ArithmeticError: Failed to meet specified tolerance.
    """

    # Verify preconditions.
    if (np.any(np.imag(symmetric_matrix) != 0) or
            not predicates.is_hermitian(symmetric_matrix)):
        raise ValueError('symmetric_matrix must be real symmetric.')
    if (not predicates.is_diagonal(diagonal_matrix) or
            np.any(np.imag(diagonal_matrix) != 0) or
            np.any(diagonal_matrix[:-1, :-1] < diagonal_matrix[1:, 1:])):
        raise ValueError(
            'diagonal_matrix must be real diagonal descending.')
    if not predicates.commutes(diagonal_matrix, symmetric_matrix):
        raise ValueError('Given matrices must commute.')

    def similar_singular(i, j):
        return tolerance.all_close(diagonal_matrix[i, i],
                                   diagonal_matrix[j, j])

    # Because the symmetric matrix commutes with the diagonal singulars matrix,
    # the symmetric matrix should be block-diagonal with a block boundary
    # wherever the singular values happen change. So we can use the singular
    # values to extract blocks that can be independently diagonalized.
    ranges = _contiguous_groups(diagonal_matrix.shape[0], similar_singular)

    # Build the overall diagonalization by diagonalizing each block.
    p = np.zeros(symmetric_matrix.shape, dtype=np.float64)
    for start, end in ranges:
        block = symmetric_matrix[start:end, start:end]
        p[start:end, start:end] = diagonalize_real_symmetric_matrix(block)

    # Check acceptability vs tolerances.
    if (not predicates.is_diagonal(p.T.dot(symmetric_matrix).dot(p),
                                    tolerance) or
            not tolerance.all_close(diagonal_matrix,
                                    p.T.dot(diagonal_matrix).dot(p))):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return p
Example #15
0
def bidiagonalize_real_matrix_pair_with_symmetric_products(
        mat1: np.ndarray,
        mat2: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT
) -> Tuple[np.ndarray, np.ndarray]:
    """Finds orthogonal matrices that diagonalize both mat1 and mat2.

    Requires mat1 and mat2 to be real.
    Requires mat1.T @ mat2 to be symmetric.
    Requires mat1 @ mat2.T to be symmetric.

    Args:
        mat1: One of the real matrices.
        mat2: The other real matrix.
        tolerance: Numeric error thresholds.

    Returns:
        A tuple (L, R) of two orthogonal matrices, such that both L @ mat1 @ R
        and L @ mat2 @ R are diagonal matrices.

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not real).
        ArithmeticError: Failed to meet specified tolerance.
    """

    if np.any(np.imag(mat1) != 0):
        raise ValueError('mat1 must be real.')
    if np.any(np.imag(mat2) != 0):
        raise ValueError('mat2 must be real.')
    if not predicates.is_hermitian(mat1.dot(mat2.T), tolerance):
        raise ValueError('mat1 @ mat2.T must be symmetric.')
    if not predicates.is_hermitian(mat1.T.dot(mat2), tolerance):
        raise ValueError('mat1.T @ mat2 must be symmetric.')

    # Use SVD to bi-diagonalize the first matrix.
    base_left, base_diag, base_right = _svd_handling_empty(np.real(mat1))
    base_diag = np.diag(base_diag)

    # Determine where we switch between diagonalization-fixup strategies.
    dim = base_diag.shape[0]
    rank = dim
    while rank > 0 and tolerance.all_near_zero(base_diag[rank - 1, rank - 1]):
        rank -= 1
    base_diag = base_diag[:rank, :rank]

    # Try diagonalizing the second matrix with the same factors as the first.
    semi_corrected = base_left.T.dot(np.real(mat2)).dot(base_right.T)

    # Fix up the part of the second matrix's diagonalization that's matched
    # against non-zero diagonal entries in the first matrix's diagonalization
    # by performing simultaneous diagonalization.
    overlap = semi_corrected[:rank, :rank]
    overlap_adjust = diagonalize_real_symmetric_and_sorted_diagonal_matrices(
        overlap, base_diag, tolerance)

    # Fix up the part of the second matrix's diagonalization that's matched
    # against zeros in the first matrix's diagonalization by performing an SVD.
    extra = semi_corrected[rank:, rank:]
    extra_left_adjust, _, extra_right_adjust = _svd_handling_empty(extra)

    # Merge the fixup factors into the initial diagonalization.
    left_adjust = combinators.block_diag(overlap_adjust, extra_left_adjust)
    right_adjust = combinators.block_diag(overlap_adjust.T,
                                           extra_right_adjust)
    left = left_adjust.T.dot(base_left.T)
    right = base_right.T.dot(right_adjust.T)

    # Check acceptability vs tolerances.
    if any(not predicates.is_diagonal(left.dot(mat).dot(right), tolerance)
           for mat in [mat1, mat2]):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return left, right