linalg.py 21.2 KB
Newer Older
Q
Quleaf 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# !/usr/bin/env python3
# Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. 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.

r"""
Q
Quleaf 已提交
17
The library of functions in linear algebra.
Q
Quleaf 已提交
18 19 20 21 22 23
"""

import paddle
import math
import numpy as np
import scipy
Q
Quleaf 已提交
24
import itertools
Q
Quleaf 已提交
25
from scipy.stats import unitary_group
Q
Quleaf 已提交
26
from functools import reduce
Q
Quleaf 已提交
27
from typing import Optional, Union, Callable, List, Tuple
Q
Quleaf 已提交
28

Q
Quleaf 已提交
29
import paddle_quantum as pq
Q
Quleaf 已提交
30
from .intrinsic import get_dtype, _get_float_dtype, _type_fetch, _type_transform
Q
Quleaf 已提交
31 32


Q
Quleaf 已提交
33
def abs_norm(mat: Union[np.ndarray, paddle.Tensor, pq.State]) -> float:
Q
Quleaf 已提交
34 35 36 37 38 39
    r""" tool for calculation of matrix norm

    Args:
        mat: matrix

    Returns:
Q
Quleaf 已提交
40
        norm of input matrix
Q
Quleaf 已提交
41 42

    """
Q
Quleaf 已提交
43
    mat = _type_transform(mat, "tensor")
Q
Quleaf 已提交
44
    mat = mat.cast(get_dtype())
Q
Quleaf 已提交
45 46 47
    return paddle.norm(paddle.abs(mat)).item()


Q
Quleaf 已提交
48
def dagger(mat: Union[np.ndarray, paddle.Tensor]) -> Union[np.ndarray, paddle.Tensor]:
Q
Quleaf 已提交
49 50 51 52 53 54 55 56 57
    r""" tool for calculation of matrix dagger

    Args:
        mat: matrix

    Returns:
        The dagger of matrix

    """
Q
Quleaf 已提交
58 59
    type_str = _type_fetch(mat)
    return np.conj(mat.T) if type_str == "numpy" else paddle.conj(mat.T)
Q
Quleaf 已提交
60 61


Q
Quleaf 已提交
62 63
def is_hermitian(mat: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = 1e-6) -> bool:
    r""" verify whether ``mat`` is Hermitian
Q
Quleaf 已提交
64 65

    Args:
Q
Quleaf 已提交
66
        mat: hermitian candidate :math:`P`
Q
Quleaf 已提交
67 68 69
        eps: tolerance of error

    Returns:
Q
Quleaf 已提交
70
        determine whether :math:`P - P^\dagger = 0`
Q
Quleaf 已提交
71 72

    """
Q
Quleaf 已提交
73
    mat = _type_transform(mat, "tensor").cast('complex128')
Q
Quleaf 已提交
74 75
    shape = mat.shape
    if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
Q
Quleaf 已提交
76
        # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
Q
Quleaf 已提交
77 78 79
        return False
    return abs_norm(mat - dagger(mat)) < eps

Q
Quleaf 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
def is_positive(mat: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = 1e-6) -> bool:
    r""" verify whether ``mat`` is a positive semi-definite matrix.

    Args:
        mat: positive operator candidate :math:`P`
        eps: tolerance of error

    Returns:
        determine whether :math:`P` is Hermitian and eigenvalues are non-negative
    
    """
    if is_hermitian(mat, eps):
        mat = _type_transform(mat, "tensor").cast('complex128')
        return (min(paddle.linalg.eigvalsh(mat)) >= -eps).item()
    return False
Q
Quleaf 已提交
95

Q
Quleaf 已提交
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171

