|
3 | 3 | """ |
4 | 4 |
|
5 | 5 | import sys |
6 | | -from typing import Any, Sequence, Union |
| 6 | +from typing import Any, Sequence, Union, Optional, Dict |
7 | 7 | from operator import and_ |
8 | 8 | from functools import reduce |
9 | 9 | from functools import partial |
10 | 10 | import numpy as np |
11 | 11 |
|
| 12 | + |
12 | 13 | from . import cons |
13 | 14 | from . import interfaces |
14 | 15 | from .cons import backend, dtypestr |
15 | 16 | from . import gates |
| 17 | +from .gates import array_to_tensor |
16 | 18 |
|
17 | 19 |
|
18 | 20 | thismodule = sys.modules[__name__] |
@@ -306,6 +308,128 @@ def phasedampingchannel(gamma: float) -> Sequence[Gate]: |
306 | 308 | return [m0, m1] |
307 | 309 |
|
308 | 310 |
|
| 311 | +def thermalrelaxationchannel( |
| 312 | + t1: float, |
| 313 | + t2: float, |
| 314 | + time: float, |
| 315 | + method: str = "general", |
| 316 | + excitedstatepopulation: float = 0.0, |
| 317 | +) -> Sequence[Gate]: |
| 318 | + r""" |
| 319 | + Return a thermal_relaxation_channel |
| 320 | +
|
| 321 | + :param t1: the T1 relaxation time. |
| 322 | + :type t1: float |
| 323 | + :param t2: the T2 dephasing time. |
| 324 | + :type t2: float |
| 325 | + :param time: gate time |
| 326 | + :type time: float |
| 327 | + :param method: method to express error (default: "general") |
| 328 | + :type time: str |
| 329 | + :param excitedstatepopulation: the population of state :math:`|1\rangle` at equilibrium (default: 0) |
| 330 | + :type excited_state_population: float, optional |
| 331 | + :return: A thermal_relaxation_channel |
| 332 | + :rtype: Sequence[Gate] |
| 333 | + """ |
| 334 | + t1 = backend.cast(array_to_tensor(t1), dtype=cons.dtypestr) |
| 335 | + t2 = backend.cast(array_to_tensor(t2), dtype=cons.dtypestr) |
| 336 | + time = backend.cast(array_to_tensor(time), dtype=cons.dtypestr) |
| 337 | + |
| 338 | + # T1 relaxation rate: :math:`|1\rangle \rightarrow \0\rangle` |
| 339 | + rate1 = 1.0 / t1 |
| 340 | + p_reset = 1 - backend.exp(-time * rate1) |
| 341 | + |
| 342 | + # T2 dephasing rate: :math:`|+\rangle \rightarrow \-\rangle` |
| 343 | + rate2 = 1.0 / t2 |
| 344 | + exp_t2 = backend.exp(-time * rate2) |
| 345 | + |
| 346 | + # Qubit state equilibrium probabilities |
| 347 | + p0 = 1 - excitedstatepopulation |
| 348 | + p1 = excitedstatepopulation |
| 349 | + p0 = backend.cast(array_to_tensor(p0), dtype=cons.dtypestr) |
| 350 | + p1 = backend.cast(array_to_tensor(p1), dtype=cons.dtypestr) |
| 351 | + |
| 352 | + if method == "T1dom": |
| 353 | + # git avaliable |
| 354 | + m0 = backend.convert_to_tensor( |
| 355 | + np.array([[1, 0], [0, 0]], dtype=cons.npdtype) |
| 356 | + ) # reset channel |
| 357 | + m1 = backend.convert_to_tensor( |
| 358 | + np.array([[0, 1], [0, 0]], dtype=cons.npdtype) |
| 359 | + ) # reset channel |
| 360 | + m2 = backend.convert_to_tensor( |
| 361 | + np.array([[0, 0], [1, 0]], dtype=cons.npdtype) |
| 362 | + ) # X gate + rest channel |
| 363 | + m3 = backend.convert_to_tensor( |
| 364 | + np.array([[0, 0], [0, 1]], dtype=cons.npdtype) |
| 365 | + ) # X gate + rest channel |
| 366 | + |
| 367 | + tup = [ |
| 368 | + gates.i().tensor, # type: ignore |
| 369 | + gates.z().tensor, # type: ignore |
| 370 | + m0, |
| 371 | + m1, |
| 372 | + m2, |
| 373 | + m3, |
| 374 | + ] |
| 375 | + |
| 376 | + p_reset0 = p_reset * p0 |
| 377 | + p_reset1 = p_reset * p1 |
| 378 | + p_z = ( |
| 379 | + (1 - p_reset) * (1 - backend.exp(-time * (rate2 - rate1))) / 2 |
| 380 | + ) # must have rate2 > rate1 |
| 381 | + p_identity = 1 - p_z - p_reset0 - p_reset1 |
| 382 | + probs = [p_identity, p_z, p_reset0, p_reset0, p_reset1, p_reset1] |
| 383 | + |
| 384 | + Gkraus = [] |
| 385 | + for pro, paugate in zip(probs, tup): |
| 386 | + Gkraus.append(Gate(_sqrt(pro) * paugate)) |
| 387 | + return Gkraus |
| 388 | + |
| 389 | + else: # method == "general" or method == "T2dom": |
| 390 | + # git avaliable |
| 391 | + choi = (1 - p1 * p_reset) * backend.convert_to_tensor( |
| 392 | + np.array( |
| 393 | + [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], |
| 394 | + dtype=cons.npdtype, |
| 395 | + ) |
| 396 | + ) |
| 397 | + choi += (exp_t2) * backend.convert_to_tensor( |
| 398 | + np.array( |
| 399 | + [[0, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0]], |
| 400 | + dtype=cons.npdtype, |
| 401 | + ) |
| 402 | + ) |
| 403 | + choi += (p1 * p_reset) * backend.convert_to_tensor( |
| 404 | + np.array( |
| 405 | + [[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], |
| 406 | + dtype=cons.npdtype, |
| 407 | + ) |
| 408 | + ) |
| 409 | + choi += (p0 * p_reset) * backend.convert_to_tensor( |
| 410 | + np.array( |
| 411 | + [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]], |
| 412 | + dtype=cons.npdtype, |
| 413 | + ) |
| 414 | + ) |
| 415 | + choi += (1 - p0 * p_reset) * backend.convert_to_tensor( |
| 416 | + np.array( |
| 417 | + [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1]], |
| 418 | + dtype=cons.npdtype, |
| 419 | + ) |
| 420 | + ) |
| 421 | + |
| 422 | + nmax = 4 |
| 423 | + if ( |
| 424 | + np.abs(excitedstatepopulation - 0.0) < 1e-3 |
| 425 | + or np.abs(excitedstatepopulation - 1.0) < 1e-3 |
| 426 | + ): |
| 427 | + nmax = 3 |
| 428 | + |
| 429 | + listKraus = choi_to_kraus(choi, truncation_rules={"max_singular_values": nmax}) |
| 430 | + return [Gate(i) for i in listKraus] |
| 431 | + |
| 432 | + |
309 | 433 | def _collect_channels() -> Sequence[str]: |
310 | 434 | r""" |
311 | 435 | Return channels names in this module. |
@@ -496,7 +620,63 @@ def reshuffle(op: Matrix, order: Sequence[int]) -> Matrix: |
496 | 620 | argnums=[0], |
497 | 621 | gate_to_tensor=True, |
498 | 622 | ) |
499 | | -def choi_to_kraus(choi: Matrix, atol: float = 1e-5) -> Matrix: |
| 623 | +# def choi_to_kraus(choi: Matrix, atol: float = 1e-5, Maxkraus: Optional[int] = None) -> Matrix: |
| 624 | +# r""" |
| 625 | +# Convert the Choi matrix representation to Kraus operator representation. |
| 626 | + |
| 627 | +# This can be done by firstly geting eigen-decomposition of Choi-matrix |
| 628 | + |
| 629 | +# .. math:: |
| 630 | +# \Lambda = \sum_k \gamma_k \vert \phi_k \rangle \langle \phi_k \vert |
| 631 | + |
| 632 | +# Then define Kraus operators |
| 633 | + |
| 634 | +# .. math:: |
| 635 | +# K_k = \sqrt{\gamma_k} V_k |
| 636 | + |
| 637 | +# where :math:`\gamma_k\geq0` and :math:`\phi_k` is the col-val vectorization of :math:`V_k` . |
| 638 | + |
| 639 | + |
| 640 | +# :Examples: |
| 641 | + |
| 642 | + |
| 643 | +# >>> kraus = tc.channels.phasedampingchannel(0.2) |
| 644 | +# >>> superop = tc.channels.kraus_to_choi(kraus) |
| 645 | +# >>> kraus_new = tc.channels.choi_to_kraus(superop) |
| 646 | + |
| 647 | + |
| 648 | +# :param choi: Choi matrix |
| 649 | +# :type choi: Matrix |
| 650 | +# :return: A list of Kraus operators |
| 651 | +# :rtype: Sequence[Matrix] |
| 652 | +# """ |
| 653 | +# dim = backend.shape_tuple(choi) |
| 654 | +# input_dim = _safe_sqrt(dim[0]) |
| 655 | +# output_dim = _safe_sqrt(dim[1]) |
| 656 | + |
| 657 | +# #if is_hermitian_matrix(choi, atol=atol): |
| 658 | +# # Get eigen-decomposition of Choi-matrix |
| 659 | +# e, v = backend.eigh(choi) # value of e is from minimal to maxmal |
| 660 | + |
| 661 | +# # CP-map Kraus representation |
| 662 | +# kraus = [] |
| 663 | +# for val, vec in zip(backend.real(e), backend.transpose(v)): |
| 664 | +# if val > atol: |
| 665 | +# k = backend.sqrt(backend.cast(val, dtypestr)) * backend.transpose( |
| 666 | +# backend.reshape(vec, [output_dim, input_dim]), [1, 0] |
| 667 | +# ) |
| 668 | +# kraus.append(k) |
| 669 | + |
| 670 | +# if not kraus: |
| 671 | +# kraus.append(backend.zeros([output_dim, input_dim], dtype=dtypestr)) |
| 672 | +# return kraus |
| 673 | + |
| 674 | +# # raise ValueError("illegal Choi matrix") |
| 675 | + |
| 676 | + |
| 677 | +def choi_to_kraus( |
| 678 | + choi: Matrix, truncation_rules: Optional[Dict[str, Any]] = None |
| 679 | +) -> Matrix: |
500 | 680 | r""" |
501 | 681 | Convert the Choi matrix representation to Kraus operator representation. |
502 | 682 |
|
@@ -530,24 +710,44 @@ def choi_to_kraus(choi: Matrix, atol: float = 1e-5) -> Matrix: |
530 | 710 | input_dim = _safe_sqrt(dim[0]) |
531 | 711 | output_dim = _safe_sqrt(dim[1]) |
532 | 712 |
|
533 | | - if is_hermitian_matrix(choi, atol=atol): |
534 | | - # Get eigen-decomposition of Choi-matrix |
535 | | - e, v = backend.eigh(choi) |
| 713 | + # Get eigen-decomposition of Choi-matrix |
| 714 | + e, v = backend.eigh(choi) # value of e is from minimal to maxmal |
| 715 | + e = backend.real(e) |
| 716 | + v = backend.transpose(v) |
| 717 | + |
| 718 | + # CP-map Kraus representation |
| 719 | + kraus = [] |
| 720 | + |
| 721 | + if truncation_rules is None: |
| 722 | + truncation_rules = {} |
| 723 | + |
| 724 | + if truncation_rules.get("max_singular_values", None) is not None: |
536 | 725 |
|
537 | | - # CP-map Kraus representation |
538 | | - kraus = [] |
539 | | - for val, vec in zip(backend.real(e), backend.transpose(v)): |
540 | | - if val > atol: |
541 | | - k = backend.sqrt(backend.cast(val, dtypestr)) * backend.transpose( |
542 | | - backend.reshape(vec, [output_dim, input_dim]), [1, 0] |
| 726 | + nkraus = truncation_rules["max_singular_values"] |
| 727 | + for i in range(nkraus): |
| 728 | + k = backend.sqrt(backend.cast(e[-(i + 1)], dtypestr)) * backend.transpose( |
| 729 | + backend.reshape(v[-(i + 1)], [output_dim, input_dim]), [1, 0] |
| 730 | + ) |
| 731 | + kraus.append(k) |
| 732 | + |
| 733 | + else: |
| 734 | + if truncation_rules.get("max_singular_values", None) is None: |
| 735 | + atol = 1e-5 |
| 736 | + else: |
| 737 | + atol = truncation_rules["max_truncattion_err"] |
| 738 | + |
| 739 | + for i in range(dim[0]): |
| 740 | + if e[-(i + 1)] > atol: |
| 741 | + k = backend.sqrt( |
| 742 | + backend.cast(e[-(i + 1)], dtypestr) |
| 743 | + ) * backend.transpose( |
| 744 | + backend.reshape(v[-(i + 1)], [output_dim, input_dim]), [1, 0] |
543 | 745 | ) |
544 | 746 | kraus.append(k) |
545 | 747 |
|
546 | | - if not kraus: |
547 | | - kraus.append(backend.zeros([output_dim, input_dim], dtype=dtypestr)) |
548 | | - return kraus |
549 | | - |
550 | | - raise ValueError("illegal Choi matrix") |
| 748 | + if not kraus: |
| 749 | + kraus.append(backend.zeros([output_dim, input_dim], dtype=dtypestr)) |
| 750 | + return kraus |
551 | 751 |
|
552 | 752 |
|
553 | 753 | @partial( |
|
0 commit comments