def is_state_vector(vec: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = None) -> Tuple[bool, int]:
    r""" verify whether ``vec`` is a legal quantum state vector
    
    Args:
        vec: state vector candidate :math:`x`
        eps: tolerance of error, default to be `None` i.e. no testing for data correctness
    
    Returns:
        determine whether :math:`x^\dagger x = 1`, and return the number of qubits or an error message
        
    Note:
        error message is:
        * ``-1`` if the above equation does not hold
        * ``-2`` if the dimension of ``vec`` is not a power of 2
        * ``-3`` if ``vec`` is not a vector
    
    """
    vec = _type_transform(vec, "tensor")
    vec = paddle.squeeze(vec)
    
    dimension = vec.shape[0]
    if len(vec.shape) != 1:
        return False, -3
    
    num_qubits = int(math.log2(dimension))
    if 2 ** num_qubits != dimension:
        return False, -2
    
    if eps is None:
        return True, num_qubits
    
    vec = vec.reshape([dimension, 1])
    vec_bra = paddle.conj(vec.T)   
    eps = min(eps * dimension, 1e-2)
    return {False, -1} if paddle.abs(vec_bra @ vec - (1 + 0j)) > eps else {True, num_qubits}


def is_density_matrix(rho: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = None) -> Tuple[bool, int]:
    r""" verify whether ``rho`` is a legal quantum density matrix
    
    Args:
        rho: density matrix candidate
        eps: tolerance of error, default to be `None` i.e. no testing for data correctness
    
    Returns:
        determine whether ``rho`` is a PSD matrix with trace 1 and return the number of qubits or an error message.
    
    Note:
        error message is:
        * ``-1`` if ``rho`` is not PSD
        * ``-2`` if the trace of ``rho`` is not 1
        * ``-3`` if the dimension of ``rho`` is not a power of 2 
        * ``-4`` if ``rho`` is not a square matrix
    
    """
    rho = _type_transform(rho, "tensor")
    
    dimension = rho.shape[0]
    if len(rho.shape) != 2 or dimension != rho.shape[1]:
        return False, -4
    
    num_qubits = int(math.log2(dimension))
    if 2 ** num_qubits != dimension:
        return False, -3
    
    if eps is None:
        return True, num_qubits
    
    eps = min(eps * dimension, 1e-2)
    if paddle.abs(paddle.trace(rho) - (1 + 0j)).item() > eps:
        return False, -2
    
    return {False, -1} if not is_positive(rho, eps) else {True, num_qubits}


Q
Quleaf 已提交
172 173
def is_projector(mat: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = 1e-6) -> bool:
    r""" verify whether ``mat`` is a projector
Q
Quleaf 已提交
174 175

    Args:
Q
Quleaf 已提交
176
        mat: projector candidate :math:`P`
Q
Quleaf 已提交
177 178 179 180 181 182
        eps: tolerance of error

    Returns:
        determine whether :math:`PP - P = 0`

    """
Q
Quleaf 已提交
183
    mat = _type_transform(mat, "tensor").cast('complex128')
Q
Quleaf 已提交
184 185
    shape = mat.shape
    if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
Q
Quleaf 已提交
186
        # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
Q
Quleaf 已提交
187 188 189 190
        return False
    return abs_norm(mat @ mat - mat) < eps


Q
Quleaf 已提交
191 192
def is_unitary(mat: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = 1e-4) -> bool:
    r""" verify whether ``mat`` is a unitary
Q
Quleaf 已提交
193 194

    Args:
Q
Quleaf 已提交
195
        mat: unitary candidate :math:`P`
Q
Quleaf 已提交
196 197 198 199 200 201
        eps: tolerance of error

    Returns:
        determine whether :math:`PP^\dagger - I = 0`

    """
Q
Quleaf 已提交
202
    mat = _type_transform(mat, "tensor").cast('complex128')
Q
Quleaf 已提交
203
    shape = mat.shape
Q
Quleaf 已提交
204
    eps = min(eps * shape[0], 1e-2)
Q
Quleaf 已提交
205
    if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
Q
Quleaf 已提交
206
        # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
Q
Quleaf 已提交
207 208 209 210 211
        return False
    return abs_norm(mat @ dagger(mat) - paddle.cast(paddle.eye(shape[0]), mat.dtype)) < eps


def hermitian_random(num_qubits: int) -> paddle.Tensor:
Q
Quleaf 已提交
212
    r"""randomly generate a :math:`2^n \times 2^n` hermitian matrix
Q
Quleaf 已提交
213 214

    Args:
Q
Quleaf 已提交
215
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
216 217

    Returns:
Q
Quleaf 已提交
218
         a :math:`2^n \times 2^n` hermitian matrix
Q
Quleaf 已提交
219 220 221 222

    """
    assert num_qubits > 0
    n = 2 ** num_qubits
Q
Quleaf 已提交
223
    
Q
Quleaf 已提交
224 225 226 227 228 229 230 231
    mat = np.random.randn(n, n) + 1j * np.random.randn(n, n)
    for i in range(n):
        mat[i, i] = np.abs(mat[i, i])
        for j in range(i):
            mat[i, j] = np.conj(mat[j, i])
    
    eigval= np.linalg.eigvalsh(mat)
    max_eigval = np.max(np.abs(eigval))
Q
Quleaf 已提交
232
    return paddle.to_tensor(mat / max_eigval, dtype=get_dtype())
Q
Quleaf 已提交
233 234 235


def orthogonal_projection_random(num_qubits: int) -> paddle.Tensor:
Q
Quleaf 已提交
236
    r"""randomly generate a :math:`2^n \times 2^n` rank-1 orthogonal projector
Q
Quleaf 已提交
237 238

    Args:
Q
Quleaf 已提交
239
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
240 241

    Returns:
Q
Quleaf 已提交
242
         a :math:`2^n \times 2^n` orthogonal projector
Q
Quleaf 已提交
243 244 245
    """
    assert num_qubits > 0
    n = 2 ** num_qubits
Q
Quleaf 已提交
246
    float_dtype = _get_float_dtype(get_dtype())
Q
Quleaf 已提交
247
    vec = paddle.randn([n, 1], dtype=float_dtype) + 1j * paddle.randn([n, 1], dtype=float_dtype)
Q
Quleaf 已提交
248 249 250 251
    mat = vec @ dagger(vec)
    return mat / paddle.trace(mat)


Q
Quleaf 已提交
252 253 254 255
def density_matrix_random(num_qubits: int) -> paddle.Tensor:
    r""" randomly generate an num_qubits-qubit state in density matrix form
    
    Args:
Q
Quleaf 已提交
256
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
257 258
    
    Returns:
Q
Quleaf 已提交
259
        a :math:`2^n \times 2^n` density matrix
Q
Quleaf 已提交
260 261
        
    """
Q
Quleaf 已提交
262
    return haar_density_operator(num_qubits, rank=np.random.randint(1,2**num_qubits))
Q
Quleaf 已提交
263 264 265


def unitary_random(num_qubits: int) -> paddle.Tensor:
Q
Quleaf 已提交
266
    r"""randomly generate a :math:`2^n \times 2^n` unitary
Q
Quleaf 已提交
267 268

    Args:
Q
Quleaf 已提交
269
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
270 271

    Returns:
Q
Quleaf 已提交
272
         a :math:`2^n \times 2^n` unitary matrix
Q
Quleaf 已提交
273 274
         
    """
Q
Quleaf 已提交
275
    return paddle.to_tensor(unitary_group.rvs(2 ** num_qubits), dtype=get_dtype())
Q
Quleaf 已提交
276 277


Q
Quleaf 已提交
278
def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor:
Q
Quleaf 已提交
279
    r"""randomly generate a :math:`2^n \times 2^n` hermitian unitary
Q
Quleaf 已提交
280 281

    Args:
Q
Quleaf 已提交
282
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
283 284

    Returns:
Q
Quleaf 已提交
285
         a :math:`2^n \times 2^n` hermitian unitary matrix
Q
Quleaf 已提交
286 287 288 289 290 291 292
         
    """
    proj_mat = orthogonal_projection_random(num_qubits)
    id_mat = paddle.eye(2 ** num_qubits)
    return (2 + 0j) * proj_mat - id_mat


Q
Quleaf 已提交
293
def unitary_random_with_hermitian_block(num_qubits: int, is_unitary: bool = False) -> paddle.Tensor:
Q
Quleaf 已提交
294
    r"""randomly generate a unitary :math:`2^n \times 2^n` matrix that is a block encoding of a :math:`2^{n/2} \times 2^{n/2}` Hermitian matrix
Q
Quleaf 已提交
295 296

    Args:
Q
Quleaf 已提交
297
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
298
        is_unitary: whether the hermitian block is a unitary divided by 2 (for tutorial only)
Q
Quleaf 已提交
299 300

    Returns:
Q
Quleaf 已提交
301
         a :math:`2^n \times 2^n` unitary matrix that its upper-left block is a Hermitian matrix
Q
Quleaf 已提交
302 303 304

    """
    assert num_qubits > 0
Q
Quleaf 已提交
305 306 307 308 309
    
    if is_unitary:
        mat0 = unitary_hermitian_random(num_qubits - 1).numpy() / 2
    else:
        mat0 = hermitian_random(num_qubits - 1).numpy()
Q
Quleaf 已提交
310 311 312 313 314
    id_mat = np.eye(2 ** (num_qubits - 1))
    mat1 = 1j * scipy.linalg.sqrtm(id_mat - np.matmul(mat0, mat0))

    mat = np.block([[mat0, mat1], [mat1, mat0]])

Q
Quleaf 已提交
315
    return paddle.to_tensor(mat, dtype=get_dtype())
Q
Quleaf 已提交
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345


def block_enc_herm(mat: Union[np.ndarray, paddle.Tensor], 
                   num_block_qubits: int = 1) -> Union[np.ndarray, paddle.Tensor]:
    r""" generate a (qubitized) block encoding of hermitian ``mat``
    
    Args:
        mat: matrix to be block encoded
        num_block_qubits: ancilla qubits used in block encoding
    
    Returns:
        a unitary that is a block encoding of ``mat``
    
    """
    assert is_hermitian(mat), "the input matrix is not a hermitian"
    assert mat.shape[0] == mat.shape[1], "the input matrix is not a square matrix"
    
    type_mat = _type_fetch(mat)
    H = _type_transform(mat, "numpy")
    complex_dtype = mat.dtype
    
    num_qubits = int(math.log2(mat.shape[0]))
    H_complement = scipy.linalg.sqrtm(np.eye(2 ** num_qubits) - H @ H)
    block_enc = np.block([[H, 1j * H_complement], [1j * H_complement, H]])
    block_enc = paddle.to_tensor(block_enc, dtype=complex_dtype)
    
    if num_block_qubits > 1:
        block_enc = direct_sum(block_enc, paddle.eye(2 ** (num_block_qubits + num_qubits) - 2 ** (num_qubits + 1)).cast(complex_dtype))
    
    return _type_transform(block_enc, type_mat)
Q
Quleaf 已提交
346 347 348 349 350 351


def haar_orthogonal(num_qubits: int) -> paddle.Tensor:
    r""" randomly generate an orthogonal matrix following Haar random, referenced by arXiv:math-ph/0609050v2

    Args:
Q
Quleaf 已提交
352
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
353 354

    Returns:
Q
Quleaf 已提交
355
        a :math:`2^n \times 2^n` orthogonal matrix
Q
Quleaf 已提交
356 357 358 359 360 361 362 363 364 365 366
        
    """
    # Matrix dimension
    dim = 2 ** num_qubits
    # Step 1: sample from Ginibre ensemble
    ginibre = (np.random.randn(dim, dim))
    # Step 2: perform QR decomposition of G
    mat_q, mat_r = np.linalg.qr(ginibre)
    # Step 3: make the decomposition unique
    mat_lambda = np.diag(mat_r) / abs(np.diag(mat_r))
    mat_u = mat_q @ np.diag(mat_lambda)
Q
Quleaf 已提交
367
    return paddle.to_tensor(mat_u, dtype=get_dtype())
Q
Quleaf 已提交
368 369 370 371 372 373


def haar_unitary(num_qubits: int) -> paddle.Tensor:
    r""" randomly generate a unitary following Haar random, referenced by arXiv:math-ph/0609050v2

    Args:
Q
Quleaf 已提交
374
        num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
375 376

    Returns:
Q
Quleaf 已提交
377
        a :math:`2^n \times 2^n` unitary
Q
Quleaf 已提交
378 379 380 381 382 383 384 385 386 387 388
        
    """
    # Matrix dimension
    dim = 2 ** num_qubits
    # Step 1: sample from Ginibre ensemble
    ginibre = (np.random.randn(dim, dim) + 1j * np.random.randn(dim, dim)) / np.sqrt(2)
    # Step 2: perform QR decomposition of G
    mat_q, mat_r = np.linalg.qr(ginibre)
    # Step 3: make the decomposition unique
    mat_lambda = np.diag(mat_r) / np.abs(np.diag(mat_r))
    mat_u = mat_q @ np.diag(mat_lambda)
Q
Quleaf 已提交
389
    return paddle.to_tensor(mat_u, dtype=get_dtype())
Q
Quleaf 已提交
390 391 392 393 394 395


def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddle.Tensor:
    r""" randomly generate a state vector following Haar random

        Args:
Q
Quleaf 已提交
396
            num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
397 398 399
            is_real: whether the vector is real, default to be False

        Returns:
Q
Quleaf 已提交
400
            a :math:`2^n \times 1` state vector
Q
Quleaf 已提交
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
            
    """
    # Vector dimension
    dim = 2 ** num_qubits
    if is_real:
        # Generate a Haar random orthogonal matrix
        mat_orthog = haar_orthogonal(num_qubits)
        # Perform u onto |0>, i.e., the first column of o
        phi = mat_orthog[:, 0]
    else:
        # Generate a Haar random unitary
        unitary = haar_unitary(num_qubits)
        # Perform u onto |0>, i.e., the first column of u
        phi = unitary[:, 0]

Q
Quleaf 已提交
416
    return paddle.to_tensor(phi, dtype=get_dtype())
Q
Quleaf 已提交
417 418 419 420 421 422


def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real: Optional[bool] = False) -> paddle.Tensor:
    r""" randomly generate a density matrix following Haar random

        Args:
Q
Quleaf 已提交
423
            num_qubits: number of qubits :math:`n`
Q
Quleaf 已提交
424
            rank: rank of density matrix, default to be ``None`` refering to full ranks
Q
Quleaf 已提交
425 426 427
            is_real: whether the density matrix is real, default to be False

        Returns:
Q
Quleaf 已提交
428
            a :math:`2^n \times 2^n` density matrix
Q
Quleaf 已提交
429 430 431 432 433 434 435 436 437 438 439
    """
    dim = 2 ** num_qubits
    rank = rank if rank is not None else dim
    assert 0 < rank <= dim, 'rank is an invalid number'
    if is_real:
        ginibre_matrix = np.random.randn(dim, rank)
        rho = ginibre_matrix @ ginibre_matrix.T
    else:
        ginibre_matrix = np.random.randn(dim, rank) + 1j * np.random.randn(dim, rank)
        rho = ginibre_matrix @ ginibre_matrix.conj().T
    rho = rho / np.trace(rho)
Q
Quleaf 已提交
440
    return paddle.to_tensor(rho, dtype=get_dtype())
Q
Quleaf 已提交
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464


def direct_sum(A: Union[np.ndarray, paddle.Tensor], 
               B: Union[np.ndarray, paddle.Tensor]) -> Union[np.ndarray, paddle.Tensor]:
    r""" calculate the direct sum of A and B
    
    Args:
        A: :math:`m \times n` matrix
        B: :math:`p \times q` matrix
        
    Returns:
        a direct sum of A and B, with shape :math:`(m + p) \times (n + q)`
    
    
    """
    type_A, type_B = _type_fetch(A), _type_fetch(B)
    A, B = _type_transform(A, "numpy"), _type_transform(B, "numpy")

    assert A.dtype == B.dtype, f"A's dtype {A.dtype} does not agree with B's dtype {B.dtype}"

    zero_AB, zero_BA = np.zeros([A.shape[0], B.shape[1]]), np.zeros([B.shape[0], A.shape[1]])
    mat = np.block([[A, zero_AB], [zero_BA, B]])

    return mat if type_A == "numpy" or type_B == "numpy" else paddle.to_tensor(mat)
Q
Quleaf 已提交
465 466


Q
Quleaf 已提交
467 468 469 470
def NKron(
        matrix_A: Union[paddle.Tensor, np.ndarray],
        matrix_B: Union[paddle.Tensor, np.ndarray],
        *args: Union[paddle.Tensor, np.ndarray]
Q
Quleaf 已提交
471
    ) -> Union[paddle.Tensor, np.ndarray]:
Q
Quleaf 已提交
472 473 474
    r""" calculate Kronecker product of at least two matrices

    Args:
Q
Quleaf 已提交
475 476 477
        matrix_A: matrix, as paddle.Tensor or numpy.ndarray
        matrix_B: matrix, as paddle.Tensor or numpy.ndarray
        *args: other matrices, as paddle.Tensor or numpy.ndarray
Q
Quleaf 已提交
478 479

    Returns:
Q
Quleaf 已提交
480 481 482 483
        Kronecker product of matrices, determined by input type of matrix_A

    .. code-block:: python

Q
Quleaf 已提交
484 485
        from paddle_quantum.state import density_op_random
        from paddle_quantum.linalg import NKron
Q
Quleaf 已提交
486 487 488 489 490 491
        A = density_op_random(2)
        B = density_op_random(2)
        C = density_op_random(2)
        result = NKron(A, B, C)

    Note:
Q
Quleaf 已提交
492
        ``result`` from above code block should be A \otimes B \otimes C
Q
Quleaf 已提交
493
    """
Q
Quleaf 已提交
494 495 496 497
    type_A, type_B = _type_fetch(matrix_A), _type_fetch(matrix_A)
    assert type_A == type_B, f"the input data types do not agree: received {type_A} and {type_B}"

    if type_A == "tensor":
Q
Quleaf 已提交
498 499 500
        return reduce(lambda result, index: paddle.kron(result, index), args, paddle.kron(matrix_A, matrix_B), )
    else:
        return reduce(lambda result, index: np.kron(result, index), args, np.kron(matrix_A, matrix_B), )
Q
Quleaf 已提交
501 502

    
Q
Quleaf 已提交
503
def herm_transform(fcn: Callable[[float], float], mat: Union[paddle.Tensor, np.ndarray, pq.State], 
Q
Quleaf 已提交
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
                   ignore_zero: Optional[bool] = False) -> paddle.Tensor:
    r""" function transformation for Hermitian matrix
    
    Args:
        fcn: function :math:`f` that can be expanded by Taylor series
        mat: hermitian matrix :math:`H`
        ignore_zero: whether ignore eigenspaces with zero eigenvalue, defaults to be ``False``
    
    Returns
        :math:`f(H)`
    
    """
    assert is_hermitian(mat), \
        "the input matrix is not Hermitian: check your input"
    type_str = _type_fetch(mat)
    mat = _type_transform(mat, "tensor") if type_str != "state_vector" else mat.ket @ mat.bra
    
    eigval, eigvec = paddle.linalg.eigh(mat)
    eigval = eigval.tolist()
    eigvec = eigvec.T
    
    mat = paddle.zeros(mat.shape).cast(mat.dtype)
    for i in range(len(eigval)):
        vec = eigvec[i].reshape([mat.shape[0], 1])
        
        if np.abs(eigval[i]) < 1e-5 and ignore_zero:
            continue
        mat += (fcn(eigval[i]) + 0j) * vec @ dagger(vec)
    return mat.numpy() if type_str == "numpy" else mat
Q
Quleaf 已提交
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548


def pauli_basis_generation(num_qubits: int) -> List[paddle.Tensor]:
    r"""Generate a Pauli basis.
    
    Args:
        num_qubits: the number of qubits :math:`n`.
        
    Returns:
        The Pauli basis of :math:`\mathbb{C}^{2^n \times 2^n}`.

    """
    
    def __single_pauli_basis() -> List[paddle.Tensor]:
        r"""The Pauli basis in single-qubit case.
        """
Q
Quleaf 已提交
549
        complex_dtype = get_dtype()
Q
Quleaf 已提交
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
        I = paddle.to_tensor([[0.5, 0],
                            [0, 0.5]], dtype=complex_dtype)
        X = paddle.to_tensor([[0, 0.5],
                            [0.5, 0]], dtype=complex_dtype)
        Y = paddle.to_tensor([[0, -0.5j],
                            [0.5j, 0]], dtype=complex_dtype)
        Z = paddle.to_tensor([[0.5, 0],
                            [0, -0.5]], dtype=complex_dtype)
        return [I, X, Y, Z]
    
    def __basis_kron(basis_A: List[paddle.Tensor], basis_B: List[paddle.Tensor]) -> List[paddle.Tensor]:
        r"""Kronecker product between bases
        """
        return [
            paddle.kron(basis_A[i], basis_B[j])
            for i, j in itertools.product(range(len(basis_A)), range(len(basis_B)))
        ]
    
    
    list_bases = [__single_pauli_basis() for _ in range(num_qubits)]
    if num_qubits == 1:
        return list_bases[0]
    
    return reduce(lambda result, index: __basis_kron(result, index), list_bases[2:], __basis_kron(list_bases[0], list_bases[1]))


def pauli_decomposition(mat: Union[np.ndarray, paddle.Tensor]) -> Union[np.ndarray, paddle.Tensor]:
    r"""Decompose the matrix by the Pauli basis.
    
    Args:
        mat: the matrix to be decomposed
    
    Returns:
        The list of coefficients corresponding to Pauli basis.
    
    """
    type_str = _type_fetch(mat)
    mat = _type_transform(mat, "tensor")
    
    dimension = mat.shape[0]
    num_qubits = int(math.log2(dimension))
    assert 2 ** num_qubits == dimension, \
        f"Input matrix is not a valid quantum data: received shape {mat.shape}"
        
    basis = pauli_basis_generation(num_qubits)
    decomp = paddle.concat([paddle.trace(mat @ basis[i]) for i in range(dimension ** 2)])
    return _type_transform(decomp, type_str)


def subsystem_decomposition(mat: Union[np.ndarray, paddle.Tensor], 
                            first_basis: Union[List[np.ndarray], List[paddle.Tensor]], 
                            second_basis: Union[List[np.ndarray], List[paddle.Tensor]],
                            inner_prod: Union[Callable[[np.ndarray, np.ndarray], np.ndarray], 
                                              Callable[[paddle.Tensor, paddle.Tensor], paddle.Tensor]]
                            ) -> Union[np.ndarray, paddle.Tensor]:
    r"""Decompose the input matrix by two given bases in two subsystems.
    
    Args:
        mat: the matrix :math:`w` to be decomposed
        first_basis: a basis :math:`\{e_i\}_i` from the first space
        second_basis: a basis :math:`\{f_j\}_j` from the second space
        inner_prod: the inner product of these subspaces
        
    Returns:
        a coefficient matrix :math:`[\beta_{ij}]` such that :math:`w = \sum_{i, j} \beta_{ij} e_i \otimes f_j`.
    
    """
    type_str = _type_fetch(mat)
    mat = _type_transform(mat, "tensor")
    
    if type_str == "numpy":
        first_basis = [paddle.to_tensor(ele) for ele in first_basis]
        second_basis = [paddle.to_tensor(ele) for ele in second_basis]
    
    assert mat.shape == paddle.kron(first_basis[0], second_basis[0]).shape, \
        f"The shape does not agree: received {mat.shape, first_basis[0].shape, second_basis[0].shape}"
    
    first_dim, second_dim = len(first_basis), len(second_basis)
    coef = [
        inner_prod(paddle.kron(first_basis[i], second_basis[j]), mat)
        for i, j in itertools.product(range(first_dim), range(second_dim))
    ]
    coef = paddle.concat(coef).reshape([first_dim, second_dim])
    
    return _type_transform(coef, type_str)